github.com/hashicorp/vault/sdk@v0.11.0/helper/ldaputil/config.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package ldaputil
     5  
     6  import (
     7  	"crypto/tls"
     8  	"crypto/x509"
     9  	"encoding/pem"
    10  	"errors"
    11  	"fmt"
    12  	"strings"
    13  	"text/template"
    14  
    15  	capldap "github.com/hashicorp/cap/ldap"
    16  	"github.com/hashicorp/go-secure-stdlib/tlsutil"
    17  
    18  	"github.com/hashicorp/vault/sdk/framework"
    19  
    20  	"github.com/hashicorp/errwrap"
    21  
    22  	"github.com/go-ldap/ldap/v3"
    23  )
    24  
    25  var ldapDerefAliasMap = map[string]int{
    26  	"never":     ldap.NeverDerefAliases,
    27  	"finding":   ldap.DerefFindingBaseObj,
    28  	"searching": ldap.DerefInSearching,
    29  	"always":    ldap.DerefAlways,
    30  }
    31  
    32  // ConfigFields returns all the config fields that can potentially be used by the LDAP client.
    33  // Not all fields will be used by every integration.
    34  func ConfigFields() map[string]*framework.FieldSchema {
    35  	return map[string]*framework.FieldSchema{
    36  		"anonymous_group_search": {
    37  			Type:        framework.TypeBool,
    38  			Default:     false,
    39  			Description: "Use anonymous binds when performing LDAP group searches (if true the initial credentials will still be used for the initial connection test).",
    40  			DisplayAttrs: &framework.DisplayAttributes{
    41  				Name: "Anonymous group search",
    42  			},
    43  		},
    44  		"url": {
    45  			Type:        framework.TypeString,
    46  			Default:     "ldap://127.0.0.1",
    47  			Description: "LDAP URL to connect to (default: ldap://127.0.0.1). Multiple URLs can be specified by concatenating them with commas; they will be tried in-order.",
    48  			DisplayAttrs: &framework.DisplayAttributes{
    49  				Name: "URL",
    50  			},
    51  		},
    52  
    53  		"userdn": {
    54  			Type:        framework.TypeString,
    55  			Description: "LDAP domain to use for users (eg: ou=People,dc=example,dc=org)",
    56  			DisplayAttrs: &framework.DisplayAttributes{
    57  				Name: "User DN",
    58  			},
    59  		},
    60  
    61  		"binddn": {
    62  			Type:        framework.TypeString,
    63  			Description: "LDAP DN for searching for the user DN (optional)",
    64  			DisplayAttrs: &framework.DisplayAttributes{
    65  				Name: "Name of Object to bind (binddn)",
    66  			},
    67  		},
    68  
    69  		"bindpass": {
    70  			Type:        framework.TypeString,
    71  			Description: "LDAP password for searching for the user DN (optional)",
    72  			DisplayAttrs: &framework.DisplayAttributes{
    73  				Sensitive: true,
    74  			},
    75  		},
    76  
    77  		"groupdn": {
    78  			Type:        framework.TypeString,
    79  			Description: "LDAP search base to use for group membership search (eg: ou=Groups,dc=example,dc=org)",
    80  			DisplayAttrs: &framework.DisplayAttributes{
    81  				Name: "Group DN",
    82  			},
    83  		},
    84  
    85  		"groupfilter": {
    86  			Type:    framework.TypeString,
    87  			Default: "(|(memberUid={{.Username}})(member={{.UserDN}})(uniqueMember={{.UserDN}}))",
    88  			Description: `Go template for querying group membership of user (optional)
    89  The template can access the following context variables: UserDN, Username
    90  Example: (&(objectClass=group)(member:1.2.840.113556.1.4.1941:={{.UserDN}}))
    91  Default: (|(memberUid={{.Username}})(member={{.UserDN}})(uniqueMember={{.UserDN}}))`,
    92  			DisplayAttrs: &framework.DisplayAttributes{
    93  				Name: "Group Filter",
    94  			},
    95  		},
    96  
    97  		"groupattr": {
    98  			Type:    framework.TypeString,
    99  			Default: "cn",
   100  			Description: `LDAP attribute to follow on objects returned by <groupfilter>
   101  in order to enumerate user group membership.
   102  Examples: "cn" or "memberOf", etc.
   103  Default: cn`,
   104  			DisplayAttrs: &framework.DisplayAttributes{
   105  				Name:  "Group Attribute",
   106  				Value: "cn",
   107  			},
   108  		},
   109  
   110  		"userfilter": {
   111  			Type:    framework.TypeString,
   112  			Default: "({{.UserAttr}}={{.Username}})",
   113  			Description: `Go template for LDAP user search filer (optional)
   114  The template can access the following context variables: UserAttr, Username
   115  Default: ({{.UserAttr}}={{.Username}})`,
   116  			DisplayAttrs: &framework.DisplayAttributes{
   117  				Name: "User Search Filter",
   118  			},
   119  		},
   120  
   121  		"upndomain": {
   122  			Type:        framework.TypeString,
   123  			Description: "Enables userPrincipalDomain login with [username]@UPNDomain (optional)",
   124  			DisplayAttrs: &framework.DisplayAttributes{
   125  				Name: "User Principal (UPN) Domain",
   126  			},
   127  		},
   128  
   129  		"username_as_alias": {
   130  			Type:        framework.TypeBool,
   131  			Default:     false,
   132  			Description: "If true, sets the alias name to the username",
   133  		},
   134  
   135  		"userattr": {
   136  			Type:        framework.TypeString,
   137  			Default:     "cn",
   138  			Description: "Attribute used for users (default: cn)",
   139  			DisplayAttrs: &framework.DisplayAttributes{
   140  				Name:  "User Attribute",
   141  				Value: "cn",
   142  			},
   143  		},
   144  
   145  		"certificate": {
   146  			Type:        framework.TypeString,
   147  			Description: "CA certificate to use when verifying LDAP server certificate, must be x509 PEM encoded (optional)",
   148  			DisplayAttrs: &framework.DisplayAttributes{
   149  				Name:     "CA certificate",
   150  				EditType: "file",
   151  			},
   152  		},
   153  
   154  		"client_tls_cert": {
   155  			Type:        framework.TypeString,
   156  			Description: "Client certificate to provide to the LDAP server, must be x509 PEM encoded (optional)",
   157  			DisplayAttrs: &framework.DisplayAttributes{
   158  				Name:     "Client certificate",
   159  				EditType: "file",
   160  			},
   161  		},
   162  
   163  		"client_tls_key": {
   164  			Type:        framework.TypeString,
   165  			Description: "Client certificate key to provide to the LDAP server, must be x509 PEM encoded (optional)",
   166  			DisplayAttrs: &framework.DisplayAttributes{
   167  				Name:     "Client key",
   168  				EditType: "file",
   169  			},
   170  		},
   171  
   172  		"discoverdn": {
   173  			Type:        framework.TypeBool,
   174  			Description: "Use anonymous bind to discover the bind DN of a user (optional)",
   175  			DisplayAttrs: &framework.DisplayAttributes{
   176  				Name: "Discover DN",
   177  			},
   178  		},
   179  
   180  		"insecure_tls": {
   181  			Type:        framework.TypeBool,
   182  			Description: "Skip LDAP server SSL Certificate verification - VERY insecure (optional)",
   183  			DisplayAttrs: &framework.DisplayAttributes{
   184  				Name: "Insecure TLS",
   185  			},
   186  		},
   187  
   188  		"starttls": {
   189  			Type:        framework.TypeBool,
   190  			Description: "Issue a StartTLS command after establishing unencrypted connection (optional)",
   191  			DisplayAttrs: &framework.DisplayAttributes{
   192  				Name: "Issue StartTLS",
   193  			},
   194  		},
   195  
   196  		"tls_min_version": {
   197  			Type:        framework.TypeString,
   198  			Default:     "tls12",
   199  			Description: "Minimum TLS version to use. Accepted values are 'tls10', 'tls11', 'tls12' or 'tls13'. Defaults to 'tls12'",
   200  			DisplayAttrs: &framework.DisplayAttributes{
   201  				Name: "Minimum TLS Version",
   202  			},
   203  			AllowedValues: []interface{}{"tls10", "tls11", "tls12", "tls13"},
   204  		},
   205  
   206  		"tls_max_version": {
   207  			Type:        framework.TypeString,
   208  			Default:     "tls12",
   209  			Description: "Maximum TLS version to use. Accepted values are 'tls10', 'tls11', 'tls12' or 'tls13'. Defaults to 'tls12'",
   210  			DisplayAttrs: &framework.DisplayAttributes{
   211  				Name: "Maximum TLS Version",
   212  			},
   213  			AllowedValues: []interface{}{"tls10", "tls11", "tls12", "tls13"},
   214  		},
   215  
   216  		"deny_null_bind": {
   217  			Type:        framework.TypeBool,
   218  			Default:     true,
   219  			Description: "Denies an unauthenticated LDAP bind request if the user's password is empty; defaults to true",
   220  		},
   221  
   222  		"case_sensitive_names": {
   223  			Type:        framework.TypeBool,
   224  			Description: "If true, case sensitivity will be used when comparing usernames and groups for matching policies.",
   225  		},
   226  
   227  		"use_token_groups": {
   228  			Type:        framework.TypeBool,
   229  			Default:     false,
   230  			Description: "If true, use the Active Directory tokenGroups constructed attribute of the user to find the group memberships. This will find all security groups including nested ones.",
   231  		},
   232  
   233  		"use_pre111_group_cn_behavior": {
   234  			Type:        framework.TypeBool,
   235  			Description: "In Vault 1.1.1 a fix for handling group CN values of different cases unfortunately introduced a regression that could cause previously defined groups to not be found due to a change in the resulting name. If set true, the pre-1.1.1 behavior for matching group CNs will be used. This is only needed in some upgrade scenarios for backwards compatibility. It is enabled by default if the config is upgraded but disabled by default on new configurations.",
   236  		},
   237  
   238  		"request_timeout": {
   239  			Type:        framework.TypeDurationSecond,
   240  			Description: "Timeout, in seconds, for the connection when making requests against the server before returning back an error.",
   241  			Default:     "90s",
   242  		},
   243  
   244  		"connection_timeout": {
   245  			Type:        framework.TypeDurationSecond,
   246  			Description: "Timeout, in seconds, when attempting to connect to the LDAP server before trying the next URL in the configuration.",
   247  			Default:     "30s",
   248  		},
   249  
   250  		"dereference_aliases": {
   251  			Type:          framework.TypeString,
   252  			Description:   "When aliases should be dereferenced on search operations. Accepted values are 'never', 'finding', 'searching', 'always'. Defaults to 'never'.",
   253  			Default:       "never",
   254  			AllowedValues: []interface{}{"never", "finding", "searching", "always"},
   255  		},
   256  
   257  		"max_page_size": {
   258  			Type:        framework.TypeInt,
   259  			Description: "If set to a value greater than 0, the LDAP backend will use the LDAP server's paged search control to request pages of up to the given size. This can be used to avoid hitting the LDAP server's maximum result size limit. Otherwise, the LDAP backend will not use the paged search control.",
   260  			Default:     0,
   261  		},
   262  	}
   263  }
   264  
   265  /*
   266   * Creates and initializes a ConfigEntry object with its default values,
   267   * as specified by the passed schema.
   268   */
   269  func NewConfigEntry(existing *ConfigEntry, d *framework.FieldData) (*ConfigEntry, error) {
   270  	var hadExisting bool
   271  	var cfg *ConfigEntry
   272  
   273  	if existing != nil {
   274  		cfg = existing
   275  		hadExisting = true
   276  	} else {
   277  		cfg = new(ConfigEntry)
   278  	}
   279  
   280  	if _, ok := d.Raw["anonymous_group_search"]; ok || !hadExisting {
   281  		cfg.AnonymousGroupSearch = d.Get("anonymous_group_search").(bool)
   282  	}
   283  
   284  	if _, ok := d.Raw["username_as_alias"]; ok || !hadExisting {
   285  		cfg.UsernameAsAlias = d.Get("username_as_alias").(bool)
   286  	}
   287  
   288  	if _, ok := d.Raw["url"]; ok || !hadExisting {
   289  		cfg.Url = strings.ToLower(d.Get("url").(string))
   290  	}
   291  
   292  	if _, ok := d.Raw["userfilter"]; ok || !hadExisting {
   293  		userfilter := d.Get("userfilter").(string)
   294  		if userfilter != "" {
   295  			// Validate the template before proceeding
   296  			_, err := template.New("queryTemplate").Parse(userfilter)
   297  			if err != nil {
   298  				return nil, errwrap.Wrapf("invalid userfilter: {{err}}", err)
   299  			}
   300  		}
   301  
   302  		cfg.UserFilter = userfilter
   303  	}
   304  
   305  	if _, ok := d.Raw["userattr"]; ok || !hadExisting {
   306  		cfg.UserAttr = strings.ToLower(d.Get("userattr").(string))
   307  	}
   308  
   309  	if _, ok := d.Raw["userdn"]; ok || !hadExisting {
   310  		cfg.UserDN = d.Get("userdn").(string)
   311  	}
   312  
   313  	if _, ok := d.Raw["groupdn"]; ok || !hadExisting {
   314  		cfg.GroupDN = d.Get("groupdn").(string)
   315  	}
   316  
   317  	if _, ok := d.Raw["groupfilter"]; ok || !hadExisting {
   318  		groupfilter := d.Get("groupfilter").(string)
   319  		if groupfilter != "" {
   320  			// Validate the template before proceeding
   321  			_, err := template.New("queryTemplate").Parse(groupfilter)
   322  			if err != nil {
   323  				return nil, errwrap.Wrapf("invalid groupfilter: {{err}}", err)
   324  			}
   325  		}
   326  
   327  		cfg.GroupFilter = groupfilter
   328  	}
   329  
   330  	if _, ok := d.Raw["groupattr"]; ok || !hadExisting {
   331  		cfg.GroupAttr = d.Get("groupattr").(string)
   332  	}
   333  
   334  	if _, ok := d.Raw["upndomain"]; ok || !hadExisting {
   335  		cfg.UPNDomain = d.Get("upndomain").(string)
   336  	}
   337  
   338  	if _, ok := d.Raw["certificate"]; ok || !hadExisting {
   339  		certificate := d.Get("certificate").(string)
   340  		if certificate != "" {
   341  			if err := validateCertificate([]byte(certificate)); err != nil {
   342  				return nil, errwrap.Wrapf("failed to parse server tls cert: {{err}}", err)
   343  			}
   344  		}
   345  		cfg.Certificate = certificate
   346  	}
   347  
   348  	if _, ok := d.Raw["client_tls_cert"]; ok || !hadExisting {
   349  		clientTLSCert := d.Get("client_tls_cert").(string)
   350  		cfg.ClientTLSCert = clientTLSCert
   351  	}
   352  
   353  	if _, ok := d.Raw["client_tls_key"]; ok || !hadExisting {
   354  		clientTLSKey := d.Get("client_tls_key").(string)
   355  		cfg.ClientTLSKey = clientTLSKey
   356  	}
   357  
   358  	if cfg.ClientTLSCert != "" && cfg.ClientTLSKey != "" {
   359  		if _, err := tls.X509KeyPair([]byte(cfg.ClientTLSCert), []byte(cfg.ClientTLSKey)); err != nil {
   360  			return nil, errwrap.Wrapf("failed to parse client X509 key pair: {{err}}", err)
   361  		}
   362  	} else if cfg.ClientTLSCert != "" || cfg.ClientTLSKey != "" {
   363  		return nil, fmt.Errorf("both client_tls_cert and client_tls_key must be set")
   364  	}
   365  
   366  	if _, ok := d.Raw["insecure_tls"]; ok || !hadExisting {
   367  		cfg.InsecureTLS = d.Get("insecure_tls").(bool)
   368  	}
   369  
   370  	if _, ok := d.Raw["tls_min_version"]; ok || !hadExisting {
   371  		cfg.TLSMinVersion = d.Get("tls_min_version").(string)
   372  		_, ok = tlsutil.TLSLookup[cfg.TLSMinVersion]
   373  		if !ok {
   374  			return nil, errors.New("invalid 'tls_min_version'")
   375  		}
   376  	}
   377  
   378  	if _, ok := d.Raw["tls_max_version"]; ok || !hadExisting {
   379  		cfg.TLSMaxVersion = d.Get("tls_max_version").(string)
   380  		_, ok = tlsutil.TLSLookup[cfg.TLSMaxVersion]
   381  		if !ok {
   382  			return nil, fmt.Errorf("invalid 'tls_max_version'")
   383  		}
   384  	}
   385  	if cfg.TLSMaxVersion < cfg.TLSMinVersion {
   386  		return nil, fmt.Errorf("'tls_max_version' must be greater than or equal to 'tls_min_version'")
   387  	}
   388  
   389  	if _, ok := d.Raw["starttls"]; ok || !hadExisting {
   390  		cfg.StartTLS = d.Get("starttls").(bool)
   391  	}
   392  
   393  	if _, ok := d.Raw["binddn"]; ok || !hadExisting {
   394  		cfg.BindDN = d.Get("binddn").(string)
   395  	}
   396  
   397  	if _, ok := d.Raw["bindpass"]; ok || !hadExisting {
   398  		cfg.BindPassword = d.Get("bindpass").(string)
   399  	}
   400  
   401  	if _, ok := d.Raw["deny_null_bind"]; ok || !hadExisting {
   402  		cfg.DenyNullBind = d.Get("deny_null_bind").(bool)
   403  	}
   404  
   405  	if _, ok := d.Raw["discoverdn"]; ok || !hadExisting {
   406  		cfg.DiscoverDN = d.Get("discoverdn").(bool)
   407  	}
   408  
   409  	if _, ok := d.Raw["case_sensitive_names"]; ok || !hadExisting {
   410  		cfg.CaseSensitiveNames = new(bool)
   411  		*cfg.CaseSensitiveNames = d.Get("case_sensitive_names").(bool)
   412  	}
   413  
   414  	usePre111GroupCNBehavior, ok := d.GetOk("use_pre111_group_cn_behavior")
   415  	if ok {
   416  		cfg.UsePre111GroupCNBehavior = new(bool)
   417  		*cfg.UsePre111GroupCNBehavior = usePre111GroupCNBehavior.(bool)
   418  	}
   419  
   420  	if _, ok := d.Raw["use_token_groups"]; ok || !hadExisting {
   421  		cfg.UseTokenGroups = d.Get("use_token_groups").(bool)
   422  	}
   423  
   424  	if _, ok := d.Raw["request_timeout"]; ok || !hadExisting {
   425  		cfg.RequestTimeout = d.Get("request_timeout").(int)
   426  	}
   427  
   428  	if _, ok := d.Raw["connection_timeout"]; ok || !hadExisting {
   429  		cfg.ConnectionTimeout = d.Get("connection_timeout").(int)
   430  	}
   431  
   432  	if _, ok := d.Raw["dereference_aliases"]; ok || !hadExisting {
   433  		cfg.DerefAliases = d.Get("dereference_aliases").(string)
   434  	}
   435  
   436  	if _, ok := d.Raw["max_page_size"]; ok || !hadExisting {
   437  		cfg.MaximumPageSize = d.Get("max_page_size").(int)
   438  	}
   439  
   440  	return cfg, nil
   441  }
   442  
   443  type ConfigEntry struct {
   444  	Url                      string `json:"url"`
   445  	UserDN                   string `json:"userdn"`
   446  	AnonymousGroupSearch     bool   `json:"anonymous_group_search"`
   447  	GroupDN                  string `json:"groupdn"`
   448  	GroupFilter              string `json:"groupfilter"`
   449  	GroupAttr                string `json:"groupattr"`
   450  	UPNDomain                string `json:"upndomain"`
   451  	UsernameAsAlias          bool   `json:"username_as_alias"`
   452  	UserFilter               string `json:"userfilter"`
   453  	UserAttr                 string `json:"userattr"`
   454  	Certificate              string `json:"certificate"`
   455  	InsecureTLS              bool   `json:"insecure_tls"`
   456  	StartTLS                 bool   `json:"starttls"`
   457  	BindDN                   string `json:"binddn"`
   458  	BindPassword             string `json:"bindpass"`
   459  	DenyNullBind             bool   `json:"deny_null_bind"`
   460  	DiscoverDN               bool   `json:"discoverdn"`
   461  	TLSMinVersion            string `json:"tls_min_version"`
   462  	TLSMaxVersion            string `json:"tls_max_version"`
   463  	UseTokenGroups           bool   `json:"use_token_groups"`
   464  	UsePre111GroupCNBehavior *bool  `json:"use_pre111_group_cn_behavior"`
   465  	RequestTimeout           int    `json:"request_timeout"`
   466  	ConnectionTimeout        int    `json:"connection_timeout"` // deprecated: use RequestTimeout
   467  	DerefAliases             string `json:"dereference_aliases"`
   468  	MaximumPageSize          int    `json:"max_page_size"`
   469  
   470  	// These json tags deviate from snake case because there was a past issue
   471  	// where the tag was being ignored, causing it to be jsonified as "CaseSensitiveNames", etc.
   472  	// To continue reading in users' previously stored values,
   473  	// we chose to carry that forward.
   474  	CaseSensitiveNames *bool  `json:"CaseSensitiveNames,omitempty"`
   475  	ClientTLSCert      string `json:"ClientTLSCert"`
   476  	ClientTLSKey       string `json:"ClientTLSKey"`
   477  }
   478  
   479  func (c *ConfigEntry) Map() map[string]interface{} {
   480  	m := c.PasswordlessMap()
   481  	m["bindpass"] = c.BindPassword
   482  	return m
   483  }
   484  
   485  func (c *ConfigEntry) PasswordlessMap() map[string]interface{} {
   486  	m := map[string]interface{}{
   487  		"url":                    c.Url,
   488  		"userdn":                 c.UserDN,
   489  		"groupdn":                c.GroupDN,
   490  		"groupfilter":            c.GroupFilter,
   491  		"groupattr":              c.GroupAttr,
   492  		"userfilter":             c.UserFilter,
   493  		"upndomain":              c.UPNDomain,
   494  		"userattr":               c.UserAttr,
   495  		"certificate":            c.Certificate,
   496  		"insecure_tls":           c.InsecureTLS,
   497  		"starttls":               c.StartTLS,
   498  		"binddn":                 c.BindDN,
   499  		"deny_null_bind":         c.DenyNullBind,
   500  		"discoverdn":             c.DiscoverDN,
   501  		"tls_min_version":        c.TLSMinVersion,
   502  		"tls_max_version":        c.TLSMaxVersion,
   503  		"use_token_groups":       c.UseTokenGroups,
   504  		"anonymous_group_search": c.AnonymousGroupSearch,
   505  		"request_timeout":        c.RequestTimeout,
   506  		"connection_timeout":     c.ConnectionTimeout,
   507  		"username_as_alias":      c.UsernameAsAlias,
   508  		"dereference_aliases":    c.DerefAliases,
   509  		"max_page_size":          c.MaximumPageSize,
   510  	}
   511  	if c.CaseSensitiveNames != nil {
   512  		m["case_sensitive_names"] = *c.CaseSensitiveNames
   513  	}
   514  	if c.UsePre111GroupCNBehavior != nil {
   515  		m["use_pre111_group_cn_behavior"] = *c.UsePre111GroupCNBehavior
   516  	}
   517  	return m
   518  }
   519  
   520  func validateCertificate(pemBlock []byte) error {
   521  	block, _ := pem.Decode([]byte(pemBlock))
   522  	if block == nil || block.Type != "CERTIFICATE" {
   523  		return errors.New("failed to decode PEM block in the certificate")
   524  	}
   525  	_, err := x509.ParseCertificate(block.Bytes)
   526  	if err != nil {
   527  		return fmt.Errorf("failed to parse certificate %s", err.Error())
   528  	}
   529  	return nil
   530  }
   531  
   532  func (c *ConfigEntry) Validate() error {
   533  	if len(c.Url) == 0 {
   534  		return errors.New("at least one url must be provided")
   535  	}
   536  	// Note: This logic is driven by the logic in GetUserBindDN.
   537  	// If updating this, please also update the logic there.
   538  	if !c.DiscoverDN && (c.BindDN == "" || c.BindPassword == "") && c.UPNDomain == "" && c.UserDN == "" {
   539  		return errors.New("cannot derive UserBindDN")
   540  	}
   541  	tlsMinVersion, ok := tlsutil.TLSLookup[c.TLSMinVersion]
   542  	if !ok {
   543  		return errors.New("invalid 'tls_min_version' in config")
   544  	}
   545  	tlsMaxVersion, ok := tlsutil.TLSLookup[c.TLSMaxVersion]
   546  	if !ok {
   547  		return errors.New("invalid 'tls_max_version' in config")
   548  	}
   549  	if tlsMaxVersion < tlsMinVersion {
   550  		return errors.New("'tls_max_version' must be greater than or equal to 'tls_min_version'")
   551  	}
   552  	if c.Certificate != "" {
   553  		if err := validateCertificate([]byte(c.Certificate)); err != nil {
   554  			return errwrap.Wrapf("failed to parse server tls cert: {{err}}", err)
   555  		}
   556  	}
   557  	if c.ClientTLSCert != "" && c.ClientTLSKey != "" {
   558  		if _, err := tls.X509KeyPair([]byte(c.ClientTLSCert), []byte(c.ClientTLSKey)); err != nil {
   559  			return errwrap.Wrapf("failed to parse client X509 key pair: {{err}}", err)
   560  		}
   561  	}
   562  	return nil
   563  }
   564  
   565  func ConvertConfig(cfg *ConfigEntry) *capldap.ClientConfig {
   566  	// cap/ldap doesn't have a notion of connection_timeout, and uses a single timeout value for
   567  	// both the net.Dialer and ldap connection timeout.
   568  	// So take the smaller of the two values and use that as the timeout value.
   569  	minTimeout := min(cfg.ConnectionTimeout, cfg.RequestTimeout)
   570  	urls := strings.Split(cfg.Url, ",")
   571  	config := &capldap.ClientConfig{
   572  		URLs:                                 urls,
   573  		UserDN:                               cfg.UserDN,
   574  		AnonymousGroupSearch:                 cfg.AnonymousGroupSearch,
   575  		GroupDN:                              cfg.GroupDN,
   576  		GroupFilter:                          cfg.GroupFilter,
   577  		GroupAttr:                            cfg.GroupAttr,
   578  		UPNDomain:                            cfg.UPNDomain,
   579  		UserFilter:                           cfg.UserFilter,
   580  		UserAttr:                             cfg.UserAttr,
   581  		ClientTLSCert:                        cfg.ClientTLSCert,
   582  		ClientTLSKey:                         cfg.ClientTLSKey,
   583  		InsecureTLS:                          cfg.InsecureTLS,
   584  		StartTLS:                             cfg.StartTLS,
   585  		BindDN:                               cfg.BindDN,
   586  		BindPassword:                         cfg.BindPassword,
   587  		AllowEmptyPasswordBinds:              !cfg.DenyNullBind,
   588  		DiscoverDN:                           cfg.DiscoverDN,
   589  		TLSMinVersion:                        cfg.TLSMinVersion,
   590  		TLSMaxVersion:                        cfg.TLSMaxVersion,
   591  		UseTokenGroups:                       cfg.UseTokenGroups,
   592  		RequestTimeout:                       minTimeout,
   593  		IncludeUserAttributes:                true,
   594  		ExcludedUserAttributes:               nil,
   595  		IncludeUserGroups:                    true,
   596  		MaximumPageSize:                      cfg.MaximumPageSize,
   597  		DerefAliases:                         cfg.DerefAliases,
   598  		DeprecatedVaultPre111GroupCNBehavior: cfg.UsePre111GroupCNBehavior,
   599  	}
   600  
   601  	if cfg.Certificate != "" {
   602  		config.Certificates = []string{cfg.Certificate}
   603  	}
   604  
   605  	return config
   606  }
   607  
   608  func min(a, b int) int {
   609  	if a < b {
   610  		return a
   611  	}
   612  	return b
   613  }