From 9d65fb853358ba599138edd6f071f3a42a2b9482 Mon Sep 17 00:00:00 2001 From: caryxychen Date: Tue, 19 Apr 2022 11:47:12 +0800 Subject: [PATCH] support cloud industry oidc authn and userinfo --- cmd/tke-auth-api/app/config/config.go | 50 +++- cmd/tke-auth-api/app/options/auth.go | 76 +++-- go.mod | 1 + go.sum | 2 + .../authentication/authenticator/oidc/oidc.go | 19 ++ pkg/auth/apiserver/apiserver.go | 4 +- pkg/auth/authentication/oidc/claims/claims.go | 2 + .../cloudindustry/cloudindustry.go | 268 ++++++++++++++++++ .../cloudindustry/hook_cloudindustry.go | 85 ++++++ .../identityprovider/storage/storage.go | 11 +- pkg/auth/util/dex/conn.go | 2 + pkg/auth/util/user.go | 4 +- .../registry/portal/storage/storage.go | 4 +- pkg/business/registry/util/auth.go | 1 + 14 files changed, 482 insertions(+), 47 deletions(-) create mode 100644 pkg/auth/authentication/oidc/identityprovider/cloudindustry/cloudindustry.go create mode 100644 pkg/auth/authentication/oidc/identityprovider/cloudindustry/hook_cloudindustry.go diff --git a/cmd/tke-auth-api/app/config/config.go b/cmd/tke-auth-api/app/config/config.go index df919b5e67..404fb9fcee 100644 --- a/cmd/tke-auth-api/app/config/config.go +++ b/cmd/tke-auth-api/app/config/config.go @@ -26,6 +26,7 @@ import ( "regexp" "strings" "time" + "tkestack.io/tke/pkg/auth/authentication/oidc/identityprovider/cloudindustry" "github.com/casbin/casbin/v2" casbinlog "github.com/casbin/casbin/v2/log" @@ -185,6 +186,11 @@ func CreateConfigFromOptions(serverName string, opts *options.Options) (*Config, if err != nil { return nil, err } + case cloudindustry.ConnectorType: + err = setupCloudIndustryConnector(opts.Auth) + if err != nil { + return nil, err + } default: log.Warn("Unknown init tenant type", log.String("type", opts.Auth.InitTenantType)) } @@ -362,11 +368,30 @@ func setupLDAPConnector(auth *options.AuthOptions) error { if err != nil { return err } + identityprovider.SetIdentityProvider(auth.InitTenantID, idp) + return nil +} + +func setupCloudIndustryConnector(auth *options.AuthOptions) error { + log.Info("setup cloud industry connector", log.Any("tenantID", auth.InitTenantID)) + const errFmt = "failed to load cloud industry config file %s, error %v" + // compute absolute path based on current working dir + cloudIndustryConfigFile, err := filepath.Abs(auth.CloudIndustryConfigFile) + if err != nil { + return fmt.Errorf(errFmt, cloudIndustryConfigFile, err) + } - if _, ok := identityprovider.GetIdentityProvider(auth.InitTenantID); !ok { - identityprovider.SetIdentityProvider(auth.InitTenantID, idp) + bytes, err := ioutil.ReadFile(cloudIndustryConfigFile) + var sdkConfig cloudindustry.SDKConfig + if err := json.Unmarshal(bytes, &sdkConfig); err != nil { + return fmt.Errorf(errFmt, cloudIndustryConfigFile, err) } + idp, err := cloudindustry.NewCloudIndustryIdentityProvider(auth.InitTenantID, auth.InitIDPAdmins, &sdkConfig) + if err != nil { + return err + } + identityprovider.SetIdentityProvider(auth.InitTenantID, idp) return nil } @@ -383,20 +408,25 @@ func setupDefaultClient(store dexstorage.Storage, auth *options.AuthOptions) err continue } } + cli := dexstorage.Client{ + ID: auth.InitClientID, + Secret: auth.InitClientSecret, + Name: auth.InitClientID, + RedirectURIs: auth.InitClientRedirectUris, + } if !exists { - cli := dexstorage.Client{ - ID: auth.InitClientID, - Secret: auth.InitClientSecret, - Name: auth.InitClientID, - RedirectURIs: auth.InitClientRedirectUris, - } - // Create a default connector if err = store.CreateClient(cli); err != nil && err != dexstorage.ErrAlreadyExists { return err } + } else { + // Update default connector + if err = store.UpdateClient(auth.InitClientID, func(old dexstorage.Client) (dexstorage.Client, error) { + return cli, nil + }); err != nil { + return err + } } - return nil } diff --git a/cmd/tke-auth-api/app/options/auth.go b/cmd/tke-auth-api/app/options/auth.go index 352f175ba7..6c8e9731bc 100644 --- a/cmd/tke-auth-api/app/options/auth.go +++ b/cmd/tke-auth-api/app/options/auth.go @@ -21,6 +21,7 @@ package options import ( "fmt" "time" + "tkestack.io/tke/pkg/auth/authentication/oidc/identityprovider/cloudindustry" "tkestack.io/tke/pkg/auth/authentication/oidc/identityprovider/ldap" "tkestack.io/tke/pkg/auth/authentication/oidc/identityprovider/local" @@ -30,43 +31,46 @@ import ( ) const ( - flagAuthAssetsPath = "assets-path" - flagAuthIDTokenTimeout = "id-token-timeout" - flagAuthInitTenantType = "init-tenant-type" - flagAuthInitTenantID = "init-tenant-id" - flagAuthInitIDPAdmins = "init-idp-administrators" - flagAuthLDAPConfigFile = "ldap-config-file" - flagAuthInitClientID = "init-client-id" - flagAuthInitClientSecret = "init-client-secret" - flagAuthInitClientRedirectUris = "init-client-redirect-uris" - flagAuthPasswordGrantConnID = "password-grant-conn-id" + flagAuthAssetsPath = "assets-path" + flagAuthIDTokenTimeout = "id-token-timeout" + flagAuthInitTenantType = "init-tenant-type" + flagAuthInitTenantID = "init-tenant-id" + flagAuthInitIDPAdmins = "init-idp-administrators" + flagAuthLDAPConfigFile = "ldap-config-file" + flagAuthCloudIndustryConfigFile = "cloudindustry-config-file" + flagAuthInitClientID = "init-client-id" + flagAuthInitClientSecret = "init-client-secret" + flagAuthInitClientRedirectUris = "init-client-redirect-uris" + flagAuthPasswordGrantConnID = "password-grant-conn-id" ) const ( - configAuthAssetsPath = "auth.assets_path" - configAuthIDTokenTimeout = "auth.id_token_timeout" - configAuthInitTenantType = "auth.init_tenant_type" - configAuthInitTenantID = "auth.init_tenant_id" - configAuthInitIDPAdmins = "auth.init_idp_administrators" - configAuthLDAPConfigFile = "auth.ldap_config_file" - configAuthInitClientID = "auth.init_client_id" - configAuthInitClientSecret = "auth.init_client_secret" - configAuthInitClientRedirectUris = "auth.init_client_redirect_uris" - configAuthPasswordGrantConnID = "auth.password_grant_conn_id" + configAuthAssetsPath = "auth.assets_path" + configAuthIDTokenTimeout = "auth.id_token_timeout" + configAuthInitTenantType = "auth.init_tenant_type" + configAuthInitTenantID = "auth.init_tenant_id" + configAuthInitIDPAdmins = "auth.init_idp_administrators" + configAuthLDAPConfigFile = "auth.ldap_config_file" + configAuthCloudIndustryConfigFile = "auth.cloudindustry_config_file" + configAuthInitClientID = "auth.init_client_id" + configAuthInitClientSecret = "auth.init_client_secret" + configAuthInitClientRedirectUris = "auth.init_client_redirect_uris" + configAuthPasswordGrantConnID = "auth.password_grant_conn_id" ) // AuthOptions contains configuration items related to auth attributes. type AuthOptions struct { - AssetsPath string - IDTokenTimeout time.Duration - InitTenantType string - InitTenantID string - InitIDPAdmins []string - LdapConfigFile string - InitClientID string - InitClientSecret string - InitClientRedirectUris []string - PasswordGrantConnID string + AssetsPath string + IDTokenTimeout time.Duration + InitTenantType string + InitTenantID string + InitIDPAdmins []string + LdapConfigFile string + CloudIndustryConfigFile string + InitClientID string + InitClientSecret string + InitClientRedirectUris []string + PasswordGrantConnID string } // NewAuthOptions creates a AuthOptions object with default parameters. @@ -102,9 +106,13 @@ func (o *AuthOptions) AddFlags(fs *pflag.FlagSet) { _ = viper.BindPFlag(configAuthInitIDPAdmins, fs.Lookup(flagAuthInitIDPAdmins)) fs.String(flagAuthLDAPConfigFile, o.LdapConfigFile, - "Config file path for ldap ldap, must specify if init-tenant-type is ldap.") + "Config file path for ldap identity provider, must specify if init-tenant-type is ldap.") _ = viper.BindPFlag(configAuthLDAPConfigFile, fs.Lookup(flagAuthLDAPConfigFile)) + fs.String(flagAuthCloudIndustryConfigFile, o.CloudIndustryConfigFile, + "Config file path for cloud industry identity provider, must specify if init-tenant-type is cloudindustry.") + _ = viper.BindPFlag(configAuthCloudIndustryConfigFile, fs.Lookup(flagAuthCloudIndustryConfigFile)) + fs.String(flagAuthInitClientID, o.InitClientID, "Default client id will be created when started.") _ = viper.BindPFlag(configAuthInitClientID, fs.Lookup(flagAuthInitClientID)) @@ -140,6 +148,12 @@ func (o *AuthOptions) ApplyFlags() []error { if o.InitTenantType == ldap.ConnectorType && o.LdapConfigFile == "" { errs = append(errs, fmt.Errorf("--%s must be specified for ldap type tenant", flagAuthLDAPConfigFile)) } + + o.CloudIndustryConfigFile = viper.GetString(configAuthCloudIndustryConfigFile) + if o.InitTenantType == cloudindustry.ConnectorType && o.CloudIndustryConfigFile == "" { + errs = append(errs, fmt.Errorf("--%s must be specified for ldap type tenant", flagAuthCloudIndustryConfigFile)) + } + o.InitTenantID = viper.GetString(configAuthInitTenantID) if len(o.InitTenantID) == 0 { errs = append(errs, fmt.Errorf("--%s must be specified", flagAuthInitTenantID)) diff --git a/go.mod b/go.mod index bae19ee927..398d1a5fe2 100644 --- a/go.mod +++ b/go.mod @@ -19,6 +19,7 @@ require ( github.com/aws/aws-sdk-go v1.40.37 github.com/bitly/go-simplejson v0.5.0 github.com/caddyserver/caddy v1.0.5 + github.com/caryxychen/cloudindustry-sdk-go v1.0.0 github.com/casbin/casbin/v2 v2.2.1 github.com/chartmuseum/helm-push v0.9.0 github.com/chartmuseum/storage v0.11.0 diff --git a/go.sum b/go.sum index e4fc757a06..323e028a61 100644 --- a/go.sum +++ b/go.sum @@ -265,6 +265,8 @@ github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3k github.com/caddyserver/caddy v1.0.5 h1:5B1Hs0UF2x2tggr2X9jL2qOZtDXbIWQb9YLbmlxHSuM= github.com/caddyserver/caddy v1.0.5/go.mod h1:AnFHB+/MrgRC+mJAvuAgQ38ePzw+wKeW0wzENpdQQKY= github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8= +github.com/caryxychen/cloudindustry-sdk-go v1.0.0 h1:bsdS6BP49K+VCzHbsa+hUEmOfryXuDpxdFrA24Z67vo= +github.com/caryxychen/cloudindustry-sdk-go v1.0.0/go.mod h1:ZqQXwtco2MGa2s6MAP6snl5bg/M+0rufb7E8zDuCn1M= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/casbin/casbin/v2 v2.2.1 h1:ijrSMfBfbQlDc4LnMTGtGYWmhKuuR6RLSQRj8vHrMzc= github.com/casbin/casbin/v2 v2.2.1/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= diff --git a/pkg/apiserver/authentication/authenticator/oidc/oidc.go b/pkg/apiserver/authentication/authenticator/oidc/oidc.go index f6e67596ae..6c8418a56d 100644 --- a/pkg/apiserver/authentication/authenticator/oidc/oidc.go +++ b/pkg/apiserver/authentication/authenticator/oidc/oidc.go @@ -698,6 +698,25 @@ type ProviderJSON struct { UserInfoURL string `json:"userinfo_endpoint"` } +// staticKeySet implements oidc.KeySet. +type staticKeySet struct {} + +func parseJWT(p string) ([]byte, error) { + parts := strings.Split(p, ".") + if len(parts) < 2 { + return nil, fmt.Errorf("oidc: malformed jwt, expected 3 parts got %d", len(parts)) + } + payload, err := base64.RawURLEncoding.DecodeString(parts[1]) + if err != nil { + return nil, fmt.Errorf("oidc: malformed jwt payload: %v", err) + } + return payload, nil +} + +func (s *staticKeySet) VerifySignature(ctx context.Context, jwt string) (payload []byte, err error) { + return parseJWT(jwt) +} + // NewIDTokenVerifier uses the OpenID Connect discovery mechanism to construct a verifier manually from a issuer URL. // The issuer is the URL identifier for the service. For example: "https://accounts.google.com" // or "https://login.salesforce.com". diff --git a/pkg/auth/apiserver/apiserver.go b/pkg/auth/apiserver/apiserver.go index 013f7110a2..ba69f17d00 100644 --- a/pkg/auth/apiserver/apiserver.go +++ b/pkg/auth/apiserver/apiserver.go @@ -23,6 +23,7 @@ import ( "fmt" "net/http" "time" + "tkestack.io/tke/pkg/auth/authentication/oidc/identityprovider/cloudindustry" "tkestack.io/tke/api/auth" "tkestack.io/tke/pkg/auth/authentication/oidc/identityprovider" @@ -228,11 +229,12 @@ func (c completedConfig) registerHooks(dexHandler *identityprovider.DexHander, s localIdpHook := local.NewLocalHookHandler(authClient) ldapIdpHook := ldap.NewLdapHookHandler(authClient) + cloudIndustryIdpHook := cloudindustry.NewCloudIndustryHookHandler(authClient) authVersionedClient := versionedclientset.NewForConfigOrDie(s.LoopbackClientConfig) adapterHook := local2.NewAdapterHookHandler(authVersionedClient, c.ExtraConfig.CasbinEnforcer, c.ExtraConfig.VersionedInformers, c.ExtraConfig.CasbinReloadInterval) - return []genericapiserver.PostStartHookProvider{dexHook, apiSigningKeyHook, localIdpHook, ldapIdpHook, adapterHook} + return []genericapiserver.PostStartHookProvider{dexHook, apiSigningKeyHook, localIdpHook, ldapIdpHook, cloudIndustryIdpHook, adapterHook} } // installCasbinPreStopHook is used to register preStop hook to stop casbin enforcer sync. diff --git a/pkg/auth/authentication/oidc/claims/claims.go b/pkg/auth/authentication/oidc/claims/claims.go index 0ff302bbd2..5341a558d1 100644 --- a/pkg/auth/authentication/oidc/claims/claims.go +++ b/pkg/auth/authentication/oidc/claims/claims.go @@ -37,6 +37,8 @@ type IDTokenClaims struct { // FederatedIDClaims represents the extension struct of claims. type FederatedIDClaims struct { + // 租户ID ConnectorID string `json:"connector_id,omitempty"` + // 用户ID UserID string `json:"user_id,omitempty"` } diff --git a/pkg/auth/authentication/oidc/identityprovider/cloudindustry/cloudindustry.go b/pkg/auth/authentication/oidc/identityprovider/cloudindustry/cloudindustry.go new file mode 100644 index 0000000000..be72d7e3bb --- /dev/null +++ b/pkg/auth/authentication/oidc/identityprovider/cloudindustry/cloudindustry.go @@ -0,0 +1,268 @@ +package cloudindustry + +import ( + "context" + "encoding/json" + "fmt" + "github.com/caryxychen/cloudindustry-sdk-go/client/iam" + iammodel "github.com/caryxychen/cloudindustry-sdk-go/model/iam" + "github.com/dexidp/dex/connector" + dexlog "github.com/dexidp/dex/pkg/log" + dexserver "github.com/dexidp/dex/server" + "github.com/pkg/errors" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metainternal "k8s.io/apimachinery/pkg/apis/meta/internalversion" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "strconv" + "sync" + "tkestack.io/tke/api/auth" + "tkestack.io/tke/pkg/apiserver/authentication/authenticator/oidc" + "tkestack.io/tke/pkg/auth/authentication/oidc/identityprovider" + "tkestack.io/tke/pkg/auth/util" + "tkestack.io/tke/pkg/util/log" +) + +const ( + ConnectorType = "cloudindustry" +) + +var store idpStore + +func init() { + // create dex identity provider for cloudindustry connector. + dexserver.ConnectorsConfig[ConnectorType] = func() dexserver.ConnectorConfig { + return new(identityProvider) + } + store = idpStore{ + cache: map[string]*identityProvider{}, + } +} + +// identityProvider is the third-party idp that support CloudIndustry. +type identityProvider struct { + tenantID string + administrators []string + config *SDKConfig + client *iam.Client +} + +type idpStore struct { + sync.Mutex + cache map[string]*identityProvider +} + +type SDKConfig struct { + SecretId string `json:"secret_id"` + SecretKey string `json:"secret_key"` + Endpoint string `json:"endpoint"` + Region string `json:"region"` + MasterId string `json:"master_id"` +} + +var ( + _ identityprovider.IdentityProvider = &identityProvider{} + _ identityprovider.UserGetter = &identityProvider{} + _ identityprovider.UserLister = &identityProvider{} + _ identityprovider.GroupGetter = &identityProvider{} + _ identityprovider.GroupLister = &identityProvider{} +) + +func NewCloudIndustryIdentityProvider(tenantID string, administrators []string, config *SDKConfig) (identityprovider.IdentityProvider, error) { + bytes, err := json.Marshal(config) + if err != nil { + log.Warnf("illegal sdk config, err '%v'", err) + return nil, err + } + + log.Infof("NewCloudIndustryIdentityProvider, tenantID '%s', administrators '%v', config '%s'", tenantID, administrators, string(bytes)) + cpf := profile.NewClientProfile() + cpf.HttpProfile.Endpoint = fmt.Sprintf("iam-api.%s", config.Endpoint) + client, err := iam.NewClient(common.NewCredential(config.SecretId, config.SecretKey), config.Region, cpf) + if err != nil { + log.Warnf("init client failed, err: '%v'", err) + return nil, err + } + + store.Mutex.Lock() + defer store.Mutex.Unlock() + store.cache[tenantID] = &identityProvider{ + tenantID: tenantID, + administrators: append(administrators, tenantID), + config: config, + client: client, + } + return store.cache[tenantID], nil +} + +func (i *identityProvider) Open(id string, logger dexlog.Logger) (connector.Connector, error) { + return &cloudIndustryConnector{tenantID: id}, nil +} + +func (i *identityProvider) Store() (*auth.IdentityProvider, error) { + if i.tenantID == "" { + return nil, fmt.Errorf("must specify tenantID") + } + bytes, err := json.Marshal(i.config) + if err != nil { + return nil, fmt.Errorf("mashal cloudindustry config failed: %+v", err) + } + return &auth.IdentityProvider{ + ObjectMeta: metav1.ObjectMeta{Name: i.tenantID}, + Spec: auth.IdentityProviderSpec{ + Name: i.tenantID, + Type: ConnectorType, + Administrators: i.administrators, + Config: string(bytes), + }, + }, nil +} + +type cloudIndustryConnector struct { + tenantID string +} + +func (c *cloudIndustryConnector) Prompt() string { + return "Username" +} + +func (c *cloudIndustryConnector) Login(ctx context.Context, scopes connector.Scopes, key, accessToken string) (connector.Identity, bool, error) { + ident := connector.Identity{} + if key != "access_token" { + return ident, false, nil + } + store.Mutex.Lock() + provider, ok := store.cache[c.tenantID] + if !ok { + err := errors.Errorf("unexpected error, can't find config for tenant '%s'", c.tenantID) + return ident, false, err + } + store.Mutex.Unlock() + + credential := common.NewCredential(provider.config.SecretId, provider.config.SecretKey) + credential.Token = accessToken + cpf := profile.NewClientProfile() + cpf.HttpProfile.Endpoint = fmt.Sprintf("iam-app-api.%s", provider.config.Endpoint) + cpf.HttpProfile.ReqTimeout = 30 + + log.Infof("%v, %v", credential, cpf) + client, err := iam.NewClient(credential, provider.config.Region, cpf) + if err != nil { + log.Warnf("failed to create cloudindustry client, err '%v'", err) + return ident, false, err + } + + account, err := client.DescribeAccount(iam.NewDescribeAccountRequest()) + if err != nil { + log.Warnf("failed to describe account, err '%v'", err) + return ident, false, err + } + if account.Response == nil { + log.Warnf("got empty accounts") + return ident, false, nil + } + userInfo := account.Response.Account + + ident.UserID = fmt.Sprintf("%d", userInfo.AccountId) + ident.Username = userInfo.AccountName + ident.PreferredUsername = userInfo.NickName + ident.Email = userInfo.ContactMail + ident.EmailVerified = userInfo.MailBindStatus == 1 + extra := map[string]string{ + oidc.TenantIDKey: c.tenantID, + } + ident.ConnectorData, _ = json.Marshal(extra) + log.Infof("user '%s' login successful", ident.Username) + return ident, true, nil +} + +func (p *cloudIndustryConnector) Refresh(ctx context.Context, s connector.Scopes, identity connector.Identity) (connector.Identity, error) { + return identity, nil +} + +func (i *identityProvider) GetUser(ctx context.Context, name string, options *metav1.GetOptions) (*auth.User, error) { + accountsRequest := iam.NewDescribeAccountsRequest() + accountsRequest.AccountIds = []string{name} + accounts, err := i.client.DescribeAccounts(accountsRequest) + if err != nil { + return nil, apierrors.NewInternalError(err) + } + if accounts.Response == nil || len(accounts.Response.Accounts) == 0 { + return nil, apierrors.NewNotFound(auth.Resource("user"), name) + } + return &i.usersFromAccounts(accounts.Response.Accounts).Items[0], nil +} + +func (i *identityProvider) ListUsers(ctx context.Context, options *metainternal.ListOptions) (*auth.UserList, error) { + subIdsRequest := iam.NewDescribeSubIdsRequest() + subIdsRequest.MasterId = i.config.MasterId + + ids, err := i.client.DescribeSubIds(subIdsRequest) + if err != nil { + log.Warnf("failed to describe subIds, err '%v'", err) + return nil, apierrors.NewInternalError(err) + } + log.Infof("get subids '%s'", ids) + + if ids.Response == nil || len(ids.Response.SubIds) == 0 { + return &auth.UserList{}, nil + } + + accountsRequest := iam.NewDescribeAccountsRequest() + accountsRequest.AccountIds = ids.Response.SubIds + if options != nil { + keyword, limit := util.ParseQueryKeywordAndLimit(options) + params := accountsRequest.GetParams() + params["SearchKey"] = keyword + params["Limit"] = strconv.Itoa(limit) + } + bytes, _ := json.Marshal(accountsRequest.GetParams()) + log.Infof("request params '%s'", string(bytes)) + + accounts, err := i.client.DescribeAccounts(accountsRequest) + if err != nil { + log.Warnf("failed to describe accounts, err '%v'", err) + return nil, apierrors.NewInternalError(err) + } + bytes, _ = json.Marshal(accounts) + log.Infof("accounts '%s'", string(bytes)) + if accounts.Response == nil || len(accounts.Response.Accounts) == 0 { + return &auth.UserList{}, nil + } + return i.usersFromAccounts(accounts.Response.Accounts), nil +} + +func (i *identityProvider) GetGroup(ctx context.Context, name string, options *metav1.GetOptions) (*auth.Group, error) { + return nil, apierrors.NewNotFound(auth.Resource("group"), name) +} + +func (i *identityProvider) ListGroups(ctx context.Context, options *metainternal.ListOptions) (*auth.GroupList, error) { + return &auth.GroupList{}, nil +} + +func (i *identityProvider) usersFromAccounts(accounts []iammodel.Account) *auth.UserList { + result := &auth.UserList{} + for _, account := range accounts { + uid := fmt.Sprintf("%d", account.AccountId) + extra := map[string]string{} + if uid == i.config.MasterId { + extra["administrator"] = "true" + } + result.Items = append(result.Items, auth.User{ + ObjectMeta: metav1.ObjectMeta{ + Name: uid, + }, + Spec: auth.UserSpec{ + ID: uid, + Name: account.AccountName, + DisplayName: account.NickName, + Email: account.ContactMail, + PhoneNumber: account.ContactMobile, + TenantID: i.tenantID, + Extra: extra, + }, + }) + } + return result +} diff --git a/pkg/auth/authentication/oidc/identityprovider/cloudindustry/hook_cloudindustry.go b/pkg/auth/authentication/oidc/identityprovider/cloudindustry/hook_cloudindustry.go new file mode 100644 index 0000000000..d30c0497a5 --- /dev/null +++ b/pkg/auth/authentication/oidc/identityprovider/cloudindustry/hook_cloudindustry.go @@ -0,0 +1,85 @@ +/* + * Tencent is pleased to support the open source community by making TKEStack + * available. + * + * Copyright (C) 2012-2019 Tencent. All Rights Reserved. + * + * 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 + * + * https://opensource.org/licenses/Apache-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 OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package cloudindustry + +import ( + "context" + "encoding/json" + "time" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/util/wait" + genericapiserver "k8s.io/apiserver/pkg/server" + + authinternalclient "tkestack.io/tke/api/client/clientset/internalversion/typed/auth/internalversion" + "tkestack.io/tke/pkg/auth/authentication/oidc/identityprovider" + "tkestack.io/tke/pkg/util/log" +) + +type cloudIndustryHookHandler struct { + authClient authinternalclient.AuthInterface +} + +// NewCloudIndustryHookHandler creates a new cloudIndustryHookHandler object. +func NewCloudIndustryHookHandler(authClient authinternalclient.AuthInterface) genericapiserver.PostStartHookProvider { + return &cloudIndustryHookHandler{ + authClient: authClient, + } +} + +func (d *cloudIndustryHookHandler) PostStartHook() (string, genericapiserver.PostStartHookFunc, error) { + return "load-cloudindustry-idp", func(ctx genericapiserver.PostStartHookContext) error { + go wait.JitterUntil(func() { + tenantUserSelector := fields.AndSelectors( + fields.OneTermEqualSelector("spec.type", ConnectorType), + ) + conns, err := d.authClient.IdentityProviders().List(context.Background(), v1.ListOptions{FieldSelector: tenantUserSelector.String()}) + if err != nil { + log.Error("List ldap idp from registry failed", log.Err(err)) + return + } + + for _, conn := range conns.Items { + if _, ok := identityprovider.GetIdentityProvider(conn.Name); ok { + continue + } + + var sdkConfig SDKConfig + err = json.Unmarshal([]byte(conn.Spec.Config), &sdkConfig) + if err != nil { + log.Error("Unmarshal cloudindustry config failed", log.String("idp", conn.Spec.Name), log.Err(err)) + continue + } + + idp, err := NewCloudIndustryIdentityProvider(conn.Name, conn.Spec.Administrators, &sdkConfig) + if err != nil { + log.Error("NewCloudIndustryIdentityProvider failed", log.String("idp", conn.Spec.Name), log.Err(err)) + continue + } + + identityprovider.SetIdentityProvider(conn.Name, idp) + log.Info("load cloudindustry identity provider successfully", log.String("idp", conn.Name)) + } + + }, 30*time.Second, 0.0, false, ctx.StopCh) + + return nil + }, nil +} diff --git a/pkg/auth/registry/identityprovider/storage/storage.go b/pkg/auth/registry/identityprovider/storage/storage.go index 6812e11672..28a074068b 100644 --- a/pkg/auth/registry/identityprovider/storage/storage.go +++ b/pkg/auth/registry/identityprovider/storage/storage.go @@ -21,6 +21,7 @@ package storage import ( "context" "encoding/json" + "tkestack.io/tke/pkg/auth/authentication/oidc/identityprovider/cloudindustry" dexldap "github.com/dexidp/dex/connector/ldap" "k8s.io/apimachinery/pkg/api/errors" @@ -103,11 +104,19 @@ func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation if err = json.Unmarshal([]byte(idpObj.Spec.Config), &ldapConfig); err != nil { return nil, errors.NewBadRequest(err.Error()) } - idp, err = ldap.NewLDAPIdentityProvider(ldapConfig, idpObj.Spec.Administrators, idpObj.Name) if err != nil { return nil, errors.NewInternalError(err) } + case cloudindustry.ConnectorType: + var sdkConfig cloudindustry.SDKConfig + if err = json.Unmarshal([]byte(idpObj.Spec.Config), &sdkConfig); err != nil { + return nil, errors.NewBadRequest(err.Error()) + } + idp, err = cloudindustry.NewCloudIndustryIdentityProvider(idpObj.Name, idpObj.Spec.Administrators, &sdkConfig) + if err != nil { + return nil, errors.NewInternalError(err) + } default: log.Warn("Identity provider type has not implemented users or groups api", log.String("type", idpObj.Spec.Type)) } diff --git a/pkg/auth/util/dex/conn.go b/pkg/auth/util/dex/conn.go index e3baff3522..1ddd38f9f2 100644 --- a/pkg/auth/util/dex/conn.go +++ b/pkg/auth/util/dex/conn.go @@ -60,6 +60,7 @@ func (c *conn) UpdateConnector(id string, updater func(s dexstorage.Connector) ( return err } + administrators := current.Spec.Administrators currConn := toDexConnector(current) updatedConn, err := updater(currConn) if err != nil { @@ -69,6 +70,7 @@ func (c *conn) UpdateConnector(id string, updater func(s dexstorage.Connector) ( updated := fromDexConnector(updatedConn) current.Spec = updated.Spec + current.Spec.Administrators = administrators _, err = c.authClient.IdentityProviders().Update(context.Background(), current, metav1.UpdateOptions{}) return err diff --git a/pkg/auth/util/user.go b/pkg/auth/util/user.go index 1da7733636..33a7b3aa92 100644 --- a/pkg/auth/util/user.go +++ b/pkg/auth/util/user.go @@ -257,8 +257,8 @@ func IsPlatformAdministrator(authClient authversionedclient.AuthV1Interface, use log.Errorf("get IdentityProviders failed: %v", err) return false } - administrators := idp.Spec.Administrators - for _, admin := range administrators { + log.Debugf("userName '%s', administrator '%v'", user.Spec.Name, idp.Spec.Administrators) + for _, admin := range idp.Spec.Administrators { if admin == user.Spec.Name { return true } diff --git a/pkg/business/registry/portal/storage/storage.go b/pkg/business/registry/portal/storage/storage.go index 3a54c266cb..c4b153c7b2 100644 --- a/pkg/business/registry/portal/storage/storage.go +++ b/pkg/business/registry/portal/storage/storage.go @@ -93,7 +93,7 @@ func (r *REST) ConvertToTable(ctx context.Context, object runtime.Object, tableO func (r *REST) List(ctx context.Context, _ *metainternal.ListOptions) (runtime.Object, error) { log.Debugf("business portal list, ctx %v", ctx) _, tenantID := authentication.UsernameAndTenantID(ctx) - if tenantID == "" { + if tenantID == "" || tenantID == "default" { return &business.Portal{ Administrator: true, Projects: make(map[string]string), @@ -107,7 +107,7 @@ func (r *REST) List(ctx context.Context, _ *metainternal.ListOptions) (runtime.O log.Debugf("business portal list, before FilterWithUser: %v", projectList) isAdmin, projectList, err := registryUtil.FilterWithUser(ctx, projectList, r.authClient, r.businessClient) - log.Debugf("business portal list, after FilterWithUser: %v", projectList) + log.Debugf("business portal list, after FilterWithUser: %v, isAdmin '%b'", projectList, isAdmin) if err != nil { return nil, err } diff --git a/pkg/business/registry/util/auth.go b/pkg/business/registry/util/auth.go index 488f2d6308..a19f9c99c8 100644 --- a/pkg/business/registry/util/auth.go +++ b/pkg/business/registry/util/auth.go @@ -64,6 +64,7 @@ func FilterWithUser(ctx context.Context, ).String(), }) if err != nil { + log.Warnf("failed to list user for tenant '%s', err '%v'", tenantID, err) return false, nil, err } for _, user := range userList.Items {