Merge pull request #3140 from wansir/identity-provider
improve identity provider plugin
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -17,6 +17,7 @@ limitations under the License.
|
||||
package v1alpha2
|
||||
|
||||
import (
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer"
|
||||
"net/http"
|
||||
|
||||
"github.com/emicklei/go-restful"
|
||||
@@ -25,9 +26,7 @@ import (
|
||||
v1 "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/apis/iam/v1alpha2"
|
||||
authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/runtime"
|
||||
"kubesphere.io/kubesphere/pkg/constants"
|
||||
"kubesphere.io/kubesphere/pkg/models/iam/am"
|
||||
@@ -42,9 +41,9 @@ const (
|
||||
|
||||
var GroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha2"}
|
||||
|
||||
func AddToContainer(container *restful.Container, im im.IdentityManagementInterface, am am.AccessManagementInterface, group group.GroupOperator, options *authoptions.AuthenticationOptions) error {
|
||||
func AddToContainer(container *restful.Container, im im.IdentityManagementInterface, am am.AccessManagementInterface, group group.GroupOperator, authorizer authorizer.Authorizer) error {
|
||||
ws := runtime.NewWebService(GroupVersion)
|
||||
handler := newIAMHandler(im, am, group, options)
|
||||
handler := newIAMHandler(im, am, group, authorizer)
|
||||
|
||||
// users
|
||||
ws.Route(ws.POST("/users").
|
||||
@@ -69,7 +68,7 @@ func AddToContainer(container *restful.Container, im im.IdentityManagementInterf
|
||||
ws.Route(ws.PUT("/users/{user}/password").
|
||||
To(handler.ModifyPassword).
|
||||
Doc("Reset password of the specified user.").
|
||||
Reads(iam.PasswordReset{}).
|
||||
Reads(PasswordReset{}).
|
||||
Param(ws.PathParameter("user", "username")).
|
||||
Returns(http.StatusOK, api.StatusOK, errors.None).
|
||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.UserTag}))
|
||||
|
||||
@@ -20,62 +20,101 @@ import (
|
||||
"fmt"
|
||||
"github.com/emicklei/go-restful"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/klog"
|
||||
"kubesphere.io/kubesphere/pkg/api"
|
||||
"kubesphere.io/kubesphere/pkg/api/auth"
|
||||
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
|
||||
authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/query"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/request"
|
||||
"kubesphere.io/kubesphere/pkg/models/auth"
|
||||
"kubesphere.io/kubesphere/pkg/models/iam/im"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type handler struct {
|
||||
im im.IdentityManagementInterface
|
||||
options *authoptions.AuthenticationOptions
|
||||
tokenOperator im.TokenManagementInterface
|
||||
authenticator im.PasswordAuthenticator
|
||||
loginRecorder im.LoginRecorder
|
||||
const (
|
||||
KindTokenReview = "TokenReview"
|
||||
passwordGrantType = "password"
|
||||
refreshTokenGrantType = "refresh_token"
|
||||
)
|
||||
|
||||
type Spec struct {
|
||||
Token string `json:"token" description:"access token"`
|
||||
}
|
||||
|
||||
func newHandler(im im.IdentityManagementInterface, tokenOperator im.TokenManagementInterface, authenticator im.PasswordAuthenticator, loginRecorder im.LoginRecorder, options *authoptions.AuthenticationOptions) *handler {
|
||||
return &handler{im: im, tokenOperator: tokenOperator, authenticator: authenticator, loginRecorder: loginRecorder, options: options}
|
||||
type Status struct {
|
||||
Authenticated bool `json:"authenticated" description:"is authenticated"`
|
||||
User map[string]interface{} `json:"user,omitempty" description:"user info"`
|
||||
}
|
||||
|
||||
type TokenReview struct {
|
||||
APIVersion string `json:"apiVersion" description:"Kubernetes API version"`
|
||||
Kind string `json:"kind" description:"kind of the API object"`
|
||||
Spec *Spec `json:"spec,omitempty"`
|
||||
Status *Status `json:"status,omitempty" description:"token review status"`
|
||||
}
|
||||
|
||||
type LoginRequest struct {
|
||||
Username string `json:"username" description:"username"`
|
||||
Password string `json:"password" description:"password"`
|
||||
}
|
||||
|
||||
func (request *TokenReview) Validate() error {
|
||||
if request.Spec == nil || request.Spec.Token == "" {
|
||||
return fmt.Errorf("token must not be null")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
im im.IdentityManagementInterface
|
||||
options *authoptions.AuthenticationOptions
|
||||
tokenOperator auth.TokenManagementInterface
|
||||
passwordAuthenticator auth.PasswordAuthenticator
|
||||
oauth2Authenticator auth.OAuth2Authenticator
|
||||
loginRecorder auth.LoginRecorder
|
||||
}
|
||||
|
||||
func newHandler(im im.IdentityManagementInterface,
|
||||
tokenOperator auth.TokenManagementInterface,
|
||||
passwordAuthenticator auth.PasswordAuthenticator,
|
||||
oauth2Authenticator auth.OAuth2Authenticator,
|
||||
loginRecorder auth.LoginRecorder,
|
||||
options *authoptions.AuthenticationOptions) *handler {
|
||||
return &handler{im: im,
|
||||
tokenOperator: tokenOperator,
|
||||
passwordAuthenticator: passwordAuthenticator,
|
||||
oauth2Authenticator: oauth2Authenticator,
|
||||
loginRecorder: loginRecorder,
|
||||
options: options}
|
||||
}
|
||||
|
||||
// Implement webhook authentication interface
|
||||
// https://kubernetes.io/docs/reference/access-authn-authz/authentication/#webhook-token-authentication
|
||||
func (h *handler) TokenReview(req *restful.Request, resp *restful.Response) {
|
||||
var tokenReview auth.TokenReview
|
||||
var tokenReview 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
|
||||
}
|
||||
|
||||
authenticated, err := h.tokenOperator.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{
|
||||
success := TokenReview{APIVersion: tokenReview.APIVersion,
|
||||
Kind: KindTokenReview,
|
||||
Status: &Status{
|
||||
Authenticated: true,
|
||||
User: map[string]interface{}{"username": authenticated.GetName(), "uid": authenticated.GetUID()},
|
||||
},
|
||||
@@ -93,34 +132,33 @@ func (h *handler) Authorize(req *restful.Request, resp *restful.Response) {
|
||||
conf, err := h.options.OAuthOptions.OAuthClient(clientId)
|
||||
if err != nil {
|
||||
err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))
|
||||
resp.WriteError(http.StatusUnauthorized, err)
|
||||
api.HandleError(resp, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
if responseType != "token" {
|
||||
err := apierrors.NewBadRequest(fmt.Sprintf("Response type %s is not supported", responseType))
|
||||
resp.WriteError(http.StatusUnauthorized, err)
|
||||
api.HandleError(resp, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
if !ok {
|
||||
err := apierrors.NewUnauthorized("Unauthorized")
|
||||
resp.WriteError(http.StatusUnauthorized, err)
|
||||
api.HandleError(resp, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
token, err := h.tokenOperator.IssueTo(authenticated)
|
||||
if err != nil {
|
||||
err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))
|
||||
resp.WriteError(http.StatusUnauthorized, err)
|
||||
api.HandleError(resp, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
redirectURL, err := conf.ResolveRedirectURL(redirectURI)
|
||||
|
||||
if err != nil {
|
||||
err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))
|
||||
resp.WriteError(http.StatusUnauthorized, err)
|
||||
api.HandleError(resp, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -133,105 +171,41 @@ func (h *handler) Authorize(req *restful.Request, resp *restful.Response) {
|
||||
http.Redirect(resp, req.Request, redirectURL, http.StatusFound)
|
||||
}
|
||||
|
||||
func (h *handler) oAuthCallBack(req *restful.Request, resp *restful.Response) {
|
||||
|
||||
func (h *handler) oauthCallBack(req *restful.Request, resp *restful.Response) {
|
||||
code := req.QueryParameter("code")
|
||||
name := req.PathParameter("callback")
|
||||
provider := req.PathParameter("callback")
|
||||
|
||||
if code == "" {
|
||||
err := apierrors.NewUnauthorized("Unauthorized: missing code")
|
||||
resp.WriteError(http.StatusUnauthorized, err)
|
||||
api.HandleError(resp, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
providerOptions, err := h.options.OAuthOptions.IdentityProviderOptions(name)
|
||||
|
||||
authenticated, provider, err := h.oauth2Authenticator.Authenticate(provider, code)
|
||||
if err != nil {
|
||||
err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))
|
||||
resp.WriteError(http.StatusUnauthorized, err)
|
||||
api.HandleUnauthorized(resp, req, apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err)))
|
||||
return
|
||||
}
|
||||
|
||||
oauthIdentityProvider, err := identityprovider.GetOAuthProvider(providerOptions.Type, providerOptions.Provider)
|
||||
|
||||
result, err := h.tokenOperator.IssueTo(authenticated)
|
||||
if err != nil {
|
||||
err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))
|
||||
resp.WriteError(http.StatusUnauthorized, err)
|
||||
api.HandleInternalError(resp, req, apierrors.NewInternalError(err))
|
||||
return
|
||||
}
|
||||
|
||||
identity, err := oauthIdentityProvider.IdentityExchange(code)
|
||||
|
||||
if err != nil {
|
||||
err = apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))
|
||||
resp.WriteError(http.StatusUnauthorized, err)
|
||||
return
|
||||
}
|
||||
|
||||
authenticated, err := h.im.DescribeUser(identity.GetName())
|
||||
if err != nil {
|
||||
// create user if not exist
|
||||
if (oauth.MappingMethodAuto == providerOptions.MappingMethod ||
|
||||
oauth.MappingMethodMixed == providerOptions.MappingMethod) &&
|
||||
apierrors.IsNotFound(err) {
|
||||
create := &iamv1alpha2.User{
|
||||
ObjectMeta: v1.ObjectMeta{Name: identity.GetName(),
|
||||
Annotations: map[string]string{iamv1alpha2.IdentifyProviderLabel: providerOptions.Name}},
|
||||
Spec: iamv1alpha2.UserSpec{Email: identity.GetEmail()},
|
||||
}
|
||||
if authenticated, err = h.im.CreateUser(create); err != nil {
|
||||
klog.Error(err)
|
||||
api.HandleInternalError(resp, req, err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
klog.Error(err)
|
||||
api.HandleInternalError(resp, req, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if oauth.MappingMethodLookup == providerOptions.MappingMethod &&
|
||||
authenticated == nil {
|
||||
err := apierrors.NewUnauthorized(fmt.Sprintf("user %s cannot bound to this identify provider", identity.GetName()))
|
||||
klog.Error(err)
|
||||
resp.WriteError(http.StatusUnauthorized, err)
|
||||
return
|
||||
}
|
||||
|
||||
// oauth.MappingMethodAuto
|
||||
// Fails if a user with that user name is already mapped to another identity.
|
||||
if providerOptions.MappingMethod == oauth.MappingMethodAuto && authenticated.Annotations[iamv1alpha2.IdentifyProviderLabel] != providerOptions.Name {
|
||||
err := apierrors.NewUnauthorized(fmt.Sprintf("user %s is already bound to other identify provider", identity.GetName()))
|
||||
klog.Error(err)
|
||||
resp.WriteError(http.StatusUnauthorized, err)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.tokenOperator.IssueTo(&user.DefaultInfo{
|
||||
Name: authenticated.Name,
|
||||
UID: string(authenticated.UID),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))
|
||||
resp.WriteError(http.StatusUnauthorized, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = h.loginRecorder.RecordLogin(authenticated.Name, iamv1alpha2.OAuth, providerOptions.Name, nil, req.Request); err != nil {
|
||||
klog.Error(err)
|
||||
err := apierrors.NewInternalError(err)
|
||||
resp.WriteError(http.StatusInternalServerError, err)
|
||||
return
|
||||
requestInfo, _ := request.RequestInfoFrom(req.Request.Context())
|
||||
if err = h.loginRecorder.RecordLogin(authenticated.GetName(), iamv1alpha2.Token, provider, requestInfo.SourceIP, requestInfo.UserAgent, nil); err != nil {
|
||||
klog.Errorf("Failed to record successful login for user %s, error: %v", authenticated.GetName(), err)
|
||||
}
|
||||
|
||||
resp.WriteEntity(result)
|
||||
}
|
||||
|
||||
func (h *handler) Login(request *restful.Request, response *restful.Response) {
|
||||
var loginRequest auth.LoginRequest
|
||||
var loginRequest LoginRequest
|
||||
err := request.ReadEntity(&loginRequest)
|
||||
if err != nil || loginRequest.Username == "" || loginRequest.Password == "" {
|
||||
response.WriteHeaderAndEntity(http.StatusUnauthorized, fmt.Errorf("empty username or password"))
|
||||
if err != nil {
|
||||
api.HandleBadRequest(response, request, err)
|
||||
return
|
||||
}
|
||||
h.passwordGrant(loginRequest.Username, loginRequest.Password, request, response)
|
||||
@@ -240,71 +214,53 @@ func (h *handler) Login(request *restful.Request, response *restful.Response) {
|
||||
func (h *handler) Token(req *restful.Request, response *restful.Response) {
|
||||
grantType, err := req.BodyParameter("grant_type")
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
api.HandleBadRequest(response, req, err)
|
||||
return
|
||||
}
|
||||
switch grantType {
|
||||
case "password":
|
||||
username, err := req.BodyParameter("username")
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
api.HandleBadRequest(response, req, err)
|
||||
return
|
||||
}
|
||||
password, err := req.BodyParameter("password")
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
api.HandleBadRequest(response, req, err)
|
||||
return
|
||||
}
|
||||
case passwordGrantType:
|
||||
username, _ := req.BodyParameter("username")
|
||||
password, _ := req.BodyParameter("password")
|
||||
h.passwordGrant(username, password, req, response)
|
||||
break
|
||||
case "refresh_token":
|
||||
case refreshTokenGrantType:
|
||||
h.refreshTokenGrant(req, response)
|
||||
break
|
||||
default:
|
||||
err := apierrors.NewBadRequest(fmt.Sprintf("Grant type %s is not supported", grantType))
|
||||
response.WriteError(http.StatusBadRequest, err)
|
||||
api.HandleBadRequest(response, req, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) passwordGrant(username string, password string, req *restful.Request, response *restful.Response) {
|
||||
authenticated, err := h.authenticator.Authenticate(username, password)
|
||||
authenticated, provider, err := h.passwordAuthenticator.Authenticate(username, password)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
switch err {
|
||||
case im.AuthFailedIncorrectPassword:
|
||||
if err := h.loginRecorder.RecordLogin(username, iamv1alpha2.Token, "", err, req.Request); err != nil {
|
||||
klog.Error(err)
|
||||
response.WriteError(http.StatusInternalServerError, apierrors.NewInternalError(err))
|
||||
return
|
||||
case auth.IncorrectPasswordError:
|
||||
requestInfo, _ := request.RequestInfoFrom(req.Request.Context())
|
||||
if err := h.loginRecorder.RecordLogin(username, iamv1alpha2.Token, provider, requestInfo.SourceIP, requestInfo.UserAgent, err); err != nil {
|
||||
klog.Errorf("Failed to record unsuccessful login attempt for user %s, error: %v", username, err)
|
||||
}
|
||||
response.WriteError(http.StatusUnauthorized, apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err)))
|
||||
api.HandleUnauthorized(response, req, apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err)))
|
||||
return
|
||||
case im.AuthFailedIdentityMappingNotMatch:
|
||||
response.WriteError(http.StatusUnauthorized, apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err)))
|
||||
return
|
||||
case im.AuthRateLimitExceeded:
|
||||
response.WriteError(http.StatusTooManyRequests, apierrors.NewTooManyRequests(fmt.Sprintf("Unauthorized: %s", err), 60))
|
||||
case auth.RateLimitExceededError:
|
||||
api.HandleTooManyRequests(response, req, apierrors.NewTooManyRequestsError(fmt.Sprintf("Unauthorized: %s", err)))
|
||||
return
|
||||
default:
|
||||
response.WriteError(http.StatusInternalServerError, apierrors.NewInternalError(err))
|
||||
api.HandleInternalError(response, req, apierrors.NewInternalError(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
result, err := h.tokenOperator.IssueTo(authenticated)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
response.WriteError(http.StatusInternalServerError, apierrors.NewInternalError(err))
|
||||
api.HandleInternalError(response, req, apierrors.NewInternalError(err))
|
||||
return
|
||||
}
|
||||
|
||||
if err = h.loginRecorder.RecordLogin(authenticated.GetName(), iamv1alpha2.Token, "", nil, req.Request); err != nil {
|
||||
klog.Error(err)
|
||||
response.WriteError(http.StatusInternalServerError, apierrors.NewInternalError(err))
|
||||
return
|
||||
requestInfo, _ := request.RequestInfoFrom(req.Request.Context())
|
||||
if err = h.loginRecorder.RecordLogin(authenticated.GetName(), iamv1alpha2.Token, provider, requestInfo.SourceIP, requestInfo.UserAgent, nil); err != nil {
|
||||
klog.Errorf("Failed to record successful login for user %s, error: %v", username, err)
|
||||
}
|
||||
|
||||
response.WriteEntity(result)
|
||||
@@ -313,22 +269,46 @@ func (h *handler) passwordGrant(username string, password string, req *restful.R
|
||||
func (h *handler) refreshTokenGrant(req *restful.Request, response *restful.Response) {
|
||||
refreshToken, err := req.BodyParameter("refresh_token")
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
api.HandleBadRequest(response, req, err)
|
||||
api.HandleBadRequest(response, req, apierrors.NewBadRequest(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
authenticated, err := h.tokenOperator.Verify(refreshToken)
|
||||
if err != nil {
|
||||
err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))
|
||||
response.WriteError(http.StatusUnauthorized, err)
|
||||
api.HandleUnauthorized(response, req, apierrors.NewUnauthorized(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// update token after registration
|
||||
if authenticated.GetName() == iamv1alpha2.PreRegistrationUser &&
|
||||
authenticated.GetExtra() != nil &&
|
||||
len(authenticated.GetExtra()[iamv1alpha2.ExtraIdentityProvider]) > 0 &&
|
||||
len(authenticated.GetExtra()[iamv1alpha2.ExtraUID]) > 0 {
|
||||
|
||||
idp := authenticated.GetExtra()[iamv1alpha2.ExtraIdentityProvider][0]
|
||||
uid := authenticated.GetExtra()[iamv1alpha2.ExtraUID][0]
|
||||
queryParam := query.New()
|
||||
queryParam.LabelSelector = labels.SelectorFromSet(labels.Set{
|
||||
iamv1alpha2.IdentifyProviderLabel: idp,
|
||||
iamv1alpha2.OriginUIDLabel: uid}).String()
|
||||
result, err := h.im.ListUsers(queryParam)
|
||||
if err != nil {
|
||||
api.HandleInternalError(response, req, apierrors.NewInternalError(err))
|
||||
return
|
||||
}
|
||||
if len(result.Items) != 1 {
|
||||
err := apierrors.NewUnauthorized("authenticated user does not exist")
|
||||
api.HandleUnauthorized(response, req, apierrors.NewUnauthorized(err.Error()))
|
||||
return
|
||||
}
|
||||
authenticated = &user.DefaultInfo{Name: result.Items[0].(*iamv1alpha2.User).Name}
|
||||
}
|
||||
|
||||
result, err := h.tokenOperator.IssueTo(authenticated)
|
||||
if err != nil {
|
||||
err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))
|
||||
response.WriteError(http.StatusUnauthorized, err)
|
||||
api.HandleUnauthorized(response, req, apierrors.NewUnauthorized(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -20,10 +20,10 @@ 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/apiserver/authentication/oauth"
|
||||
authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
|
||||
"kubesphere.io/kubesphere/pkg/constants"
|
||||
"kubesphere.io/kubesphere/pkg/models/auth"
|
||||
"kubesphere.io/kubesphere/pkg/models/iam/im"
|
||||
"net/http"
|
||||
)
|
||||
@@ -34,22 +34,28 @@ import (
|
||||
// Most authentication integrations place an authenticating proxy in front of this endpoint, or configure ks-apiserver
|
||||
// to validate credentials against a backing identity provider.
|
||||
// Requests to <ks-apiserver>/oauth/authorize can come from user-agents that cannot display interactive login pages, such as the CLI.
|
||||
func AddToContainer(c *restful.Container, im im.IdentityManagementInterface, tokenOperator im.TokenManagementInterface, authenticator im.PasswordAuthenticator, loginRecorder im.LoginRecorder, options *authoptions.AuthenticationOptions) error {
|
||||
func AddToContainer(c *restful.Container, im im.IdentityManagementInterface,
|
||||
tokenOperator auth.TokenManagementInterface,
|
||||
passwordAuthenticator auth.PasswordAuthenticator,
|
||||
oauth2Authenticator auth.OAuth2Authenticator,
|
||||
loginRecorder auth.LoginRecorder,
|
||||
options *authoptions.AuthenticationOptions) error {
|
||||
|
||||
ws := &restful.WebService{}
|
||||
ws.Path("/oauth").
|
||||
Consumes(restful.MIME_JSON).
|
||||
Produces(restful.MIME_JSON)
|
||||
|
||||
handler := newHandler(im, tokenOperator, authenticator, loginRecorder, options)
|
||||
handler := newHandler(im, tokenOperator, passwordAuthenticator, oauth2Authenticator, loginRecorder, options)
|
||||
|
||||
// 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{}).
|
||||
Reads(TokenReview{}).
|
||||
To(handler.TokenReview).
|
||||
Returns(http.StatusOK, api.StatusOK, auth.TokenReview{}).
|
||||
Returns(http.StatusOK, api.StatusOK, TokenReview{}).
|
||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AuthenticationTag}))
|
||||
|
||||
// Only support implicit grant flow
|
||||
@@ -98,7 +104,7 @@ func AddToContainer(c *restful.Container, im im.IdentityManagementInterface, tok
|
||||
"otherwise, REQUIRED. The scope of the access token as described by [RFC6479] Section 3.3.").Required(false)).
|
||||
Param(ws.QueryParameter("state", "if the \"state\" parameter was present in the client authorization request."+
|
||||
"The exact value received from the client.").Required(true)).
|
||||
To(handler.oAuthCallBack).
|
||||
To(handler.oauthCallBack).
|
||||
Returns(http.StatusOK, api.StatusOK, oauth.Token{}).
|
||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AuthenticationTag}))
|
||||
|
||||
@@ -113,7 +119,7 @@ func AddToContainer(c *restful.Container, im im.IdentityManagementInterface, tok
|
||||
To(handler.Login).
|
||||
Deprecate().
|
||||
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(auth.LoginRequest{}).
|
||||
Reads(LoginRequest{}).
|
||||
Returns(http.StatusOK, api.StatusOK, oauth.Token{}).
|
||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AuthenticationTag}))
|
||||
|
||||
|
||||
@@ -21,26 +21,25 @@ import (
|
||||
"k8s.io/klog"
|
||||
"kubesphere.io/kubesphere/pkg/api"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/query"
|
||||
"kubesphere.io/kubesphere/pkg/informers"
|
||||
"kubesphere.io/kubesphere/pkg/models/components"
|
||||
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha2"
|
||||
resourcev1alpha2 "kubesphere.io/kubesphere/pkg/models/resources/v1alpha2/resource"
|
||||
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/resource"
|
||||
resourcev1alpha3 "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/resource"
|
||||
"kubesphere.io/kubesphere/pkg/server/params"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
resourceGetterV1alpha3 *resource.ResourceGetter
|
||||
resourceGetterV1alpha3 *resourcev1alpha3.ResourceGetter
|
||||
resourcesGetterV1alpha2 *resourcev1alpha2.ResourceGetter
|
||||
componentsGetter components.ComponentsGetter
|
||||
}
|
||||
|
||||
func New(factory informers.InformerFactory) *Handler {
|
||||
func New(resourceGetterV1alpha3 *resourcev1alpha3.ResourceGetter, resourcesGetterV1alpha2 *resourcev1alpha2.ResourceGetter, componentsGetter components.ComponentsGetter) *Handler {
|
||||
return &Handler{
|
||||
resourceGetterV1alpha3: resource.NewResourceGetter(factory),
|
||||
resourcesGetterV1alpha2: resourcev1alpha2.NewResourceGetter(factory),
|
||||
componentsGetter: components.NewComponentsGetter(factory.KubernetesSharedInformerFactory()),
|
||||
resourceGetterV1alpha3: resourceGetterV1alpha3,
|
||||
resourcesGetterV1alpha2: resourcesGetterV1alpha2,
|
||||
componentsGetter: componentsGetter,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +54,7 @@ func (h *Handler) handleGetResources(request *restful.Request, response *restful
|
||||
return
|
||||
}
|
||||
|
||||
if err != resource.ErrResourceNotSupported {
|
||||
if err != resourcev1alpha3.ErrResourceNotSupported {
|
||||
klog.Error(err, resourceType)
|
||||
api.HandleInternalError(response, nil, err)
|
||||
return
|
||||
@@ -87,7 +86,7 @@ func (h *Handler) handleListResources(request *restful.Request, response *restfu
|
||||
return
|
||||
}
|
||||
|
||||
if err != resource.ErrResourceNotSupported {
|
||||
if err != resourcev1alpha3.ErrResourceNotSupported {
|
||||
klog.Error(err, resourceType)
|
||||
api.HandleInternalError(response, nil, err)
|
||||
return
|
||||
|
||||
@@ -29,7 +29,10 @@ import (
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/query"
|
||||
fakeks "kubesphere.io/kubesphere/pkg/client/clientset/versioned/fake"
|
||||
"kubesphere.io/kubesphere/pkg/informers"
|
||||
"kubesphere.io/kubesphere/pkg/models/components"
|
||||
resourcev1alpha2 "kubesphere.io/kubesphere/pkg/models/resources/v1alpha2/resource"
|
||||
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/resource"
|
||||
resourcev1alpha3 "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/resource"
|
||||
fakeapp "sigs.k8s.io/application/pkg/client/clientset/versioned/fake"
|
||||
"testing"
|
||||
)
|
||||
@@ -88,7 +91,9 @@ func TestResourceV1alpha2Fallback(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
handler := New(factory)
|
||||
handler := New(resourcev1alpha3.NewResourceGetter(factory),
|
||||
resourcev1alpha2.NewResourceGetter(factory),
|
||||
components.NewComponentsGetter(factory.KubernetesSharedInformerFactory()))
|
||||
|
||||
for _, test := range tests {
|
||||
got, err := listResources(test.namespace, test.resource, test.query, handler)
|
||||
|
||||
@@ -25,6 +25,9 @@ import (
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/query"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/runtime"
|
||||
"kubesphere.io/kubesphere/pkg/informers"
|
||||
"kubesphere.io/kubesphere/pkg/models/components"
|
||||
resourcev1alpha2 "kubesphere.io/kubesphere/pkg/models/resources/v1alpha2/resource"
|
||||
resourcev1alpha3 "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/resource"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
@@ -47,7 +50,9 @@ func Resource(resource string) schema.GroupResource {
|
||||
func AddToContainer(c *restful.Container, informerFactory informers.InformerFactory) error {
|
||||
|
||||
webservice := runtime.NewWebService(GroupVersion)
|
||||
handler := New(informerFactory)
|
||||
handler := New(resourcev1alpha3.NewResourceGetter(informerFactory),
|
||||
resourcev1alpha2.NewResourceGetter(informerFactory),
|
||||
components.NewComponentsGetter(informerFactory.KubernetesSharedInformerFactory()))
|
||||
|
||||
webservice.Route(webservice.GET("/{resources}").
|
||||
To(handler.handleListResources).
|
||||
|
||||
Reference in New Issue
Block a user