An application that reads passwords from a configuration file and cannot call the Stronghold API can be connected without code changes. Stronghold Agent authenticates to Stronghold, renders the configuration file from a template, and reloads the application when secrets change.
Goal
Set up Stronghold Agent under systemd on a virtual machine. Agent logs in with AppRole using a one-time wrapped secret_id, builds the application.properties file, and runs systemctl reload myapp when secrets change.
Prerequisites
Before you start, prepare the following:
- A Linux virtual machine with systemd and the
myappapplication running as themyapp.serviceservice. - The Stronghold binary on the VM (
/usr/local/bin/strongholdin the examples) and network access to Stronghold. - A Stronghold token with permissions to configure AppRole, policies, and KV.
- KV version 2 enabled at the
secretpath.
Step 1. Store the secret and create a policy
Write the database credentials to KV and create a policy that allows reading them:
d8 stronghold kv put -mount=secret myapp/config db_user=app_user db_password='S3cure-P@ss'
d8 stronghold policy write myapp-vm - <<'POLICY'
path "secret/data/myapp/config" {
capabilities = ["read"]
}
POLICYStep 2. Create an AppRole role
Enable the AppRole auth method and create a role for the VM:
d8 stronghold auth enable approle
d8 stronghold write auth/approle/role/myapp-vm token_policies=myapp-vm token_ttl=1h token_max_ttl=24h secret_id_ttl=720h secret_id_bound_cidrs="10.0.10.15/32"The secret_id_bound_cidrs parameter allows login only from the VM address. secret_id_ttl limits how long Agent can log in again with one secret_id.
Step 3. Deliver role_id and a wrapped secret_id to the VM
Agent logs in with role_id and a one-time wrapped secret_id. Prepare both files and copy them to the VM.
Get the
role_id. It is not a secret and can be distributed by a configuration management system:d8 stronghold read -field=role_id auth/approle/role/myapp-vm/role-id > role-idIssue a wrapped
secret_id. Instead of thesecret_iditself, a one-time token with a 10-minute TTL is returned:d8 stronghold write -wrap-ttl=10m -field=wrapping_token -f auth/approle/role/myapp-vm/secret-id > secret-idCopy both files to the VM into
/etc/stronghold-agent/and restrict access. The directory must belong to thestronghold-agentuser: Agent deletes thesecret_idfile after reading it.sudo install -d -o stronghold-agent -g stronghold-agent -m 0750 /etc/stronghold-agent sudo install -o stronghold-agent -g stronghold-agent -m 0640 role-id /etc/stronghold-agent/role-id sudo install -o stronghold-agent -g stronghold-agent -m 0600 secret-id /etc/stronghold-agent/secret-id
If the wrapping token is intercepted and unwrapped by someone else, Agent cannot unwrap it, which indicates an incident. The verification procedure is described in Response wrapping.
Step 4. Create a template
Create the /etc/myapp/templates/application.properties.ctmpl file:
{{ with secret "secret/data/myapp/config" }}
spring.datasource.username={{ .Data.data.db_user }}
spring.datasource.password={{ .Data.data.db_password }}
{{ end }}Step 5. Configure Agent
Create the /etc/stronghold-agent/agent.hcl file:
stronghold {
address = "https://stronghold.example.com"
}
auto_auth {
method "approle" {
mount_path = "auth/approle"
config = {
role_id_file_path = "/etc/stronghold-agent/role-id"
secret_id_file_path = "/etc/stronghold-agent/secret-id"
secret_id_response_wrapping_path = "auth/approle/role/myapp-vm/secret-id"
remove_secret_id_file_after_reading = true
}
}
sink "file" {
config = {
path = "/var/run/stronghold-agent/token"
mode = 0600
}
}
}
template_config {
static_secret_render_interval = "5m"
}
template {
source = "/etc/myapp/templates/application.properties.ctmpl"
destination = "/etc/myapp/application.properties"
perms = "0640"
group = "myapp"
command = "/usr/bin/systemctl reload myapp"
command_timeout = "30s"
error_on_missing_key = true
}secret_id_response_wrapping_path: Agent recognizes the wrappedsecret_id, checks its creation path, and unwraps it.remove_secret_id_file_after_reading = true: thesecret_idfile is deleted after reading.static_secret_render_interval: how often Agent re-reads static KV secrets.permsandgroup: the file belongs to thestronghold-agentuser and themyappgroup, so the application can read it. Agent can assign only a group that thestronghold-agentuser is a member of. Add the user to the group withsudo usermod -aG myapp stronghold-agent.command: runs after each write of the file. The command is run withoutsudo: the Agent unit runs withNoNewPrivileges=true, sosudodoes not work. The permission to reload the application is granted by a polkit rule in step 6.
The unwrapped secret_id is kept only in Agent memory. After Agent or the VM restarts, or when secret_id_ttl expires, Agent needs a new wrapped secret_id. Build issuing and delivering secret_id into your deployment process, for example in CI/CD.
Step 6. Run Agent under systemd
Configure the Agent unit, grant Agent the permission to reload the application, and start the service.
Create the
/etc/systemd/system/stronghold-agent.serviceunit file following the example in Running and managing Agent. InReadWritePaths, list the/var/run/stronghold-agentand/etc/myappdirectories.Allow Agent to delete the
secret_idfile. The unit usesProtectSystem=strict, so add the/etc/stronghold-agentdirectory toReadWritePathsin a drop-in file. Runsudo systemctl edit stronghold-agentand add the following lines:[Service] ReadWritePaths=/etc/stronghold-agentAllow the
stronghold-agentuser to reload onlymyapp.service. Create the/etc/polkit-1/rules.d/50-stronghold-agent-myapp.rulesfile:polkit.addRule(function(action, subject) { if (action.id == "org.freedesktop.systemd1.manage-units" && subject.user == "stronghold-agent" && action.lookup("unit") == "myapp.service" && action.lookup("verb") == "reload") { return polkit.Result.YES; } });polkit reads the rules files automatically. The
unitandverbdetails are passed by systemd 226 and later. If the application does not support reload, replace"reload"with"restart"in the rule andreloadwithrestartincommand.Start the service:
sudo systemctl daemon-reload sudo systemctl enable --now stronghold-agent
Verification
Check that Agent has logged in, rendered the file, and reacts to secret changes.
Check the Agent log: it must contain messages about successful authentication and rendering:
sudo journalctl -u stronghold-agent -n 50Make sure the secret file is deleted and the configuration is built:
ls -la /etc/stronghold-agent/secret-id /etc/myapp/application.propertiesChange the secret and wait for the file to be re-rendered and the application to reload (no longer than
static_secret_render_interval):d8 stronghold kv patch -mount=secret myapp/config db_password='N3w-P@ss' sudo journalctl -u myapp -f
Cleanup
To remove the example configuration, follow these steps:
Stop and disable Agent:
sudo systemctl disable --now stronghold-agent.Delete the
/etc/stronghold-agent/role-idand/etc/myapp/application.propertiesfiles and the token file in/var/run/stronghold-agent/.Delete the
/etc/polkit-1/rules.d/50-stronghold-agent-myapp.rulesfile and the Agent drop-in file:sudo systemctl revert stronghold-agent.Delete the role and the policy:
d8 stronghold delete auth/approle/role/myapp-vm d8 stronghold policy delete myapp-vm