diff --git a/builtin/credential/ldap/backend.go b/builtin/credential/ldap/backend.go index 5d2b1d23974..74adacb253c 100644 --- a/builtin/credential/ldap/backend.go +++ b/builtin/credential/ldap/backend.go @@ -132,6 +132,14 @@ func (b *backend) Login(ctx context.Context, req *logical.Request, username stri return nil, logical.ErrorResponse(err.Error()), nil, nil } + if cfg.AnonymousGroupSearch { + c, err = ldapClient.DialLDAP(cfg.ConfigEntry) + if err != nil { + return nil, logical.ErrorResponse("ldap operation failed"), nil, nil + } + defer c.Close() // Defer closing of this connection as the deferal above closes the other defined connection + } + ldapGroups, err := ldapClient.GetLdapGroups(cfg.ConfigEntry, c, userDN, username) if err != nil { return nil, logical.ErrorResponse(err.Error()), nil, nil diff --git a/sdk/helper/ldaputil/client.go b/sdk/helper/ldaputil/client.go index fe0fd670f1e..2b453d47172 100644 --- a/sdk/helper/ldaputil/client.go +++ b/sdk/helper/ldaputil/client.go @@ -489,5 +489,14 @@ func getTLSConfig(cfg *ConfigEntry, host string) (*tls.Config, error) { } tlsConfig.RootCAs = caPool } + if cfg.ClientTLSCert != "" && cfg.ClientTLSKey != "" { + certificate, err := tls.X509KeyPair([]byte(cfg.ClientTLSCert), []byte(cfg.ClientTLSKey)) + if err != nil { + return nil, errwrap.Wrapf("failed to parse client X509 key pair: {{err}}", err) + } + tlsConfig.Certificates = append(tlsConfig.Certificates, certificate) + } else if cfg.ClientTLSCert != "" || cfg.ClientTLSKey != "" { + return nil, fmt.Errorf("both client_tls_cert and client_tls_key must be set") + } return tlsConfig, nil } diff --git a/sdk/helper/ldaputil/config.go b/sdk/helper/ldaputil/config.go index 47f3c042e53..b23f0304e80 100644 --- a/sdk/helper/ldaputil/config.go +++ b/sdk/helper/ldaputil/config.go @@ -1,6 +1,7 @@ package ldaputil import ( + "crypto/tls" "crypto/x509" "encoding/pem" "errors" @@ -18,6 +19,14 @@ import ( // Not all fields will be used by every integration. func ConfigFields() map[string]*framework.FieldSchema { return map[string]*framework.FieldSchema{ + "anonymous_group_search": { + Type: framework.TypeBool, + Default: false, + Description: "Use anonymous binds when performing LDAP group searches (if true the initial credentials will still be used for the initial connection test).", + DisplayAttrs: &framework.DisplayAttributes{ + Name: "Anonymous group search", + }, + }, "url": { Type: framework.TypeString, Default: "ldap://127.0.0.1", @@ -105,6 +114,28 @@ Default: cn`, "certificate": { Type: framework.TypeString, Description: "CA certificate to use when verifying LDAP server certificate, must be x509 PEM encoded (optional)", + DisplayAttrs: &framework.DisplayAttributes{ + Name: "CA certificate", + EditType: "file", + }, + }, + + "client_tls_cert": { + Type: framework.TypeString, + Description: "Client certificate to provide to the LDAP server, must be x509 PEM encoded (optional)", + DisplayAttrs: &framework.DisplayAttributes{ + Name: "Client certificate", + EditType: "file", + }, + }, + + "client_tls_key": { + Type: framework.TypeString, + Description: "Client certificate key to provide to the LDAP server, must be x509 PEM encoded (optional)", + DisplayAttrs: &framework.DisplayAttributes{ + Name: "Client key", + EditType: "file", + }, }, "discoverdn": { @@ -196,6 +227,10 @@ func NewConfigEntry(existing *ConfigEntry, d *framework.FieldData) (*ConfigEntry cfg = new(ConfigEntry) } + if _, ok := d.Raw["anonymous_group_search"]; ok || !hadExisting { + cfg.AnonymousGroupSearch = d.Get("anonymous_group_search").(bool) + } + if _, ok := d.Raw["url"]; ok || !hadExisting { cfg.Url = strings.ToLower(d.Get("url").(string)) } @@ -236,20 +271,31 @@ func NewConfigEntry(existing *ConfigEntry, d *framework.FieldData) (*ConfigEntry if _, ok := d.Raw["certificate"]; ok || !hadExisting { certificate := d.Get("certificate").(string) if certificate != "" { - block, _ := pem.Decode([]byte(certificate)) - - if block == nil || block.Type != "CERTIFICATE" { - return nil, errors.New("failed to decode PEM block in the certificate") - } - _, err := x509.ParseCertificate(block.Bytes) - if err != nil { - return nil, errwrap.Wrapf("failed to parse certificate: {{err}}", err) + if err := validateCertificate([]byte(certificate)); err != nil { + return nil, errwrap.Wrapf("failed to parse server tls cert: {{err}}", err) } } - cfg.Certificate = certificate } + if _, ok := d.Raw["client_tls_cert"]; ok || !hadExisting { + clientTLSCert := d.Get("client_tls_cert").(string) + cfg.ClientTLSCert = clientTLSCert + } + + if _, ok := d.Raw["client_tls_key"]; ok || !hadExisting { + clientTLSKey := d.Get("client_tls_key").(string) + cfg.ClientTLSKey = clientTLSKey + } + + if cfg.ClientTLSCert != "" && cfg.ClientTLSKey != "" { + if _, err := tls.X509KeyPair([]byte(cfg.ClientTLSCert), []byte(cfg.ClientTLSKey)); err != nil { + return nil, errwrap.Wrapf("failed to parse client X509 key pair: {{err}}", err) + } + } else if cfg.ClientTLSCert != "" || cfg.ClientTLSKey != "" { + return nil, fmt.Errorf("both client_tls_cert and client_tls_key must be set") + } + if _, ok := d.Raw["insecure_tls"]; ok || !hadExisting { cfg.InsecureTLS = d.Get("insecure_tls").(bool) } @@ -318,12 +364,15 @@ func NewConfigEntry(existing *ConfigEntry, d *framework.FieldData) (*ConfigEntry type ConfigEntry struct { Url string `json:"url"` UserDN string `json:"userdn"` + AnonymousGroupSearch bool `json:"anonymous_group_search"` GroupDN string `json:"groupdn"` GroupFilter string `json:"groupfilter"` GroupAttr string `json:"groupattr"` UPNDomain string `json:"upndomain"` UserAttr string `json:"userattr"` Certificate string `json:"certificate"` + ClientTLSCert string `json:"client_tls_cert` + ClientTLSKey string `json:"client_tls_key` InsecureTLS bool `json:"insecure_tls"` StartTLS bool `json:"starttls"` BindDN string `json:"binddn"` @@ -351,22 +400,23 @@ func (c *ConfigEntry) Map() map[string]interface{} { func (c *ConfigEntry) PasswordlessMap() map[string]interface{} { m := map[string]interface{}{ - "url": c.Url, - "userdn": c.UserDN, - "groupdn": c.GroupDN, - "groupfilter": c.GroupFilter, - "groupattr": c.GroupAttr, - "upndomain": c.UPNDomain, - "userattr": c.UserAttr, - "certificate": c.Certificate, - "insecure_tls": c.InsecureTLS, - "starttls": c.StartTLS, - "binddn": c.BindDN, - "deny_null_bind": c.DenyNullBind, - "discoverdn": c.DiscoverDN, - "tls_min_version": c.TLSMinVersion, - "tls_max_version": c.TLSMaxVersion, - "use_token_groups": c.UseTokenGroups, + "url": c.Url, + "userdn": c.UserDN, + "groupdn": c.GroupDN, + "groupfilter": c.GroupFilter, + "groupattr": c.GroupAttr, + "upndomain": c.UPNDomain, + "userattr": c.UserAttr, + "certificate": c.Certificate, + "insecure_tls": c.InsecureTLS, + "starttls": c.StartTLS, + "binddn": c.BindDN, + "deny_null_bind": c.DenyNullBind, + "discoverdn": c.DiscoverDN, + "tls_min_version": c.TLSMinVersion, + "tls_max_version": c.TLSMaxVersion, + "use_token_groups": c.UseTokenGroups, + "anonymous_group_search": c.AnonymousGroupSearch, } if c.CaseSensitiveNames != nil { m["case_sensitive_names"] = *c.CaseSensitiveNames @@ -377,6 +427,18 @@ func (c *ConfigEntry) PasswordlessMap() map[string]interface{} { return m } +func validateCertificate(pemBlock []byte) error { + block, _ := pem.Decode([]byte(pemBlock)) + if block == nil || block.Type != "CERTIFICATE" { + return errors.New("failed to decode PEM block in the certificate") + } + _, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return fmt.Errorf("failed to parse certificate %s", err.Error()) + } + return nil +} + func (c *ConfigEntry) Validate() error { if len(c.Url) == 0 { return errors.New("at least one url must be provided") @@ -398,13 +460,13 @@ func (c *ConfigEntry) Validate() error { return errors.New("'tls_max_version' must be greater than or equal to 'tls_min_version'") } if c.Certificate != "" { - block, _ := pem.Decode([]byte(c.Certificate)) - if block == nil || block.Type != "CERTIFICATE" { - return errors.New("failed to decode PEM block in the certificate") + if err := validateCertificate([]byte(c.Certificate)); err != nil { + return errwrap.Wrapf("failed to parse server tls cert: {{err}}", err) } - _, err := x509.ParseCertificate(block.Bytes) - if err != nil { - return fmt.Errorf("failed to parse certificate %s", err.Error()) + } + if c.ClientTLSCert != "" && c.ClientTLSKey != "" { + if _, err := tls.X509KeyPair([]byte(c.ClientTLSCert), []byte(c.ClientTLSKey)); err != nil { + return errwrap.Wrapf("failed to parse client X509 key pair: {{err}}", err) } } return nil diff --git a/ui/app/models/auth-config/ldap.js b/ui/app/models/auth-config/ldap.js index 5ab79c1d3a4..5ba84c5a859 100644 --- a/ui/app/models/auth-config/ldap.js +++ b/ui/app/models/auth-config/ldap.js @@ -20,8 +20,11 @@ export default AuthConfig.extend({ 'tlsMinVersion', 'tlsMaxVersion', 'certificate', + 'clientTlsCert', + 'clientTlsKey', 'userattr', 'upndomain', + 'anonymousGroupSearch', ], }, { diff --git a/vendor/github.com/hashicorp/vault/sdk/helper/ldaputil/client.go b/vendor/github.com/hashicorp/vault/sdk/helper/ldaputil/client.go index fe0fd670f1e..2b453d47172 100644 --- a/vendor/github.com/hashicorp/vault/sdk/helper/ldaputil/client.go +++ b/vendor/github.com/hashicorp/vault/sdk/helper/ldaputil/client.go @@ -489,5 +489,14 @@ func getTLSConfig(cfg *ConfigEntry, host string) (*tls.Config, error) { } tlsConfig.RootCAs = caPool } + if cfg.ClientTLSCert != "" && cfg.ClientTLSKey != "" { + certificate, err := tls.X509KeyPair([]byte(cfg.ClientTLSCert), []byte(cfg.ClientTLSKey)) + if err != nil { + return nil, errwrap.Wrapf("failed to parse client X509 key pair: {{err}}", err) + } + tlsConfig.Certificates = append(tlsConfig.Certificates, certificate) + } else if cfg.ClientTLSCert != "" || cfg.ClientTLSKey != "" { + return nil, fmt.Errorf("both client_tls_cert and client_tls_key must be set") + } return tlsConfig, nil } diff --git a/vendor/github.com/hashicorp/vault/sdk/helper/ldaputil/config.go b/vendor/github.com/hashicorp/vault/sdk/helper/ldaputil/config.go index 47f3c042e53..b23f0304e80 100644 --- a/vendor/github.com/hashicorp/vault/sdk/helper/ldaputil/config.go +++ b/vendor/github.com/hashicorp/vault/sdk/helper/ldaputil/config.go @@ -1,6 +1,7 @@ package ldaputil import ( + "crypto/tls" "crypto/x509" "encoding/pem" "errors" @@ -18,6 +19,14 @@ import ( // Not all fields will be used by every integration. func ConfigFields() map[string]*framework.FieldSchema { return map[string]*framework.FieldSchema{ + "anonymous_group_search": { + Type: framework.TypeBool, + Default: false, + Description: "Use anonymous binds when performing LDAP group searches (if true the initial credentials will still be used for the initial connection test).", + DisplayAttrs: &framework.DisplayAttributes{ + Name: "Anonymous group search", + }, + }, "url": { Type: framework.TypeString, Default: "ldap://127.0.0.1", @@ -105,6 +114,28 @@ Default: cn`, "certificate": { Type: framework.TypeString, Description: "CA certificate to use when verifying LDAP server certificate, must be x509 PEM encoded (optional)", + DisplayAttrs: &framework.DisplayAttributes{ + Name: "CA certificate", + EditType: "file", + }, + }, + + "client_tls_cert": { + Type: framework.TypeString, + Description: "Client certificate to provide to the LDAP server, must be x509 PEM encoded (optional)", + DisplayAttrs: &framework.DisplayAttributes{ + Name: "Client certificate", + EditType: "file", + }, + }, + + "client_tls_key": { + Type: framework.TypeString, + Description: "Client certificate key to provide to the LDAP server, must be x509 PEM encoded (optional)", + DisplayAttrs: &framework.DisplayAttributes{ + Name: "Client key", + EditType: "file", + }, }, "discoverdn": { @@ -196,6 +227,10 @@ func NewConfigEntry(existing *ConfigEntry, d *framework.FieldData) (*ConfigEntry cfg = new(ConfigEntry) } + if _, ok := d.Raw["anonymous_group_search"]; ok || !hadExisting { + cfg.AnonymousGroupSearch = d.Get("anonymous_group_search").(bool) + } + if _, ok := d.Raw["url"]; ok || !hadExisting { cfg.Url = strings.ToLower(d.Get("url").(string)) } @@ -236,20 +271,31 @@ func NewConfigEntry(existing *ConfigEntry, d *framework.FieldData) (*ConfigEntry if _, ok := d.Raw["certificate"]; ok || !hadExisting { certificate := d.Get("certificate").(string) if certificate != "" { - block, _ := pem.Decode([]byte(certificate)) - - if block == nil || block.Type != "CERTIFICATE" { - return nil, errors.New("failed to decode PEM block in the certificate") - } - _, err := x509.ParseCertificate(block.Bytes) - if err != nil { - return nil, errwrap.Wrapf("failed to parse certificate: {{err}}", err) + if err := validateCertificate([]byte(certificate)); err != nil { + return nil, errwrap.Wrapf("failed to parse server tls cert: {{err}}", err) } } - cfg.Certificate = certificate } + if _, ok := d.Raw["client_tls_cert"]; ok || !hadExisting { + clientTLSCert := d.Get("client_tls_cert").(string) + cfg.ClientTLSCert = clientTLSCert + } + + if _, ok := d.Raw["client_tls_key"]; ok || !hadExisting { + clientTLSKey := d.Get("client_tls_key").(string) + cfg.ClientTLSKey = clientTLSKey + } + + if cfg.ClientTLSCert != "" && cfg.ClientTLSKey != "" { + if _, err := tls.X509KeyPair([]byte(cfg.ClientTLSCert), []byte(cfg.ClientTLSKey)); err != nil { + return nil, errwrap.Wrapf("failed to parse client X509 key pair: {{err}}", err) + } + } else if cfg.ClientTLSCert != "" || cfg.ClientTLSKey != "" { + return nil, fmt.Errorf("both client_tls_cert and client_tls_key must be set") + } + if _, ok := d.Raw["insecure_tls"]; ok || !hadExisting { cfg.InsecureTLS = d.Get("insecure_tls").(bool) } @@ -318,12 +364,15 @@ func NewConfigEntry(existing *ConfigEntry, d *framework.FieldData) (*ConfigEntry type ConfigEntry struct { Url string `json:"url"` UserDN string `json:"userdn"` + AnonymousGroupSearch bool `json:"anonymous_group_search"` GroupDN string `json:"groupdn"` GroupFilter string `json:"groupfilter"` GroupAttr string `json:"groupattr"` UPNDomain string `json:"upndomain"` UserAttr string `json:"userattr"` Certificate string `json:"certificate"` + ClientTLSCert string `json:"client_tls_cert` + ClientTLSKey string `json:"client_tls_key` InsecureTLS bool `json:"insecure_tls"` StartTLS bool `json:"starttls"` BindDN string `json:"binddn"` @@ -351,22 +400,23 @@ func (c *ConfigEntry) Map() map[string]interface{} { func (c *ConfigEntry) PasswordlessMap() map[string]interface{} { m := map[string]interface{}{ - "url": c.Url, - "userdn": c.UserDN, - "groupdn": c.GroupDN, - "groupfilter": c.GroupFilter, - "groupattr": c.GroupAttr, - "upndomain": c.UPNDomain, - "userattr": c.UserAttr, - "certificate": c.Certificate, - "insecure_tls": c.InsecureTLS, - "starttls": c.StartTLS, - "binddn": c.BindDN, - "deny_null_bind": c.DenyNullBind, - "discoverdn": c.DiscoverDN, - "tls_min_version": c.TLSMinVersion, - "tls_max_version": c.TLSMaxVersion, - "use_token_groups": c.UseTokenGroups, + "url": c.Url, + "userdn": c.UserDN, + "groupdn": c.GroupDN, + "groupfilter": c.GroupFilter, + "groupattr": c.GroupAttr, + "upndomain": c.UPNDomain, + "userattr": c.UserAttr, + "certificate": c.Certificate, + "insecure_tls": c.InsecureTLS, + "starttls": c.StartTLS, + "binddn": c.BindDN, + "deny_null_bind": c.DenyNullBind, + "discoverdn": c.DiscoverDN, + "tls_min_version": c.TLSMinVersion, + "tls_max_version": c.TLSMaxVersion, + "use_token_groups": c.UseTokenGroups, + "anonymous_group_search": c.AnonymousGroupSearch, } if c.CaseSensitiveNames != nil { m["case_sensitive_names"] = *c.CaseSensitiveNames @@ -377,6 +427,18 @@ func (c *ConfigEntry) PasswordlessMap() map[string]interface{} { return m } +func validateCertificate(pemBlock []byte) error { + block, _ := pem.Decode([]byte(pemBlock)) + if block == nil || block.Type != "CERTIFICATE" { + return errors.New("failed to decode PEM block in the certificate") + } + _, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return fmt.Errorf("failed to parse certificate %s", err.Error()) + } + return nil +} + func (c *ConfigEntry) Validate() error { if len(c.Url) == 0 { return errors.New("at least one url must be provided") @@ -398,13 +460,13 @@ func (c *ConfigEntry) Validate() error { return errors.New("'tls_max_version' must be greater than or equal to 'tls_min_version'") } if c.Certificate != "" { - block, _ := pem.Decode([]byte(c.Certificate)) - if block == nil || block.Type != "CERTIFICATE" { - return errors.New("failed to decode PEM block in the certificate") + if err := validateCertificate([]byte(c.Certificate)); err != nil { + return errwrap.Wrapf("failed to parse server tls cert: {{err}}", err) } - _, err := x509.ParseCertificate(block.Bytes) - if err != nil { - return fmt.Errorf("failed to parse certificate %s", err.Error()) + } + if c.ClientTLSCert != "" && c.ClientTLSKey != "" { + if _, err := tls.X509KeyPair([]byte(c.ClientTLSCert), []byte(c.ClientTLSKey)); err != nil { + return errwrap.Wrapf("failed to parse client X509 key pair: {{err}}", err) } } return nil