diff --git a/.github/workflows/build-images.yml b/.github/workflows/build-images.yml index a109234c..1375634a 100644 --- a/.github/workflows/build-images.yml +++ b/.github/workflows/build-images.yml @@ -34,6 +34,9 @@ jobs: fetch-depth: 0 persist-credentials: false + - name: Test git-identity profile script + run: bash images/base/test-git-identity.sh + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 diff --git a/images/base/git-identity.sh b/images/base/git-identity.sh index 2f80e516..eba9d954 100644 --- a/images/base/git-identity.sh +++ b/images/base/git-identity.sh @@ -3,61 +3,76 @@ # profile on first interactive login to a container. Subsequent logins skip # this entirely once git config is set. # -# The name is read from the NSS gecos field (mapped from LDAP cn via sssd.conf). -# The email is read from LDAP via an anonymous ldapsearch (REQUIRE_AUTH_FOR_SEARCH -# is disabled on the internal ldap-gateway, so no bind credentials are needed). +# The name is read from the NSS gecos field (mapped from LDAP via sssd.conf). +# The email is looked up with ldapsearch, driven by the same SSSD_* environment +# variables that render /etc/sssd/sssd.conf (see sssd.conf.template), so the +# query targets the same directory — and binds with the same credentials — that +# sssd authenticates against. # Only run for interactive shells [[ $- != *i* ]] && return -# Only if git is available +# Only if git and ldapsearch are available command -v git >/dev/null 2>&1 || return command -v ldapsearch >/dev/null 2>&1 || return # Skip if already configured — user-set values always take precedence [ -n "$(git config --global user.email 2>/dev/null)" ] && [ -n "$(git config --global user.name 2>/dev/null)" ] && return -_GIT_SETUP_USER="${USER:-$(id -un 2>/dev/null)}" -[ -z "$_GIT_SETUP_USER" ] && return -[ "$_GIT_SETUP_USER" = "root" ] && return - -# Full name from NSS (SSSD reads the LDAP gecos attribute by default via ldap_user_gecos) -_GIT_SETUP_NAME=$(getent passwd "$_GIT_SETUP_USER" 2>/dev/null | cut -d: -f5) - -# Email from LDAP anonymous query -_GIT_SETUP_LDAP_HOST="${LDAP_URI:-ldaps://ldap1:636}" - -# Resolve baseDN the same way SSSD does: rootDSE namingContexts autodiscovery. -# Use LDAP_BASE_DN if explicitly set; otherwise query rootDSE. -# - Single namingContexts entry -> use it directly -# - Multiple namingContexts -> use defaultNamingContext -# - Neither resolvable -> abort -if [ -n "${LDAP_BASE_DN:-}" ]; then - _GIT_SETUP_LDAP_BASE="$LDAP_BASE_DN" -else - _GIT_SETUP_ROOTDSE=$(ldapsearch -x -H "$_GIT_SETUP_LDAP_HOST" -b "" -s base namingContexts defaultNamingContext 2>/dev/null) - _GIT_SETUP_NC_COUNT=$(echo "$_GIT_SETUP_ROOTDSE" | grep -c '^namingContexts:') - if [ "$_GIT_SETUP_NC_COUNT" -eq 1 ]; then - _GIT_SETUP_LDAP_BASE=$(echo "$_GIT_SETUP_ROOTDSE" | awk '/^namingContexts:/{print $2; exit}') - elif [ "$_GIT_SETUP_NC_COUNT" -gt 1 ]; then - _GIT_SETUP_LDAP_BASE=$(echo "$_GIT_SETUP_ROOTDSE" | awk '/^defaultNamingContext:/{print $2; exit}') +_git_identity_setup() { + local user uri base name_attr rootdse nc_count name email + local -a bind + + user="${USER:-$(id -un 2>/dev/null)}" + [ -z "$user" ] && return + [ "$user" = "root" ] && return + + # Same server list sssd uses (ldapsearch -H accepts a comma-separated list). + # LDAP_URI is the legacy pre-SSSD_* variable name. + uri="${SSSD_LDAP_URI:-${LDAP_URI:-ldaps://ldap1:636}}" + + # Bind the same way sssd does: with the default bind DN and token when + # configured (directories such as the Authentik LDAP outpost reject + # anonymous searches), otherwise anonymously. + bind=(-x) + if [ -n "${SSSD_LDAP_DEFAULT_BIND_DN:-}" ] && [ -n "${SSSD_DEFAULT_AUTHTOK:-}" ] && + { [ -z "${SSSD_DEFAULT_AUTHTOK_TYPE:-}" ] || [ "${SSSD_DEFAULT_AUTHTOK_TYPE}" = "password" ]; }; then + bind+=(-D "$SSSD_LDAP_DEFAULT_BIND_DN" -w "$SSSD_DEFAULT_AUTHTOK") fi - unset _GIT_SETUP_ROOTDSE _GIT_SETUP_NC_COUNT -fi -[ -z "${_GIT_SETUP_LDAP_BASE:-}" ] && return -_GIT_SETUP_EMAIL=$(ldapsearch -x \ - -H "$_GIT_SETUP_LDAP_HOST" \ - -b "$_GIT_SETUP_LDAP_BASE" \ - "(uid=${_GIT_SETUP_USER})" mail 2>/dev/null \ - | awk '/^mail:/{print $2; exit}') + # Resolve the search base the same way sssd does: explicit configuration + # first, then rootDSE namingContexts autodiscovery. + # - Single namingContexts entry -> use it directly + # - Multiple namingContexts -> use defaultNamingContext + # - Neither resolvable -> abort + base="${SSSD_LDAP_USER_SEARCH_BASE:-${SSSD_LDAP_SEARCH_BASE:-${LDAP_BASE_DN:-}}}" + if [ -z "$base" ]; then + rootdse=$(ldapsearch "${bind[@]}" -H "$uri" -b "" -s base namingContexts defaultNamingContext 2>/dev/null) + nc_count=$(echo "$rootdse" | grep -c '^namingContexts:') + if [ "$nc_count" -eq 1 ]; then + base=$(echo "$rootdse" | awk '/^namingContexts:/{print $2; exit}') + elif [ "$nc_count" -gt 1 ]; then + base=$(echo "$rootdse" | awk '/^defaultNamingContext:/{print $2; exit}') + fi + fi + [ -z "$base" ] && return + + # Same login-name attribute sssd maps (ldap_user_name, default uid) + name_attr="${SSSD_LDAP_USER_NAME:-uid}" + + # Full name from NSS (sssd maps the LDAP gecos attribute via ldap_user_gecos) + name=$(getent passwd "$user" 2>/dev/null | cut -d: -f5) -if [ -n "$_GIT_SETUP_NAME" ]; then - git config --global user.name "$_GIT_SETUP_NAME" -fi + email=$(ldapsearch "${bind[@]}" \ + -H "$uri" \ + -b "$base" \ + "(${name_attr}=${user})" mail 2>/dev/null \ + | awk '/^mail:/{print $2; exit}') -if [ -n "$_GIT_SETUP_EMAIL" ]; then - git config --global user.email "$_GIT_SETUP_EMAIL" -fi +[ -n "$name" ] && [ -z "$(git config --global user.name 2>/dev/null)" ] && git config --global user.name "$name" +[ -n "$email" ] && [ -z "$(git config --global user.email 2>/dev/null)" ] && git config --global user.email "$email" + return 0 +} -unset _GIT_SETUP_USER _GIT_SETUP_NAME _GIT_SETUP_EMAIL _GIT_SETUP_LDAP_HOST _GIT_SETUP_LDAP_BASE _GIT_SETUP_ROOTDSE _GIT_SETUP_NC_COUNT +_git_identity_setup +unset -f _git_identity_setup diff --git a/images/base/test-git-identity.sh b/images/base/test-git-identity.sh new file mode 100644 index 00000000..d12db11f --- /dev/null +++ b/images/base/test-git-identity.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +# Smoke test for images/base/git-identity.sh with stubbed ldapsearch/getent. +set -u + +SCRIPT="$(cd "$(dirname "$0")" && pwd)/git-identity.sh" +export SCRIPT +STUBS=$(mktemp -d) +export HOME=$(mktemp -d) + +cat >"$STUBS/ldapsearch" <<'EOF' +#!/usr/bin/env bash +# Record args for assertions, then answer rootDSE or mail queries. +echo "$@" >>/tmp/ldapsearch-args.log +if [[ "$*" == *"-s base namingContexts"* ]]; then + echo "namingContexts: dc=example,dc=com" + exit 0 +fi +echo "mail: tester@example.com" +EOF + +cat >"$STUBS/getent" <<'EOF' +#!/usr/bin/env bash +echo "tester:x:2000:4000:Testy McTestface:/home/tester:/bin/bash" +EOF + +chmod +x "$STUBS/ldapsearch" "$STUBS/getent" +export PATH="$STUBS:$PATH" +export USER=tester +rm -f /tmp/ldapsearch-args.log + +fail=0 +check() { # desc expected actual + if [ "$2" = "$3" ]; then echo "PASS: $1"; else echo "FAIL: $1 (expected '$2', got '$3')"; fail=1; fi +} + +# --- Case 1: SSSD_* vars set incl. bind creds; explicit user search base --- +export SSSD_LDAP_URI="ldaps://a:636, ldaps://b:636" +export SSSD_LDAP_USER_SEARCH_BASE="ou=people,dc=example,dc=com" +export SSSD_LDAP_DEFAULT_BIND_DN="cn=proxy,dc=example,dc=com" +export SSSD_DEFAULT_AUTHTOK_TYPE="password" +export SSSD_DEFAULT_AUTHTOK="secret" +export SSSD_LDAP_USER_NAME="sAMAccountName" + +bash -ic '. "$SCRIPT"' +check "email set" "tester@example.com" "$(git config --global user.email)" +check "name set" "Testy McTestface" "$(git config --global user.name)" +grep -q -- "-D cn=proxy,dc=example,dc=com -w secret" /tmp/ldapsearch-args.log && echo "PASS: bound with sssd creds" || { echo "FAIL: bind creds missing"; fail=1; } +grep -q -- "-b ou=people,dc=example,dc=com (sAMAccountName=tester) mail" /tmp/ldapsearch-args.log && echo "PASS: base + name attr honored" || { echo "FAIL: base/name attr"; fail=1; } +grep -q -- "-H ldaps://a:636, ldaps://b:636" /tmp/ldapsearch-args.log && echo "PASS: SSSD_LDAP_URI used" || { echo "FAIL: uri"; fail=1; } + +# --- Case 2: no base configured -> rootDSE discovery, anonymous bind --- +git config --global --unset user.email; git config --global --unset user.name +unset SSSD_LDAP_USER_SEARCH_BASE SSSD_LDAP_DEFAULT_BIND_DN SSSD_DEFAULT_AUTHTOK SSSD_DEFAULT_AUTHTOK_TYPE SSSD_LDAP_USER_NAME +rm -f /tmp/ldapsearch-args.log + +bash -ic '. "$SCRIPT"' +check "email via rootDSE base" "tester@example.com" "$(git config --global user.email)" +grep -q -- "-b dc=example,dc=com (uid=tester) mail" /tmp/ldapsearch-args.log && echo "PASS: rootDSE base + default uid attr" || { echo "FAIL: rootDSE"; fail=1; } +grep -q -- "-D " /tmp/ldapsearch-args.log && { echo "FAIL: unexpected bind"; fail=1; } || echo "PASS: anonymous when no creds" + +# --- Case 3: existing config untouched --- +git config --global user.email keep@me.com +git config --global user.name "Keep Me" +rm -f /tmp/ldapsearch-args.log +bash -ic '. "$SCRIPT"' +check "existing email kept" "keep@me.com" "$(git config --global user.email)" +[ -f /tmp/ldapsearch-args.log ] && { echo "FAIL: ldapsearch ran despite existing config"; fail=1; } || echo "PASS: skipped when already configured" + +# --- Case 4: non-interactive shell does nothing --- +git config --global --unset user.email; git config --global --unset user.name +bash -c '. "$SCRIPT"' +check "non-interactive no-op" "" "$(git config --global user.email 2>/dev/null)" + +exit $fail diff --git a/mie-opensource-landing/docs/admins/ldap-servers.md b/mie-opensource-landing/docs/admins/ldap-servers.md index 38338c7c..02696302 100644 --- a/mie-opensource-landing/docs/admins/ldap-servers.md +++ b/mie-opensource-landing/docs/admins/ldap-servers.md @@ -77,6 +77,9 @@ In the admin UI: **Settings** → **Default Container Environment Variables**. T !!! tip `SSSD_LDAP_DEFAULT_BIND_DN`, `SSSD_DEFAULT_AUTHTOK_TYPE`, and `SSSD_DEFAULT_AUTHTOK` work as a set. Provide all three when your directory requires an authenticated bind to read users and groups; leave all three blank to bind anonymously. The service account only needs read access to the user and group subtrees — user passwords are verified by a separate bind as the authenticating user. +!!! note "Git identity" + On first interactive login, a profile script (`/etc/profile.d/git-identity.sh`) sets the user's global `git config user.name`/`user.email` from the directory: the name from the NSS gecos field (`SSSD_LDAP_USER_GECOS`) and the email from an `ldapsearch` for the user's `mail` attribute. The search uses the same `SSSD_*` variables — URI, search base, login-name attribute, and bind DN/credential — so no extra configuration is needed. Values a user sets manually are never overwritten. + !!! warning `SSSD_DEFAULT_AUTHTOK` is a secret. The rendered `/etc/sssd/sssd.conf` is written with restrictive permissions (mode `0600`), but the value is also visible in the container's environment. Prefer a dedicated, least-privilege service account and rotate it like any other credential.