Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GSSAPI/Kerberos auth #645

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,10 @@ jobs:
environment:
PGVERSION: "<< parameters.pgversion >>"
DIST: "<< parameters.dist >>"
COMPOSE_FILE: docker-compose.yml:test/docker-compose.yml
command: |
COMPOSE_FILE=docker-compose.yml:test/docker-compose.yml docker compose up --exit-code-from=test
docker compose pull
docker compose up --exit-code-from=test

pkg:
parameters:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ dist/
docker-compose.override.yml
# test/conftest.py creates .env files
.env
test/samba.keytab
5 changes: 3 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@ services:
environment:
REALM: bridoulou.fr
ADMIN_PASS: 1Ntegral
DNS_BACKEND: "NONE"
volumes:
- ./test/fixtures/samba/kerberos.sh:/docker-entrypoint-init.d/00-kerberos.sh
- ./test:/test # Use to export keytab in kerberos.sh
- ./test/fixtures/samba/nominal.sh:/docker-entrypoint-init.d/95-nominal.sh
- ./test/fixtures/samba/extra.sh:/docker-entrypoint-init.d/96-extra.sh
hostname: samba1
domainname: ldap2pg.docker
labels:
com.dnsdock.alias: samba1.ldap2pg.docker
command: [-d=1]
command: [-d=3]

postgres:
image: postgres:${PGVERSION-16}-alpine
Expand Down
9 changes: 9 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,27 @@ require (

require (
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gosimple/unidecode v1.0.1 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect
github.com/jcmturner/aescts/v2 v2.0.0 // indirect
github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
github.com/jcmturner/gofork v1.7.6 // indirect
github.com/jcmturner/goidentity/v6 v6.0.1 // indirect
github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect
github.com/jcmturner/rpc/v2 v2.0.3 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/net v0.22.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 h1:TQcrn6Wq+sKGkpyPvppOz99zsM
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gosimple/slug v1.14.0 h1:RtTL/71mJNDfpUbCOmnf/XFkzKRtD6wL6Uy+3akm4Es=
github.com/gosimple/slug v1.14.0/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ=
Expand Down
34 changes: 34 additions & 0 deletions internal/ldap/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ import (
"log/slog"
"net"
"net/url"
"os"
"strings"
"time"

"github.com/avast/retry-go/v4"
"github.com/dalibo/ldap2pg/internal/perf"
ldap3 "github.com/go-ldap/ldap/v3"
"github.com/go-ldap/ldap/v3/gssapi"
)

type Client struct {
Expand Down Expand Up @@ -90,6 +92,38 @@ func Connect() (client Client, err error) {
}
slog.Debug("LDAP SASL/DIGEST-MD5 bind.", "authcid", client.SaslAuthCID, "host", parsedURI.Host)
err = client.Conn.MD5Bind(parsedURI.Host, client.SaslAuthCID, password)
case "GSSAPI":
// Get the principal
client.SaslAuthCID = k.String("SASL_AUTHCID")
ccache, ok := os.LookupEnv("KRB5CCNAME")
if ok {
ccache = strings.TrimPrefix(ccache, "FILE:")
} else {
uid := os.Getuid()
ccache = fmt.Sprintf("/tmp/krb5cc_%d", uid)
}
krb5confPath, ok := os.LookupEnv("KRB5_CONFIG")
if !ok {
krb5confPath = "/etc/krb5.conf"
}
slog.Debug("Initial SSPI client.", "ccache", ccache, "krb5conf", krb5confPath)
sspiClient, err := gssapi.NewClientFromCCache(ccache, krb5confPath)
if err != nil {
return client, err
}
defer sspiClient.Close()
// Build service Principal from URI.
var parsedURI *url.URL
parsedURI, err = url.Parse(client.URI)
if err != nil {
return client, err
}
spn := "ldap/" + strings.Split(parsedURI.Host, ":")[0]
slog.Debug("LDAP SASL/GSSAPI bind.", "principal", client.SaslAuthCID, "spn", spn)
err = client.Conn.GSSAPIBind(sspiClient, spn, client.SaslAuthCID)
if err != nil {
return client, err
}
default:
err = fmt.Errorf("unhandled SASL_MECH")
}
Expand Down
4 changes: 4 additions & 0 deletions ldaprc
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@ TLS_REQCERT allow
NETWORK_TIMEOUT 5
TIMEOUT 5
REFERRALS off
SASL_AUTHCID Administrator
# Disable canonicalization which trigger Kerberos SPN ldap/172.X.Y.Z instead of ldap/samba1.ldap2pg.docker.
# With canonicalisation, GSSAPI fails with "Server not found in Kerberos database".
SASL_NOCANON on
11 changes: 11 additions & 0 deletions test/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
version: '3'

services:
samba1:
labels:
# Disable dnsdock on test.
com.dnsdock.alias: samba1.test.ldap2pg.docker
networks:
default:
aliases:
# Fake dnsdock for CI.
# This value is used in test/krb5.conf for KDC.
- samba1.ldap2pg.docker

test:
image: dalibo/buildpack-python:${DIST-rockylinux8}
volumes:
Expand Down
5 changes: 4 additions & 1 deletion test/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ psql -tc "SELECT version();"
# ldap-utils on CentOS does not read properly current ldaprc. Linking it in ~
# workaround this.
ln -fsv "${PWD}/ldaprc" ~/ldaprc
retry ldapsearch -x -v -w "${LDAPPASSWORD}" -z none
retry ldapsearch -x -v -w "${LDAPPASSWORD}" -z none >/dev/null

export KRB5_CONFIG="${PWD}/test/krb5.conf"
kinit -V -k -t "${PWD}/test/samba.keytab" Administrator
LDAPURI="${LDAPURI/ldaps:/ldap:}" ldapsearch -v -Y GSSAPI -U Administrator >/dev/null
"$python" -m pytest test/ "$@"
21 changes: 21 additions & 0 deletions test/fixtures/samba/kerberos.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/bin/bash

set -x
samba-tool spn add ldap/samba1.ldap2pg.docker SAMBA1$
samba-tool spn add ldap/localhost SAMBA1$
samba-tool spn add ldap/localhost.localdomain SAMBA1$

# Get Gateway (field 3) from default route (destination is 0.0.0.0).
gateway_hex="$(grep -E '^\w+\s+00000000' /proc/net/route | cut -f 3)"
gateway_bytes=( # IP is little endian.
$((16#${gateway_hex:6:2}))
$((16#${gateway_hex:4:2}))
$((16#${gateway_hex:2:2}))
$((16#${gateway_hex:0:2}))
)
printf -v gateway "%d.%d.%d.%d" "${gateway_bytes[@]}"

samba-tool spn add "ldap/$gateway" SAMBA1$
samba-tool spn list SAMBA1$
samba-tool domain exportkeytab /test/samba.keytab --principal=Administrator
chown -v "$(stat -c %u:%g "${BASH_SOURCE[0]}")" /test/samba.keytab
18 changes: 18 additions & 0 deletions test/krb5.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[libdefaults]
ticket_lifetime = 24h
renew_lifetime = 7d
forwardable = true
rdns = false
default_realm = BRIDOULOU.FR
default_keytab_name = FILE:test/samba.keytab

[realms]
BRIDOULOU.FR = {
# Requires dnsdock on network alias as in test/docker-compose.yml
kdc = samba1.ldap2pg.docker
admin_server = samba1.ldap2pg.docker
}

[domain_realm]
.ldap2pg.docker = BRIDOULOU.FR
ldap2pg.docker = BRIDOULOU.FR
13 changes: 6 additions & 7 deletions test/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,15 @@ def test_stdin(ldap2pg, capsys):
assert 'stdinuser' in err


@pytest.mark.xfail(reason="Samba does not support SASL DIGEST-MD5.")
@pytest.mark.xfail(
'CI' not in os.environ,
reason="Set CI=true to run GSSAPI test."
)
def test_sasl(ldap2pg, capsys):
env = dict(
os.environ,
# py-ldap2pg reads non-standard var USER.
LDAPUSER='testsasl',
# ldap2pg requires explicit SASL_MECH, and standard SASL_AUTHID.
LDAPSASL_MECH='DIGEST-MD5',
LDAPSASL_AUTHCID='testsasl',
LDAPPASSWORD='voyage',
LDAPSASL_MECH='GSSAPI',
LDAPSASL_AUTHCID='Administrator',
)
ldap2pg(config='ldap2pg.yml', verbose=True, _env=env)

Expand Down