Skip to content

Commit 02ebecf

Browse files
committed
ch9.8: strip hashistack_session refs and correct CT auto-renewal semantics
- docs: rewrite section 4.2/4.3 to distinguish token renewal (renew_token=true) from renewable-lease renewer goroutine (default-on for database/creds); clarify lease_renewal_threshold only applies to non-renewable leases; describe two-phase observable behavior (renewal until max_ttl, then re-fetch) - lab: lower database role max_ttl from 10m to 2m so rotation is observable; rewrite step2/text.md to honestly show renewal phase (same username, valuntil extends) then rotation phase after max_ttl
1 parent 77599cf commit 02ebecf

11 files changed

Lines changed: 1197 additions & 0 deletions

File tree

docs/ch9-legacy-agent.md

Lines changed: 315 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
#!/bin/bash
2+
# ─────────────────────────────────────────────────────────
3+
# setup-common.sh — shared setup functions for Killercoda scenarios
4+
#
5+
# This file is the SINGLE SOURCE OF TRUTH for common setup logic.
6+
# It is copied into each scenario's assets/ directory by:
7+
# npm run sync-setup (or automatically via prebuild)
8+
#
9+
# Usage in background.sh:
10+
# source /root/setup-common.sh
11+
# install_vault
12+
# finish_setup
13+
# ─────────────────────────────────────────────────────────
14+
15+
VAULT_VERSION="${VAULT_VERSION:-1.19.2}"
16+
17+
install_vault() {
18+
# Idempotency guard: skip the 70MB download if the requested version is
19+
# already installed (saves ~30s on container warm restarts).
20+
if command -v vault > /dev/null 2>&1 \
21+
&& vault version 2>/dev/null | grep -q "v${VAULT_VERSION}"; then
22+
echo "vault ${VAULT_VERSION} already installed, skipping download."
23+
return 0
24+
fi
25+
26+
if ! command -v unzip > /dev/null 2>&1; then
27+
apt-get update -qq && apt-get install -y -qq unzip > /dev/null 2>&1
28+
fi
29+
30+
curl --connect-timeout 10 --max-time 120 -fsSL \
31+
"https://releases.hashicorp.com/vault/${VAULT_VERSION}/vault_${VAULT_VERSION}_linux_amd64.zip" \
32+
-o /tmp/vault.zip \
33+
&& unzip -o -q /tmp/vault.zip -d /usr/local/bin/ \
34+
&& chmod +x /usr/local/bin/vault \
35+
&& rm -f /tmp/vault.zip
36+
37+
vault version || echo "WARNING: vault install failed"
38+
}
39+
40+
start_vault_dev() {
41+
# Start Vault in dev mode (in-memory, no TLS, root token = root)
42+
export VAULT_ADDR='http://127.0.0.1:8200'
43+
export VAULT_TOKEN='root'
44+
45+
# Persist env for ALL future shells (Killercoda's editor terminal is a
46+
# separate shell that does not inherit from background.sh, and may not
47+
# source ~/.bashrc — /etc/profile.d/*.sh is loaded by every login shell).
48+
cat > /etc/profile.d/vault.sh <<'EOF'
49+
export VAULT_ADDR='http://127.0.0.1:8200'
50+
export VAULT_TOKEN='root'
51+
EOF
52+
chmod +x /etc/profile.d/vault.sh
53+
# Also append to /root/.bashrc so non-login interactive shells pick it up.
54+
grep -q "VAULT_ADDR=" /root/.bashrc 2>/dev/null || \
55+
cat /etc/profile.d/vault.sh >> /root/.bashrc
56+
57+
vault server -dev -dev-root-token-id=root \
58+
-dev-listen-address=0.0.0.0:8200 \
59+
> /var/log/vault-dev.log 2>&1 &
60+
61+
echo "Waiting for Vault dev server to be ready..."
62+
for i in $(seq 1 30); do
63+
if vault status > /dev/null 2>&1; then
64+
echo "Vault is ready."
65+
return 0
66+
fi
67+
sleep 1
68+
done
69+
echo "WARNING: Vault did not become healthy within 30 seconds"
70+
cat /var/log/vault-dev.log
71+
}
72+
73+
start_postgres() {
74+
# Start a Postgres container for dynamic-secret demos.
75+
# Image: postgres:16. Superuser: root / rootpassword. Listens on 5432.
76+
if ! command -v docker > /dev/null 2>&1; then
77+
echo "WARNING: docker not available, cannot start postgres"
78+
return 1
79+
fi
80+
81+
docker rm -f learn-postgres > /dev/null 2>&1 || true
82+
docker run -d \
83+
--name learn-postgres \
84+
-e POSTGRES_USER=root \
85+
-e POSTGRES_PASSWORD=rootpassword \
86+
-p 5432:5432 \
87+
--rm \
88+
postgres:16 > /dev/null
89+
90+
echo "Waiting for Postgres to be ready..."
91+
for i in $(seq 1 60); do
92+
if docker exec learn-postgres pg_isready -U root > /dev/null 2>&1; then
93+
echo "Postgres is ready."
94+
# Create the read-only role that dynamic users will inherit from.
95+
docker exec -i learn-postgres psql -U root -c \
96+
"CREATE ROLE \"ro\" NOINHERIT;" > /dev/null 2>&1 || true
97+
docker exec -i learn-postgres psql -U root -c \
98+
"GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"ro\";" > /dev/null 2>&1 || true
99+
return 0
100+
fi
101+
sleep 1
102+
done
103+
echo "WARNING: Postgres did not become healthy within 60 seconds"
104+
docker logs learn-postgres || true
105+
}
106+
107+
install_awscli() {
108+
# Install AWS CLI v2 (official binary). Idempotent.
109+
if command -v aws > /dev/null 2>&1; then
110+
echo "aws CLI already installed: $(aws --version 2>&1)"
111+
else
112+
if ! command -v unzip > /dev/null 2>&1; then
113+
apt-get update -qq && apt-get install -y -qq unzip > /dev/null 2>&1
114+
fi
115+
curl --connect-timeout 10 --max-time 120 -fsSL \
116+
"https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" \
117+
-o /tmp/awscliv2.zip \
118+
&& unzip -o -q /tmp/awscliv2.zip -d /tmp/ \
119+
&& /tmp/aws/install --update > /dev/null 2>&1 \
120+
&& rm -rf /tmp/awscliv2.zip /tmp/aws
121+
122+
aws --version || echo "WARNING: awscli install failed"
123+
fi
124+
125+
# Disable AWS CLI pager globally so output prints directly in Killercoda terminal.
126+
mkdir -p /root/.aws
127+
cat > /root/.aws/config <<'AWSCFG'
128+
[default]
129+
region = us-east-1
130+
output = json
131+
cli_pager =
132+
AWSCFG
133+
134+
# Install awscli-local (provides the 'awslocal' command pointing at LocalStack on :4566).
135+
if ! command -v awslocal > /dev/null 2>&1; then
136+
pip3 install --break-system-packages awscli-local > /dev/null 2>&1 \
137+
|| {
138+
# Fallback: shell wrapper if pip is unavailable.
139+
cat > /usr/local/bin/awslocal <<'WRAPPER'
140+
#!/bin/bash
141+
export AWS_PAGER=""
142+
exec aws --endpoint-url=http://localhost:4566 --region us-east-1 "$@"
143+
WRAPPER
144+
chmod +x /usr/local/bin/awslocal
145+
}
146+
fi
147+
148+
awslocal --version || echo "WARNING: awslocal install failed"
149+
}
150+
151+
finish_setup() {
152+
touch /tmp/.setup-done
153+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# 实验完成
2+
3+
你已经在一台主机上跑通了 9.8 节里描述的两条遗留应用接入路径:
4+
5+
- **Consul-Template + 配置文件**:以 30 秒 TTL 的 PostgreSQL 动态凭据为例,演示了模板渲染、`lease_renewal_threshold = 0.5` 触发的自动重申,以及它的固有缺陷——"应用还没来得及重读旧凭据已经被撤"那段窗口;
6+
- **Vault Agent Process Supervisor Mode**:同样的 binary、同样的 AppRole,把动态凭据以 `DB_USER` / `DB_PASSWORD` 注入子进程,并通过 `restart_on_secret_changes = "always"` 在 lease 接近过期时主动 SIGTERM + 重新 exec,从应用角度看就是一次干净的"重启换密码"。
7+
8+
## 你掌握的要点
9+
10+
- PostgreSQL `database` 机密引擎的 `creation_statements` 模板 + `default_ttl=30s` 怎么搭配 `INHERIT` + `GRANT ro` 的最小权限模型;
11+
- Consul-Template 配置里 `renew_token` / `default_lease_duration` / `lease_renewal_threshold` 三个参数的真实作用;
12+
- Vault Agent Process Supervisor Mode 三个约束:必须有 ≥1 个 `env_template`、必须有恰好 1 个 `exec`、不能与文件 `template` 块同存;
13+
- `restart_on_secret_changes``restart_stop_signal` 怎么决定子进程的关闭语义;
14+
-`pg_user` / `vault list sys/leases/lookup/...` / 主动 `vault lease revoke` 三种角度交叉验证 lease 生命周期。
15+
16+
## 生产化的下一步
17+
18+
- 别在生产里用 root token + dev mode:把 AppRole 的 secret-id 换成 wrapped、用 `response-wrapping` 派发;
19+
- 把 30 秒 TTL 调到符合应用 SLA 的值(一般几十分钟),并配合监控 lease 续期失败率;
20+
- Process Supervisor Mode 适合"一次启动"的二进制;如果应用本身就支持 SIGHUP 重读,Consul-Template 路径反而更轻量;
21+
- 真正混合负载下还可以让 Agent 同时承担"缓存代理"(第 9.3 节)与"模板渲染"职责,但**不能**`env_template` 与文件 `template` 写进同一份配置。
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"title": "无法改造的遗留应用接入 Vault:Consul-Template 与 Vault Agent Process Supervisor 双轨实践",
3+
"description": "On a single Killercoda host, simulate a legacy Go application that reads PostgreSQL credentials either from /etc/legacy-app/config.toml or from DB_USER/DB_PASSWORD environment variables, with a 30-second TTL Vault database role behind it. First, use Consul-Template to render the dynamic credentials into the TOML config file and watch the file refresh as the lease rolls over. Second, run Vault Agent in process supervisor mode to inject the same credentials into the legacy binary as environment variables and observe restart_on_secret_changes restarting the child process. The two paths share the same Vault, Postgres, role and binary; only the delivery channel differs.",
4+
"details": {
5+
"intro": {
6+
"text": "init/init.md",
7+
"background": "init/background.sh",
8+
"foreground": "init/foreground.sh"
9+
},
10+
"steps": [
11+
{
12+
"title": "第一步:检查遗留应用与 Vault PostgreSQL 动态机密引擎",
13+
"text": "step1/text.md"
14+
},
15+
{
16+
"title": "第二步:Consul-Template 把动态凭据渲染成配置文件",
17+
"text": "step2/text.md"
18+
},
19+
{
20+
"title": "第三步:Vault Agent Process Supervisor 把同一份凭据注入环境变量",
21+
"text": "step3/text.md"
22+
},
23+
{
24+
"title": "第四步:对比两条路径并观察 lease 在 Postgres 端的真实生命周期",
25+
"text": "step4/text.md"
26+
}
27+
],
28+
"finish": {
29+
"text": "finish/finish.md"
30+
},
31+
"assets": {
32+
"host01": [
33+
{"file": "setup-common.sh", "target": "/root", "chmod": "+x"}
34+
]
35+
}
36+
},
37+
"backend": {
38+
"imageid": "ubuntu"
39+
},
40+
"interface": {
41+
"layout": "editor-terminal"
42+
}
43+
}

0 commit comments

Comments
 (0)