From bb047b4006843f1082b0deb790ffd29eba34d2c3 Mon Sep 17 00:00:00 2001 From: cmyers-mieweb Date: Fri, 4 Sep 2026 12:11:58 -0700 Subject: [PATCH 1/2] Align git identity with SSSD settings Update the container git identity profile to derive LDAP URI, search base, username attribute, and bind credentials from the same SSSD environment used for authentication. Add a workflow smoke test covering bound and anonymous lookups, rootDSE discovery, and no-op cases, and document the first-login git config behavior for LDAP-backed users. --- .github/workflows/build-images.yml | 3 + images/base/git-identity.sh | 101 ++++++++++-------- images/base/test-git-identity.sh | 74 +++++++++++++ .../docs/admins/ldap-servers.md | 3 + 4 files changed, 138 insertions(+), 43 deletions(-) create mode 100644 images/base/test-git-identity.sh 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..a3777014 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" ] && git config --global user.name "$name" + [ -n "$email" ] && 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. From 115e009979059b9eb07209a964bf8f452ba81bff Mon Sep 17 00:00:00 2001 From: cmyers-mieweb Date: Fri, 4 Sep 2026 12:19:46 -0700 Subject: [PATCH 2/2] Add checks before setting global git user config Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- images/base/git-identity.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/images/base/git-identity.sh b/images/base/git-identity.sh index a3777014..eba9d954 100644 --- a/images/base/git-identity.sh +++ b/images/base/git-identity.sh @@ -69,8 +69,8 @@ _git_identity_setup() { "(${name_attr}=${user})" mail 2>/dev/null \ | awk '/^mail:/{print $2; exit}') - [ -n "$name" ] && git config --global user.name "$name" - [ -n "$email" ] && git config --global user.email "$email" +[ -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 }