providers/ldap: add StartTLS support (#5861)

* providers/ldap: add StartTLS support

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add starttls test

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* update form and docs

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* re-add tls server name

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* update release notes

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens L 2023-06-06 21:40:19 +02:00 committed by GitHub
parent 69f0460f69
commit 0ce41a1b2d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 147 additions and 54 deletions

2
go.mod
View File

@ -3,6 +3,7 @@ module goauthentik.io
go 1.20 go 1.20
require ( require (
beryju.io/ldap v0.1.0
github.com/Netflix/go-env v0.0.0-20210215222557-e437a7e7f9fb github.com/Netflix/go-env v0.0.0-20210215222557-e437a7e7f9fb
github.com/coreos/go-oidc v2.2.1+incompatible github.com/coreos/go-oidc v2.2.1+incompatible
github.com/garyburd/redigo v1.6.4 github.com/garyburd/redigo v1.6.4
@ -20,7 +21,6 @@ require (
github.com/gorilla/websocket v1.5.0 github.com/gorilla/websocket v1.5.0
github.com/jellydator/ttlcache/v3 v3.0.1 github.com/jellydator/ttlcache/v3 v3.0.1
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484 github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484
github.com/nmcclain/ldap v0.0.0-20210720162743-7f8d1e44eeba
github.com/pires/go-proxyproto v0.7.0 github.com/pires/go-proxyproto v0.7.0
github.com/prometheus/client_golang v1.15.1 github.com/prometheus/client_golang v1.15.1
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3

4
go.sum
View File

@ -1,3 +1,5 @@
beryju.io/ldap v0.1.0 h1:rPjGE3qR1Klbvn9N+iECWdzt/tK87XHgz8W5wZJg9B8=
beryju.io/ldap v0.1.0/go.mod h1:sOrYV+ZlDTDu/IvIiEiuAaXzjcpMBE+XXr4V+NJ0pWI=
cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e h1:NeAW1fUYUEWhft7pkxDf6WoUvEZJ/uOKsvtpjLnn8MU= github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e h1:NeAW1fUYUEWhft7pkxDf6WoUvEZJ/uOKsvtpjLnn8MU=
github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
@ -165,8 +167,6 @@ github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJ
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484 h1:D9EvfGQvlkKaDr2CRKN++7HbSXbefUNDrPq60T+g24s= github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484 h1:D9EvfGQvlkKaDr2CRKN++7HbSXbefUNDrPq60T+g24s=
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484/go.mod h1:O1EljZ+oHprtxDDPHiMWVo/5dBT6PlvWX5PSwj80aBA= github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484/go.mod h1:O1EljZ+oHprtxDDPHiMWVo/5dBT6PlvWX5PSwj80aBA=
github.com/nmcclain/ldap v0.0.0-20210720162743-7f8d1e44eeba h1:DO8NFYdcRv1dnyAINJIBm6Bw2XibtLvQniNFGzf2W8E=
github.com/nmcclain/ldap v0.0.0-20210720162743-7f8d1e44eeba/go.mod h1:4S0XndRL8HNOaQBfdViJ2F/GPCgL524xlXRuXFH12/U=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=

View File

@ -3,8 +3,8 @@ package ldap
import ( import (
"net" "net"
"beryju.io/ldap"
"github.com/getsentry/sentry-go" "github.com/getsentry/sentry-go"
"github.com/nmcclain/ldap"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"goauthentik.io/internal/outpost/ldap/bind" "goauthentik.io/internal/outpost/ldap/bind"

View File

@ -3,7 +3,7 @@ package bind
import ( import (
"context" "context"
"github.com/nmcclain/ldap" "beryju.io/ldap"
) )
type Binder interface { type Binder interface {

View File

@ -3,8 +3,8 @@ package direct
import ( import (
"context" "context"
"beryju.io/ldap"
"github.com/getsentry/sentry-go" "github.com/getsentry/sentry-go"
"github.com/nmcclain/ldap"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"goauthentik.io/internal/outpost/flow" "goauthentik.io/internal/outpost/flow"

View File

@ -1,7 +1,7 @@
package direct package direct
import ( import (
"github.com/nmcclain/ldap" "beryju.io/ldap"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"goauthentik.io/internal/outpost/flow" "goauthentik.io/internal/outpost/flow"
"goauthentik.io/internal/outpost/ldap/bind" "goauthentik.io/internal/outpost/ldap/bind"

View File

@ -3,8 +3,8 @@ package memory
import ( import (
"time" "time"
"beryju.io/ldap"
ttlcache "github.com/jellydator/ttlcache/v3" ttlcache "github.com/jellydator/ttlcache/v3"
"github.com/nmcclain/ldap"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"goauthentik.io/internal/outpost/ldap/bind" "goauthentik.io/internal/outpost/ldap/bind"
"goauthentik.io/internal/outpost/ldap/bind/direct" "goauthentik.io/internal/outpost/ldap/bind/direct"

View File

@ -5,7 +5,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/nmcclain/ldap" "beryju.io/ldap"
"goauthentik.io/api/v3" "goauthentik.io/api/v3"
"goauthentik.io/internal/outpost/ldap/constants" "goauthentik.io/internal/outpost/ldap/constants"
"goauthentik.io/internal/outpost/ldap/utils" "goauthentik.io/internal/outpost/ldap/utils"

View File

@ -3,7 +3,7 @@ package group
import ( import (
"strconv" "strconv"
"github.com/nmcclain/ldap" "beryju.io/ldap"
"goauthentik.io/api/v3" "goauthentik.io/api/v3"
"goauthentik.io/internal/outpost/ldap/constants" "goauthentik.io/internal/outpost/ldap/constants"
"goauthentik.io/internal/outpost/ldap/server" "goauthentik.io/internal/outpost/ldap/server"

View File

@ -6,8 +6,8 @@ import (
"strings" "strings"
"sync" "sync"
"beryju.io/ldap"
"github.com/go-openapi/strfmt" "github.com/go-openapi/strfmt"
"github.com/nmcclain/ldap"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"goauthentik.io/api/v3" "goauthentik.io/api/v3"

View File

@ -12,8 +12,9 @@ import (
"goauthentik.io/internal/crypto" "goauthentik.io/internal/crypto"
"goauthentik.io/internal/outpost/ak" "goauthentik.io/internal/outpost/ak"
"goauthentik.io/internal/outpost/ldap/metrics" "goauthentik.io/internal/outpost/ldap/metrics"
"goauthentik.io/internal/utils"
"github.com/nmcclain/ldap" "beryju.io/ldap"
) )
type LDAPServer struct { type LDAPServer struct {
@ -26,15 +27,21 @@ type LDAPServer struct {
} }
func NewServer(ac *ak.APIController) *LDAPServer { func NewServer(ac *ak.APIController) *LDAPServer {
s := ldap.NewServer()
s.EnforceLDAP = true
ls := &LDAPServer{ ls := &LDAPServer{
s: s,
log: log.WithField("logger", "authentik.outpost.ldap"), log: log.WithField("logger", "authentik.outpost.ldap"),
ac: ac, ac: ac,
cs: ak.NewCryptoStore(ac.Client.CryptoApi), cs: ak.NewCryptoStore(ac.Client.CryptoApi),
providers: []*ProviderInstance{}, providers: []*ProviderInstance{},
} }
s := ldap.NewServer()
s.EnforceLDAP = true
tlsConfig := utils.GetTLSConfig()
tlsConfig.GetCertificate = ls.getCertificates
s.StartTLS = tlsConfig
ls.s = s
defaultCert, err := crypto.GenerateSelfSignedCert() defaultCert, err := crypto.GenerateSelfSignedCert()
if err != nil { if err != nil {
log.Warning(err) log.Warning(err)
@ -67,7 +74,7 @@ func (ls *LDAPServer) StartLDAPServer() error {
return err return err
} }
ls.log.WithField("listen", listen).Info("Stopping LDAP server") ls.log.WithField("listen", listen).Info("Stopping LDAP server")
return ls.s.ListenAndServe(listen) return nil
} }
func (ls *LDAPServer) Start() error { func (ls *LDAPServer) Start() error {

View File

@ -5,9 +5,9 @@ import (
"net" "net"
"strings" "strings"
"beryju.io/ldap"
"github.com/getsentry/sentry-go" "github.com/getsentry/sentry-go"
goldap "github.com/go-ldap/ldap/v3" goldap "github.com/go-ldap/ldap/v3"
"github.com/nmcclain/ldap"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"goauthentik.io/internal/outpost/ldap/metrics" "goauthentik.io/internal/outpost/ldap/metrics"
@ -37,24 +37,24 @@ func (ls *LDAPServer) Search(bindDN string, searchReq ldap.SearchRequest, conn n
}() }()
if searchReq.BaseDN == "" { if searchReq.BaseDN == "" {
return ldap.ServerSearchResult{ return ldap.ServerSearchResult{
Entries: []*ldap.Entry{ Entries: []*ldap.Entry{
{ {
DN: "", DN: "",
Attributes: []*ldap.EntryAttribute{ Attributes: []*ldap.EntryAttribute{
{ {
Name: "objectClass", Name: "objectClass",
Values: []string{"top", "OpenLDAProotDSE"}, Values: []string{"top", "OpenLDAProotDSE"},
}, },
{ {
Name: "subschemaSubentry", Name: "subschemaSubentry",
Values: []string{"cn=subschema"}, Values: []string{"cn=subschema"},
}, },
}, },
}, },
}, },
Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldap.LDAPResultSuccess, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldap.LDAPResultSuccess,
}, nil }, nil
} }
bd, err := goldap.ParseDN(strings.ToLower(searchReq.BaseDN)) bd, err := goldap.ParseDN(strings.ToLower(searchReq.BaseDN))
if err != nil { if err != nil {
@ -68,7 +68,7 @@ func (ls *LDAPServer) Search(bindDN string, searchReq ldap.SearchRequest, conn n
return provider.searcher.Search(req) return provider.searcher.Search(req)
} }
} }
return ldap.ServerSearchResult{ return ldap.ServerSearchResult{
Entries: []*ldap.Entry{ Entries: []*ldap.Entry{
{ {
DN: "", DN: "",

View File

@ -3,7 +3,7 @@ package direct
import ( import (
"fmt" "fmt"
"github.com/nmcclain/ldap" "beryju.io/ldap"
"goauthentik.io/internal/constants" "goauthentik.io/internal/constants"
"goauthentik.io/internal/outpost/ldap/search" "goauthentik.io/internal/outpost/ldap/search"
) )

View File

@ -8,8 +8,8 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
"beryju.io/ldap"
"github.com/getsentry/sentry-go" "github.com/getsentry/sentry-go"
"github.com/nmcclain/ldap"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"goauthentik.io/api/v3" "goauthentik.io/api/v3"
"goauthentik.io/internal/outpost/ldap/constants" "goauthentik.io/internal/outpost/ldap/constants"

View File

@ -6,8 +6,8 @@ import (
"fmt" "fmt"
"strings" "strings"
"beryju.io/ldap"
"github.com/getsentry/sentry-go" "github.com/getsentry/sentry-go"
"github.com/nmcclain/ldap"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"goauthentik.io/api/v3" "goauthentik.io/api/v3"

View File

@ -6,9 +6,9 @@ import (
"net" "net"
"strings" "strings"
"beryju.io/ldap"
"github.com/getsentry/sentry-go" "github.com/getsentry/sentry-go"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/nmcclain/ldap"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"goauthentik.io/internal/utils" "goauthentik.io/internal/utils"
) )

View File

@ -1,7 +1,7 @@
package search package search
import ( import (
"github.com/nmcclain/ldap" "beryju.io/ldap"
) )
type Searcher interface { type Searcher interface {

View File

@ -1,8 +1,8 @@
package server package server
import ( import (
"beryju.io/ldap"
"github.com/go-openapi/strfmt" "github.com/go-openapi/strfmt"
"github.com/nmcclain/ldap"
"goauthentik.io/api/v3" "goauthentik.io/api/v3"
"goauthentik.io/internal/outpost/ldap/flags" "goauthentik.io/internal/outpost/ldap/flags"
) )

View File

@ -4,7 +4,7 @@ import (
"net" "net"
"github.com/getsentry/sentry-go" "github.com/getsentry/sentry-go"
"github.com/nmcclain/ldap" "beryju.io/ldap"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"goauthentik.io/internal/outpost/ldap/bind" "goauthentik.io/internal/outpost/ldap/bind"

View File

@ -5,7 +5,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/nmcclain/ldap" "beryju.io/ldap"
ldapConstants "goauthentik.io/internal/outpost/ldap/constants" ldapConstants "goauthentik.io/internal/outpost/ldap/constants"
) )

View File

@ -3,9 +3,9 @@ package utils
import ( import (
"strings" "strings"
"beryju.io/ldap"
goldap "github.com/go-ldap/ldap/v3" goldap "github.com/go-ldap/ldap/v3"
ber "github.com/nmcclain/asn1-ber" ber "github.com/nmcclain/asn1-ber"
"github.com/nmcclain/ldap"
"goauthentik.io/api/v3" "goauthentik.io/api/v3"
"goauthentik.io/internal/outpost/ldap/constants" "goauthentik.io/internal/outpost/ldap/constants"
) )

View File

@ -1,9 +1,9 @@
package utils package utils
import ( import (
"beryju.io/ldap"
goldap "github.com/go-ldap/ldap/v3" goldap "github.com/go-ldap/ldap/v3"
ber "github.com/nmcclain/asn1-ber" ber "github.com/nmcclain/asn1-ber"
"github.com/nmcclain/ldap"
"goauthentik.io/api/v3" "goauthentik.io/api/v3"
"goauthentik.io/internal/outpost/ldap/constants" "goauthentik.io/internal/outpost/ldap/constants"
) )

View File

@ -133,6 +133,34 @@ class TestProviderLDAP(SeleniumTestCase):
) )
) )
@retry()
@apply_blueprint(
"default/flow-default-authentication-flow.yaml",
"default/flow-default-invalidation-flow.yaml",
)
def test_ldap_bind_success_starttls(self):
"""Test simple bind with ssl"""
self._prepare()
server = Server("ldap://localhost:3389")
_connection = Connection(
server,
raise_exceptions=True,
user=f"cn={self.user.username},ou=users,DC=ldap,DC=goauthentik,DC=io",
password=self.user.username,
)
_connection.start_tls()
_connection.bind()
self.assertTrue(
Event.objects.filter(
action=EventAction.LOGIN,
user={
"pk": self.user.pk,
"email": self.user.email,
"username": self.user.username,
},
)
)
@retry() @retry()
@apply_blueprint( @apply_blueprint(
"default/flow-default-authentication-flow.yaml", "default/flow-default-authentication-flow.yaml",

View File

@ -52,7 +52,6 @@ export class LDAPProviderFormPage extends ModelForm<LDAPProvider, number> {
lDAPProviderRequest: data, lDAPProviderRequest: data,
}); });
} else { } else {
data.tlsServerName = "";
return new ProvidersApi(DEFAULT_CONFIG).providersLdapCreate({ return new ProvidersApi(DEFAULT_CONFIG).providersLdapCreate({
lDAPProviderRequest: data, lDAPProviderRequest: data,
}); });
@ -240,12 +239,24 @@ export class LDAPProviderFormPage extends ModelForm<LDAPProvider, number> {
</ak-search-select> </ak-search-select>
<p class="pf-c-form__helper-text"> <p class="pf-c-form__helper-text">
${msg( ${msg(
"Due to protocol limitations, this certificate is only used when the outpost has a single provider, or all providers use the same certificate.", "The certificate for the above configured Base DN. As a fallback, the provider uses a self-signed certificate.",
)} )}
</p> </p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("TLS Server name")}
?required=${true}
name="tlsServerName"
>
<input
type="text"
value="${first(this.instance?.tlsServerName, "")}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text"> <p class="pf-c-form__helper-text">
${msg( ${msg(
"If multiple providers share an outpost, a self-signed certificate is used.", "DNS name for which the above configured certificate should be used. The certificate cannot be detected based on the base DN, as the SSL/TLS negotiation happens before such data is exchanged.",
)} )}
</p> </p>
</ak-form-element-horizontal> </ak-form-element-horizontal>

View File

@ -56,11 +56,13 @@ Starting with 2021.9.1, custom attributes will override the inbuilt attributes.
Starting with 2023.3, periods and slashes in custom attributes will be sanitized. Starting with 2023.3, periods and slashes in custom attributes will be sanitized.
::: :::
## SSL ## SSL / StartTLS
You can also configure SSL for your LDAP Providers by selecting a certificate and a server name in the provider settings. You can also configure SSL for your LDAP Providers by selecting a certificate and a server name in the provider settings.
This enables you to bind on port 636 using LDAPS, StartTLS is not supported. Starting with authentik 2023.6, StartTLS is supported, and the provider will pick the correct certificate based on the DN a bind attempt is made with.
This enables you to bind on port 636 using LDAPS.
## Integrations ## Integrations

View File

@ -0,0 +1,45 @@
---
title: Release 2023.6
slug: "/releases/2023.6"
---
<!-- ## Breaking changes -->
## New features
- LDAP StartTLS support
authentik's [LDAP Provider](../../providers/ldap/index.md) now supports StartTLS in addition to supporting SSL. The StartTLS is a more modern method of encrypting LDAP traffic. With this added support, the LDAP [Outpost](../../outposts/index.mdx) can now support multiple certificates.
## Upgrading
This release does not introduce any new requirements.
### docker-compose
To upgrade, download the new docker-compose file and update the Docker stack with the new version, using these commands:
```
wget -O docker-compose.yml https://goauthentik.io/version/2023.6/docker-compose.yml
docker-compose up -d
```
The `-O` flag retains the downloaded file's name, overwriting any existing local file with the same name.
### Kubernetes
Update your values to use the new images:
```yaml
image:
repository: ghcr.io/goauthentik/server
tag: 2023.6.0
```
## Minor changes/fixes
<!-- _Insert the output of `make gen-changelog` here_ -->
## API Changes
<!-- _Insert output of `make gen-diff` here_ -->

View File

@ -3,7 +3,7 @@ title: Release xxxx.x
slug: "/releases/xxxx.x" slug: "/releases/xxxx.x"
--- ---
## Breaking changes <!-- ## Breaking changes -->
## New features ## New features
@ -34,8 +34,8 @@ image:
## Minor changes/fixes ## Minor changes/fixes
_Insert the output of `make gen-changelog` here_ <!-- _Insert the output of `make gen-changelog` here_ -->
## API Changes ## API Changes
_Insert output of `make gen-diff` here_ <!-- _Insert output of `make gen-diff` here_ -->