outposts: add LDAP Binding using flows
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
parent
a525d6c3a9
commit
6c9b3ebd2b
|
@ -6,6 +6,7 @@ require (
|
|||
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
|
||||
github.com/coreos/go-oidc v2.2.1+incompatible
|
||||
github.com/getsentry/sentry-go v0.10.0
|
||||
github.com/go-ldap/ldap/v3 v3.3.0 // indirect
|
||||
github.com/go-openapi/analysis v0.20.1 // indirect
|
||||
github.com/go-openapi/errors v0.20.0
|
||||
github.com/go-openapi/runtime v0.19.28
|
||||
|
|
|
@ -34,6 +34,8 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX
|
|||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28=
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
||||
github.com/Bose/minisentinel v0.0.0-20200130220412-917c5a9223bb h1:ZVN4Iat3runWOFLaBCDVU5a9X/XikSRBosye++6gojw=
|
||||
github.com/Bose/minisentinel v0.0.0-20200130220412-917c5a9223bb/go.mod h1:WsAABbY4HQBgd3mGuG4KMNTbHJCPvx9IVBHzysbknss=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
|
@ -136,6 +138,8 @@ github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NB
|
|||
github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
|
||||
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
|
||||
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
|
@ -143,6 +147,8 @@ github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9
|
|||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-ldap/ldap/v3 v3.3.0 h1:lwx+SJpgOHd8tG6SumBQZXCmNX51zM8B1cfxJ5gv4tQ=
|
||||
github.com/go-ldap/ldap/v3 v3.3.0/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
|
||||
|
@ -668,6 +674,7 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U
|
|||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
|
|
|
@ -9,7 +9,7 @@ func (ls *LDAPServer) Refresh() error {
|
|||
}
|
||||
|
||||
func (ls *LDAPServer) Start() error {
|
||||
listen := "0.0.0.0:3389"
|
||||
listen := "127.0.0.1:3390"
|
||||
log.Debugf("Listening on %s", listen)
|
||||
err := ls.s.ListenAndServe(listen)
|
||||
if err != nil {
|
||||
|
|
|
@ -1,12 +1,114 @@
|
|||
package ldap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"strings"
|
||||
|
||||
goldap "github.com/go-ldap/ldap/v3"
|
||||
|
||||
"github.com/nmcclain/ldap"
|
||||
"goauthentik.io/outpost/pkg/client/flows"
|
||||
)
|
||||
|
||||
func (ls *LDAPServer) Bind(bindDN string, bindSimplePw string, conn net.Conn) (ldap.LDAPResultCode, error) {
|
||||
ls.log.WithField("dn", bindDN).WithField("pw", bindSimplePw).Debug("bind")
|
||||
return ldap.LDAPResultSuccess, nil
|
||||
type UIDResponse struct {
|
||||
UIDFIeld string `json:"uid_field"`
|
||||
}
|
||||
|
||||
type PasswordResponse struct {
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
func (ls *LDAPServer) getUsername(dn string) (string, error) {
|
||||
if !strings.HasSuffix(dn, ls.BaseDN) {
|
||||
return "", errors.New("invalid base DN")
|
||||
}
|
||||
dns, err := goldap.ParseDN(dn)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, part := range dns.RDNs {
|
||||
for _, attribute := range part.Attributes {
|
||||
if attribute.Type == "DN" {
|
||||
return attribute.Value, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", errors.New("failed to find dn")
|
||||
}
|
||||
|
||||
func (ls *LDAPServer) Bind(bindDN string, bindPW string, conn net.Conn) (ldap.LDAPResultCode, error) {
|
||||
username, err := ls.getUsername(bindDN)
|
||||
if err != nil {
|
||||
ls.log.WithError(err).Warning("failed to parse user dn")
|
||||
return ldap.LDAPResultInvalidCredentials, nil
|
||||
}
|
||||
ls.log.WithField("dn", username).Debug("bind")
|
||||
jar, err := cookiejar.New(nil)
|
||||
if err != nil {
|
||||
ls.log.WithError(err).Warning("Failed to create cookiejar")
|
||||
return ldap.LDAPResultOperationsError, nil
|
||||
}
|
||||
client := &http.Client{
|
||||
Jar: jar,
|
||||
}
|
||||
passed, err := ls.solveFlowChallenge(username, bindPW, client)
|
||||
if err != nil {
|
||||
ls.log.WithError(err).Warning("failed to solve challenge")
|
||||
return ldap.LDAPResultOperationsError, nil
|
||||
}
|
||||
if passed {
|
||||
return ldap.LDAPResultSuccess, nil
|
||||
}
|
||||
return ldap.LDAPResultInvalidCredentials, nil
|
||||
}
|
||||
|
||||
func (ls *LDAPServer) solveFlowChallenge(bindDN string, password string, client *http.Client) (bool, error) {
|
||||
challenge, err := ls.ac.Client.Flows.FlowsExecutorGet(&flows.FlowsExecutorGetParams{
|
||||
FlowSlug: ls.flowSlug,
|
||||
Query: "ldap=true",
|
||||
Context: context.Background(),
|
||||
HTTPClient: client,
|
||||
}, ls.ac.Auth)
|
||||
if err != nil {
|
||||
ls.log.WithError(err).Warning("Failed to get challenge")
|
||||
return false, err
|
||||
}
|
||||
ls.log.WithField("component", challenge.Payload.Component).WithField("type", *challenge.Payload.Type).Debug("Got challenge")
|
||||
responseParams := &flows.FlowsExecutorSolveParams{
|
||||
FlowSlug: ls.flowSlug,
|
||||
Query: "ldap=true",
|
||||
Context: context.Background(),
|
||||
HTTPClient: client,
|
||||
}
|
||||
switch challenge.Payload.Component {
|
||||
case "ak-stage-identification":
|
||||
responseParams.Data = &UIDResponse{UIDFIeld: bindDN}
|
||||
case "ak-stage-password":
|
||||
responseParams.Data = &PasswordResponse{Password: password}
|
||||
default:
|
||||
return false, fmt.Errorf("unsupported challenge type: %s", challenge.Payload.Component)
|
||||
}
|
||||
response, err := ls.ac.Client.Flows.FlowsExecutorSolve(responseParams, ls.ac.Auth)
|
||||
ls.log.WithField("component", response.Payload.Component).WithField("type", *response.Payload.Type).Debug("Got response")
|
||||
if *response.Payload.Type == "redirect" {
|
||||
return true, nil
|
||||
}
|
||||
if err != nil {
|
||||
ls.log.WithError(err).Warning("Failed to submit challenge")
|
||||
return false, err
|
||||
}
|
||||
if len(response.Payload.ResponseErrors) > 0 {
|
||||
for key, errs := range response.Payload.ResponseErrors {
|
||||
for _, err := range errs {
|
||||
ls.log.WithField("key", key).WithField("code", *err.Code).Debug(*err.String)
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return ls.solveFlowChallenge(bindDN, password, client)
|
||||
}
|
||||
|
|
|
@ -22,6 +22,9 @@ type LDAPServer struct {
|
|||
s *ldap.Server
|
||||
log *log.Entry
|
||||
ac *ak.APIController
|
||||
|
||||
// TODO: Make configurable
|
||||
flowSlug string
|
||||
}
|
||||
|
||||
func NewServer(ac *ak.APIController) *LDAPServer {
|
||||
|
|
|
@ -42,7 +42,7 @@ func (ls *LDAPServer) Search(bindDN string, searchReq ldap.SearchRequest, conn n
|
|||
},
|
||||
{
|
||||
Name: "uid",
|
||||
Values: []string{strconv.Itoa(int(g.Pk))},
|
||||
Values: []string{string(g.Pk)},
|
||||
},
|
||||
{
|
||||
Name: "objectClass",
|
||||
|
|
Reference in New Issue