Added kerberos step

This commit is contained in:
2026-04-22 21:53:08 +12:00
parent 031482c540
commit e86513d5ea
3 changed files with 204 additions and 3 deletions
+54 -3
View File
@@ -7,7 +7,6 @@ use airflow user to perform configuration
sudo -iu airflow
pwd
you should be located in home directory of the airflow user
image.png
we now need to create keytab file which will be used to authenticate our service account
@@ -19,7 +18,6 @@ have your service account ready as above commands will ask to enter service acco
check that file was created and you can see content of keytab file which holds Pricipal
klist -ekt airflow.keytab
image.png
initiate ticket for airflow user
@@ -308,4 +306,57 @@ systemctl restart "$APP_SERVICE_NAME" \
|| { log_error "Failed to restart ${APP_SERVICE_NAME}"; return 1; }
systemctl --no-pager status "$APP_SERVICE_NAME"
```
```
Files created
steps/05a-kerberos.sh — new deploy step (style matches 01-user-setup.sh and 06-app-service.sh).
scripts/mssql_probe.py — SQLAlchemy integrated-auth probe.
Wire it into deploy.sh
Insert between the network-mounts and app-service lines:
if [[ "${KRB_ENABLED:-false}" == "true" ]]; then
run_step "5a. Kerberos" "${DEPLOY_DIR}/steps/05a-kerberos.sh"
else
log_info "Skipping Kerberos setup (KRB_ENABLED=false)"
fi
Put it before 6. App Service so the ticket exists when the app starts.
New deploy.conf variables
Var Purpose Example
KRB_ENABLED Gates the whole step true
KRB_PRINCIPAL Service principal zReconRangerDEV@PROD.ASBGROUP.CO.NZ
KRB_KEYTAB_SRC Path on the deployer where the keytab has been pre-staged (e.g. from a secret store) /root/secrets/recon-ranger.keytab
KRB_KEYTAB_PATH Where the step installs it (owned by APP_USER, mode 0600) /opt/recon-ranger/recon-ranger.keytab
KRB_CCACHE_PATH Ticket cache on disk, shared with the app service /var/lib/recon-ranger/krb5_ccache
KRB_RENEW_INTERVAL Systemd timer OnUnitActiveSec (optional, default 30min) 30min
Generate the keytab once (on a trusted host) with ktutil — same pattern as the airflow doc — then copy it to $KRB_KEYTAB_SRC before running deploy.
Runtime env vars for the app (.env, loaded by the app service)
KRB5CCNAME=FILE:/var/lib/recon-ranger/krb5_ccache
MSSQL_HOST=sql01.prod.example
MSSQL_PORT=1433
MSSQL_DB=ReconRanger
MSSQL_ODBC_DRIVER=ODBC Driver 18 for SQL Server
The existing 06-app-service.sh already does EnvironmentFile=${APP_ENV_DIR}/.env, so SQLAlchemy/pyodbc will pick up KRB5CCNAME automatically — no changes to that unit needed.
Python deps for the probe + app
Add to your app's pyproject.toml:
sqlalchemy
pyodbc
And on the RHEL host you'll need the MS ODBC driver (msodbcsql18) + unixODBC-devel before pyodbc can build/install. Those belong in your 03-app-install.sh or a new prereqs step.
How the renewal works
The step installs two units: a .service (Type=oneshot, runs kinit -k -t … -c FILE:…) and a .timer that fires it every KRB_RENEW_INTERVAL. This is cleaner than airflow's Type=simple loop — systemd tracks each kinit invocation individually, so failures show up clearly in journalctl -u <service>-kerberos-renewal.service.
Verify after deploy:
systemctl list-timers | grep kerberos
sudo -u recon-ranger klist -c FILE:/var/lib/recon-ranger/krb5_ccache
sudo -u recon-ranger KRB5CCNAME=FILE:/var/lib/recon-ranger/krb5_ccache \
/opt/recon-ranger/.venv/bin/python /opt/recon-ranger/scripts/mssql_probe.py
+54
View File
@@ -0,0 +1,54 @@
"""Quick SQL Server connectivity probe using SQLAlchemy + Kerberos (integrated auth).
Prereqs on the host:
- krb5-workstation installed and /etc/krb5.conf configured
- A valid TGT in the cache pointed to by $KRB5CCNAME
e.g. KRB5CCNAME=FILE:/var/lib/recon-ranger/krb5_ccache
- Microsoft ODBC Driver 18 for SQL Server (msodbcsql18) + unixODBC
- Python packages: sqlalchemy, pyodbc
Usage:
MSSQL_HOST=sql01.prod.example MSSQL_DB=ReconRanger \
python scripts/mssql_probe.py
"""
from __future__ import annotations
import os
import sys
from urllib.parse import quote_plus
from sqlalchemy import create_engine, text
def build_url() -> str:
host = os.environ["MSSQL_HOST"]
database = os.environ["MSSQL_DB"]
port = os.environ.get("MSSQL_PORT", "1433")
driver = os.environ.get("MSSQL_ODBC_DRIVER", "ODBC Driver 18 for SQL Server")
odbc = (
f"DRIVER={{{driver}}};"
f"SERVER={host},{port};"
f"DATABASE={database};"
"Trusted_Connection=yes;"
"Encrypt=yes;"
"TrustServerCertificate=yes;"
)
return f"mssql+pyodbc:///?odbc_connect={quote_plus(odbc)}"
def main() -> int:
engine = create_engine(build_url(), pool_pre_ping=True)
with engine.connect() as conn:
row = conn.execute(
text("SELECT SUSER_SNAME() AS login, DB_NAME() AS db, @@VERSION AS version")
).one()
print(f"Logged in as : {row.login}")
print(f"Database : {row.db}")
print(f"Server : {row.version.splitlines()[0]}")
return 0
if __name__ == "__main__":
sys.exit(main())
+96
View File
@@ -0,0 +1,96 @@
#!/usr/bin/env bash
# =============================================================================
# 05a-kerberos.sh — Install Kerberos client, stage keytab, and install a
# systemd timer that renews the TGT on a schedule.
#
# The app service reads KRB5CCNAME from its .env file and uses the shared
# ticket cache when opening SQL Server connections via pyodbc/SQLAlchemy.
# =============================================================================
require_vars APP_USER APP_GROUP \
KRB_PRINCIPAL KRB_KEYTAB_SRC KRB_KEYTAB_PATH KRB_CCACHE_PATH \
|| return 1
KRB_RENEW_INTERVAL="${KRB_RENEW_INTERVAL:-30min}"
KRB_RENEWAL_SERVICE="${APP_SERVICE_NAME:-recon-ranger}-kerberos-renewal"
# ---- Install the Kerberos client ------------------------------------------
if ! command -v kinit &>/dev/null; then
log_info "Installing krb5-workstation"
dnf install -y krb5-workstation \
|| { log_error "Failed to install krb5-workstation"; return 1; }
else
log_info "krb5-workstation already installed"
fi
# ---- Sanity check /etc/krb5.conf ------------------------------------------
if [[ ! -s /etc/krb5.conf ]] || ! grep -q "default_realm" /etc/krb5.conf; then
log_error "/etc/krb5.conf missing or has no default_realm — configure the realm first"
return 1
fi
# ---- Stage the keytab at the target path ----------------------------------
if [[ ! -f "$KRB_KEYTAB_SRC" ]]; then
log_error "Keytab source not found: $KRB_KEYTAB_SRC"
return 1
fi
install -d -o "$APP_USER" -g "$APP_GROUP" -m 0750 "$(dirname "$KRB_KEYTAB_PATH")"
install -o "$APP_USER" -g "$APP_GROUP" -m 0600 "$KRB_KEYTAB_SRC" "$KRB_KEYTAB_PATH"
# Show what is inside the keytab so deploy logs record the principals/enctypes.
log_info "Keytab contents:"
klist -ekt "$KRB_KEYTAB_PATH" || log_warn "Could not list keytab contents"
# ---- Prepare the ticket cache directory -----------------------------------
install -d -o "$APP_USER" -g "$APP_GROUP" -m 0700 "$(dirname "$KRB_CCACHE_PATH")"
# ---- Prime the cache immediately ------------------------------------------
log_info "Running initial kinit as $APP_USER"
sudo -u "$APP_USER" \
kinit -k -t "$KRB_KEYTAB_PATH" -c "FILE:$KRB_CCACHE_PATH" "$KRB_PRINCIPAL" \
|| { log_error "Initial kinit failed for $KRB_PRINCIPAL"; return 1; }
sudo -u "$APP_USER" klist -c "FILE:$KRB_CCACHE_PATH" \
|| log_warn "klist failed after kinit"
# ---- Renewal service (oneshot, fires kinit) -------------------------------
RENEWAL_UNIT="/etc/systemd/system/${KRB_RENEWAL_SERVICE}.service"
RENEWAL_TIMER="/etc/systemd/system/${KRB_RENEWAL_SERVICE}.timer"
log_info "Writing $RENEWAL_UNIT"
cat > "$RENEWAL_UNIT" <<UNITEOF
[Unit]
Description=Kerberos TGT renewal for ${APP_SERVICE_NAME:-recon-ranger}
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
User=${APP_USER}
Group=${APP_GROUP}
ExecStart=/usr/bin/kinit -k -t ${KRB_KEYTAB_PATH} -c FILE:${KRB_CCACHE_PATH} ${KRB_PRINCIPAL}
UNITEOF
log_info "Writing $RENEWAL_TIMER"
cat > "$RENEWAL_TIMER" <<TIMEREOF
[Unit]
Description=Periodic Kerberos TGT renewal for ${APP_SERVICE_NAME:-recon-ranger}
[Timer]
OnBootSec=1min
OnUnitActiveSec=${KRB_RENEW_INTERVAL}
Unit=${KRB_RENEWAL_SERVICE}.service
Persistent=true
[Install]
WantedBy=timers.target
TIMEREOF
systemctl daemon-reload \
|| { log_error "systemctl daemon-reload failed"; return 1; }
systemctl enable --now "${KRB_RENEWAL_SERVICE}.timer" \
|| { log_error "Failed to enable ${KRB_RENEWAL_SERVICE}.timer"; return 1; }
systemctl --no-pager status "${KRB_RENEWAL_SERVICE}.timer" || true