Deploy OpenBao on AWS: Healthcare Secrets
Deploy OpenBao on AWS with KMS auto-unseal for healthcare secrets management.
Introduction
30 Second Summary
Healthcare organizations handle some of the most sensitive data imaginable. Patient records, prescription details, and clinical system credentials all need to be locked down with strict access controls.
In this project, you will deploy OpenBao (an open-source secrets management server) on AWS. You'll configure automatic unsealing, store healthcare application secrets in a structured hierarchy, and enforce role-based access control.
What You'll Build
A secrets management server for a fictional healthcare organization that securely stores and controls access to credentials for two clinical applications.
By the end of this project, you'll have:
- An OpenBao server running on an EC2 instance with AWS KMS auto-unseal so it recovers automatically from restarts.
- A structured secrets hierarchy for two healthcare applications: a Patient Records System and a Pharmacy Management System.
- Role-based ACL policies enforcing separation of duties between clinical staff, pharmacy staff, and DevOps engineers.
- Secret Mission: Enable audit logging for healthcare compliance and create a break-glass emergency access procedure.
Are there any prerequisites?
You'll need an AWS account with permissions to create EC2 instances, KMS keys, and IAM roles. This is Part 1 of a 3-part series that builds a complete multi-application, multi-user secrets architecture.
Before We Start
Before We Start...
Create AWS Resources
MedVault Health's secrets server needs three AWS building blocks before you can install anything.
A KMS key handles encryption, an IAM role grants permissions, and an EC2 instance provides the server itself. In this step, you will create all three resources and connect to your instance via SSH.
In this step, get ready to:
- Create a KMS symmetric encryption key for auto-unseal.
- Create an IAM role granting OpenBao KMS permissions.
- Launch an EC2 instance and connect via SSH.
Create an AWS KMS symmetric encryption key
OpenBao needs a KMS key to automatically unseal itself after every restart. Without this, you would have to manually provide unseal keys each time the server reboots.
- Sign in to the AWS Management Console at https://console.aws.amazon.com.
- Use the Region selector in the upper-right corner of the page to set your region to US East (N. Virginia) us-east-1.
- In the search bar at the top, type KMS and select Key Management Service.
- In the navigation pane, choose Customer managed keys.
- Choose Create key.
What is a KMS key?
A KMS key is a cryptographic key managed by AWS. It never leaves the KMS service in plaintext. OpenBao uses it to encrypt and decrypt its master key, which means the server can unseal itself automatically whenever it starts up.
- For Key type, select Symmetric.
- Under Key usage, confirm that Encrypt and decrypt is already selected.
- Click Next.
- In the Alias field, type openbao-unseal.
- Click Next through the remaining steps (Key administrators, Key usage permissions) accepting the defaults.
- On the review page, click Finish to create the key.
You should see openbao-unseal appear in your Customer managed keys list.
- Click on openbao-unseal to open its details page.
- Copy the Key ID value (a UUID like 1234abcd-12ab-34cd-56ef-1234567890ab) and save it somewhere. You will need it in the next step.
- Also copy the full ARN (it starts with arn:aws:kms:us-east-1:). You will need this for the IAM policy.
Don't see Customer managed keys?
Make sure your region is set to us-east-1 in the upper-right corner. KMS keys are region-specific, so if you create it in the wrong region, OpenBao will not be able to find it.
help me troubleshoot my KMS key creation in AWS
Create an IAM role for EC2
OpenBao runs on an EC2 instance. For it to use the KMS key you just created, the instance needs an IAM role that grants the correct KMS permissions.
- In the AWS Console search bar, type IAM and select IAM.
- In the navigation pane, choose Roles.
- Click Create role.
- Under Trusted entity type, select AWS service.
- Under Use case, select EC2.
- Click Next.
- On the Add permissions page, skip adding any managed policies for now. Click Next.
- For Role name, enter openbao-kms-unseal.
- Click Create role.
Now you need to add an inline policy that grants the three KMS permissions OpenBao needs.
- In the Roles list, click on openbao-kms-unseal to open it.
- Select the Permissions tab.
- Click Add permissions, then Create inline policy.
- Switch to the JSON tab.
- Delete the existing content and paste the following policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"kms:Encrypt",
"kms:Decrypt",
"kms:DescribeKey"
],
"Resource": "[[KMS_KEY_ARN=\"arn:aws:kms:us-east-1:YOUR-ACCOUNT-ID:key/YOUR-KEY-ID\"]]"
}
]
}
What does this policy do?
This policy grants exactly three permissions on your KMS key. kms:Encrypt lets OpenBao encrypt its master key. kms:Decrypt lets it decrypt the master key during auto-unseal. kms:DescribeKey lets it verify the key exists and is enabled.
The Resource field restricts these permissions to only your specific KMS key ARN. This follows the principle of least privilege.
- Replace the Resource value with the full KMS key ARN you copied earlier.
- Click Next.
- For Policy name, enter openbao-kms-access.
- Click Create policy.
You should see the openbao-kms-access policy listed under your role's permissions.
Seeing a policy validation error?
Double-check that your ARN follows the format arn:aws:kms:us-east-1:123456789012:key/abcdef12-3456-7890-abcd-ef1234567890. The most common mistake is leaving the placeholder text in the Resource field instead of pasting your actual ARN.
help me fix my IAM inline policy for OpenBao KMS access
Launch an EC2 instance and connect via SSH
With the encryption key and permissions in place, you can now launch the server that will run OpenBao.
- In the AWS Console search bar, type EC2 and select EC2.
- Click Launch instance.
- Under Name and tags, enter openbao-server.
- Under Application and OS Images, search for Ubuntu and select Ubuntu Server 24.04 LTS (free tier eligible).
- Under Instance type, select t3.micro (free tier eligible).
- Under Key pair (login), click Create new key pair.
- Name it openbao-key, keep the defaults (RSA, .pem format), and click Create key pair.
- A .pem file will download automatically. Save it somewhere you can find it.
What is a key pair?
A key pair is a set of cryptographic keys (public and private) used to authenticate your SSH connection. AWS stores the public key on the instance. You hold the private key (the .pem file) on your computer. Without it, you cannot connect to your server.
- Under Network settings, click Edit.
- Confirm that Auto-assign public IP is set to Enable.
- Under the security group rule for SSH (port 22), change the Source type to My IP. This restricts SSH access to only your current IP address.
- Expand Advanced details at the bottom.
- For IAM instance profile, select openbao-kms-unseal from the dropdown.
- Click Launch instance.
Wait about 30 seconds for the instance to reach the Running state.
- Click on your instance ID to open its details.
- Copy the Public IPv4 address.
Now connect to your instance via SSH. Open a terminal on your computer.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Terminal (macOS) or Windows Terminal (Windows) and press Enter to open it.
- Connect to your instance by running this command:
ssh -i [[PEMPATH="path/to/openbao-key.pem"]] ubuntu@[[EC2_PUBLIC_IP="your EC2 public IPv4 address"]]
What does this command do?
The -i flag specifies the private key file you downloaded. The ubuntu username is the default user for Ubuntu AMIs on AWS. The IP address is your instance's public address.
💡 Fix the key permissions error
If you see a "Permissions are too open" error, your key file needs stricter permissions. Run this before retrying the SSH command:
chmod 400 [[PEMPATH="path/to/openbao-key.pem"]]
On Windows, right-click the .pem file, go to Properties > Security > Advanced, and remove all users except your own account.
- When prompted "Are you sure you want to continue connecting?", type yes and press Enter.
You should see a welcome message with ubuntu@ip- followed by your instance's private IP. This confirms you are connected to your EC2 instance.
Connection timed out or refused?
Verify that your security group allows SSH (port 22) from your IP. If your IP changed since you launched the instance, go back to EC2 > Security Groups, find the group attached to your instance, and update the inbound rule to your current IP.
Also confirm that the instance state is Running (not Pending or Stopped) before attempting SSH.
help me connect to my EC2 instance via SSH
Your AWS infrastructure is ready. Next up, you will install OpenBao on this server and configure it to use your KMS key for auto-unseal.
Install OpenBao on EC2
With your EC2 instance running and connected to AWS KMS via its IAM role, you now have a secure foundation.
In this step, you will install the OpenBao binary on your instance, configure it as a system service, and point it at the KMS key for auto-unseal.
In this step, get ready to:
- Download and install the OpenBao v2.5.4 binary.
- Create the system user, directories, and systemd service.
- Write the OpenBao configuration file with KMS auto-unseal.
Download and install the OpenBao binary
OpenBao is distributed as a precompiled binary that you download from GitHub, extract, and place on your system path.
- Download the OpenBao v2.5.4 Linux binary by running this command in your SSH session:
wget https://github.com/openbao/openbao/releases/download/v2.5.4/bao_2.5.4_Linux_x86_64.tar.gz
- Extract the binary from the archive by running:
tar -xzf bao_2.5.4_Linux_x86_64.tar.gz
- Move the bao binary to /usr/local/bin/ so it is available system-wide:
sudo mv bao /usr/local/bin/
- Verify the installation by running:
bao -h
What does this command do?
Running bao -h prints the help output for the OpenBao CLI. If you see a list of available commands, the binary is installed correctly on your PATH.
- You should see a help menu listing commands like server, operator, secrets, and others.
Command not found?
Make sure you ran the sudo mv command to move the binary to /usr/local/bin/. Check the file exists by running ls /usr/local/bin/bao.
If the file is there but the command still fails, confirm the file has execute permissions by running sudo chmod +x /usr/local/bin/bao.
Help me troubleshoot why bao is not found on my EC2 instance
Create the system user, directories, and service file
OpenBao should run as its own dedicated system user. This limits the blast radius if the process is ever compromised.
- Create the openbao system user and group by running:
sudo useradd --system --home /etc/openbao --shell /bin/false openbao
- Create the data directory and configuration directory by running:
sudo mkdir -p /opt/openbao/data
sudo mkdir -p /etc/openbao
- Set ownership so the openbao user can read and write to both directories:
sudo chown -R openbao:openbao /opt/openbao
sudo chown -R openbao:openbao /etc/openbao
Why a dedicated user?
Running OpenBao as its own system user means the process has no access to other system files by default. If an attacker ever exploits a vulnerability in OpenBao, they cannot escalate to root privileges or read other users' data.
- Create the systemd service file by running:
sudo tee /etc/systemd/system/openbao.service > /dev/null <<'EOF'
[Unit]
Description=OpenBao Secret Management Server
Documentation=https://openbao.org/docs/
Requires=network-online.target
After=network-online.target
ConditionFileNotEmpty=/etc/openbao/config.hcl
[Service]
User=openbao
Group=openbao
ExecStart=/usr/local/bin/bao server -config=/etc/openbao/config.hcl
ExecReload=/bin/kill --signal HUP $MAINPID
KillMode=process
KillSignal=SIGINT
Restart=on-failure
RestartSec=5
TimeoutStopSec=30
LimitNOFILE=65536
LimitMEMLOCK=infinity
MemorySwapMax=0
[Install]
WantedBy=multi-user.target
EOF
What does MemorySwapMax=0 do?
Setting MemorySwapMax=0 prevents the operating system from swapping OpenBao's memory to disk. Secrets live in memory while OpenBao is running. If that memory were swapped to disk, a malicious user with disk access could read those secrets from the swap file.
- Confirm the service file was written by running:
sudo systemctl daemon-reload
systemctl cat openbao.service
- You should see the full service file content printed back to you, starting with [Unit].
Service file not showing?
Make sure you copied the entire sudo tee block including the closing EOF line. If you see an error about the unit not found, re-run the sudo tee command and then sudo systemctl daemon-reload again.
Help me troubleshoot the OpenBao systemd service file on my EC2 instance
Create the OpenBao configuration file
The configuration file tells OpenBao where to listen, where to store data, and how to unseal itself using your KMS key.
- Create the configuration file at /etc/openbao/config.hcl by running the command below. Replace your-kms-key-id-here with the Key ID you recorded when creating the KMS key in Step 1:
sudo tee /etc/openbao/config.hcl > /dev/null <<'EOF'
ui = true
listener "tcp" {
address = "0.0.0.0:8200"
tls_disable = 1
}
storage "file" {
path = "/opt/openbao/data"
}
seal "awskms" {
region = "us-east-1"
kms_key_id = "[[KMS_KEY_ID="your-kms-key-id-here"]]"
}
api_addr = "http://127.0.0.1:8200"
EOF
What does this configuration do?
- ui = true enables the web UI at port 8200 (optional, but useful for learning).
- listener "tcp" tells OpenBao to listen on all network interfaces at port 8200. TLS is disabled for this learning environment.
- storage "file" uses the local filesystem to store encrypted secrets.
- seal "awskms" configures auto-unseal using your AWS KMS key. OpenBao will call KMS to decrypt its master key on startup.
- api_addr is the address OpenBao advertises to clients.
💡 Why is TLS disabled?
In production, you would always enable TLS to encrypt traffic between clients and OpenBao. For this learning project, disabling TLS keeps the setup simpler so you can focus on secrets management concepts. Since you are connecting over SSH to localhost, the traffic never leaves the instance unencrypted.
- Set the correct ownership on the config file:
sudo chown openbao:openbao /etc/openbao/config.hcl
- Verify the config file was written correctly by running:
cat /etc/openbao/config.hcl
- You should see the full HCL configuration with your KMS Key ID in the seal "awskms" block.
Seeing an empty file or permission denied?
Make sure you used sudo tee when creating the file. Without sudo, your SSH user does not have write permissions to /etc/openbao/.
Help me fix my OpenBao config.hcl file on EC2
✔️ Awesome, I've got everything!
Great. Double check you saved both files and that your KMS Key ID is in the config. You are ready for the next step.
ⓧ I'd like to double check the full code
Here is the full /etc/openbao/config.hcl file:
ui = true
listener "tcp" {
address = "0.0.0.0:8200"
tls_disable = 1
}
storage "file" {
path = "/opt/openbao/data"
}
seal "awskms" {
region = "us-east-1"
kms_key_id = "your-kms-key-id-here"
}
api_addr = "http://127.0.0.1:8200"
Here is the full /etc/systemd/system/openbao.service file:
[Unit]
Description=OpenBao Secret Management Server
Documentation=https://openbao.org/docs/
Requires=network-online.target
After=network-online.target
ConditionFileNotEmpty=/etc/openbao/config.hcl
[Service]
User=openbao
Group=openbao
ExecStart=/usr/local/bin/bao server -config=/etc/openbao/config.hcl
ExecReload=/bin/kill --signal HUP $MAINPID
KillMode=process
KillSignal=SIGINT
Restart=on-failure
RestartSec=5
TimeoutStopSec=30
LimitNOFILE=65536
LimitMEMLOCK=infinity
MemorySwapMax=0
[Install]
WantedBy=multi-user.target
Your OpenBao installation is fully configured and ready to run. Next up, you will start the server, initialize it, and watch the auto-unseal magic happen.
Initialize OpenBao and Verify Auto-Unseal
Your OpenBao server is installed and configured, but it cannot do anything yet. By default, OpenBao starts in a sealed state. Sealed means the server is running but cannot access any stored data.
In this step, you will bring OpenBao to life by starting the service, initializing the encryption keys, and proving that AWS KMS auto-unseal works. After a restart, the server will unseal itself automatically without any manual intervention.
In this step, get ready to:
- Start the OpenBao service and confirm its initial sealed state.
- Initialize OpenBao and securely save your root token and recovery keys.
- Verify that AWS KMS auto-unseal works after a service restart.
Start the OpenBao service
Before you can initialize OpenBao, the server process needs to be running. You will start it with systemd and tell the bao CLI where to find it.
- Start and enable the OpenBao service by running these commands:
sudo systemctl start openbao
sudo systemctl enable openbao
Why both start and enable?
The start command launches the service right now. The enable command tells the system to start it automatically on future reboots. You need both for a production-style setup.
- Set the BAO_ADDR environment variable so the CLI knows where to connect:
export BAO_ADDR='http://127.0.0.1:8200'
What does BAO_ADDR do?
The bao CLI needs to know the address of the OpenBao server it should talk to. Setting BAO_ADDR tells every subsequent bao command to use http://127.0.0.1:8200 as the target.
- Check the server status by running:
bao status
You should see output showing Sealed: true in the status table. This confirms the server is running but sealed and not yet initialized.
Seeing "connection refused" or the service failed to start?
- Check the service logs with sudo journalctl -u openbao --no-pager -n 50 to see what went wrong.
- If you see a permissions error on the data directory, make sure /opt/openbao/data is owned by the openbao user.
- If the KMS key ID in your config is incorrect, the service may start but fail on unseal later. Double-check /etc/openbao/config.hcl to verify the kms_key_id value matches what you recorded.
Help me troubleshoot my OpenBao service. I ran sudo systemctl start openbao and it's not connecting.
Initialize OpenBao with recovery keys
Initialization is a one-time operation that generates the encryption keys OpenBao uses to protect your secrets. Because you configured AWS KMS auto-unseal, this process produces recovery keys instead of traditional unseal keys.
What is Shamir's Secret Sharing?
OpenBao splits its master encryption key into multiple pieces called key shares using Shamir's Secret Sharing algorithm. A threshold number of shares must be combined to reconstruct the key.
With auto-unseal configured, AWS KMS handles the unsealing automatically. The shares you receive are called recovery keys. They are used for administrative operations like key rotation, not for day-to-day unsealing.
- Initialize OpenBao by running:
bao operator init
This command outputs several recovery key shares and an initial root token. You should see lines labeled Recovery Key followed by long strings, and a line labeled Initial Root Token.
- Copy and save ALL the recovery keys and the initial root token to a secure location (a password manager or a local text file for this learning project).
Save your root token now
The initial root token is only displayed once during initialization. If you lose it, you will need to go through a recovery process. Copy it somewhere safe before continuing.
💡 Why recovery keys instead of unseal keys?
In a traditional setup without auto-unseal, operators must manually provide unseal keys after every server restart. With KMS auto-unseal, AWS handles this automatically. Recovery keys serve a different purpose. They are used for operations like rotating the encryption key or generating a new root token.
🙋♀️ Seeing an error about KMS permissions?
- Verify that your EC2 instance has the openbao-kms-unseal IAM role attached. Check in the EC2 console under your instance's details.
- Confirm your IAM policy grants kms:Encrypt, kms:Decrypt, and kms:DescribeKey on the correct KMS key ARN.
- Make sure the region in your /etc/openbao/config.hcl seal block matches the region where you created the KMS key.
Help me fix my KMS permissions for OpenBao initialization.
Verify auto-unseal after a restart
The real test of auto-unseal is what happens after a reboot. In production, servers restart for maintenance, patching, and scaling events. If OpenBao stayed sealed after each restart, someone would need to manually intervene every time.
- Restart the OpenBao service to simulate a server reboot:
sudo systemctl restart openbao
- Check the status again to verify auto-unseal worked:
bao status
You should now see Sealed: false in the output. This confirms that AWS KMS automatically unsealed OpenBao without any manual key entry.
What just happened?
When OpenBao restarted, it reached out to your AWS KMS key to decrypt its master encryption key. Because the EC2 instance has an IAM role with the required KMS permissions, this happened automatically.
Without auto-unseal, an operator would need to provide multiple unseal key shares after every restart. For healthcare systems that require high availability, this manual step is unacceptable.
🙋♀️ Still seeing Sealed: true after restart?
- Wait a few seconds and run bao status again. The unseal process requires a network call to KMS and may take a moment.
- Check sudo journalctl -u openbao --no-pager -n 20 for errors mentioning KMS connectivity or authentication failures.
- Verify your instance can reach the KMS endpoint. Run curl -s https://kms.us-east-1.amazonaws.com to confirm network connectivity.
Help me troubleshoot why OpenBao is still sealed after restart.
Your OpenBao server is fully operational. It starts automatically, unseals itself via AWS KMS, and is ready to store secrets. Next up, you will build the secrets hierarchy for MedVault Health's two applications.
Build the Healthcare Secrets Hierarchy
Your OpenBao server is running and auto-unsealing itself after restarts. That solves the availability problem. Now you need to solve the organization problem.
MedVault Health runs two applications that each need their own credentials: a Patient Records System (PRS) and a Pharmacy Management System (PMS). In this step, you will create a structured secrets hierarchy that keeps each application's credentials isolated and easy to manage.
In this step, get ready to:
- Authenticate to OpenBao with the root token.
- Enable the KV v2 secrets engine.
- Store secrets for the Patient Records System and Pharmacy Management System.
Authenticate and enable the KV v2 secrets engine
Before you can store any secrets, you need to authenticate to OpenBao and enable the storage engine that will hold them.
- Log in to OpenBao with your root token by running this command:
bao login
OpenBao prompts you with Token (will be hidden): and waits for input. Paste your root token (the one you saved during initialization) and press Enter. The characters will not appear on screen.
What does this command do?
The bao login command authenticates your terminal session against the OpenBao server. Once authenticated, all future bao commands in this session will use the token you provided.
You should see output confirming your authentication, including token_policies ["root"] which means you have full admin access.
Seeing permission denied?
Make sure you pasted the root token (not a recovery key) from when you ran bao operator init. The root token starts with a different prefix than recovery keys.
Also confirm your BAO_ADDR environment variable is still set correctly.
Help me troubleshoot this
- Enable the KV v2 secrets engine at the secret path by running:
bao secrets enable -version=2 -path=secret kv
What is a KV v2 secrets engine?
A secrets engine is a component within OpenBao that stores, generates, or encrypts data. The KV (key-value) engine is the simplest type. It stores arbitrary key-value pairs.
Version 2 adds secret versioning, soft-delete, and metadata tracking. These features let you roll back to a previous secret value or see when a secret was last updated.
You should see Success! Enabled the kv secrets engine at: secret/ in your terminal.
Seeing 'path is already in use'?
This means the secret path already has a secrets engine mounted. If you previously ran this command, it's safe to continue. Your secrets engine is already enabled.
Help me fix this
Store Patient Records System secrets
The Patient Records System (PRS) needs database credentials and API keys to operate. You will store these as two separate secret entries, organized under the prs/ prefix.
- Store the PRS database credentials by running:
bao kv put -mount=secret prs/database host=prs-db.medvault.internal port=5432 username=prs_app password=PrsSecure2026! dbname=patient_records
What does this command do?
The bao kv put command writes key-value pairs to the specified path. The -mount=secret flag tells OpenBao which secrets engine to use.
The path prs/database creates a logical folder structure. The PRS application will eventually read from this exact path to get its database connection details.
You should see output showing the secret was created at secret/data/prs/database with version 1.
- Store the PRS API keys by running:
bao kv put -mount=secret prs/api-keys hl7_fhir_key=fhir-ak-29x7mQ encryption_key=aes256-pk-healthcare-2026
What are these API keys for?
The hl7_fhir_key is a credential for connecting to a health information exchange using the HL7 FHIR standard. The encryption_key is used to encrypt patient data at the application level before storing it in the database.
In a real deployment, these would be generated by the respective services. For this project, you are using representative placeholder values.
You should see output showing the secret was created at secret/data/prs/api-keys with version 1.
Seeing a 'missing data' error?
Double-check that each key-value pair uses = with no spaces around it. For example, host=prs-db.medvault.internal is correct. host = prs-db.medvault.internal with spaces will fail.
Help me debug this error
Store Pharmacy Management System secrets and verify
The Pharmacy Management System (PMS) needs its own database credentials and integration keys. These go under the pms/ prefix, completely separate from the PRS secrets.
- Store the PMS database credentials by running:
bao kv put -mount=secret pms/database host=pms-db.medvault.internal port=5432 username=pms_app password=PmsSecure2026! dbname=pharmacy
You should see output showing the secret was created at secret/data/pms/database with version 1.
- Store the PMS integration keys by running:
bao kv put -mount=secret pms/integrations drug_interaction_api_key=ddi-key-8hN3xQ insurance_verify_key=ins-vfy-mK92pL
What are these integration keys?
The drug_interaction_api_key connects the pharmacy system to a drug interaction checking service. The insurance_verify_key connects to an insurance verification provider.
Keeping these separate from the PRS secrets is a healthcare security principle called separation of duties. Clinical staff should never need pharmacy integration credentials, and vice versa.
You should see output showing the secret was created at secret/data/pms/integrations with version 1.
- Verify the PRS database secrets are stored correctly by running:
bao kv get -mount=secret prs/database
You should see a table showing all five key-value pairs: host, port, username, password, and dbname with their corresponding values.
- Verify the PMS database secrets are stored correctly by running:
bao kv get -mount=secret pms/database
You should see the same table format with the PMS database credentials. Both applications now have their secrets stored in isolated paths within the same secrets engine.
Seeing 'No value found'?
Make sure you include the -mount=secret flag. Without it, OpenBao looks for the secret at the wrong API path.
Also verify the path matches exactly what you used in the bao kv put command. Paths are case-sensitive.
Help me troubleshoot this
💡 Why organize secrets by application?
The prs/ and pms/ prefixes create a logical boundary between the two applications. In the next step, you will write access control policies that grant each team access only to their own application's prefix.
This hierarchy-based approach means adding a third application later is as simple as creating a new prefix and a matching policy.
Your secrets hierarchy is in place. Both healthcare applications have their credentials stored in isolated, well-organized paths. Next up, you will create the access control policies that enforce who can read which secrets.
Create Role-Based Policies and Users
Your secrets are stored, but right now only the root token can access them. In a real healthcare organization, a clinical nurse should never see pharmacy credentials, and a pharmacist has no business reading patient record database passwords.
In this step, you will create ACL policies that map each role to specific secret paths, then attach those policies to individual users. This enforces separation of duties at the secrets management layer.
In this step, get ready to:
- Write and apply three role-based ACL policy files.
- Enable the userpass auth method and create three users.
- Test separation of duties by logging in as each user and verifying access boundaries.
Create and apply ACL policy files
OpenBao policies are deny-by-default. Each policy file explicitly grants access to specific paths and capabilities. You will create three policies matching the three roles at MedVault Health.
- Create the clinical-read.hcl policy file by running this command:
cat << 'EOF' > clinical-read.hcl
# Clinical staff: read-only access to Patient Records System secrets
path "secret/data/prs/*" {
capabilities = ["read", "list"]
}
path "secret/metadata/prs/*" {
capabilities = ["read", "list"]
}
EOF
What does this policy do?
The secret/data/prs/* path grants read access to the actual secret values stored under the PRS hierarchy.
The secret/metadata/prs/* path grants access to secret metadata like version history and creation time. KV v2 stores data and metadata at separate API paths, so both must be granted explicitly.
- Create the pharmacy-read.hcl policy file by running this command:
cat << 'EOF' > pharmacy-read.hcl
# Pharmacy staff: read-only access to Pharmacy Management System secrets
path "secret/data/pms/*" {
capabilities = ["read", "list"]
}
path "secret/metadata/pms/*" {
capabilities = ["read", "list"]
}
EOF
- Create the devops-admin.hcl policy file by running this command:
cat << 'EOF' > devops-admin.hcl
# DevOps administrators: full access to all application secrets
path "secret/data/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
path "secret/metadata/*" {
capabilities = ["read", "update", "delete", "list"]
}
path "sys/policies/acl/*" {
capabilities = ["read", "list"]
}
path "auth/userpass/users/*" {
capabilities = ["read", "list"]
}
EOF
Why does DevOps get broader access?
The DevOps role needs to manage secrets across both applications. It also gets read-only access to sys/policies/acl/* and auth/userpass/users/* so operators can audit policies and user configurations without needing the root token.
- Apply all three policies to OpenBao by running these commands:
bao policy write clinical-read clinical-read.hcl
bao policy write pharmacy-read pharmacy-read.hcl
bao policy write devops-admin devops-admin.hcl
- Verify the policies were created by running:
bao policy list
You should see clinical-read, pharmacy-read, and devops-admin listed alongside the built-in default and root policies.
Not seeing your policies in the list?
- Make sure you are still authenticated as root. Run bao token lookup to check your current token.
- Verify the .hcl files exist in your current directory by running ls *.hcl.
Help me troubleshoot why my OpenBao policies are not showing up after running bao policy write.
✔️ Awesome, I've got everything!
Great. Make sure you have all three .hcl files created and confirmed the policies appear in the list.
ⓧ I'd like to double check the full code
Here is each policy file for comparison:
clinical-read.hcl
# Clinical staff: read-only access to Patient Records System secrets
path "secret/data/prs/*" {
capabilities = ["read", "list"]
}
path "secret/metadata/prs/*" {
capabilities = ["read", "list"]
}
pharmacy-read.hcl
# Pharmacy staff: read-only access to Pharmacy Management System secrets
path "secret/data/pms/*" {
capabilities = ["read", "list"]
}
path "secret/metadata/pms/*" {
capabilities = ["read", "list"]
}
devops-admin.hcl
# DevOps administrators: full access to all application secrets
path "secret/data/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
path "secret/metadata/*" {
capabilities = ["read", "update", "delete", "list"]
}
path "sys/policies/acl/*" {
capabilities = ["read", "list"]
}
path "auth/userpass/users/*" {
capabilities = ["read", "list"]
}
Enable userpass authentication and create users
Policies define what each role can do, but you still need a way for people to authenticate and receive those policies. The userpass auth method provides simple username and password authentication for this learning environment.
- Enable the userpass auth method by running:
bao auth enable userpass
You should see a success message confirming the method is enabled at userpass/.
- Create the clinical staff user dr-chen with the clinical-read policy by running:
bao write auth/userpass/users/dr-chen password=ClinicalPass2026! policies=clinical-read
- Create the pharmacy staff user pharm-jones with the pharmacy-read policy by running:
bao write auth/userpass/users/pharm-jones password=PharmacyPass2026! policies=pharmacy-read
- Create the DevOps user devops-taylor with the devops-admin policy by running:
bao write auth/userpass/users/devops-taylor password=DevOpsPass2026! policies=devops-admin
What does this command do?
Each bao write auth/userpass/users/<name> command creates a user in OpenBao's internal user database. The policies parameter attaches the named ACL policy to that user's token whenever they log in.
🙋♀️ Seeing a permission denied error?
- You must be authenticated with the root token to create users. Run bao login and paste your root token to re-authenticate.
- Confirm that BAO_ADDR is still set by running echo $BAO_ADDR. It should output http://127.0.0.1:8200.
Help me troubleshoot a permission denied error when creating userpass users in OpenBao.
Test separation of duties
The real proof that your policies work is testing them from each user's perspective. You will log in as each user and confirm they can only access the secrets their role permits.
- Log in as dr-chen by running:
bao login -method=userpass username=dr-chen password=ClinicalPass2026!
- Verify dr-chen can read Patient Records System secrets by running:
bao kv get -mount=secret prs/database
You should see the PRS database credentials (host, port, username, password, dbname) displayed successfully.
- Now verify dr-chen cannot access Pharmacy Management System secrets by running:
bao kv get -mount=secret pms/database
You should see a permission denied error. This confirms clinical staff cannot access pharmacy secrets.
- Log in as pharm-jones by running:
bao login -method=userpass username=pharm-jones password=PharmacyPass2026!
- Verify pharm-jones can read Pharmacy secrets by running:
bao kv get -mount=secret pms/database
You should see the PMS database credentials displayed successfully.
- Verify pharm-jones cannot access Patient Records System secrets by running:
bao kv get -mount=secret prs/database
You should see a permission denied error. Pharmacy staff cannot access clinical secrets.
- Log in as devops-taylor by running:
bao login -method=userpass username=devops-taylor password=DevOpsPass2026!
- Verify devops-taylor can access both PRS and PMS secrets by running:
bao kv get -mount=secret prs/database
bao kv get -mount=secret pms/database
Both commands should succeed, displaying the credentials for each system. DevOps administrators have full visibility across all application secrets.
Why does this matter for healthcare?
Healthcare regulations require that access to patient data is limited to those with a clinical need to know. Pharmacy systems handle controlled substance data that clinical staff should not access.
By enforcing this separation at the secrets layer, even a compromised application credential cannot cross organizational boundaries. The policy engine acts as a guardrail that exists independently of the applications themselves.
- Log back in with your root token to restore full access for future operations:
bao login
When prompted, paste your initial root token (the one you saved during initialization).
Getting permission denied when you expect access?
- Verify which user you are currently authenticated as by running bao token lookup and checking the policies field.
- Double check the policy file paths match the KV v2 path structure. Remember that KV v2 uses secret/data/ for reading values, not just secret/.
- Confirm the user was created with the correct policy name by running bao read auth/userpass/users/dr-chen (while authenticated as root).
Help me debug why my OpenBao user is getting permission denied when I expect them to have access based on their policy.
Secret mission
Enable Audit Logging and Break-Glass Access
Healthcare compliance regulations require a complete audit trail of every secrets access event. Can you enable audit logging and create a break-glass emergency access procedure that automatically expires after 15 minutes?
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project uses an EC2 instance and a KMS key that can incur ongoing charges if left running.
Cost warning
Your KMS key costs $1/month (prorated). Your EC2 t3.micro instance is free-tier eligible for the first 12 months of your AWS account. Outside of free tier, it costs approximately $0.0104/hour.
If you plan to continue with Parts 2 and 3 of this series, keep your resources running. Otherwise, pause or delete them to avoid charges.
Resources you used:
- EC2 instance (t3.micro, Ubuntu 24.04 LTS) running OpenBao.
- AWS KMS key (openbao-unseal) for auto-unseal.
- IAM role (openbao-kms-unseal) attached to the EC2 instance.
Keep everything running
No action needed. Choose this if you plan to continue with Parts 2 and 3 of this series, where you will configure AppRole authentication and connect multiple applications to your OpenBao server.
- Your EC2 instance stays running with OpenBao auto-unsealed and ready.
- Your KMS key remains active at $1/month.
- Your IAM role remains attached at no cost.
- If your EC2 instance is within the free-tier window, compute charges are covered.
Pause - I'll come back to this later
Stop your EC2 instance to pause compute charges. Your data on disk (EBS volume) is preserved. The KMS key remains active at $1/month.
- In the AWS Console, navigate to EC2 > Instances.
- Select your OpenBao instance.
- Click Instance state > Stop instance.
- Confirm by clicking Stop.
- When you are ready to resume, select the instance and click Instance state > Start instance. OpenBao will auto-unseal via KMS on restart.
Delete - I don't want to use this again
Remove all project resources permanently. You will need to redo Part 1 setup before starting Parts 2 or 3.
Terminate the EC2 instance:
- In the AWS Console, navigate to EC2 > Instances.
- Select your OpenBao instance.
- Click Instance state > Terminate instance.
- Confirm by clicking Terminate. The instance status will change to Terminated.
Schedule the KMS key for deletion:
- In the AWS Console, navigate to KMS > Customer managed keys.
- Select the openbao-unseal key.
- Click Key actions > Schedule key deletion.
- Enter 7 for the waiting period (the minimum). Confirm the checkbox and click Schedule deletion.
- The key cannot be used during the waiting period. It will be permanently deleted after 7 days.
Delete the IAM role:
- In the AWS Console, navigate to IAM > Roles.
- Search for openbao-kms-unseal and select it.
- Click Delete.
- Type the role name to confirm, then click Delete.
Delete the security group:
- In the AWS Console, navigate to EC2 > Security Groups.
- Select the security group you created for SSH access to your OpenBao instance.
- Click Actions > Delete security groups. Confirm the deletion.
That's a Wrap!
That's a Wrap!
Nice work! You just deployed the secrets management backbone for a healthcare organization on AWS.
MedVault Health now has a secure, centrally managed system for storing and controlling access to sensitive application credentials.
You've learned how to:
- Deploy OpenBao v2.5.4 on an EC2 instance with AWS KMS auto-unseal, so the server recovers from restarts without manual intervention.
- Build a structured KV v2 secrets hierarchy for two healthcare applications (Patient Records System and Pharmacy Management System) and store application credentials securely.
- Create role-based ACL policies and userpass authentication that enforce separation of duties between clinical staff, pharmacy staff, and DevOps administrators.
- Enable audit logging for healthcare compliance and create a time-limited break-glass emergency access procedure for incident response.
Ready to quiz yourself?