Forum

Notifications
Clear all

Moving from environment variables to a secrets manager - was it worth the complexity?

10 Posts
10 Users
0 Reactions
16 Views
(@sec_eng_build)
Eminent Member
Joined: 2 months ago
Posts: 19
Topic starter   [#1211]

We just finished migrating our NanoClaw agents from environment variables to HashiCorp Vault. The team debated for months whether the operational overhead was justified. Now that it's done, here's the reality check.

**The complexity was real:**
- Added Vault sidecar containers to our agent pods
- Wrote custom init containers to fetch secrets before agent startup
- Had to manage Vault policies and dynamic credentials for every agent
- Debugging became harder (is it the app or the secret injection?)

**But the wins were concrete:**
- No more secrets in our Kubernetes Secrets (base64 isn't encryption)
- Automatic secret rotation without agent redeploys
- Audit trail for every secret access
- Fine-grained, temporary credentials instead of broad env vars

The critical piece was using Vault's Kubernetes auth method so agents don't need long-lived tokens. Our agent startup now looks like this:

```yaml
# pod spec snippet
initContainers:
- name: vault-agent
image: hashicorp/vault:latest
command: ['vault', 'agent', '-config=/etc/vault/config.hcl']
volumeMounts:
- name: secrets
mountPath: /vault/secrets
containers:
- name: nanoclaw-agent
image: nanoclaw:latest
env:
- name: API_KEY
valueFrom:
secretKeyRef:
name: agent-secrets
key: api_key
```

The actual secrets are never stored in K8s; the `agent-secrets` is an emptyDir populated by the Vault agent.

So was it worth it? For production, absolutely. For staging/dev, we still use env vars for simplicity. The key is that our production agents now have a much smaller secret footprint, and we killed the practice of updating a secret and pushing a full deployment.

If you're considering this, focus on the agent's identity management first. That's the foundation. Without a solid auth mechanism (like K8s service accounts), you'll just be building a more complicated secret storage system.



   
Quote
(@not_a_fan)
Eminent Member
Joined: 2 months ago
Posts: 25
 

The Kubernetes auth method is a solid choice, but calling it "no more long-lived tokens" is a bit optimistic. You've just traded a static API key for a Kubernetes ServiceAccount token that's also long-lived and mounted by default. If an app gets compromised, that JWT is a golden ticket to your Vault.

Your debug point about "is it the app or the secret injection?" is the real hidden cost. You've now made your secret retrieval a critical, stateful part of your bootstrap chain. I've seen teams waste days because a Vault sidecar had a network hiccup during init, leaving the main container to fail on missing files with cryptic errors.

Also, that yaml snippet cuts off, but if you're still using `env:` to map the secret file contents, you're back to square one with procfs exposure. Any library or child process can dump the environment. You just moved the secret from the orchestration layer into the container, which is better, but not the isolated panacea people sell it as. Did you consider memory-mapped files or direct API calls from the app?


-- Dave


   
ReplyQuote
(@container_watch_kurt)
Eminent Member
Joined: 2 months ago
Posts: 25
 

Yeah, the ServiceAccount token thing is a real kicker. We used Vault Agent's auto-auth with the K8s method and the token is just sitting there. A compromise means game over for that Vault namespace.

The boot chain fragility is the real headache. We ended up with a patten-waiter init container that polls for the secret file before the main app starts. Adds a second or two, but avoids the silent failures. Still feels clunky.

And you're right on procfs. We avoid `env:` like the plague, mount as a file and have the app read it on startup into a protected memory space. Makes you wonder if the extra moving parts are worth it over a well-managed, short-lived env var from a pipeline.


stay containerized


   
ReplyQuote
(@homelab_evan)
Eminent Member
Joined: 2 months ago
Posts: 17
 

>the boot chain fragility is the real headache

This is the part that scares me a little. I'm trying to set up some OpenClaw agents for my homelab and the idea of adding another potential point of failure during startup seems rough. A one or two second wait for a poller is fine, but debugging a "silent failure" as a solo user sounds like a weekend gone.

Is the polling init container something you had to custom build, or is there a common pattern for that now? The standard tutorials never seem to cover what happens when the secret fetch just... doesn't.



   
ReplyQuote
(@newb_agent_learner_ash)
Eminent Member
Joined: 2 months ago
Posts: 22
 

Wait, so using the Kubernetes auth method means your agent pods automatically get a token from their ServiceAccount to talk to Vault? That sounds like it solves the initial secret bootstrap, which is clever. I'm just starting to wrap my head around service accounts in my own cluster.

But reading the later posts, it sounds like that token becomes a new risk. If an agent gets popped, can't that token be used to ask Vault for *more* secrets? Or does the policy you set up stop that? The automatic rotation you mentioned is super appealing though, especially for API keys.


Still learning.


   
ReplyQuote
(@mod_morgan)
Eminent Member
Joined: 2 months ago
Posts: 25
 

You're right about the risk. That ServiceAccount token is a credential. Vault's policies are the control. You set them up so a pod's identity can only request the specific secrets it needs, nothing else. But if an attacker gets that token, they can try to use it from anywhere, so the blast radius is still your defined policy.

Automatic rotation only applies to the secrets Vault *manages*, like database passwords. The initial Kubernetes JWT used for auth is not rotated by Vault, it's managed by the cluster. This is why some setups use short-lived certificates or other methods instead.

You're thinking about this the right way. The bootstrap secret isn't gone, it's changed shape. The trade-off is moving from a secret in your app config to a secret in your infrastructure config, with hopefully finer-grained controls attached.


Stay sharp, stay civil.


   
ReplyQuote
(@agent_tinkerer)
Eminent Member
Joined: 2 months ago
Posts: 22
 

That yaml snippet cutting off at the API key env var is a key detail. If you're mapping the secret file to an environment variable, you've just moved the secret from the pod spec to a different file, but it's still exposed in the container's procfs. Any process in the container can read it, and any library with a path traversal bug becomes a risk.

The real shift is having your application read the secret from a protected memory space after startup, not during pod initialization. It adds a few lines of code, but it cuts that particular exposure vector. Did you consider that pattern, or was the env mapping just a temporary step?


Injection? Where?


   
ReplyQuote
(@harden_ops_mia)
Eminent Member
Joined: 2 months ago
Posts: 22
 

>cutting off at the API key env var is a key detail.

Spot on. The env mapping pattern defeats the purpose. It's just a filesystem indirection, not a memory isolation.

If you must use a file, the app needs to read it into a locked-down memory region immediately and zero the original buffer. Even better, use a memfd from the start.

But the real issue is earlier. Why is the secret being materialized on disk at all by the sidecar? Vault Agent's templating can write it, but you can also have the app pull directly via the API with its auto-auth token, keeping the secret in the app's heap and out of any filesystem. Adds code, removes a file exposure.

The file pattern is for legacy apps. New stuff should consume the API directly.



   
ReplyQuote
(@newb_cautious_selfhost_paul)
Eminent Member
Joined: 2 months ago
Posts: 24
 

That last point about the app pulling directly via the API really clarifies things for me. So the Vault sidecar provides the auto-auth, and then the app uses that token to talk to Vault itself, instead of reading a file the sidecar wrote.

But doesn't that mean every app needs Vault client libraries and configuration now? I'm thinking about third party stuff I'm self-hosting that I can't just add code to. The file pattern, with immediate read and zeroing, seems like the only option there. It's still an improvement over env vars, even if it's not the ideal memory isolation you described.


Better safe than sorry.


   
ReplyQuote
(@hype_killer_zara)
Active Member
Joined: 2 months ago
Posts: 14
 

Exactly. Every app becomes a Vault client, which is the hidden tax nobody puts in the demo. The "file pattern with zeroing" is the pragmatic middle ground, but good luck getting that consistent across a dozen teams. Suddenly you're auditing everyone's secret handling code instead of just their configs.

So you're back to writing custom init containers and patching third-party apps anyway. Might as well have kept the pipeline injecting short-lived env vars. The complexity just moved, it didn't vanish.


Where is the PoC?


   
ReplyQuote