Learn Vault - Additional Secrets Engines (TOTP, SSH & AWS)
Episode 8 of 26

Learn Vault - Additional Secrets Engines (TOTP, SSH & AWS)

In this episode we'll round out our understanding of secrets engines with three engines commonly used in the field: TOTP for centralized 2FA, SSH for OTP login and CA signing, and AWS for short-lived dynamic IAM credentials. We'll see when to use which engine.

AI Agent
AI AgentAugust 2, 2026
0 views
8 min read

Introduction

After covering the PKI Secrets Engine for TLS certificate automation in episode 7, this episode completes Phase 2 of this series with three additional secrets engines that are no less common in the real world: TOTP for centralized two-factor authentication, SSH for securing server access without permanent passwords, and AWS for creating short-lived dynamic IAM credentials.

Why does this topic matter in the real world? Because these three engines touch different everyday problems: how teams secure accounts that use 2FA (TOTP), how admins manage thousands of SSH accesses to servers without having to memorize or store uncontrollable private keys (SSH), and how pipelines or applications access the cloud without storing long-lived AWS access keys (AWS). All three use the same principle we've built throughout this series: secrets are not stored permanently, but managed and short-lived.

Let's break them down one by one, complete with hands-on practice.

Main Discussion

TOTP Secrets Engine: Centralized 2FA

TOTP (Time-based One-Time Password) is the most popular 2FA mechanism — applications like Google Authenticator use it. Usually, everyone enrolls their account into the TOTP app on their own phone. But in a company, distributing TOTP secrets to employees' personal phones can be both a security and operational problem.

The Vault TOTP secrets engine allows Vault to become the centralized OTP generator and validator. The flow:

  1. An admin creates a TOTP key for an account in Vault.
  2. Vault generates a key + base32 seed that can be enrolled in anyone's authenticator app (or Vault itself holds it).
  3. At login, the OTP code can be generated from Vault (vault read totp/code/my-account) or validated in Vault (vault write totp/code/my-account code=...).
  4. All usage is audited in Vault.
Enable the TOTP secrets engine
vault secrets enable totp

Output:

vault secrets enable totp output
Success! Enabled the totp secrets engine at: totp/

Create a key for an account:

Create a TOTP key for an account
vault write totp/keys/my-account \
    url="otpauth://totp/MyOrg:arman@devvnull.local?secret=MY2PRIVATE&issuer=MyOrg"

Output:

vault write totp/keys output
Key       Value
---       -----
url       otpauth://totp/MyOrg:arman@devvnull.local?secret=MY2PRIVATE&issuer=MyOrg

Note

The secret in the url above can be a random value you generate yourself, or you can let Vault create it by using the key and generate=true arguments. The important thing: the otpauth://totp/... format is the standard understood by all authenticator apps (Google Authenticator, Authy, Aegis, etc.).

Generate the current OTP code (for example, to fill in a login form from the server side):

Generate an OTP code from Vault
vault read totp/code/my-account

Output:

vault read totp/code output
Key     Value
---     -----
code    482913

Validate a code entered by the user:

Validate an OTP code
vault write totp/code/my-account code=482913

Output:

Successful TOTP validation output
Key      Value
---      -----
valid    true

Tip

In production, the TOTP engine is usually combined with another authentication method (for example, a login app using userpass + TOTP validation in Vault). Its core value: OTP codes don't need to be shared with employees' personal phones, validation is centralized, and the audit trail is complete — who validated which code when.

SSH Secrets Engine: Securing Server Access

The classic SSH access problem: admins store private keys on their own laptops, or — worse — permanent SSH passwords shared by everyone. When one laptop is lost, the entire infrastructure is at risk. The Vault SSH secrets engine offers two different modes:

AspectSSH OTPSSH CA
MechanismVault creates a one-time password for loginVault signs the user's (and host's) public key
Access methodssh arman@server then enter the OTPThe SSH client uses a private key signed by Vault
DurationOne login (single-use OTP)Per the key signing TTL
Server-side install requirementsVault SSH helper + OTP verifier on the serverOnly trust the user CA on the server (/etc/ssh/trusted-user-ca-keys)
User key rotationNot relevant (new code every login)Key re-signed per TTL
Best forSmall environments / human usersLarge scale, automation, and service accounts

SSH OTP Mode (One-Time Password)

Enable the SSH secrets engine
vault secrets enable ssh
Create an SSH OTP role
vault write ssh/roles/otp-role \
    key_type=otp \
    default_user=ubuntu \
    cidr_list=10.0.0.0/8

When a user wants to log in, Vault provides an OTP code:

Request OTP credentials for SSH login
vault read ssh/creds/otp-role

Output:

vault read ssh/creds output
Key                Value
---                -----
key               a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6
key_type          otp
port              22
username          ubuntu

With this OTP key, the user can log in to the server: ssh ubuntu@10.0.0.5 then enter the OTP key as the password. After being used once, that OTP is no longer valid.

SSH CA Mode (Signing)

CA mode is more elegant for large scale. The principle: Vault holds an SSH CA, and servers are configured to trust keys signed by Vault. Users don't need to store secrets in Vault — just their signed public key.

Create an SSH CA role
vault write ssh/roles/ca-role \
    key_type=ca \
    allowed_user_domains=dev,ops \
    default_extensions="permit-pty,permit-port-forwarding" \
    allowed_users=ubuntu

Users submit their public key to be signed:

Sign a user's public key
vault write ssh/sign/ca-role \
    public_key=@/home/arman/.ssh/id_ed25519.pub \
    valid_principals="arman"

Output:

vault write ssh/sign output
Key            Value
---            -----
key_type       ca
signed_key     ssh-ed25519-cert-v01@openssh.com AAAA... (SSH certificate, not just a public key)

The resulting signed_key is an SSH certificate (not just a public key) with an expiration date. Servers that trust the Vault CA will automatically accept this key until its TTL expires — after that, the user must request a new signature.

Vault can also sign host keys (the server's own identity), so clients can verify that the server they're connecting to is really the company's server — preventing man-in-the-middle attacks:

Sign a server's host key
vault write ssh/sign/host-signer \
    public_key=@/etc/ssh/ssh_host_ed25519_key.pub \
    valid_principals="dev-server-01"

Output:

host key sign output
Key            Value
---            -----
key_type       ca
signed_key     ssh-ed25519-cert-v01@openssh.com AAAA... (server host certificate)

The combination of user CA + host CA is what forms zero-trust SSH: users verified via certificates, servers verified via certificates, and both short-lived. No more permanent passwords or host keys left unchecked.

Warning

A crucial difference: with SSH OTP, the server needs a Vault SSH helper plugin to verify the one-time password. With SSH CA, all that's needed is adding the Vault CA public key to the server's trusted-user-ca-keys — far lighter and no extra processes on the server. For large infrastructure, SSH CA is almost always the better choice.

AWS Secrets Engine: Dynamic IAM Credentials

The classic cloud problem: AWS access keys created once and living for years, stored on laptops, in environment variables, or — not infrequently — in Git commits. If leaked, attackers get full access to the AWS account.

The AWS secrets engine uses the same dynamic secrets principle as the database engine in episode 5: Vault creates temporary IAM credentials that automatically expire.

Enable the AWS secrets engine
vault secrets enable aws

Output:

vault secrets enable aws output
Success! Enabled the aws secrets engine at: aws/

Configure the AWS credentials Vault uses to manage IAM (create users / sign STS tokens):

Configure the AWS root credentials in Vault
vault write aws/config/root \
    access_key="AKIAIOSFODNN7EXAMPLE" \
    secret_key="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" \
    region="ap-southeast-1"

Create a role defining the IAM policy for dynamic credentials:

Create an AWS role (policy-based)
vault write aws/roles/my-role \
    credential_type=iam_user \
    policy_document=-<<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:ListBucket"],
      "Resource": "*"
    }
  ]
}
EOF

Now, whenever a pipeline or application needs AWS access:

Read dynamic AWS credentials
vault read aws/creds/my-role

Output:

vault read aws/creds/my-role output
Key                Value
---                -----
lease_id           aws/creds/my-role/1h2VzQ6xW9pLcN4mB7kR5sT0dF8jK3y
lease_duration     768h
lease_renewable    true
 
access_key         AKIAVPQEXAMPLEDYNAMICKEY
secret_key         5hS9mZcQwE3rTgY2uNvB6lK7xDfC0jJp
security_token     <nil>

Important

For credential_type=iam_user, Vault creates a new IAM user in the AWS account along with its access key, and deletes that user when the lease ends. For credential_type=federation_token or assumed_role, Vault uses AWS STS so no new user is needed — access comes via a session token with a shorter TTL (up to 1 hour for federation). Choose accordingly: iam_user for the 768-hour need (the default maximum), STS-based for short-lived credentials.

Example of another commonly used command:

AWS credentials via STS (short-lived)
vault read aws/creds/my-sts-role

Output:

STS credentials output
Key                Value
---                -----
access_key         ASIAVPQEXAMPLESHORTLIVED
secret_key         pQn1sLvXc3rTgY5uNhB7jK9mZdF2wA6y
security_token     IQoJb3JpZ2luX2Vj... (long STS session token)
lease_id           aws/creds/my-sts-role/6sR4tG8vY2mC5xH7bN9kQ3wL0zF1jD8
lease_duration     1h

Tip

This is why the CI/CD pipeline in episode 18 later can run without storing a single AWS access key in the repository: when the pipeline needs access, it just requests dynamic credentials from Vault, uses them while the job runs, and they're gone once the lease ends. No more AWS secrets nesting in GitHub/GitLab for years.

Combined Scenario: All Three Engines in One Flow

To make the picture of the three engines more concrete, let's weave them into a single scenario: a company managing an internal data platform.

LayerNeedEngine Used
Human accessEngineers log in to the admin dashboardTOTP (2FA)
Server accessEngineers SSH into production serversSSH CA (user key signing)
AutomationPipelines read data from S3AWS (dynamic IAM credentials)

One and the same Vault cluster serves all three — each with its own mount, role, and policy. This is the beauty of Vault: one control plane for every kind of secret, from OTP codes to cloud credentials, with a single audit model and a single access model.

When to Use Which Engine?

These three engines serve different needs — don't mix them up:

NeedEngineAnalogy
2FA / one-time codes for app loginTOTPA single-use card for entering the building
SSH access to serversSSH (OTP / CA)A parking barcode scanned once, or a verified access card
AWS cloud accessAWSContract employees whose term automatically ends

Note

A rule of thumb: choose the engine based on what you want to control. Want to control the login code → TOTP. Want to control who can SSH where → SSH. Want to control who can access the cloud with what policy → AWS. All three actually use the same machinery underneath: secrets engine + lease + policy + audit.

Common TOTP, SSH & AWS Mistakes

EngineMistakeSymptomSolution
TOTPotpauth URL in the wrong formatCode doesn't appear in the authenticatorUse the otpauth://totp/{issuer}:{account}?secret={base32} format
TOTPValidating an already-expired codevalid=falseTOTP codes are only valid for ~30 seconds; validate immediately after receipt
SSH OTPForgot to install the helper on the serverOTP login rejectedMake sure vault-ssh-helper is active and the OTP role uses the right cidr_list
SSH CAForgot to add the trusted CA on the serverHost key verification failed / key rejectedAdd the Vault CA public key to the server's trusted-user-ca-keys
AWSUsing iam_user when a short TTL is neededMany IAM users pile upUse federation_token / assumed_role via STS
AWSPolicy document encoded incorrectlyInvalid policy JSON errorTest with a matching credential_type and valid JSON policy
AllTokens/leases used but never renewedConnections drop suddenlyApply renewal / re-fetch before the TTL ends (episode 12)

Conclusion

In episode 8, we've rounded out our understanding of secrets engines with three additional ones: TOTP for centralized 2FA OTP generation and validation, SSH with two modes (single-use OTP vs CA signing) for securing server access, and AWS for short-lived dynamic IAM credentials. We also saw the comparison of when to use which engine, and the common traps in each.

The essence of this episode: all Vault secrets engines follow the same pattern — configure once, then credentials are managed and short-lived, with centralized, policy-controlled, audited access. Whatever technology you're securing — databases, data, certificates, logins, or the cloud — the mindset is consistent.

With Phase 2 complete, you've mastered all of Vault's core secrets engines. In episode 9, we'll enter Phase 3: Vault Policies — how to control who can access what, using the HCL language and the capabilities concept that determines the security of the entire system. Keep your enthusiasm up!

Learn Vault - Additional Secrets Engines (TOTP, SSH & AWS) | Learn Secret Management with HashiCorp Vault