@@ -19,21 +19,44 @@ type LdapServiceConfig struct {
1919 BaseDN string
2020 Insecure bool
2121 SearchFilter string
22+ AuthCert string
23+ AuthKey string
2224}
2325
2426type LdapService struct {
25- Config LdapServiceConfig // exported so as the auth service can use it
27+ config LdapServiceConfig
2628 conn * ldapgo.Conn
2729 mutex sync.RWMutex
30+ cert * tls.Certificate
2831}
2932
3033func NewLdapService (config LdapServiceConfig ) * LdapService {
3134 return & LdapService {
32- Config : config ,
35+ config : config ,
3336 }
3437}
3538
3639func (ldap * LdapService ) Init () error {
40+ // Check whether authentication with client certificate is possible
41+ if ldap .config .AuthCert != "" && ldap .config .AuthKey != "" {
42+ cert , err := tls .LoadX509KeyPair (ldap .config .AuthCert , ldap .config .AuthKey )
43+ if err != nil {
44+ return fmt .Errorf ("failed to initialize LDAP with mTLS authentication: %w" , err )
45+ }
46+ ldap .cert = & cert
47+ log .Info ().Msg ("Using LDAP with mTLS authentication" )
48+
49+ // TODO: Add optional extra CA certificates, instead of `InsecureSkipVerify`
50+ /*
51+ caCert, _ := ioutil.ReadFile(*caFile)
52+ caCertPool := x509.NewCertPool()
53+ caCertPool.AppendCertsFromPEM(caCert)
54+ tlsConfig := &tls.Config{
55+ ...
56+ RootCAs: caCertPool,
57+ }
58+ */
59+ }
3760 _ , err := ldap .connect ()
3861 if err != nil {
3962 return fmt .Errorf ("failed to connect to LDAP server: %w" , err )
@@ -60,31 +83,46 @@ func (ldap *LdapService) connect() (*ldapgo.Conn, error) {
6083 ldap .mutex .Lock ()
6184 defer ldap .mutex .Unlock ()
6285
63- conn , err := ldapgo .DialURL (ldap .Config .Address , ldapgo .DialWithTLSConfig (& tls.Config {
64- InsecureSkipVerify : ldap .Config .Insecure ,
65- MinVersion : tls .VersionTLS12 ,
66- }))
86+ var conn * ldapgo.Conn
87+ var err error
88+
89+ // TODO: There's also STARTTLS (or SASL)-based mTLS authentication
90+ // scenario, where we first connect to plain text port (389) and
91+ // continue with a STARTTLS negotiation:
92+ // 1. conn = ldap.DialURL("ldap://ldap.example.com:389")
93+ // 2. conn.StartTLS(tlsConfig)
94+ // 3. conn.externalBind()
95+ if ldap .cert != nil {
96+ conn , err = ldapgo .DialURL (ldap .config .Address , ldapgo .DialWithTLSConfig (& tls.Config {
97+ MinVersion : tls .VersionTLS12 ,
98+ Certificates : []tls.Certificate {* ldap .cert },
99+ }))
100+ } else {
101+ conn , err = ldapgo .DialURL (ldap .config .Address , ldapgo .DialWithTLSConfig (& tls.Config {
102+ InsecureSkipVerify : ldap .config .Insecure ,
103+ MinVersion : tls .VersionTLS12 ,
104+ }))
105+ }
67106 if err != nil {
68107 return nil , err
69108 }
70109
71- err = conn .Bind (ldap .Config .BindDN , ldap .Config .BindPassword )
110+ ldap .conn = conn
111+
112+ err = ldap .BindService (false )
72113 if err != nil {
73114 return nil , err
74115 }
75-
76- // Set and return the connection
77- ldap .conn = conn
78- return conn , nil
116+ return ldap .conn , nil
79117}
80118
81119func (ldap * LdapService ) Search (username string ) (string , error ) {
82120 // Escape the username to prevent LDAP injection
83121 escapedUsername := ldapgo .EscapeFilter (username )
84- filter := fmt .Sprintf (ldap .Config .SearchFilter , escapedUsername )
122+ filter := fmt .Sprintf (ldap .config .SearchFilter , escapedUsername )
85123
86124 searchRequest := ldapgo .NewSearchRequest (
87- ldap .Config .BaseDN ,
125+ ldap .config .BaseDN ,
88126 ldapgo .ScopeWholeSubtree , ldapgo .NeverDerefAliases , 0 , 0 , false ,
89127 filter ,
90128 []string {"dn" },
@@ -107,6 +145,19 @@ func (ldap *LdapService) Search(username string) (string, error) {
107145 return userDN , nil
108146}
109147
148+ func (ldap * LdapService ) BindService (rebind bool ) error {
149+ // Locks must not be used for initial binding attempt
150+ if rebind {
151+ ldap .mutex .Lock ()
152+ defer ldap .mutex .Unlock ()
153+ }
154+
155+ if ldap .cert != nil {
156+ return ldap .conn .ExternalBind ()
157+ }
158+ return ldap .conn .Bind (ldap .config .BindDN , ldap .config .BindPassword )
159+ }
160+
110161func (ldap * LdapService ) Bind (userDN string , password string ) error {
111162 ldap .mutex .Lock ()
112163 defer ldap .mutex .Unlock ()
0 commit comments