OpenBao in Docker: secret, AppRole e file .env con Agent
Installa OpenBao con Docker, Raft e TLS: crea ACL e AppRole via CLI e genera file .env e config.yaml per le applicazioni con OpenBao Agent.
Password nei file Compose, token dentro il repository e credenziali copiate a mano nei container sono comodi fino al giorno in cui bisogna ruotarli, revocarli o capire chi li ha letti. OpenBao centralizza questi secret, cifra i dati a riposo e decide quale identità possa accedere a ciascun percorso.
OpenBao non sostituisce SOPS e age: risolve un problema diverso. Se devi conservare file YAML, JSON o
.envin un repository, trovi esempi pratici in Cripta informazioni sensibili prima di conservarle. SOPS e age cifrano i file prima del commit; OpenBao mantiene invece i secret fuori dal repository e li consegna a runtime applicando identità, policy e audit. Nei flussi più strutturati i due approcci possono convivere.
Cos’è OpenBao
OpenBao è un sistema open source di gestione dei secret e della cifratura basato sull’identità. Un utente o un’applicazione si autentica, riceve un token associato a una o più policy e può eseguire soltanto le operazioni permesse su determinati path. Il progetto è ospitato da OpenSSF e distribuito con licenza MPL 2.0.
In questa guida costruiremo un esempio completo:
- un server OpenBao in Docker con TLS e storage Raft persistente;
- un audit log su un volume protetto;
- un motore KV v2 con i secret di una piccola applicazione;
- una policy ACL in sola lettura;
- un’AppRole configurata interamente via CLI;
- OpenBao Agent, che autentica il workload e genera sia
.envsiaconfig.yaml; - un entrypoint che attende i file, importa solo le variabili consentite e avvia il processo principale.
Gli esempi sono stati verificati sulla release OpenBao 2.6.1. Il tag dell’immagine è fissato intenzionalmente: aggiornatelo soltanto dopo aver letto le note di rilascio e provato backup e ripristino.
Architettura e limiti
Il flusso sarà questo:
Diagramma generato con Graphviz: l’applicazione legge soltanto i file prodotti da Agent e non comunica direttamente con OpenBao.
La porta API viene pubblicata soltanto su 127.0.0.1; Agent parla con OpenBao sulla rete Docker privata, mentre l’app non ha accesso diretto al server dei secret. I file generati passano attraverso /run, normalmente una tmpfs sui sistemi Linux con systemd.
Questo è un deployment single-node: è persistente e adatto a un laboratorio o a un piccolo homelab, ma non è alta affidabilità. In produzione servono almeno tre nodi Raft distribuiti, snapshot provati, monitoraggio e un meccanismo di auto-unseal basato su KMS o HSM.
Chi controlla il Docker host come
rootpuò leggere i bind mount, ispezionare i processi e sostituire i container. OpenBao protegge dalla dispersione dei secret e applica autorizzazioni e audit, ma non rende affidabile un host già compromesso.
Prerequisiti
Servono un host Linux aggiornato, Docker Engine con il plugin Compose e OpenSSL. Riserviamo identità e gruppi numerici distinti:
2000:2000per il server OpenBao;- UID
2100per Agent; - UID
2200per l’applicazione dimostrativa; - GID
3100, chiamatoopenbao-demo-reade condiviso soltanto da Agent edemo-appper leggere i file renderizzati.
Sono UID e GID di esempio: controllate con getent passwd 2000, getent passwd 2100, getent passwd 2200 e getent group 3100 che non siano già utilizzati. I nomi nel database utenti dell’host non sono obbligatori per Docker, ma documentarli evita collisioni e rende più leggibili audit e backup. Possiamo dare un nome al gruppo sul solo host, senza aggiungervi utenti umani:
1
sudo groupadd --system --gid 3100 openbao-demo-read
I container continuano a usare il GID numerico, quindi non dipendono dal file /etc/group dell’host.
Prepariamo la struttura:
1
2
3
4
5
mkdir -p \
server/{config,tls,data,audit} \
bootstrap/policies \
agent/{config,templates,auth} \
app scripts
Il risultato sarà simile a questo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
.
|-- compose.yaml
|-- server
| |-- config/bao.hcl
| |-- tls/{ca.crt,server.crt,server.key}
| |-- data/
| `-- audit/
|-- bootstrap/policies/demo-app.hcl
|-- agent
| |-- config/agent.hcl
| |-- templates/{app.env.ctmpl,config.yaml.ctmpl}
| `-- auth/{role_id,secret_id}
|-- app/openbao-entrypoint.sh
`-- scripts/issue-agent-credentials.sh
Configurare il server
Creiamo server/config/bao.hcl:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ui = true
api_addr = "https://openbao:8200"
cluster_addr = "https://openbao:8201"
storage "raft" {
path = "/openbao/data"
node_id = "openbao-1"
}
listener "tcp" {
address = "0.0.0.0:8200"
cluster_address = "0.0.0.0:8201"
tls_cert_file = "/openbao/tls/server.crt"
tls_key_file = "/openbao/tls/server.key"
tls_min_version = "tls12"
}
api_addr deve diventare il vero FQDN raggiungibile dai client quando il server esce dalla singola rete Compose. Anche quel nome dovrà comparire nei SAN del certificato.
Certificato TLS per il laboratorio
Per rendere l’esempio riproducibile generiamo un certificato autofirmato valido per il nome Docker openbao e per l’accesso locale:
1
2
3
4
5
6
7
8
9
10
openssl req -x509 -newkey rsa:4096 -sha256 -days 365 -nodes \
-keyout server/tls/server.key \
-out server/tls/server.crt \
-subj "/CN=openbao" \
-addext "subjectAltName=DNS:openbao,IP:127.0.0.1" \
-addext "basicConstraints=critical,CA:TRUE" \
-addext "keyUsage=critical,digitalSignature,keyEncipherment,keyCertSign" \
-addext "extendedKeyUsage=serverAuth"
cp server/tls/server.crt server/tls/ca.crt
Questo certificato è accettabile solo per un laboratorio. In produzione usate una CA interna o pubblica, montate la sola catena CA insieme al certificato e alla chiave del server e non conservate mai la chiave privata della CA sul nodo OpenBao.
Definire la policy ACL
OpenBao nega implicitamente ciò che una policy non autorizza. Il file bootstrap/policies/demo-app.hcl concede la lettura di un solo secret KV v2 e le operazioni necessarie ad Agent per controllare e rinnovare il proprio token:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
path "kv/data/apps/demo" {
capabilities = ["read"]
}
path "auth/token/lookup-self" {
capabilities = ["read"]
}
path "auth/token/renew-self" {
capabilities = ["update"]
}
path "auth/token/revoke-self" {
capabilities = ["update"]
}
Con KV v2 la policy usa il path API kv/data/..., non il percorso abbreviato mostrato dai comandi bao kv. Non concediamo list, create, update o accesso agli altri secret.
Configurare OpenBao Agent
Il file agent/config/agent.hcl configura Agent. Il config.yaml che vedremo dopo è invece un file dell’applicazione generato da Agent: sono due ruoli distinti.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
pid_file = "/tmp/openbao-agent.pid"
vault {
address = "https://openbao:8200"
ca_cert = "/openbao/tls/ca.crt"
}
auto_auth {
method {
type = "approle"
mount_path = "auth/approle"
config = {
role_id_file_path = "/openbao/auth/role_id"
secret_id_file_path = "/openbao/auth/secret_id"
secret_id_response_wrapping_path = "auth/approle/role/demo-app/secret-id"
remove_secret_id_file_after_reading = true
}
}
}
template_config {
exit_on_retry_failure = true
static_secret_render_interval = "5m"
}
template {
source = "/openbao/templates/app.env.ctmpl"
destination = "/run/openbao/app.env"
perms = "0640"
backup = false
error_on_missing_key = true
}
template {
source = "/openbao/templates/config.yaml.ctmpl"
destination = "/run/openbao/config.yaml"
perms = "0640"
backup = false
error_on_missing_key = true
}
Il blocco si chiama ancora vault per compatibilità del formato. Non aggiungiamo un token sink: Agent usa il token in memoria esclusivamente per i template. backup = false evita di lasciare sul disco la versione precedente dei secret.
Template per .env
Creiamo agent/templates/app.env.ctmpl:
1
2
3
4
5
{{- with secret "kv/data/apps/demo" -}}
DB_USERNAME={{ .Data.data.db_username }}
DB_PASSWORD={{ .Data.data.db_password }}
API_TOKEN={{ .Data.data.api_token }}
{{- end }}
Questo formato va bene per valori su una sola riga. Certificati PEM, chiavi o secret multilinea devono essere renderizzati in file separati, non forzati in un .env.
Template per config.yaml
Creiamo agent/templates/config.yaml.ctmpl:
1
2
3
4
5
6
7
8
9
10
{{- with secret "kv/data/apps/demo" -}}
database:
host: {{ .Data.data.db_host | toJSON }}
port: {{ .Data.data.db_port | toJSON }}
username: {{ .Data.data.db_username | toJSON }}
password: {{ .Data.data.db_password | toJSON }}
application:
api_token: {{ .Data.data.api_token | toJSON }}
log_level: {{ .Data.data.log_level | toJSON }}
{{- end }}
toJSON produce stringhe correttamente quotate e sottoposte a escaping; JSON è un sottoinsieme valido di YAML. Nell’esempio generiamo entrambi i formati per mostrarne l’uso, ma un’applicazione reale dovrebbe preferire il solo formato che sa ricaricare correttamente.
Creare un entrypoint sicuro
Il file app/openbao-entrypoint.sh attende i template e importa soltanto tre nomi ammessi. Non usa source né eval: un valore contenente $() o backtick rimane una stringa e non diventa un comando.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#!/bin/sh
set -eu
secrets_dir=${OPENBAO_SECRETS_DIR:-/run/openbao}
env_file="$secrets_dir/app.env"
config_file="$secrets_dir/config.yaml"
attempt=0
while [ ! -s "$env_file" ] || [ ! -s "$config_file" ]; do
attempt=$((attempt + 1))
if [ "$attempt" -ge 60 ]; then
echo "OpenBao Agent non ha generato i file entro 60 secondi" >&2
exit 1
fi
sleep 1
done
while IFS= read -r line || [ -n "$line" ]; do
case "$line" in
''|'#'*)
continue
;;
DB_USERNAME=*|DB_PASSWORD=*|API_TOKEN=*)
export "$line"
;;
*)
echo "Variabile non autorizzata nel file .env" >&2
exit 1
;;
esac
done < "$env_file"
export APP_CONFIG_FILE="$config_file"
exec "$@"
exec fa diventare l’applicazione il processo principale e le consente di ricevere correttamente i segnali di stop. Nell’immagine reale conviene copiare questo script durante la build, con proprietà root:root e modalità 0555, invece di montarlo dall’host.
Docker Compose
Ora creiamo compose.yaml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
services:
openbao:
image: openbao/openbao:2.6.1
user: "2000:2000"
command: ["server"]
environment:
HOME: /tmp
SKIP_CHOWN: "1"
BAO_ADDR: https://127.0.0.1:8200
BAO_CACERT: /openbao/tls/ca.crt
ports:
- "127.0.0.1:8200:8200"
volumes:
- ./server/config:/openbao/config:ro
- ./server/tls:/openbao/tls:ro
- ./server/data:/openbao/data
- ./server/audit:/openbao/audit
- ./bootstrap/policies:/openbao/bootstrap/policies:ro
tmpfs:
- /tmp:size=16m,mode=1777
- /openbao/logs:size=1m,uid=2000,gid=2000,mode=0700
- /openbao/file:size=1m,uid=2000,gid=2000,mode=0700
read_only: true
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
mem_limit: 512m
memswap_limit: 512m
mem_swappiness: 0
networks:
- bao
restart: unless-stopped
openbao-agent:
image: openbao/openbao:2.6.1
user: "2100:3100"
command: ["agent", "-config=/openbao/agent/agent.hcl"]
environment:
HOME: /tmp
SKIP_CHOWN: "1"
volumes:
- ./agent/config:/openbao/agent:ro
- ./agent/templates:/openbao/templates:ro
- ./agent/auth:/openbao/auth
- ./server/tls/ca.crt:/openbao/tls/ca.crt:ro
- /run/openbao/demo:/run/openbao
tmpfs:
- /tmp:size=16m,mode=1777
- /openbao/logs:size=1m,uid=2100,gid=3100,mode=0700
- /openbao/file:size=1m,uid=2100,gid=3100,mode=0700
read_only: true
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
mem_limit: 128m
memswap_limit: 128m
mem_swappiness: 0
depends_on:
- openbao
networks:
- bao
restart: unless-stopped
demo-app:
image: alpine:3.22
user: "2200:3100"
entrypoint: ["/usr/local/bin/openbao-entrypoint.sh"]
command: ["tail", "-f", "/dev/null"]
environment:
OPENBAO_SECRETS_DIR: /run/openbao
volumes:
- ./app/openbao-entrypoint.sh:/usr/local/bin/openbao-entrypoint.sh:ro
- /run/openbao/demo:/run/openbao:ro
tmpfs:
- /tmp:size=16m,mode=1777
read_only: true
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
mem_limit: 128m
memswap_limit: 128m
mem_swappiness: 0
depends_on:
- openbao-agent
networks:
- app
restart: unless-stopped
networks:
bao:
internal: true
app:
internal: true
L’immagine alpine simula un processo applicativo e non legge i secret: va sostituita con la vostra immagine. depends_on ordina l’avvio, ma non garantisce che l’autenticazione sia conclusa; il ciclo di attesa nell’entrypoint gestisce proprio questa condizione.
OpenBao ha rimosso mlock dalla versione 2.0. Per ridurre lo swap dei dati sensibili usiamo limiti cgroup con memoria e swap uguali, oltre a mem_swappiness: 0. Verificate che il kernel abbia il supporto agli swap limit e adattate i valori alla dimensione reale del database.
Chi deve possedere i secret
La separazione importante non è soltanto tra host e container, ma tra credenziali di autenticazione e secret già renderizzati:
| Percorso o dato | Proprietario | Gruppo | Modo | Chi lo riceve |
|---|---|---|---|---|
server/data e server/audit | 2000 | 2000 | 0700 | solo il server OpenBao |
server.key | amministratore host | 2000 | 0640 | solo il server OpenBao |
agent/auth/role_id | 2100 | 3100 | 0400 | solo Agent |
agent/auth/secret_id | 2100 | 3100 | 0400 | solo Agent, poi viene eliminato |
/run/openbao/demo | 2100 | 3100 | 0750 | Agent scrive, demo-app attraversa |
.env e config.yaml | 2100 | 3100 | 0640 | Agent scrive, demo-app legge |
Il token AppRole esiste nella memoria di Agent e non viene scritto in un sink. L’applicazione gira come 2200:3100: grazie al gruppo può leggere i file, ma il bind mount è anche read-only, quindi non può modificarli. La directory agent/auth non viene montata nell’app, che non deve mai ricevere RoleID, SecretID o token OpenBao.
Il gruppo
openbao-demo-read(3100) è una capacità di lettura dei secret didemo-app. Non assegnatelo a utenti o container generici e non aggiungete l’app ai gruppi2000,dockero a quelli riservati ad altri workload.
Applicare i permessi
Prima dell’avvio rendiamo esplicita la proprietà di ogni percorso:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
sudo chown -R "$USER":2000 server/config server/tls bootstrap/policies
sudo chmod 0750 server/config server/tls bootstrap/policies
sudo chmod 0640 server/config/bao.hcl server/tls/server.key \
bootstrap/policies/demo-app.hcl
sudo chmod 0644 server/tls/server.crt server/tls/ca.crt
sudo chown -R 2000:2000 server/data server/audit
sudo chmod 0700 server/data server/audit
sudo chown -R "$USER":3100 agent/config agent/templates
sudo chmod 0750 agent/config agent/templates
sudo chmod 0640 agent/config/agent.hcl agent/templates/*.ctmpl
sudo chown 2100:3100 agent/auth
sudo chmod 0700 agent/auth
chmod 0555 app/openbao-entrypoint.sh
I file renderizzati contengono secret in chiaro. Creiamo quindi la directory condivisa in /run, che sui normali host Linux è volatile:
1
2
sudo install -d -o root -g root -m 0711 /run/openbao
sudo install -d -o 2100 -g 3100 -m 0750 /run/openbao/demo
Il parent 0711 è attraversabile conoscendo il nome del path ma non elencabile dagli utenti normali. La directory dell’app consente a UID 2100 di scrivere e al solo gruppo 3100 di attraversare e leggere.
Per ricrearla automaticamente dopo ogni riavvio, aggiungiamo /etc/tmpfiles.d/openbao-apps.conf:
1
2
d /run/openbao 0711 root root -
d /run/openbao/demo 0750 2100 3100 -
e applichiamo la regola:
1
sudo systemd-tmpfiles --create /etc/tmpfiles.d/openbao-apps.conf
Aggiungere altre applicazioni
Ogni nuovo confine di fiducia dovrebbe avere una policy, un’AppRole, un Agent, un UID e un gruppo propri. Per esempio:
| Workload | UID Agent | UID app | GID lettura | Directory runtime |
|---|---|---|---|---|
demo-app | 2100 | 2200 | 3100 | /run/openbao/demo |
billing-app | 2101 | 2201 | 3101 | /run/openbao/billing |
monitoring-app | 2102 | 2202 | 3102 | /run/openbao/monitoring |
La seconda app userà quindi una policy come billing-app, un’AppRole con lo stesso nome, un servizio openbao-agent-billing eseguito come 2101:3101 e un’app eseguita come 2201:3101. La sua directory sarà 0750 e i file 0640. In questo modo né demo-app né il suo Agent possono attraversare il path di billing-app, anche se i container condividono lo stesso host.
I gruppi possono chiamarsi openbao-demo-read, openbao-billing-read e openbao-monitoring-read. Il suffisso -read ricorda che concedono accesso ai secret già renderizzati, non al server OpenBao né alle credenziali AppRole.
Un singolo Agent può renderizzare più template, ma così diventa una trust boundary comune: se le app non devono condividere i secret, è più coerente usare un Agent e un’AppRole per ciascuna applicazione.
Nel repository del deployment ignoriamo almeno questi percorsi:
server/data/
server/audit/
server/tls/
agent/auth/
Policy, template e configurazioni possono invece essere versionati perché non contengono credenziali.
Avviare, inizializzare e fare unseal
Validiamo Compose e avviamo soltanto il server:
1
2
3
4
docker compose config
docker compose pull openbao
docker compose up -d openbao
docker compose logs openbao
Non usiamo mai server -dev: la modalità dev mantiene i dati in memoria, disabilita protezioni importanti e crea automaticamente un root token noto.
Inizializziamo il barrier con cinque quote e una soglia di tre:
1
2
docker compose exec openbao \
bao operator init -key-shares=5 -key-threshold=3
Distribuite le cinque unseal key a custodi distinti e conservate il root token iniziale in un password manager o in un supporto cifrato. Per un ambiente serio è preferibile cifrare già l’output di operator init con le chiavi PGP dei custodi.
Inseriamo tre quote, una alla volta e senza passarle come argomenti visibili:
1
2
3
4
docker compose exec openbao bao operator unseal
docker compose exec openbao bao operator unseal
docker compose exec openbao bao operator unseal
docker compose exec openbao bao status
Dopo un riavvio il nodo resta inizializzato ma torna sealed. L’auto-unseal evita questa procedura manuale, ma sposta la fiducia su un KMS o un HSM e va progettato insieme al piano di disaster recovery.
Amministrare OpenBao dalla CLI
Per il bootstrap usiamo il root token senza salvarlo nel token helper del client:
1
2
3
4
5
6
7
read -rsp 'Root token: ' BAO_TOKEN
export BAO_TOKEN
printf '\n'
baoctl() {
docker compose exec -T -e BAO_TOKEN openbao bao "$@"
}
Abilitiamo subito l’audit file:
1
2
3
baoctl audit enable file \
file_path=/openbao/audit/audit.log \
mode=0600
Anche l’audit log è sensibile: contiene metadati, identità e path richiesti. Va raccolto, ruotato e protetto con la stessa attenzione degli altri log di sicurezza.
Abilitare KV v2 e inserire i secret
1
baoctl secrets enable -path=kv kv-v2
Per non lasciare password nella cronologia o nella lista dei processi, creiamo fuori dal repository /tmp/openbao-demo-secrets.json con umask 077:
1
2
3
4
5
6
7
8
{
"db_host": "postgres",
"db_port": 5432,
"db_username": "demo",
"db_password": "SOSTITUIRE_CON_UN_VALORE_FORTE",
"api_token": "SOSTITUIRE_CON_UN_TOKEN_REALE",
"log_level": "info"
}
Poi inviamo il JSON tramite standard input:
1
2
3
umask 077
baoctl kv put -mount=kv apps/demo - \
< /tmp/openbao-demo-secrets.json
Eliminate il file temporaneo appena verificato il caricamento. Su filesystem copy-on-write o SSD la cancellazione sicura non è garantita: per workflow ricorrenti è meglio produrre il JSON direttamente da un password manager o da un canale cifrato e passarlo in pipe.
Caricare la policy
1
2
3
4
5
baoctl policy write \
demo-app \
/openbao/bootstrap/policies/demo-app.hcl
baoctl policy read demo-app
Creare l’AppRole
Abilitiamo il metodo e creiamo un ruolo per il solo workload demo-app:
1
2
3
4
5
6
7
8
9
10
baoctl auth enable approle
baoctl write auth/approle/role/demo-app \
bind_secret_id=true \
token_policies=demo-app \
token_no_default_policy=true \
token_type=service \
token_period=20m \
secret_id_ttl=10m \
secret_id_num_uses=1
Il token periodico viene rinnovato da Agent ogni volta che il processo resta in esecuzione; non ha quindi bisogno di un SecretID riutilizzabile. Una revoca lo interrompe immediatamente. Il RoleID identifica il ruolo e non è trattato come una password, mentre il SecretID è una credenziale sensibile, monouso e valida solo dieci minuti.
Consegnare il SecretID con response wrapping
Non mettiamo il SecretID nel Compose, in un’immagine o in Git. OpenBao genera invece un wrapping token con TTL di cinque minuti; Agent lo apre, controlla che provenga dall’endpoint previsto, usa il SecretID e cancella il file.
Creiamo scripts/issue-agent-credentials.sh:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/bin/sh
set -eu
: "${BAO_TOKEN:?Esporta prima un token amministrativo in BAO_TOKEN}"
project_dir=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
cd "$project_dir"
umask 077
tmp_dir=$(mktemp -d)
trap 'rm -rf "$tmp_dir"' EXIT HUP INT TERM
bao() {
docker compose exec -T -e BAO_TOKEN openbao bao "$@"
}
bao read -field=role_id \
auth/approle/role/demo-app/role-id \
> "$tmp_dir/role_id"
bao write -wrap-ttl=5m -field=wrapping_token -f \
auth/approle/role/demo-app/secret-id \
> "$tmp_dir/secret_id"
sudo install -o 2100 -g 3100 -m 0400 \
"$tmp_dir/role_id" agent/auth/role_id
sudo install -o 2100 -g 3100 -m 0400 \
"$tmp_dir/secret_id" agent/auth/secret_id
echo "Credenziali AppRole consegnate senza mostrarne il contenuto."
Eseguiamolo:
1
2
chmod 0750 scripts/issue-agent-credentials.sh
./scripts/issue-agent-credentials.sh
Il wrapping token è anch’esso un bearer secret, ma è monouso, scade rapidamente e non rivela il SecretID a chi lo trasporta. Se Agent viene riavviato, occorre eseguire nuovamente lo script prima della partenza: il SecretID precedente è già stato consumato e il relativo file viene eliminato.
In un orchestratore è preferibile usare l’identità nativa del workload, per esempio Kubernetes Auth o JWT/OIDC, invece di distribuire AppRole. Qui AppRole è adatta perché stiamo lavorando con normali container su un singolo host.
Avviare Agent e l’applicazione
1
2
3
docker compose pull openbao-agent demo-app
docker compose up -d openbao-agent demo-app
docker compose logs -f openbao-agent
Controlliamo i permessi senza stampare alcun valore:
1
2
3
4
5
sudo stat -c '%A %u:%g %n' /run/openbao/demo/*
docker compose exec demo-app sh -c \
'test -r /run/openbao/config.yaml && \
tr "\0" "\n" < /proc/1/environ | grep -q "^DB_USERNAME="'
I due file devono appartenere a 2100:3100 ed essere 0640. L’app gira con UID 2200, li legge grazie al solo GID 3100 e non possiede né il RoleID né il wrapping token.
Quando un valore KV cambia, Agent rigenera i template entro l’intervallo static_secret_render_interval. Il config.yaml può essere riletto da un’applicazione che supporta il reload; le variabili d’ambiente di un processo già avviato, invece, non cambiano. Per applicare il nuovo .env bisogna riavviare soltanto l’app, oppure usare il Process Supervisor di OpenBao Agent e progettare esplicitamente il ciclo di riavvio.
Terminato il bootstrap:
1
unset BAO_TOKEN
Configurate subito un metodo di autenticazione nominativo per gli amministratori e verificate che funzioni prima di revocare il root token iniziale. Un root token non dovrebbe diventare la credenziale usata ogni giorno.
Cosa manca per la produzione
Prima di affidare a questo servizio credenziali reali, aggiungerei almeno:
- tre o cinque nodi Raft su domini di guasto differenti;
- snapshot periodici con
bao operator raft snapshot savee prove di restore; - auto-unseal tramite KMS/HSM e custodia separata delle recovery key;
- autenticazione OIDC o certificati client per gli amministratori;
- raccolta remota degli audit log, metriche e alert su seal, errori e scadenze;
- policy separate per ogni applicazione e ambiente;
- rotazione dei secret e, dove possibile, credenziali database dinamiche;
- accesso alla porta 8200 limitato da firewall, VPN o rete di management.
OpenBao risolve soprattutto il problema dell’identità: non più “chi possiede questo file può leggere tutto”, ma “questa applicazione autenticata può leggere questo path e nient’altro”. Agent completa il disegno tenendo token e logica di autenticazione fuori dal codice applicativo. I file .env restano secret in chiaro, ma diventano artefatti temporanei, protetti e rigenerabili invece di credenziali permanenti sparse nei repository.
