@@ -6,7 +6,7 @@ import (
|
||||
"fmt"
|
||||
cliflag "k8s.io/component-base/cli/flag"
|
||||
"k8s.io/klog"
|
||||
"kubesphere.io/kubesphere/pkg/api/iam"
|
||||
"kubesphere.io/kubesphere/pkg/api/auth"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver"
|
||||
"kubesphere.io/kubesphere/pkg/informers"
|
||||
genericoptions "kubesphere.io/kubesphere/pkg/server/options"
|
||||
@@ -40,7 +40,7 @@ type ServerRunOptions struct {
|
||||
LoggingOptions *esclient.Options
|
||||
LdapOptions *ldap.Options
|
||||
CacheOptions *cache.Options
|
||||
AuthenticateOptions *iam.AuthenticationOptions
|
||||
AuthenticateOptions *auth.AuthenticationOptions
|
||||
|
||||
//
|
||||
DebugMode bool
|
||||
@@ -61,7 +61,7 @@ func NewServerRunOptions() *ServerRunOptions {
|
||||
LoggingOptions: esclient.NewElasticSearchOptions(),
|
||||
LdapOptions: ldap.NewOptions(),
|
||||
CacheOptions: cache.NewRedisOptions(),
|
||||
AuthenticateOptions: iam.NewAuthenticateOptions(),
|
||||
AuthenticateOptions: auth.NewAuthenticateOptions(),
|
||||
}
|
||||
|
||||
return &s
|
||||
|
||||
@@ -1,4 +1,22 @@
|
||||
package iam
|
||||
/*
|
||||
*
|
||||
* 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 auth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -3,7 +3,6 @@ package token
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"kubesphere.io/kubesphere/pkg/api/iam"
|
||||
"kubesphere.io/kubesphere/pkg/server/errors"
|
||||
"time"
|
||||
)
|
||||
@@ -13,8 +12,9 @@ const DefaultIssuerName = "kubesphere"
|
||||
var errInvalidToken = errors.New("invalid token")
|
||||
|
||||
type claims struct {
|
||||
Username string `json:"username"`
|
||||
UID string `json:"uid"`
|
||||
Username string `json:"username"`
|
||||
UID string `json:"uid"`
|
||||
Groups []string `json:"groups"`
|
||||
// Currently, we are not using any field in jwt.StandardClaims
|
||||
jwt.StandardClaims
|
||||
}
|
||||
@@ -37,13 +37,14 @@ func (s *jwtTokenIssuer) Verify(tokenString string) (User, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &iam.User{Username: clm.Username, UID: clm.UID}, nil
|
||||
return &AuthUser{Name: clm.Username, UID: clm.UID, Groups: clm.Groups}, nil
|
||||
}
|
||||
|
||||
func (s *jwtTokenIssuer) IssueTo(user User) (string, error) {
|
||||
clm := &claims{
|
||||
Username: user.GetName(),
|
||||
UID: user.GetUID(),
|
||||
Groups: user.GetGroups(),
|
||||
StandardClaims: jwt.StandardClaims{
|
||||
IssuedAt: time.Now().Unix(),
|
||||
Issuer: s.name,
|
||||
@@ -2,7 +2,6 @@ package token
|
||||
|
||||
import (
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"kubesphere.io/kubesphere/pkg/api/iam"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -25,9 +24,9 @@ func TestJwtTokenIssuer(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
user := &iam.User{
|
||||
Username: testCase.name,
|
||||
UID: testCase.uid,
|
||||
user := &AuthUser{
|
||||
Name: testCase.name,
|
||||
UID: testCase.uid,
|
||||
}
|
||||
|
||||
t.Run(testCase.description, func(t *testing.T) {
|
||||
30
pkg/api/auth/token/user.go
Normal file
30
pkg/api/auth/token/user.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package token
|
||||
|
||||
type User interface {
|
||||
// Name
|
||||
GetName() string
|
||||
|
||||
// UID
|
||||
GetUID() string
|
||||
|
||||
// Groups
|
||||
GetGroups() []string
|
||||
}
|
||||
|
||||
type AuthUser struct {
|
||||
Name string
|
||||
UID string
|
||||
Groups []string
|
||||
}
|
||||
|
||||
func (a AuthUser) GetName() string {
|
||||
return a.Name
|
||||
}
|
||||
|
||||
func (a AuthUser) GetUID() string {
|
||||
return a.UID
|
||||
}
|
||||
|
||||
func (a AuthUser) GetGroups() []string {
|
||||
return a.Groups
|
||||
}
|
||||
48
pkg/api/auth/types.go
Normal file
48
pkg/api/auth/types.go
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
*
|
||||
* 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 auth
|
||||
|
||||
import "fmt"
|
||||
|
||||
const (
|
||||
KindTokenReview = "TokenReview"
|
||||
)
|
||||
|
||||
type Spec struct {
|
||||
Token string `json:"token" description:"access token"`
|
||||
}
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
func (request *TokenReview) Validate() error {
|
||||
if request.Spec == nil || request.Spec.Token == "" {
|
||||
return fmt.Errorf("token must not be null")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package token
|
||||
|
||||
type User interface {
|
||||
// Name
|
||||
GetName() string
|
||||
|
||||
// UID
|
||||
GetUID() string
|
||||
}
|
||||
@@ -16,25 +16,16 @@ type User struct {
|
||||
Password string `json:"password,omitempty"`
|
||||
}
|
||||
|
||||
func NewUser() *User {
|
||||
return &User{
|
||||
Username: "",
|
||||
UID: "",
|
||||
Email: "",
|
||||
Lang: "",
|
||||
Description: "",
|
||||
CreateTime: time.Time{},
|
||||
Groups: nil,
|
||||
Password: "",
|
||||
}
|
||||
}
|
||||
|
||||
func (u *User) GetName() string {
|
||||
return u.Username
|
||||
}
|
||||
|
||||
func (u *User) GetUID() string {
|
||||
return ""
|
||||
return u.UID
|
||||
}
|
||||
|
||||
func (u *User) GetGroups() []string {
|
||||
return u.Groups
|
||||
}
|
||||
|
||||
func (u *User) Validate() error {
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* 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 v1alpha2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"kubesphere.io/kubesphere/pkg/api/iam"
|
||||
"net/mail"
|
||||
)
|
||||
|
||||
const minPasswordLength = 6
|
||||
|
||||
type Spec struct {
|
||||
Token string `json:"token" description:"access token"`
|
||||
}
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
type UserDetail struct {
|
||||
*iam.User
|
||||
ClusterRole string `json:"cluster_role"`
|
||||
}
|
||||
|
||||
type CreateUserRequest struct {
|
||||
*UserDetail
|
||||
}
|
||||
|
||||
func (request *CreateUserRequest) Validate() error {
|
||||
if request.Username == "" {
|
||||
return fmt.Errorf("username must not be empty")
|
||||
}
|
||||
|
||||
// Parses a single RFC 5322 address, e.g. "Barry Gibbs <bg@example.com>"
|
||||
if _, err := mail.ParseAddress(request.Email); err != nil {
|
||||
return fmt.Errorf("invalid email: %s", request.Email)
|
||||
}
|
||||
|
||||
if len(request.Password) < minPasswordLength {
|
||||
return fmt.Errorf("password must be at least %d characters long", minPasswordLength)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type ModifyUserRequest struct {
|
||||
*UserDetail
|
||||
CurrentPassword string `json:"current_password,omitempty" description:"this is necessary if you need to change your password"`
|
||||
}
|
||||
|
||||
func (request *TokenReview) Validate() error {
|
||||
if request.Spec == nil || request.Spec.Token == "" {
|
||||
return fmt.Errorf("token must not be null")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (request ModifyUserRequest) Validate() error {
|
||||
|
||||
// Parses a single RFC 5322 address, e.g. "Barry Gibbs <bg@example.com>"
|
||||
if _, err := mail.ParseAddress(request.Email); err != nil {
|
||||
return fmt.Errorf("invalid email: %s", request.Email)
|
||||
}
|
||||
|
||||
if request.Password != "" {
|
||||
if len(request.Password) < minPasswordLength {
|
||||
return fmt.Errorf("password must be at least %d characters long", minPasswordLength)
|
||||
}
|
||||
if len(request.CurrentPassword) < minPasswordLength {
|
||||
return fmt.Errorf("password must be at least %d characters long", minPasswordLength)
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ListUserResponse struct {
|
||||
Items []*UserDetail `json:"items"`
|
||||
TotalCount int `json:"total_count"`
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
unionauth "k8s.io/apiserver/pkg/authentication/request/union"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
||||
"k8s.io/klog"
|
||||
"kubesphere.io/kubesphere/pkg/api/iam"
|
||||
"kubesphere.io/kubesphere/pkg/api/auth"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/authentication/authenticators/jwttoken"
|
||||
authenticationrequest "kubesphere.io/kubesphere/pkg/apiserver/authentication/request"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizerfactory"
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
iamv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/iam/v1alpha2"
|
||||
loggingv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/logging/v1alpha2"
|
||||
monitoringv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/monitoring/v1alpha2"
|
||||
"kubesphere.io/kubesphere/pkg/kapis/oauth"
|
||||
openpitrixv1 "kubesphere.io/kubesphere/pkg/kapis/openpitrix/v1"
|
||||
operationsv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/operations/v1alpha2"
|
||||
resourcesv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/resources/v1alpha2"
|
||||
@@ -69,7 +70,7 @@ type APIServer struct {
|
||||
//
|
||||
Server *http.Server
|
||||
|
||||
AuthenticateOptions *iam.AuthenticationOptions
|
||||
AuthenticateOptions *auth.AuthenticationOptions
|
||||
|
||||
// webservice container, where all webservice defines
|
||||
container *restful.Container
|
||||
@@ -141,6 +142,7 @@ func (s *APIServer) installKubeSphereAPIs() {
|
||||
urlruntime.Must(tenantv1alpha2.AddToContainer(s.container, s.KubernetesClient, s.InformerFactory, s.DBClient.Database()))
|
||||
urlruntime.Must(terminalv1alpha2.AddToContainer(s.container, s.KubernetesClient.Kubernetes(), s.KubernetesClient.Config()))
|
||||
urlruntime.Must(iamv1alpha2.AddToContainer(s.container, s.KubernetesClient, s.InformerFactory, s.LdapClient, s.CacheClient, s.AuthenticateOptions))
|
||||
urlruntime.Must(oauth.AddToContainer(s.container, s.AuthenticateOptions))
|
||||
urlruntime.Must(servicemeshv1alpha2.AddToContainer(s.container))
|
||||
}
|
||||
|
||||
|
||||
@@ -2,10 +2,9 @@ package jwttoken
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"kubesphere.io/kubesphere/pkg/api/iam/token"
|
||||
"kubesphere.io/kubesphere/pkg/api/auth/token"
|
||||
"kubesphere.io/kubesphere/pkg/server/errors"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/cache"
|
||||
)
|
||||
@@ -50,9 +49,4 @@ func (t *tokenAuthenticator) AuthenticateToken(ctx context.Context, token string
|
||||
Groups: []string{user.AllAuthenticated},
|
||||
},
|
||||
}, true, nil
|
||||
|
||||
}
|
||||
|
||||
func tokenKeyForUsername(username, token string) string {
|
||||
return fmt.Sprintf("kubesphere:users:%s:token:%s", username, token)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"github.com/emicklei/go-restful"
|
||||
"github.com/spf13/viper"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"kubesphere.io/kubesphere/pkg/api/iam"
|
||||
"kubesphere.io/kubesphere/pkg/api/auth"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/runtime"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/alerting"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/cache"
|
||||
@@ -78,7 +78,7 @@ type Config struct {
|
||||
// Options below are only loaded from configuration file, no command line flags for these options now.
|
||||
KubeSphereOptions *kubesphere.Options `json:"-" yaml:"kubesphere,omitempty" mapstructure:"kubesphere"`
|
||||
|
||||
AuthenticateOptions *iam.AuthenticationOptions `json:"authentication,omitempty" yaml:"authenticate,omitempty" mapstructure:"authenticate"`
|
||||
AuthenticateOptions *auth.AuthenticationOptions `json:"authentication,omitempty" yaml:"authenticate,omitempty" mapstructure:"authenticate"`
|
||||
|
||||
// Options used for enabling components, not actually used now. Once we switch Alerting/Notification API to kubesphere,
|
||||
// we can add these options to kubesphere command lines
|
||||
@@ -103,7 +103,7 @@ func New() *Config {
|
||||
AlertingOptions: alerting.NewAlertingOptions(),
|
||||
NotificationOptions: notification.NewNotificationOptions(),
|
||||
LoggingOptions: elasticsearch.NewElasticSearchOptions(),
|
||||
AuthenticateOptions: iam.NewAuthenticateOptions(),
|
||||
AuthenticateOptions: auth.NewAuthenticateOptions(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"gopkg.in/yaml.v2"
|
||||
"io/ioutil"
|
||||
iamapi "kubesphere.io/kubesphere/pkg/api/iam"
|
||||
"kubesphere.io/kubesphere/pkg/api/auth"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/alerting"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/cache"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/devops/jenkins"
|
||||
@@ -106,7 +106,7 @@ func newTestConfig() *Config {
|
||||
NotificationOptions: ¬ification.Options{
|
||||
Endpoint: "http://notification.kubesphere-alerting-system.svc:9200",
|
||||
},
|
||||
AuthenticateOptions: &iamapi.AuthenticationOptions{
|
||||
AuthenticateOptions: &auth.AuthenticationOptions{
|
||||
AuthenticateRateLimiterMaxTries: 5,
|
||||
AuthenticateRateLimiterDuration: 30 * time.Minute,
|
||||
MaxAuthenticateRetries: 6,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -23,8 +23,9 @@ import (
|
||||
"golang.org/x/oauth2"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
"k8s.io/klog"
|
||||
"kubesphere.io/kubesphere/pkg/api/auth"
|
||||
"kubesphere.io/kubesphere/pkg/api/auth/token"
|
||||
"kubesphere.io/kubesphere/pkg/api/iam"
|
||||
"kubesphere.io/kubesphere/pkg/api/iam/token"
|
||||
"kubesphere.io/kubesphere/pkg/models"
|
||||
"kubesphere.io/kubesphere/pkg/server/params"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/cache"
|
||||
@@ -35,17 +36,16 @@ import (
|
||||
type IdentityManagementInterface interface {
|
||||
CreateUser(user *iam.User) (*iam.User, error)
|
||||
DeleteUser(username string) error
|
||||
ModifyUser(user *iam.User) (*iam.User, error)
|
||||
DescribeUser(username string) (*iam.User, error)
|
||||
Login(username, password, ip string) (*oauth2.Token, error)
|
||||
ModifyUser(user *iam.User) (*iam.User, error)
|
||||
ListUsers(conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error)
|
||||
GetUserRoles(username string) ([]*rbacv1.Role, error)
|
||||
GetUserRole(namespace string, username string) (*rbacv1.Role, error)
|
||||
VerifyToken(token string) (*iam.User, error)
|
||||
}
|
||||
|
||||
type imOperator struct {
|
||||
authenticateOptions *iam.AuthenticationOptions
|
||||
authenticateOptions *auth.AuthenticationOptions
|
||||
ldapClient ldap.Interface
|
||||
cacheClient cache.Interface
|
||||
issuer token.Issuer
|
||||
@@ -57,7 +57,7 @@ var (
|
||||
UserNotExists = errors.New("user not exists")
|
||||
)
|
||||
|
||||
func NewIMOperator(ldapClient ldap.Interface, cacheClient cache.Interface, options *iam.AuthenticationOptions) *imOperator {
|
||||
func NewIMOperator(ldapClient ldap.Interface, cacheClient cache.Interface, options *auth.AuthenticationOptions) *imOperator {
|
||||
return &imOperator{
|
||||
ldapClient: ldapClient,
|
||||
cacheClient: cacheClient,
|
||||
|
||||
Reference in New Issue
Block a user