Added kerberos step
This commit is contained in:
+54
-3
@@ -7,7 +7,6 @@ use airflow user to perform configuration
|
|||||||
sudo -iu airflow
|
sudo -iu airflow
|
||||||
pwd
|
pwd
|
||||||
you should be located in home directory of the airflow user
|
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
|
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
|
check that file was created and you can see content of keytab file which holds Pricipal
|
||||||
klist -ekt airflow.keytab
|
klist -ekt airflow.keytab
|
||||||
image.png
|
|
||||||
|
|
||||||
initiate ticket for airflow user
|
initiate ticket for airflow user
|
||||||
|
|
||||||
@@ -308,4 +306,57 @@ systemctl restart "$APP_SERVICE_NAME" \
|
|||||||
|| { log_error "Failed to restart ${APP_SERVICE_NAME}"; return 1; }
|
|| { log_error "Failed to restart ${APP_SERVICE_NAME}"; return 1; }
|
||||||
|
|
||||||
systemctl --no-pager status "$APP_SERVICE_NAME"
|
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
|
||||||
|
|
||||||
@@ -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())
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user