outposts: add LDAP Binding using flows

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer
2021-04-25 22:07:12 +02:00
parent a525d6c3a9
commit 6c9b3ebd2b
6 changed files with 118 additions and 5 deletions

View File

@ -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 {

View File

@ -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)
}

View File

@ -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 {

View File

@ -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",