···11# spindle secrets with openbao
2233This document covers setting up Spindle to use OpenBao for secrets
44-management instead of the default SQLite backend.
44+management via OpenBao Proxy instead of the default SQLite backend.
55+66+## overview
77+88+Spindle now uses OpenBao Proxy for secrets management. The proxy handles
99+authentication automatically using AppRole credentials, while Spindle
1010+connects to the local proxy instead of directly to the OpenBao server.
1111+1212+This approach provides better security, automatic token renewal, and
1313+simplified application code.
514615## installation
716817Install OpenBao from nixpkgs:
9181019```bash
1111-nix-env -iA nixpkgs.openbao
2020+nix shell nixpkgs#openbao # for a local server
1221```
13221414-## local development setup
2323+## setup
2424+2525+The setup process can is documented for both local development and production.
2626+2727+### local development
15281629Start OpenBao in dev mode:
17301831```bash
1919-bao server -dev
3232+bao server -dev -dev-root-token-id="root" -dev-listen-address=127.0.0.1:8201
2033```
21342222-This starts OpenBao on `http://localhost:8200` with a root token. Save
2323-the root token from the output -- you'll need it.
3535+This starts OpenBao on `http://localhost:8201` with a root token.
24362537Set up environment for bao CLI:
26382739```bash
2840export BAO_ADDR=http://localhost:8200
2929-export BAO_TOKEN=hvs.your-root-token-here
4141+export BAO_TOKEN=root
3042```
31434444+### production
4545+4646+You would typically use a systemd service with a configuration file. Refer to
4747+[@tangled.sh/infra](https://tangled.sh/@tangled.sh/infra) for how this can be
4848+achieved using Nix.
4949+5050+Then, initialize the bao server:
5151+```bash
5252+bao operator init -key-shares=1 -key-threshold=1
5353+```
5454+5555+This will print out an unseal key and a root key. Save them somewhere (like a password manager). Then unseal the vault to begin setting it up:
5656+```bash
5757+bao operator unseal <unseal_key>
5858+```
5959+6060+All steps below remain the same across both dev and production setups.
6161+6262+### configure openbao server
6363+3264Create the spindle KV mount:
33653466```bash
3567bao secrets enable -path=spindle -version=2 kv
3668```
37693838-Set up AppRole authentication:
7070+Set up AppRole authentication and policy:
39714072Create a policy file `spindle-policy.hcl`:
41734274```hcl
7575+# Full access to spindle KV v2 data
4376path "spindle/data/*" {
4444- capabilities = ["create", "read", "update", "delete", "list"]
7777+ capabilities = ["create", "read", "update", "delete"]
4578}
46798080+# Access to metadata for listing and management
4781path "spindle/metadata/*" {
4848- capabilities = ["list", "read", "delete"]
8282+ capabilities = ["list", "read", "delete", "update"]
4983}
50845151-path "spindle/*" {
8585+# Allow listing at root level
8686+path "spindle/" {
5287 capabilities = ["list"]
5388}
8989+9090+# Required for connection testing and health checks
9191+path "auth/token/lookup-self" {
9292+ capabilities = ["read"]
9393+}
5494```
55955696Apply the policy and create an AppRole:
···61101bao write auth/approle/role/spindle \
62102 token_policies="spindle-policy" \
63103 token_ttl=1h \
6464- token_max_ttl=4h
104104+ token_max_ttl=4h \
105105+ bind_secret_id=true \
106106+ secret_id_ttl=0 \
107107+ secret_id_num_uses=0
65108```
6610967110Get the credentials:
6811169112```bash
7070-bao read auth/approle/role/spindle/role-id
7171-bao write -f auth/approle/role/spindle/secret-id
113113+# Get role ID (static)
114114+ROLE_ID=$(bao read -field=role_id auth/approle/role/spindle/role-id)
115115+116116+# Generate secret ID
117117+SECRET_ID=$(bao write -field=secret_id auth/approle/role/spindle/secret-id)
118118+119119+echo "Role ID: $ROLE_ID"
120120+echo "Secret ID: $SECRET_ID"
121121+```
122122+123123+### create proxy configuration
124124+125125+Create the credential files:
126126+127127+```bash
128128+# Create directory for OpenBao files
129129+mkdir -p /tmp/openbao
130130+131131+# Save credentials
132132+echo "$ROLE_ID" > /tmp/openbao/role-id
133133+echo "$SECRET_ID" > /tmp/openbao/secret-id
134134+chmod 600 /tmp/openbao/role-id /tmp/openbao/secret-id
135135+```
136136+137137+Create a proxy configuration file `/tmp/openbao/proxy.hcl`:
138138+139139+```hcl
140140+# OpenBao server connection
141141+vault {
142142+ address = "http://localhost:8200"
143143+}
144144+145145+# Auto-Auth using AppRole
146146+auto_auth {
147147+ method "approle" {
148148+ mount_path = "auth/approle"
149149+ config = {
150150+ role_id_file_path = "/tmp/openbao/role-id"
151151+ secret_id_file_path = "/tmp/openbao/secret-id"
152152+ }
153153+ }
154154+155155+ # Optional: write token to file for debugging
156156+ sink "file" {
157157+ config = {
158158+ path = "/tmp/openbao/token"
159159+ mode = 0640
160160+ }
161161+ }
162162+}
163163+164164+# Proxy listener for Spindle
165165+listener "tcp" {
166166+ address = "127.0.0.1:8201"
167167+ tls_disable = true
168168+}
169169+170170+# Enable API proxy with auto-auth token
171171+api_proxy {
172172+ use_auto_auth_token = true
173173+}
174174+175175+# Enable response caching
176176+cache {
177177+ use_auto_auth_token = true
178178+}
179179+180180+# Logging
181181+log_level = "info"
72182```
731837474-Configure Spindle:
184184+### start the proxy
185185+186186+Start OpenBao Proxy:
187187+188188+```bash
189189+bao proxy -config=/tmp/openbao/proxy.hcl
190190+```
191191+192192+The proxy will authenticate with OpenBao and start listening on
193193+`127.0.0.1:8201`.
194194+195195+### configure spindle
7519676197Set these environment variables for Spindle:
7719878199```bash
79200export SPINDLE_SERVER_SECRETS_PROVIDER=openbao
8080-export SPINDLE_SERVER_SECRETS_OPENBAO_ADDR=http://localhost:8200
8181-export SPINDLE_SERVER_SECRETS_OPENBAO_ROLE_ID=your-role-id-from-above
8282-export SPINDLE_SERVER_SECRETS_OPENBAO_SECRET_ID=your-secret-id-from-above
201201+export SPINDLE_SERVER_SECRETS_OPENBAO_PROXY_ADDR=http://127.0.0.1:8201
83202export SPINDLE_SERVER_SECRETS_OPENBAO_MOUNT=spindle
84203```
8520486205Start Spindle:
872068888-Spindle will now use OpenBao for secrets storage with automatic token
8989-renewal.
207207+Spindle will now connect to the local proxy, which handles all
208208+authentication automatically.
209209+210210+## production setup for proxy
211211+212212+For production, you'll want to run the proxy as a service:
213213+214214+Place your production configuration in `/etc/openbao/proxy.hcl` with
215215+proper TLS settings for the vault connection.
9021691217## verifying setup
922189393-List all secrets:
219219+Test the proxy directly:
9422095221```bash
9696-bao kv list spindle/
222222+# Check proxy health
223223+curl -H "X-Vault-Request: true" http://127.0.0.1:8201/v1/sys/health
224224+225225+# Test token lookup through proxy
226226+curl -H "X-Vault-Request: true" http://127.0.0.1:8201/v1/auth/token/lookup-self
97227```
982289999-Add a test secret via Spindle API, then check it exists:
229229+Test OpenBao operations through the server:
100230101231```bash
232232+# List all secrets
233233+bao kv list spindle/
234234+235235+# Add a test secret via Spindle API, then check it exists
102236bao kv list spindle/repos/
103103-```
104237105105-Get a specific secret:
106106-107107-```bash
238238+# Get a specific secret
108239bao kv get spindle/repos/your_repo_path/SECRET_NAME
109240```
110241111242## how it works
112243244244+- Spindle connects to OpenBao Proxy on localhost (typically port 8200 or 8201)
245245+- The proxy authenticates with OpenBao using AppRole credentials
246246+- All Spindle requests go through the proxy, which injects authentication tokens
113247- Secrets are stored at `spindle/repos/{sanitized_repo_path}/{secret_key}`
114114-- Each repository gets its own namespace
115115-- Repository paths like `at://did:plc:alice/myrepo` become
116116- `at_did_plc_alice_myrepo`
117117-- The system automatically handles token renewal using AppRole
118118- authentication
119119-- On shutdown, Spindle cleanly stops the token renewal process
248248+- Repository paths like `did:plc:alice/myrepo` become `did_plc_alice_myrepo`
249249+- The proxy handles all token renewal automatically
250250+- Spindle no longer manages tokens or authentication directly
120251121252## troubleshooting
122253123123-**403 errors**: Check that your BAO_TOKEN is set and the spindle mount
124124-exists
254254+**Connection refused**: Check that the OpenBao Proxy is running and
255255+listening on the configured address.
256256+257257+**403 errors**: Verify the AppRole credentials are correct and the policy
258258+has the necessary permissions.
125259126260**404 route errors**: The spindle KV mount probably doesn't exist - run
127127-the mount creation step again
261261+the mount creation step again.
128262129129-**Token expired**: The AppRole system should handle this automatically,
130130-but you can check token status with `bao token lookup`
263263+**Proxy authentication failures**: Check the proxy logs and verify the
264264+role-id and secret-id files are readable and contain valid credentials.
265265+266266+**Secret not found after writing**: This can indicate policy permission
267267+issues. Verify the policy includes both `spindle/data/*` and
268268+`spindle/metadata/*` paths with appropriate capabilities.
269269+270270+Check proxy logs:
271271+272272+```bash
273273+# If running as systemd service
274274+journalctl -u openbao-proxy -f
275275+276276+# If running directly, check the console output
277277+```
278278+279279+Test AppRole authentication manually:
280280+281281+```bash
282282+bao write auth/approle/login \
283283+ role_id="$(cat /tmp/openbao/role-id)" \
284284+ secret_id="$(cat /tmp/openbao/secret-id)"
285285+```