improve identity provider plugin
Signed-off-by: hongming <talonwan@yunify.com>
This commit is contained in:
@@ -18,10 +18,13 @@ package basic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"k8s.io/klog"
|
||||
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/request"
|
||||
"kubesphere.io/kubesphere/pkg/models/auth"
|
||||
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"kubesphere.io/kubesphere/pkg/models/iam/im"
|
||||
)
|
||||
|
||||
// TokenAuthenticator implements kubernetes token authenticate interface with our custom logic.
|
||||
@@ -30,28 +33,36 @@ import (
|
||||
// and group from user.AllUnauthenticated. This helps requests be passed along the handler chain,
|
||||
// because some resources are public accessible.
|
||||
type basicAuthenticator struct {
|
||||
authenticator im.PasswordAuthenticator
|
||||
authenticator auth.PasswordAuthenticator
|
||||
loginRecorder auth.LoginRecorder
|
||||
}
|
||||
|
||||
func NewBasicAuthenticator(authenticator im.PasswordAuthenticator) authenticator.Password {
|
||||
func NewBasicAuthenticator(authenticator auth.PasswordAuthenticator, loginRecorder auth.LoginRecorder) authenticator.Password {
|
||||
return &basicAuthenticator{
|
||||
authenticator: authenticator,
|
||||
loginRecorder: loginRecorder,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *basicAuthenticator) AuthenticatePassword(ctx context.Context, username, password string) (*authenticator.Response, bool, error) {
|
||||
|
||||
providedUser, err := t.authenticator.Authenticate(username, password)
|
||||
|
||||
authenticated, provider, err := t.authenticator.Authenticate(username, password)
|
||||
if err != nil {
|
||||
if t.loginRecorder != nil && err == auth.IncorrectPasswordError {
|
||||
var sourceIP, userAgent string
|
||||
if requestInfo, ok := request.RequestInfoFrom(ctx); ok {
|
||||
sourceIP = requestInfo.SourceIP
|
||||
userAgent = requestInfo.UserAgent
|
||||
}
|
||||
if err := t.loginRecorder.RecordLogin(username, iamv1alpha2.BasicAuth, provider, sourceIP, userAgent, err); err != nil {
|
||||
klog.Errorf("Failed to record unsuccessful login attempt for user %s, error: %v", username, err)
|
||||
}
|
||||
}
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
return &authenticator.Response{
|
||||
User: &user.DefaultInfo{
|
||||
Name: providedUser.GetName(),
|
||||
UID: providedUser.GetUID(),
|
||||
Groups: append(providedUser.GetGroups(), user.AllAuthenticated),
|
||||
Name: authenticated.GetName(),
|
||||
Groups: append(authenticated.GetGroups(), user.AllAuthenticated),
|
||||
},
|
||||
}, true, nil
|
||||
}
|
||||
|
||||
@@ -18,13 +18,13 @@ package jwttoken
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/klog"
|
||||
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
|
||||
"kubesphere.io/kubesphere/pkg/models/auth"
|
||||
|
||||
iamv1alpha2listers "kubesphere.io/kubesphere/pkg/client/listers/iam/v1alpha2"
|
||||
"kubesphere.io/kubesphere/pkg/models/iam/im"
|
||||
)
|
||||
|
||||
// TokenAuthenticator implements kubernetes token authenticate interface with our custom logic.
|
||||
@@ -33,11 +33,11 @@ import (
|
||||
// and group from user.AllUnauthenticated. This helps requests be passed along the handler chain,
|
||||
// because some resources are public accessible.
|
||||
type tokenAuthenticator struct {
|
||||
tokenOperator im.TokenManagementInterface
|
||||
tokenOperator auth.TokenManagementInterface
|
||||
userLister iamv1alpha2listers.UserLister
|
||||
}
|
||||
|
||||
func NewTokenAuthenticator(tokenOperator im.TokenManagementInterface, userLister iamv1alpha2listers.UserLister) authenticator.Token {
|
||||
func NewTokenAuthenticator(tokenOperator auth.TokenManagementInterface, userLister iamv1alpha2listers.UserLister) authenticator.Token {
|
||||
return &tokenAuthenticator{
|
||||
tokenOperator: tokenOperator,
|
||||
userLister: userLister,
|
||||
@@ -51,6 +51,16 @@ func (t *tokenAuthenticator) AuthenticateToken(ctx context.Context, token string
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
if providedUser.GetName() == iamv1alpha2.PreRegistrationUser {
|
||||
return &authenticator.Response{
|
||||
User: &user.DefaultInfo{
|
||||
Name: providedUser.GetName(),
|
||||
Extra: providedUser.GetExtra(),
|
||||
Groups: providedUser.GetGroups(),
|
||||
},
|
||||
}, true, nil
|
||||
}
|
||||
|
||||
dbUser, err := t.userLister.Get(providedUser.GetName())
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
@@ -58,8 +68,7 @@ func (t *tokenAuthenticator) AuthenticateToken(ctx context.Context, token string
|
||||
|
||||
return &authenticator.Response{
|
||||
User: &user.DefaultInfo{
|
||||
Name: providedUser.GetName(),
|
||||
UID: providedUser.GetUID(),
|
||||
Name: dbUser.GetName(),
|
||||
Groups: append(dbUser.Spec.Groups, user.AllAuthenticated),
|
||||
},
|
||||
}, true, nil
|
||||
|
||||
@@ -20,15 +20,19 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"io/ioutil"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
"gopkg.in/yaml.v3"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
|
||||
)
|
||||
|
||||
type AliyunIDaaS struct {
|
||||
func init() {
|
||||
identityprovider.RegisterOAuthProvider(&idaasProviderFactory{})
|
||||
}
|
||||
|
||||
type aliyunIDaaS struct {
|
||||
// ClientID is the application's ID.
|
||||
ClientID string `json:"clientID" yaml:"clientID"`
|
||||
|
||||
@@ -39,7 +43,7 @@ type AliyunIDaaS struct {
|
||||
// URLs. These are constants specific to each server and are
|
||||
// often available via site-specific packages, such as
|
||||
// google.Endpoint or github.Endpoint.
|
||||
Endpoint Endpoint `json:"endpoint" yaml:"endpoint"`
|
||||
Endpoint endpoint `json:"endpoint" yaml:"endpoint"`
|
||||
|
||||
// RedirectURL is the URL to redirect users going through
|
||||
// the OAuth flow, after the resource owner's URLs.
|
||||
@@ -49,15 +53,15 @@ type AliyunIDaaS struct {
|
||||
Scopes []string `json:"scopes" yaml:"scopes"`
|
||||
}
|
||||
|
||||
// Endpoint represents an OAuth 2.0 provider's authorization and token
|
||||
// endpoint represents an OAuth 2.0 provider's authorization and token
|
||||
// endpoint URLs.
|
||||
type Endpoint struct {
|
||||
type endpoint struct {
|
||||
AuthURL string `json:"authURL" yaml:"authURL"`
|
||||
TokenURL string `json:"tokenURL" yaml:"tokenURL"`
|
||||
UserInfoURL string `json:"user_info_url" yaml:"userInfoUrl"`
|
||||
}
|
||||
|
||||
type IDaaSIdentity struct {
|
||||
type idaasIdentity struct {
|
||||
Sub string `json:"sub"`
|
||||
OuID string `json:"ou_id"`
|
||||
Nickname string `json:"nickname"`
|
||||
@@ -67,72 +71,73 @@ type IDaaSIdentity struct {
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
type UserInfoResp struct {
|
||||
type userInfoResp struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
Code string `json:"code"`
|
||||
IDaaSIdentity IDaaSIdentity `json:"data"`
|
||||
IDaaSIdentity idaasIdentity `json:"data"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
identityprovider.RegisterOAuthProvider(&AliyunIDaaS{})
|
||||
type idaasProviderFactory struct {
|
||||
}
|
||||
|
||||
func (a *AliyunIDaaS) Type() string {
|
||||
func (g *idaasProviderFactory) Type() string {
|
||||
return "AliyunIDaasProvider"
|
||||
}
|
||||
|
||||
func (a *AliyunIDaaS) Setup(options *oauth.DynamicOptions) (identityprovider.OAuthProvider, error) {
|
||||
data, err := yaml.Marshal(options)
|
||||
if err != nil {
|
||||
func (g *idaasProviderFactory) Create(options *oauth.DynamicOptions) (identityprovider.OAuthProvider, error) {
|
||||
var idaas aliyunIDaaS
|
||||
if err := mapstructure.Decode(options, &idaas); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var provider AliyunIDaaS
|
||||
err = yaml.Unmarshal(data, &provider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &provider, nil
|
||||
return &idaas, nil
|
||||
}
|
||||
|
||||
func (a IDaaSIdentity) GetName() string {
|
||||
func (a idaasIdentity) GetUserID() string {
|
||||
return a.Sub
|
||||
}
|
||||
|
||||
func (a idaasIdentity) GetUsername() string {
|
||||
return a.Username
|
||||
}
|
||||
|
||||
func (a IDaaSIdentity) GetEmail() string {
|
||||
func (a idaasIdentity) GetEmail() string {
|
||||
return a.Email
|
||||
}
|
||||
|
||||
func (g *AliyunIDaaS) IdentityExchange(code string) (identityprovider.Identity, error) {
|
||||
func (a idaasIdentity) GetDisplayName() string {
|
||||
return a.Nickname
|
||||
}
|
||||
|
||||
func (a *aliyunIDaaS) IdentityExchange(code string) (identityprovider.Identity, error) {
|
||||
config := oauth2.Config{
|
||||
ClientID: g.ClientID,
|
||||
ClientSecret: g.ClientSecret,
|
||||
ClientID: a.ClientID,
|
||||
ClientSecret: a.ClientSecret,
|
||||
Endpoint: oauth2.Endpoint{
|
||||
AuthURL: g.Endpoint.AuthURL,
|
||||
TokenURL: g.Endpoint.TokenURL,
|
||||
AuthURL: a.Endpoint.AuthURL,
|
||||
TokenURL: a.Endpoint.TokenURL,
|
||||
AuthStyle: oauth2.AuthStyleAutoDetect,
|
||||
},
|
||||
RedirectURL: g.RedirectURL,
|
||||
Scopes: g.Scopes,
|
||||
RedirectURL: a.RedirectURL,
|
||||
Scopes: a.Scopes,
|
||||
}
|
||||
token, err := config.Exchange(context.Background(), code)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := oauth2.NewClient(context.Background(), oauth2.StaticTokenSource(token)).Get(g.Endpoint.UserInfoURL)
|
||||
resp, err := oauth2.NewClient(context.Background(), oauth2.StaticTokenSource(token)).Get(a.Endpoint.UserInfoURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var UserInfoResp UserInfoResp
|
||||
var UserInfoResp userInfoResp
|
||||
err = json.Unmarshal(data, &UserInfoResp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -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 identityprovider
|
||||
|
||||
import (
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
|
||||
)
|
||||
|
||||
var (
|
||||
builtinGenericProviders = make(map[string]GenericProviderFactory)
|
||||
)
|
||||
|
||||
type GenericProvider interface {
|
||||
// Authenticate from remote server
|
||||
Authenticate(username string, password string) (Identity, error)
|
||||
}
|
||||
|
||||
type GenericProviderFactory interface {
|
||||
// Type unique type of the provider
|
||||
Type() string
|
||||
// Apply the dynamic options from kubesphere-config
|
||||
Create(options *oauth.DynamicOptions) (GenericProvider, error)
|
||||
}
|
||||
|
||||
func CreateGenericProvider(providerType string, options *oauth.DynamicOptions) (GenericProvider, error) {
|
||||
if factory, ok := builtinGenericProviders[providerType]; ok {
|
||||
return factory.Create(options)
|
||||
}
|
||||
return nil, identityProviderNotFound
|
||||
}
|
||||
|
||||
func RegisterGenericProvider(factory GenericProviderFactory) {
|
||||
builtinGenericProviders[factory.Type()] = factory
|
||||
}
|
||||
@@ -19,8 +19,8 @@ package github
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"golang.org/x/oauth2"
|
||||
"gopkg.in/yaml.v3"
|
||||
"io/ioutil"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
|
||||
@@ -31,7 +31,11 @@ const (
|
||||
UserInfoURL = "https://api.github.com/user"
|
||||
)
|
||||
|
||||
type Github struct {
|
||||
func init() {
|
||||
identityprovider.RegisterOAuthProvider(&githubProviderFactory{})
|
||||
}
|
||||
|
||||
type github struct {
|
||||
// ClientID is the application's ID.
|
||||
ClientID string `json:"clientID" yaml:"clientID"`
|
||||
|
||||
@@ -41,8 +45,8 @@ type Github struct {
|
||||
// Endpoint contains the resource server's token endpoint
|
||||
// URLs. These are constants specific to each server and are
|
||||
// often available via site-specific packages, such as
|
||||
// google.Endpoint or github.Endpoint.
|
||||
Endpoint Endpoint `json:"endpoint" yaml:"endpoint"`
|
||||
// google.Endpoint or github.endpoint.
|
||||
Endpoint endpoint `json:"endpoint" yaml:"endpoint"`
|
||||
|
||||
// RedirectURL is the URL to redirect users going through
|
||||
// the OAuth flow, after the resource owner's URLs.
|
||||
@@ -52,14 +56,14 @@ type Github struct {
|
||||
Scopes []string `json:"scopes" yaml:"scopes"`
|
||||
}
|
||||
|
||||
// Endpoint represents an OAuth 2.0 provider's authorization and token
|
||||
// endpoint represents an OAuth 2.0 provider's authorization and token
|
||||
// endpoint URLs.
|
||||
type Endpoint struct {
|
||||
type endpoint struct {
|
||||
AuthURL string `json:"authURL" yaml:"authURL"`
|
||||
TokenURL string `json:"tokenURL" yaml:"tokenURL"`
|
||||
}
|
||||
|
||||
type GithubIdentity struct {
|
||||
type githubIdentity struct {
|
||||
Login string `json:"login"`
|
||||
ID int `json:"id"`
|
||||
NodeID string `json:"node_id"`
|
||||
@@ -98,36 +102,38 @@ type GithubIdentity struct {
|
||||
Collaborators int `json:"collaborators"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
identityprovider.RegisterOAuthProvider(&Github{})
|
||||
type githubProviderFactory struct {
|
||||
}
|
||||
|
||||
func (g *Github) Type() string {
|
||||
func (g *githubProviderFactory) Type() string {
|
||||
return "GitHubIdentityProvider"
|
||||
}
|
||||
|
||||
func (g *Github) Setup(options *oauth.DynamicOptions) (identityprovider.OAuthProvider, error) {
|
||||
data, err := yaml.Marshal(options)
|
||||
if err != nil {
|
||||
func (g *githubProviderFactory) Create(options *oauth.DynamicOptions) (identityprovider.OAuthProvider, error) {
|
||||
var github github
|
||||
if err := mapstructure.Decode(options, &github); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var provider Github
|
||||
err = yaml.Unmarshal(data, &provider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &provider, nil
|
||||
return &github, nil
|
||||
}
|
||||
|
||||
func (g GithubIdentity) GetName() string {
|
||||
func (g githubIdentity) GetUserID() string {
|
||||
return g.Login
|
||||
}
|
||||
|
||||
func (g GithubIdentity) GetEmail() string {
|
||||
func (g githubIdentity) GetUsername() string {
|
||||
return g.Login
|
||||
}
|
||||
|
||||
func (g githubIdentity) GetEmail() string {
|
||||
return g.Email
|
||||
}
|
||||
|
||||
func (g *Github) IdentityExchange(code string) (identityprovider.Identity, error) {
|
||||
func (g githubIdentity) GetDisplayName() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (g *github) IdentityExchange(code string) (identityprovider.Identity, error) {
|
||||
config := oauth2.Config{
|
||||
ClientID: g.ClientID,
|
||||
ClientSecret: g.ClientSecret,
|
||||
@@ -141,27 +147,23 @@ func (g *Github) IdentityExchange(code string) (identityprovider.Identity, error
|
||||
}
|
||||
|
||||
token, err := config.Exchange(context.Background(), code)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := oauth2.NewClient(context.Background(), oauth2.StaticTokenSource(token)).Get(UserInfoURL)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var githubIdentity GithubIdentity
|
||||
|
||||
var githubIdentity githubIdentity
|
||||
err = json.Unmarshal(data, &githubIdentity)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -17,6 +17,12 @@ limitations under the License.
|
||||
package identityprovider
|
||||
|
||||
type Identity interface {
|
||||
GetName() string
|
||||
// required
|
||||
GetUserID() string
|
||||
// optional
|
||||
GetUsername() string
|
||||
// optional
|
||||
GetDisplayName() string
|
||||
// optional
|
||||
GetEmail() string
|
||||
}
|
||||
@@ -1,22 +1,20 @@
|
||||
/*
|
||||
Copyright 2020 The KubeSphere Authors.
|
||||
|
||||
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
|
||||
|
||||
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.
|
||||
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 identityprovider
|
||||
package ldap
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
@@ -26,24 +24,23 @@ import (
|
||||
"github.com/go-ldap/ldap"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"io/ioutil"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/klog"
|
||||
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
|
||||
"kubesphere.io/kubesphere/pkg/constants"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
LdapIdentityProvider = "LDAPIdentityProvider"
|
||||
ldapIdentityProvider = "LDAPIdentityProvider"
|
||||
defaultReadTimeout = 15000
|
||||
)
|
||||
|
||||
type LdapProvider interface {
|
||||
Authenticate(username string, password string) (*iamv1alpha2.User, error)
|
||||
func init() {
|
||||
identityprovider.RegisterGenericProvider(&ldapProviderFactory{})
|
||||
}
|
||||
|
||||
type ldapOptions struct {
|
||||
type ldapProvider struct {
|
||||
// Host and optional port of the LDAP server in the form "host:port".
|
||||
// If the port is not supplied, 389 for insecure or StartTLS connections, 636
|
||||
Host string `json:"host,omitempty" yaml:"managerDN"`
|
||||
@@ -73,105 +70,125 @@ type ldapOptions struct {
|
||||
UserMemberAttribute string `json:"userMemberAttribute,omitempty" yaml:"userMemberAttribute"`
|
||||
// Attribute on a group object storing the information for primary group membership.
|
||||
GroupMemberAttribute string `json:"groupMemberAttribute,omitempty" yaml:"groupMemberAttribute"`
|
||||
// login attribute used for comparing user entries.
|
||||
// The following three fields are direct mappings of attributes on the user entry.
|
||||
// login attribute used for comparing user entries.
|
||||
LoginAttribute string `json:"loginAttribute" yaml:"loginAttribute"`
|
||||
MailAttribute string `json:"mailAttribute" yaml:"mailAttribute"`
|
||||
DisplayNameAttribute string `json:"displayNameAttribute" yaml:"displayNameAttribute"`
|
||||
}
|
||||
|
||||
type ldapProvider struct {
|
||||
options ldapOptions
|
||||
type ldapProviderFactory struct {
|
||||
}
|
||||
|
||||
func NewLdapProvider(options *oauth.DynamicOptions) (LdapProvider, error) {
|
||||
var ldapOptions ldapOptions
|
||||
if err := mapstructure.Decode(options, &ldapOptions); err != nil {
|
||||
func (l *ldapProviderFactory) Type() string {
|
||||
return ldapIdentityProvider
|
||||
}
|
||||
|
||||
func (l *ldapProviderFactory) Create(options *oauth.DynamicOptions) (identityprovider.GenericProvider, error) {
|
||||
var ldapProvider ldapProvider
|
||||
if err := mapstructure.Decode(options, &ldapProvider); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ldapOptions.ReadTimeout <= 0 {
|
||||
ldapOptions.ReadTimeout = defaultReadTimeout
|
||||
if ldapProvider.ReadTimeout <= 0 {
|
||||
ldapProvider.ReadTimeout = defaultReadTimeout
|
||||
}
|
||||
return &ldapProvider{options: ldapOptions}, nil
|
||||
return &ldapProvider, nil
|
||||
}
|
||||
|
||||
func (l ldapProvider) Authenticate(username string, password string) (*iamv1alpha2.User, error) {
|
||||
type ldapIdentity struct {
|
||||
Username string
|
||||
Email string
|
||||
DisplayName string
|
||||
}
|
||||
|
||||
func (l *ldapIdentity) GetUserID() string {
|
||||
return l.Username
|
||||
}
|
||||
|
||||
func (l *ldapIdentity) GetUsername() string {
|
||||
return l.Username
|
||||
}
|
||||
|
||||
func (l *ldapIdentity) GetEmail() string {
|
||||
return l.Email
|
||||
}
|
||||
|
||||
func (l *ldapIdentity) GetDisplayName() string {
|
||||
return l.DisplayName
|
||||
}
|
||||
|
||||
func (l ldapProvider) Authenticate(username string, password string) (identityprovider.Identity, error) {
|
||||
conn, err := l.newConn()
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
conn.SetTimeout(time.Duration(l.options.ReadTimeout) * time.Millisecond)
|
||||
conn.SetTimeout(time.Duration(l.ReadTimeout) * time.Millisecond)
|
||||
defer conn.Close()
|
||||
err = conn.Bind(l.options.ManagerDN, l.options.ManagerPassword)
|
||||
err = conn.Bind(l.ManagerDN, l.ManagerPassword)
|
||||
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
filter := fmt.Sprintf("(&(%s=%s)%s)", l.options.LoginAttribute, username, l.options.UserSearchFilter)
|
||||
filter := fmt.Sprintf("(&(%s=%s)%s)", l.LoginAttribute, username, l.UserSearchFilter)
|
||||
result, err := conn.Search(&ldap.SearchRequest{
|
||||
BaseDN: l.options.UserSearchBase,
|
||||
BaseDN: l.UserSearchBase,
|
||||
Scope: ldap.ScopeWholeSubtree,
|
||||
DerefAliases: ldap.NeverDerefAliases,
|
||||
SizeLimit: 1,
|
||||
TimeLimit: 0,
|
||||
TypesOnly: false,
|
||||
Filter: filter,
|
||||
Attributes: []string{l.options.LoginAttribute, l.options.MailAttribute, l.options.DisplayNameAttribute},
|
||||
Attributes: []string{l.LoginAttribute, l.MailAttribute, l.DisplayNameAttribute},
|
||||
})
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(result.Entries) == 1 {
|
||||
entry := result.Entries[0]
|
||||
err = conn.Bind(entry.DN, password)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
email := entry.GetAttributeValue(l.options.MailAttribute)
|
||||
displayName := entry.GetAttributeValue(l.options.DisplayNameAttribute)
|
||||
return &iamv1alpha2.User{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: username,
|
||||
Annotations: map[string]string{
|
||||
constants.DisplayNameAnnotationKey: displayName,
|
||||
},
|
||||
},
|
||||
Spec: iamv1alpha2.UserSpec{
|
||||
Email: email,
|
||||
DisplayName: displayName,
|
||||
},
|
||||
}, nil
|
||||
if len(result.Entries) != 1 {
|
||||
return nil, errors.NewUnauthorized("incorrect password")
|
||||
}
|
||||
|
||||
return nil, ldap.NewError(ldap.LDAPResultNoSuchObject, fmt.Errorf("could not find user %s in LDAP directory", username))
|
||||
entry := result.Entries[0]
|
||||
if err = conn.Bind(entry.DN, password); err != nil {
|
||||
klog.Error(err)
|
||||
if ldap.IsErrorWithCode(err, ldap.LDAPResultInvalidCredentials) {
|
||||
return nil, errors.NewUnauthorized("incorrect password")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
email := entry.GetAttributeValue(l.MailAttribute)
|
||||
displayName := entry.GetAttributeValue(l.DisplayNameAttribute)
|
||||
return &ldapIdentity{
|
||||
Username: username,
|
||||
DisplayName: displayName,
|
||||
Email: email,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *ldapProvider) newConn() (*ldap.Conn, error) {
|
||||
if !l.options.StartTLS {
|
||||
return ldap.Dial("tcp", l.options.Host)
|
||||
if !l.StartTLS {
|
||||
return ldap.Dial("tcp", l.Host)
|
||||
}
|
||||
tlsConfig := tls.Config{}
|
||||
if l.options.InsecureSkipVerify {
|
||||
if l.InsecureSkipVerify {
|
||||
tlsConfig.InsecureSkipVerify = true
|
||||
}
|
||||
tlsConfig.RootCAs = x509.NewCertPool()
|
||||
var caCert []byte
|
||||
var err error
|
||||
// Load CA cert
|
||||
if l.options.RootCA != "" {
|
||||
if caCert, err = ioutil.ReadFile(l.options.RootCA); err != nil {
|
||||
if l.RootCA != "" {
|
||||
if caCert, err = ioutil.ReadFile(l.RootCA); err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if l.options.RootCAData != "" {
|
||||
if caCert, err = base64.StdEncoding.DecodeString(l.options.RootCAData); err != nil {
|
||||
if l.RootCAData != "" {
|
||||
if caCert, err = base64.StdEncoding.DecodeString(l.RootCAData); err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
@@ -179,5 +196,5 @@ func (l *ldapProvider) newConn() (*ldap.Conn, error) {
|
||||
if caCert != nil {
|
||||
tlsConfig.RootCAs.AppendCertsFromPEM(caCert)
|
||||
}
|
||||
return ldap.DialTLS("tcp", l.options.Host, &tlsConfig)
|
||||
return ldap.DialTLS("tcp", l.Host, &tlsConfig)
|
||||
}
|
||||
@@ -1,22 +1,20 @@
|
||||
/*
|
||||
Copyright 2020 The KubeSphere Authors.
|
||||
|
||||
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
|
||||
|
||||
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.
|
||||
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 identityprovider
|
||||
package ldap
|
||||
|
||||
import (
|
||||
"github.com/google/go-cmp/cmp"
|
||||
@@ -42,12 +40,11 @@ mailAttribute: mail
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
provider, err := NewLdapProvider(&dynamicOptions)
|
||||
got, err := new(ldapProviderFactory).Create(&dynamicOptions)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := provider.(*ldapProvider).options
|
||||
expected := ldapOptions{
|
||||
expected := &ldapProvider{
|
||||
Host: "test.sn.mynetname.net:389",
|
||||
StartTLS: false,
|
||||
InsecureSkipVerify: false,
|
||||
@@ -81,14 +78,14 @@ func TestLdapProvider_Authenticate(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var dynamicOptions oauth.DynamicOptions
|
||||
if err := yaml.Unmarshal(options, &dynamicOptions); err != nil {
|
||||
if err = yaml.Unmarshal(options, &dynamicOptions); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
provider, err := NewLdapProvider(&dynamicOptions)
|
||||
ldapProvider, err := new(ldapProviderFactory).Create(&dynamicOptions)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := provider.Authenticate("test", "test"); err != nil {
|
||||
if _, err = ldapProvider.Authenticate("test", "test"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,18 @@
|
||||
/*
|
||||
Copyright 2020 The KubeSphere Authors.
|
||||
|
||||
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
|
||||
|
||||
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.
|
||||
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 identityprovider
|
||||
|
||||
import (
|
||||
@@ -24,23 +21,29 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
oauthProviders = make(map[string]OAuthProvider, 0)
|
||||
ErrorIdentityProviderNotFound = errors.New("the identity provider was not found")
|
||||
builtinOAuthProviders = make(map[string]OAuthProviderFactory)
|
||||
identityProviderNotFound = errors.New("identity provider not found")
|
||||
)
|
||||
|
||||
type OAuthProvider interface {
|
||||
Type() string
|
||||
Setup(options *oauth.DynamicOptions) (OAuthProvider, error)
|
||||
// IdentityExchange exchange identity from remote server
|
||||
IdentityExchange(code string) (Identity, error)
|
||||
}
|
||||
|
||||
func GetOAuthProvider(providerType string, options *oauth.DynamicOptions) (OAuthProvider, error) {
|
||||
if provider, ok := oauthProviders[providerType]; ok {
|
||||
return provider.Setup(options)
|
||||
}
|
||||
return nil, ErrorIdentityProviderNotFound
|
||||
type OAuthProviderFactory interface {
|
||||
// Type unique type of the provider
|
||||
Type() string
|
||||
// Apply the dynamic options from kubesphere-config
|
||||
Create(options *oauth.DynamicOptions) (OAuthProvider, error)
|
||||
}
|
||||
|
||||
func RegisterOAuthProvider(provider OAuthProvider) {
|
||||
oauthProviders[provider.Type()] = provider
|
||||
func CreateOAuthProvider(providerType string, options *oauth.DynamicOptions) (OAuthProvider, error) {
|
||||
if provider, ok := builtinOAuthProviders[providerType]; ok {
|
||||
return provider.Create(options)
|
||||
}
|
||||
return nil, identityProviderNotFound
|
||||
}
|
||||
|
||||
func RegisterOAuthProvider(factory OAuthProviderFactory) {
|
||||
builtinOAuthProviders[factory.Type()] = factory
|
||||
}
|
||||
|
||||
@@ -35,12 +35,13 @@ const (
|
||||
// GrantHandlerDeny auto-denies client authorization grant requests
|
||||
GrantHandlerDeny GrantHandlerType = "deny"
|
||||
// MappingMethodAuto The default value.The user will automatically create and mapping when login successful.
|
||||
// Fails if a user with that user name is already mapped to another identity.
|
||||
// Fails if a user with that username is already mapped to another identity.
|
||||
MappingMethodAuto MappingMethod = "auto"
|
||||
// MappingMethodLookup Looks up an existing identity, user identity mapping, and user, but does not automatically
|
||||
// provision users or identities. Using this method requires you to manually provision users.
|
||||
MappingMethodLookup MappingMethod = "lookup"
|
||||
// MappingMethodMixed A user entity can be mapped with multiple identifyProvider.
|
||||
// not supported yet.
|
||||
MappingMethodMixed MappingMethod = "mixed"
|
||||
)
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"github.com/spf13/pflag"
|
||||
_ "kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider/aliyunidaas"
|
||||
_ "kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider/github"
|
||||
_ "kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider/ldap"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
|
||||
"time"
|
||||
)
|
||||
@@ -66,7 +67,6 @@ func (options *AuthenticationOptions) Validate() []error {
|
||||
if len(options.JwtSecret) == 0 {
|
||||
errs = append(errs, fmt.Errorf("jwt secret is empty"))
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
|
||||
@@ -29,9 +29,10 @@ const (
|
||||
)
|
||||
|
||||
type Claims struct {
|
||||
Username string `json:"username"`
|
||||
UID string `json:"uid"`
|
||||
TokenType TokenType `json:"token_type"`
|
||||
Username string `json:"username"`
|
||||
Groups []string `json:"groups,omitempty"`
|
||||
Extra map[string][]string `json:"extra,omitempty"`
|
||||
TokenType TokenType `json:"token_type"`
|
||||
// Currently, we are not using any field in jwt.StandardClaims
|
||||
jwt.StandardClaims
|
||||
}
|
||||
@@ -51,7 +52,7 @@ func (s *jwtTokenIssuer) Verify(tokenString string) (user.Info, TokenType, error
|
||||
klog.Error(err)
|
||||
return nil, "", err
|
||||
}
|
||||
return &user.DefaultInfo{Name: clm.Username, UID: clm.UID}, clm.TokenType, nil
|
||||
return &user.DefaultInfo{Name: clm.Username, Groups: clm.Groups, Extra: clm.Extra}, clm.TokenType, nil
|
||||
}
|
||||
|
||||
func (s *jwtTokenIssuer) IssueTo(user user.Info, tokenType TokenType, expiresIn time.Duration) (string, error) {
|
||||
@@ -59,7 +60,8 @@ func (s *jwtTokenIssuer) IssueTo(user user.Info, tokenType TokenType, expiresIn
|
||||
notBefore := issueAt
|
||||
clm := &Claims{
|
||||
Username: user.GetName(),
|
||||
UID: user.GetUID(),
|
||||
Groups: user.GetGroups(),
|
||||
Extra: user.GetExtra(),
|
||||
TokenType: tokenType,
|
||||
StandardClaims: jwt.StandardClaims{
|
||||
IssuedAt: issueAt,
|
||||
|
||||
Reference in New Issue
Block a user