@@ -1,27 +1,14 @@
|
||||
package v1alpha2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/emicklei/go-restful"
|
||||
"k8s.io/klog"
|
||||
"kubesphere.io/kubesphere/pkg/api"
|
||||
iamv1alpha2 "kubesphere.io/kubesphere/pkg/api/iam/v1alpha2"
|
||||
"kubesphere.io/kubesphere/pkg/api/auth"
|
||||
"kubesphere.io/kubesphere/pkg/informers"
|
||||
"kubesphere.io/kubesphere/pkg/models/iam/am"
|
||||
"kubesphere.io/kubesphere/pkg/models/iam/im"
|
||||
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha2"
|
||||
"kubesphere.io/kubesphere/pkg/server/params"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/cache"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
|
||||
ldappool "kubesphere.io/kubesphere/pkg/simple/client/ldap"
|
||||
"kubesphere.io/kubesphere/pkg/utils/iputil"
|
||||
"net/http"
|
||||
|
||||
iamapi "kubesphere.io/kubesphere/pkg/api/iam"
|
||||
)
|
||||
|
||||
const (
|
||||
kindTokenReview = "TokenReview"
|
||||
)
|
||||
|
||||
type iamHandler struct {
|
||||
@@ -29,81 +16,13 @@ type iamHandler struct {
|
||||
imOperator im.IdentityManagementInterface
|
||||
}
|
||||
|
||||
func newIAMHandler(k8sClient k8s.Client, factory informers.InformerFactory, ldapClient ldappool.Interface, cacheClient cache.Interface, options *iamapi.AuthenticationOptions) *iamHandler {
|
||||
func newIAMHandler(k8sClient k8s.Client, factory informers.InformerFactory, ldapClient ldappool.Interface, cacheClient cache.Interface, options *auth.AuthenticationOptions) *iamHandler {
|
||||
return &iamHandler{
|
||||
amOperator: am.NewAMOperator(k8sClient.Kubernetes(), factory.KubernetesSharedInformerFactory()),
|
||||
imOperator: im.NewIMOperator(ldapClient, cacheClient, options),
|
||||
}
|
||||
}
|
||||
|
||||
// Implement webhook authentication interface
|
||||
// https://kubernetes.io/docs/reference/access-authn-authz/authentication/#webhook-token-authentication
|
||||
func (h *iamHandler) TokenReviewHandler(req *restful.Request, resp *restful.Response) {
|
||||
var tokenReview iamv1alpha2.TokenReview
|
||||
|
||||
err := req.ReadEntity(&tokenReview)
|
||||
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
api.HandleBadRequest(resp, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = tokenReview.Validate(); err != nil {
|
||||
klog.Error(err)
|
||||
api.HandleBadRequest(resp, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := h.imOperator.VerifyToken(tokenReview.Spec.Token)
|
||||
|
||||
if err != nil {
|
||||
klog.Errorln(err)
|
||||
api.HandleInternalError(resp, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
success := iamv1alpha2.TokenReview{APIVersion: tokenReview.APIVersion,
|
||||
Kind: kindTokenReview,
|
||||
Status: &iamv1alpha2.Status{
|
||||
Authenticated: true,
|
||||
User: map[string]interface{}{"username": user.Username, "uid": user.Username, "groups": user.Groups},
|
||||
},
|
||||
}
|
||||
|
||||
resp.WriteEntity(success)
|
||||
}
|
||||
|
||||
func (h *iamHandler) Login(req *restful.Request, resp *restful.Response) {
|
||||
var loginRequest iamv1alpha2.LoginRequest
|
||||
|
||||
err := req.ReadEntity(&loginRequest)
|
||||
|
||||
if err != nil || loginRequest.Username == "" || loginRequest.Password == "" {
|
||||
err = errors.New("incorrect username or password")
|
||||
klog.V(4).Infoln(err)
|
||||
resp.WriteHeaderAndEntity(http.StatusUnauthorized, err)
|
||||
return
|
||||
}
|
||||
|
||||
ip := iputil.RemoteIp(req.Request)
|
||||
|
||||
token, err := h.imOperator.Login(loginRequest.Username, loginRequest.Password, ip)
|
||||
|
||||
if err != nil {
|
||||
if err == im.AuthRateLimitExceeded {
|
||||
klog.V(4).Infoln(err)
|
||||
resp.WriteHeaderAndEntity(http.StatusTooManyRequests, err)
|
||||
return
|
||||
}
|
||||
klog.V(4).Infoln(err)
|
||||
resp.WriteHeaderAndEntity(http.StatusUnauthorized, err)
|
||||
return
|
||||
}
|
||||
|
||||
resp.WriteEntity(token)
|
||||
}
|
||||
|
||||
func (h *iamHandler) CreateUser(req *restful.Request, resp *restful.Response) {
|
||||
panic("implement me")
|
||||
}
|
||||
@@ -121,27 +40,7 @@ func (h *iamHandler) DescribeUser(req *restful.Request, resp *restful.Response)
|
||||
}
|
||||
|
||||
func (h *iamHandler) ListUsers(req *restful.Request, resp *restful.Response) {
|
||||
|
||||
limit, offset := params.ParsePaging(req)
|
||||
orderBy := params.GetStringValueWithDefault(req, params.OrderByParam, v1alpha2.CreateTime)
|
||||
reverse := params.GetBoolValueWithDefault(req, params.ReverseParam, true)
|
||||
conditions, err := params.ParseConditions(req)
|
||||
|
||||
if err != nil {
|
||||
klog.V(4).Infoln(err)
|
||||
api.HandleBadRequest(resp, nil, err)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.imOperator.ListUsers(conditions, orderBy, reverse, limit, offset)
|
||||
|
||||
if err != nil {
|
||||
klog.Errorln(err)
|
||||
api.HandleInternalError(resp, nil, err)
|
||||
return
|
||||
}
|
||||
|
||||
resp.WriteEntity(result)
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (h *iamHandler) ListUserRoles(req *restful.Request, resp *restful.Response) {
|
||||
|
||||
@@ -20,11 +20,9 @@ package v1alpha2
|
||||
import (
|
||||
"github.com/emicklei/go-restful"
|
||||
"github.com/emicklei/go-restful-openapi"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"kubesphere.io/kubesphere/pkg/api"
|
||||
"kubesphere.io/kubesphere/pkg/api/iam"
|
||||
iamv1alpha2 "kubesphere.io/kubesphere/pkg/api/iam/v1alpha2"
|
||||
"kubesphere.io/kubesphere/pkg/api/auth"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/runtime"
|
||||
"kubesphere.io/kubesphere/pkg/constants"
|
||||
"kubesphere.io/kubesphere/pkg/informers"
|
||||
@@ -40,88 +38,44 @@ const groupName = "iam.kubesphere.io"
|
||||
|
||||
var GroupVersion = schema.GroupVersion{Group: groupName, Version: "v1alpha2"}
|
||||
|
||||
func AddToContainer(c *restful.Container, k8sClient k8s.Client, factory informers.InformerFactory, ldapClient ldappool.Interface, cacheClient cache.Interface, options *iam.AuthenticationOptions) error {
|
||||
func AddToContainer(c *restful.Container, k8sClient k8s.Client, factory informers.InformerFactory, ldapClient ldappool.Interface, cacheClient cache.Interface, options *auth.AuthenticationOptions) error {
|
||||
ws := runtime.NewWebService(GroupVersion)
|
||||
|
||||
handler := newIAMHandler(k8sClient, factory, ldapClient, cacheClient, options)
|
||||
|
||||
ws.Route(ws.POST("/authenticate").
|
||||
To(handler.TokenReviewHandler).
|
||||
Doc("TokenReview attempts to authenticate a token to a known user. Note: TokenReview requests may be cached by the webhook token authenticator plugin in the kube-apiserver.").
|
||||
Reads(iamv1alpha2.TokenReview{}).
|
||||
Returns(http.StatusOK, api.StatusOK, iamv1alpha2.TokenReview{}).
|
||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.IdentityManagementTag}))
|
||||
ws.Route(ws.POST("/login").
|
||||
To(handler.Login).
|
||||
Doc("KubeSphere APIs support token-based authentication via the Authtoken request header. The POST Login API is used to retrieve the authentication token. After the authentication token is obtained, it must be inserted into the Authtoken header for all requests.").
|
||||
Reads(iamv1alpha2.LoginRequest{}).
|
||||
Returns(http.StatusOK, api.StatusOK, models.AuthGrantResponse{}).
|
||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.IdentityManagementTag}))
|
||||
ws.Route(ws.POST("/users").
|
||||
To(handler.CreateUser).
|
||||
Doc("Create a user account.").
|
||||
Reads(iamv1alpha2.CreateUserRequest{}).
|
||||
Returns(http.StatusOK, api.StatusOK, iamv1alpha2.UserDetail{}).
|
||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.IdentityManagementTag}))
|
||||
ws.Route(ws.DELETE("/users/{user}").
|
||||
To(handler.DeleteUser).
|
||||
Doc("Delete the specified user.").
|
||||
Param(ws.PathParameter("user", "username")).
|
||||
Returns(http.StatusOK, api.StatusOK, errors.Error{}).
|
||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.IdentityManagementTag}))
|
||||
ws.Route(ws.PUT("/users/{user}").
|
||||
To(handler.ModifyUser).
|
||||
Doc("Update information about the specified user.").
|
||||
Param(ws.PathParameter("user", "username")).
|
||||
Reads(iamv1alpha2.ModifyUserRequest{}).
|
||||
Returns(http.StatusOK, api.StatusOK, errors.Error{}).
|
||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.IdentityManagementTag}))
|
||||
ws.Route(ws.GET("/users/{user}").
|
||||
To(handler.DescribeUser).
|
||||
Doc("Describe the specified user.").
|
||||
Param(ws.PathParameter("user", "username")).
|
||||
Returns(http.StatusOK, api.StatusOK, iamv1alpha2.UserDetail{}).
|
||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.IdentityManagementTag}))
|
||||
ws.Route(ws.GET("/users").
|
||||
To(handler.ListUsers).
|
||||
Doc("List all users.").
|
||||
Returns(http.StatusOK, api.StatusOK, iamv1alpha2.ListUserResponse{}).
|
||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.IdentityManagementTag}))
|
||||
ws.Route(ws.GET("/users/{user}/roles").
|
||||
To(handler.ListUserRoles).
|
||||
Doc("Retrieve all the roles that are assigned to the specified user.").
|
||||
Param(ws.PathParameter("user", "username")).
|
||||
Returns(http.StatusOK, api.StatusOK, []*rbacv1.Role{}).
|
||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
|
||||
// implemented by create CRD object.
|
||||
ws.Route(ws.POST("/users"))
|
||||
ws.Route(ws.DELETE("/users/{user}"))
|
||||
ws.Route(ws.PUT("/users/{user}"))
|
||||
ws.Route(ws.GET("/users/{user}"))
|
||||
|
||||
// TODO move to resources api
|
||||
ws.Route(ws.GET("/users"))
|
||||
|
||||
ws.Route(ws.GET("/namespaces/{namespace}/roles").
|
||||
To(handler.ListRoles).
|
||||
Doc("Retrieve the roles that are assigned to the user in the specified namespace.").
|
||||
Param(ws.PathParameter("namespace", "kubernetes namespace")).
|
||||
Returns(http.StatusOK, api.StatusOK, models.PageableResponse{}).
|
||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
|
||||
|
||||
ws.Route(ws.GET("/clusterroles").
|
||||
To(handler.ListClusterRoles).
|
||||
Doc("List all cluster roles.").
|
||||
Returns(http.StatusOK, api.StatusOK, models.PageableResponse{}).
|
||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
|
||||
ws.Route(ws.GET("/namespaces/{namespace}/roles/{role}/users").
|
||||
To(handler.ListRoleUsers).
|
||||
Doc("Retrieve the users that are bound to the role in the specified namespace.").
|
||||
Param(ws.PathParameter("namespace", "kubernetes namespace")).
|
||||
Param(ws.PathParameter("role", "role name")).
|
||||
Returns(http.StatusOK, api.StatusOK, []iamv1alpha2.ListUserResponse{}).
|
||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
|
||||
|
||||
// TODO merge
|
||||
ws.Route(ws.GET("/namespaces/{namespace}/roles/{role}/users"))
|
||||
ws.Route(ws.GET("/namespaces/{namespace}/users").
|
||||
To(handler.ListNamespaceUsers).
|
||||
Doc("List all users in the specified namespace.").
|
||||
Param(ws.PathParameter("namespace", "kubernetes namespace")).
|
||||
Returns(http.StatusOK, api.StatusOK, []iamv1alpha2.ListUserResponse{}).
|
||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
|
||||
ws.Route(ws.GET("/clusterroles/{clusterrole}/users").
|
||||
To(handler.ListClusterRoleUsers).
|
||||
Doc("List all users that are bound to the specified cluster role.").
|
||||
Param(ws.PathParameter("clusterrole", "cluster role name")).
|
||||
Returns(http.StatusOK, api.StatusOK, []iamv1alpha2.ListUserResponse{}).
|
||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
|
||||
|
||||
ws.Route(ws.GET("/workspaces/{workspace}/roles").
|
||||
@@ -129,23 +83,14 @@ func AddToContainer(c *restful.Container, k8sClient k8s.Client, factory informer
|
||||
Doc("List all workspace roles.").
|
||||
Param(ws.PathParameter("workspace", "workspace name")).
|
||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
|
||||
ws.Route(ws.GET("/workspaces/{workspace}/roles/{role}").
|
||||
To(handler.DescribeWorkspaceRole).
|
||||
Doc("Describe the workspace role.").
|
||||
Param(ws.PathParameter("workspace", "workspace name")).
|
||||
Param(ws.PathParameter("role", "workspace role name")).
|
||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
|
||||
ws.Route(ws.GET("/workspaces/{workspace}/roles/{role}/rules").
|
||||
To(handler.ListWorkspaceRoleRules).
|
||||
Doc("List all policy rules of the specified workspace role.").
|
||||
Param(ws.PathParameter("workspace", "workspace name")).
|
||||
Param(ws.PathParameter("role", "workspace role name")).
|
||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
|
||||
|
||||
ws.Route(ws.GET("/workspaces/{workspace}/members").
|
||||
To(handler.ListWorkspaceUsers).
|
||||
Doc("List all members in the specified workspace.").
|
||||
Param(ws.PathParameter("workspace", "workspace name")).
|
||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
|
||||
|
||||
// TODO re-design
|
||||
ws.Route(ws.POST("/workspaces/{workspace}/members").
|
||||
To(handler.InviteUser).
|
||||
Doc("Invite a member to the specified workspace.").
|
||||
@@ -158,12 +103,7 @@ func AddToContainer(c *restful.Container, k8sClient k8s.Client, factory informer
|
||||
Param(ws.PathParameter("member", "username")).
|
||||
Returns(http.StatusOK, api.StatusOK, errors.Error{}).
|
||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
|
||||
ws.Route(ws.GET("/workspaces/{workspace}/members/{member}").
|
||||
To(handler.DescribeWorkspaceUser).
|
||||
Doc("Describe the specified user in the given workspace.").
|
||||
Param(ws.PathParameter("workspace", "workspace name")).
|
||||
Param(ws.PathParameter("member", "username")).
|
||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
|
||||
|
||||
c.Add(ws)
|
||||
return nil
|
||||
}
|
||||
|
||||
73
pkg/kapis/oauth/handler.go
Normal file
73
pkg/kapis/oauth/handler.go
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2020 The KubeSphere Authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* /
|
||||
*/
|
||||
|
||||
package oauth
|
||||
|
||||
import (
|
||||
"github.com/emicklei/go-restful"
|
||||
"k8s.io/klog"
|
||||
"kubesphere.io/kubesphere/pkg/api"
|
||||
"kubesphere.io/kubesphere/pkg/api/auth"
|
||||
"kubesphere.io/kubesphere/pkg/api/auth/token"
|
||||
)
|
||||
|
||||
type oauthHandler struct {
|
||||
issuer token.Issuer
|
||||
}
|
||||
|
||||
func newOAUTHHandler(issuer token.Issuer) *oauthHandler {
|
||||
return &oauthHandler{issuer: issuer}
|
||||
}
|
||||
|
||||
// Implement webhook authentication interface
|
||||
// https://kubernetes.io/docs/reference/access-authn-authz/authentication/#webhook-token-authentication
|
||||
func (h *oauthHandler) TokenReviewHandler(req *restful.Request, resp *restful.Response) {
|
||||
var tokenReview auth.TokenReview
|
||||
|
||||
err := req.ReadEntity(&tokenReview)
|
||||
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
api.HandleBadRequest(resp, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = tokenReview.Validate(); err != nil {
|
||||
klog.Error(err)
|
||||
api.HandleBadRequest(resp, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := h.issuer.Verify(tokenReview.Spec.Token)
|
||||
|
||||
if err != nil {
|
||||
klog.Errorln(err)
|
||||
api.HandleInternalError(resp, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
success := auth.TokenReview{APIVersion: tokenReview.APIVersion,
|
||||
Kind: auth.KindTokenReview,
|
||||
Status: &auth.Status{
|
||||
Authenticated: true,
|
||||
User: map[string]interface{}{"username": user.GetName(), "uid": user.GetUID(), "groups": user.GetGroups()},
|
||||
},
|
||||
}
|
||||
|
||||
resp.WriteEntity(success)
|
||||
}
|
||||
61
pkg/kapis/oauth/register.go
Normal file
61
pkg/kapis/oauth/register.go
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2020 The KubeSphere Authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* /
|
||||
*/
|
||||
|
||||
package oauth
|
||||
|
||||
import (
|
||||
"github.com/emicklei/go-restful"
|
||||
restfulspec "github.com/emicklei/go-restful-openapi"
|
||||
"kubesphere.io/kubesphere/pkg/api"
|
||||
"kubesphere.io/kubesphere/pkg/api/auth"
|
||||
"kubesphere.io/kubesphere/pkg/api/auth/token"
|
||||
"kubesphere.io/kubesphere/pkg/constants"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func AddToContainer(c *restful.Container, options *auth.AuthenticationOptions) error {
|
||||
ws := restful.WebService{}
|
||||
ws.Path("/oauth").
|
||||
Consumes(restful.MIME_JSON).
|
||||
Produces(restful.MIME_JSON)
|
||||
|
||||
handler := newOAUTHHandler(token.NewJwtTokenIssuer(token.DefaultIssuerName, []byte(options.JwtSecret)))
|
||||
|
||||
// Implement webhook authentication interface
|
||||
// https://kubernetes.io/docs/reference/access-authn-authz/authentication/#webhook-token-authentication
|
||||
ws.Route(ws.POST("/authenticate").
|
||||
Doc("TokenReview attempts to authenticate a token to a known user. Note: TokenReview requests may be cached by the webhook token authenticator plugin in the kube-apiserver.").
|
||||
Reads(auth.TokenReview{}).
|
||||
To(handler.TokenReviewHandler).
|
||||
Returns(http.StatusOK, api.StatusOK, auth.TokenReview{}).
|
||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.IdentityManagementTag}))
|
||||
|
||||
// TODO Built-in oauth2 server (provider)
|
||||
// Low priority
|
||||
c.Add(ws.Route(ws.POST("/authorize")))
|
||||
|
||||
// web console use 'Resource Owner Password Credentials Grant' or 'Client Credentials Grant' request for an OAuth token
|
||||
// https://tools.ietf.org/html/rfc6749#section-4.3
|
||||
// https://tools.ietf.org/html/rfc6749#section-4.4
|
||||
c.Add(ws.Route(ws.POST("/token")))
|
||||
|
||||
// oauth2 client callback
|
||||
c.Add(ws.Route(ws.POST("/callback/{callback}")))
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user