improve LDAP identity provider

Signed-off-by: hongming <talonwan@yunify.com>
This commit is contained in:
hongming
2020-11-20 17:27:59 +08:00
parent f6fea24a75
commit 00920d3d51
2 changed files with 181 additions and 21 deletions

View File

@@ -19,27 +19,62 @@
package identityprovider
import (
"crypto/tls"
"crypto/x509"
"encoding/base64"
"fmt"
"github.com/go-ldap/ldap"
"gopkg.in/yaml.v3"
"github.com/mitchellh/mapstructure"
"io/ioutil"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/klog"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
"kubesphere.io/kubesphere/pkg/constants"
"time"
)
const LdapIdentityProvider = "LDAPIdentityProvider"
const (
LdapIdentityProvider = "LDAPIdentityProvider"
defaultReadTimeout = 15000
)
type LdapProvider interface {
Authenticate(username string, password string) (*iamv1alpha2.User, error)
}
type ldapOptions struct {
Host string `json:"host" yaml:"host"`
ManagerDN string `json:"managerDN" yaml:"managerDN"`
ManagerPassword string `json:"-" yaml:"managerPassword"`
UserSearchBase string `json:"userSearchBase" yaml:"userSearchBase"`
//This is typically uid
// 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"`
// Timeout duration when reading data from remote server. Default to 15s.
ReadTimeout int `json:"readTimeout" yaml:"readTimeout"`
// If specified, connections will use the ldaps:// protocol
StartTLS bool `json:"startTLS,omitempty" yaml:"startTLS"`
// Used to turn off TLS certificate checks
InsecureSkipVerify bool `json:"insecureSkipVerify" yaml:"insecureSkipVerify"`
// Path to a trusted root certificate file. Default: use the host's root CA.
RootCA string `json:"rootCA,omitempty" yaml:"rootCA"`
// A raw certificate file can also be provided inline. Base64 encoded PEM file
RootCAData string `json:"rootCAData,omitempty" yaml:"rootCAData"`
// Username (DN) of the "manager" user identity.
ManagerDN string `json:"managerDN,omitempty" yaml:"managerDN"`
// The password for the manager DN.
ManagerPassword string `json:"-,omitempty" yaml:"managerPassword"`
// User search scope.
UserSearchBase string `json:"userSearchBase,omitempty" yaml:"userSearchBase"`
// LDAP filter used to identify objects of type user. e.g. (objectClass=person)
UserSearchFilter string `json:"userSearchFilter,omitempty" yaml:"userSearchFilter"`
// Group search scope.
GroupSearchBase string `json:"groupSearchBase,omitempty" yaml:"groupSearchBase"`
// LDAP filter used to identify objects of type group. e.g. (objectclass=group)
GroupSearchFilter string `json:"groupSearchFilter,omitempty" yaml:"groupSearchFilter"`
// Attribute on a user object storing the groups the user is a member of.
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.
LoginAttribute string `json:"loginAttribute" yaml:"loginAttribute"`
MailAttribute string `json:"mailAttribute" yaml:"mailAttribute"`
DisplayNameAttribute string `json:"displayNameAttribute" yaml:"displayNameAttribute"`
@@ -50,24 +85,23 @@ type ldapProvider struct {
}
func NewLdapProvider(options *oauth.DynamicOptions) (LdapProvider, error) {
data, err := yaml.Marshal(options)
if err != nil {
var ldapOptions ldapOptions
if err := mapstructure.Decode(options, &ldapOptions); err != nil {
return nil, err
}
var ldapOptions ldapOptions
err = yaml.Unmarshal(data, &ldapOptions)
if err != nil {
return nil, err
if ldapOptions.ReadTimeout <= 0 {
ldapOptions.ReadTimeout = defaultReadTimeout
}
return &ldapProvider{options: ldapOptions}, nil
}
func (l ldapProvider) Authenticate(username string, password string) (*iamv1alpha2.User, error) {
conn, err := ldap.Dial("tcp", l.options.Host)
conn, err := l.newConn()
if err != nil {
klog.Error(err)
return nil, err
}
conn.SetTimeout(time.Duration(l.options.ReadTimeout) * time.Millisecond)
defer conn.Close()
err = conn.Bind(l.options.ManagerDN, l.options.ManagerPassword)
@@ -76,8 +110,7 @@ func (l ldapProvider) Authenticate(username string, password string) (*iamv1alph
return nil, err
}
filter := fmt.Sprintf("(&(%s=%s))", l.options.LoginAttribute, username)
filter := fmt.Sprintf("(&(%s=%s)%s)", l.options.LoginAttribute, username, l.options.UserSearchFilter)
result, err := conn.Search(&ldap.SearchRequest{
BaseDN: l.options.UserSearchBase,
Scope: ldap.ScopeWholeSubtree,
@@ -88,7 +121,6 @@ func (l ldapProvider) Authenticate(username string, password string) (*iamv1alph
Filter: filter,
Attributes: []string{l.options.LoginAttribute, l.options.MailAttribute, l.options.DisplayNameAttribute},
})
if err != nil {
klog.Error(err)
return nil, err
@@ -101,17 +133,51 @@ func (l ldapProvider) Authenticate(username string, password string) (*iamv1alph
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: entry.GetAttributeValue(l.options.MailAttribute),
DisplayName: entry.GetAttributeValue(l.options.DisplayNameAttribute),
Email: email,
DisplayName: displayName,
},
}, nil
}
return nil, ldap.NewError(ldap.LDAPResultNoSuchObject, fmt.Errorf(" could not find user %s in LDAP directory", username))
return nil, ldap.NewError(ldap.LDAPResultNoSuchObject, fmt.Errorf("could not find user %s in LDAP directory", username))
}
func (l *ldapProvider) newConn() (*ldap.Conn, error) {
if !l.options.StartTLS {
return ldap.Dial("tcp", l.options.Host)
}
tlsConfig := tls.Config{}
if l.options.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 {
klog.Error(err)
return nil, err
}
}
if l.options.RootCAData != "" {
if caCert, err = base64.StdEncoding.DecodeString(l.options.RootCAData); err != nil {
klog.Error(err)
return nil, err
}
}
if caCert != nil {
tlsConfig.RootCAs.AppendCertsFromPEM(caCert)
}
return ldap.DialTLS("tcp", l.options.Host, &tlsConfig)
}