Vault/OpenBao configuration for mere humans
Configuring Vault/OpenBao at scale is complex and opaque. It requires a steep learning curve and its flexibility exposes many configuration paths, most of which are unnecessary for common organizational use cases and difficult to review consistently.
This project distills GatePlaneβs operational experience with Vault/OpenBao into a single, declarative configuration format that surfaces the most common and security-relevant options explicitly.
This project provides a declarative YAML format for configuring Vault and OpenBao that focuses on solving the critical security question: "who has what access to what?"
By defining secret engines, roles, policies, and access control in a single, human-readable YAML file, you get:
- Simplified configuration β Only the most common and security-relevant options are exposed explicitly
- Auditable access control β Clear visibility into who can access which resources
- Reduced complexity β Avoid the steep learning curve and many configuration paths of native Vault
- GitOps-friendly β Version control your entire access model alongside your infrastructure
The YAML schema is consumed by Terraform modules located in /terraform/modules, with working examples available in /terraform.
- π¨ Problem statement
- π Overview
- π Table of Contents
- ποΈ Top-level Structure
- π Getting Started
- π― GatePlane Services
- π¦ What
vault-yamlmanages - βοΈ License
The /access.example.yaml file demonstrates all supported features with inline comments to guide you through them.
Each top-level key represents a logical Vault mount (secrets engine) or feature group:
<pki-engine-path>:
type: pki
roles: {...}
<kubernetes-engine-path>:
type: kubernetes
roles: {...}
adhoc:
type: vault
roles: {...}The key itself is the mount path in Vault (e.g., pki, kubernetes, or a custom path like ssh/signer). The special adhoc key is reserved for Vault policies not tied to any secrets engine.
Defines the Vault engine or feature being configured.
-
kubernetes-terraform/modules/secret-engines/kubernetes
CA that generates/signs certificates to used for Code Signing, OpenVPN authentication, etc.
Currently works with the agent-less SSH CA method.
vaultterraform/modules/secret-engines/vault(policies only)
Is used only by the adhoc key, to directly create templated Vault policies that do not adhere to secret engine use-cases.
- more to come (AWS, GCP, Azure, Databases, etc)
For each role defined in your YAML, vault-yaml automatically generates appropriate policies based on each secrets engine's use case of credential generation. These policies are then attached to all principals referenced in the role's access block.
Defines roles that map directly to Vault/OpenBao Secret Engine roles.
roles:
web:
ttl: 7776000
allowed_domains:
- example.com
access: [...]Roles serve two purposes:
- Vault configuration: They configure the underlying Vault secret engine role (e.g., setting TTL, allowed domains, certificate parameters)
- Access control: They define who can use this role through the
accessblock
The keys you define in a role (e.g., ttl, allowed_domains) map directly to the corresponding Vault Terraform provider resource for that secret engine type. For example, allowed_domains corresponds to the same field in pki_secret_backend_role.
The access key is explained further below.
Roles can define both static and conditional access:
-
Static access: Principals have immediate, permanent access to use the role. No approval is required.
-
Conditional access: Principals must request access and receive approval before using the role. This is implemented through the GatePlane Policy Gate plugin and is ideal for:
- Sensitive operations requiring oversight
- Multi-person approval workflows
- Situations requiring justification documentation
- Temporary access scenarios (hotfixes, maintenance, onboarding)
You can define both static and conditional access on the same role, allowing different levels of access for different users.
In this example, anyone can request a client certificate, but it must be approved by an Onboarder before being issued:
roles:
client-generate:
client_flag: true
ttl: 31536000
access:
conditional:
requestors:
- "ldap.groups.Everyone"
approvers:
- "ldap.groups.Onboarders"
required_approvals: 1
require_justification: falseThe access block is defined under each role to control who may use that role. It accepts two optional sub-blocks:
Grants immediate, permanent access to principals without requiring approval. Accepts a list ([]) of principal strings.
Use this for:
- Automated systems that need constant access
- Teams that require ongoing access to resources
- Operations that don't need oversight
access:
static:
- "ldap.groups.Administrators"
- "ldap.users.jdoe"Requires approval before access is granted. Accepts a map ({}) with the following keys:
requestors: List of principals who can submit access requestsapprovers: List of principals who can approve requestsrequired_approvals(optional, default: 1): Minimum number of approvals requiredrequire_justification(optional, default: false): Whether requestors must provide a reason
Use this for:
- Production break-glass scenarios
- Temporary elevated privileges
- Security-sensitive operations
- Audit trail requirements
access:
conditional:
requestors:
- "ldap.groups.Developers"
approvers:
- "ldap.groups.KubernetesAdministrators"
- "ldap.groups.cab"
required_approvals: 2
require_justification: trueA role can have both static and conditional access defined:
access:
static:
- "ldap.groups.Administrators"
conditional:
requestors:
- "ldap.groups.Developers"
approvers:
- "ldap.groups.Administrators"
required_approvals: 1
require_justification: truePrincipals are represented as strings that combine the authentication method with the identity. This format tells vault-yaml how the user authenticates and who they are.
1. LDAP authentication
Format: ldap.groups.<group-name> or ldap.users.<username>
These principals are authenticated through your configured LDAP auth method. The user's username and group membership are pulled directly from your LDAP directory.
Examples:
ldap.groups.Developersldap.users.jdoe
2. Vault/OpenBao Identities
Format: identity.entity_id.<id> or identity.entity_name.<name> or identity.group_id.<id> or identity.group_name.<name>
These principals reference entities or groups stored in Vault/OpenBao's identity system. They can be created manually or automatically through auth methods.
Examples:
identity.entity_id.f84a6248-b907-4119-bdd7-f47c8bf40bbfidentity.entity_name.entity_e13c9ca6identity.group_id.30210932-96a2-f3de-bc39-24e49d543832identity.group_name.InternalAdmins
Note: If an Entity or Group does not exist, but is referenced in the
accesses.yaml,terraform applywill exit with an error.
Note: Avoid managing external Vault groups with
vault-yaml, as this can cause Terraform drift issues.
3. Username and Password
Format: userpass.<username>
These principals reference usernames created through the "Username and Password (userpass)" Auth Method of Vault/OpenBao.
Examples:
userpass.jdoeuserpass.mitchellh
Note: If the username does not exist, but is referenced in the
accesses.yaml,terraform applywill exit with an error.
4. Client Certificates (mTLS)
Format: cert.<common_name>[::ip_bind=true]
These principals reference Client Certificate Common Names of x509 Certificates authenticated through the "TLS Certificates" Auth Method of Vault/OpenBao.
ip_bind: This principal extension is used for machine attestation, such as Kubernetes Nodes, Crypto Validators, etc.
Terraform resolves the domain found in principal's Common Name (IPv4 and IPv6 addresses) and allows the Vault / OpenBao tokens created by the principal's certificate to be used only by these IPs.
Note: The
ip_bindextension must not be used for machines with non-static IP addresses. The Vault / OpenBao token created through an IP-bound certificate cannot be used outside the machine with the resolved IPs
Note: If the
ip_bindextension is used, but the domain found in the principal cannot resolve, Terraform will fail. Additionally, it is possible to change the default DNS server of the Terraform runner by configuring Terraform DNS Provider
Examples:
cert.example.comcert.jdoe@mail.example.comcert.node1.example.com::ip_bind=true
5. JWT authentication (Work in Progress)
Format: jwt.<role>.<jwt-sub>
Used for CI/CD pipelines and service accounts authenticating via JWT.
Example:
jwt.cicd.org/repo1- Matches JWTsubclaimorg/repo1from the JWT auth mount with rolecicd
The adhoc section defines custom Vault/OpenBao policies that aren't tied to any specific Secrets Engine. Use this when you need fine-grained control over paths and capabilities that don't fit into the standard secret engine role model.
Each role in the adhoc section references a policy template file (.hcl) that contains the actual Vault policy rules. The template can reference variables for dynamic path generation.
adhoc:
type: vault
roles:
secrets-personal:
access:
static:
- ldap.groups.EveryoneThis configuration creates a policy from the template file roles/vault/secrets-personal.hcl and attaches it to all users in the Everyone LDAP group.
Your policy templates can access these pre-defined variables:
secrets_engines- A map of all configured secret engines and their mount pathsauth_methods- A map of all configured auth methods and their mount paths
This lets you write policies that dynamically reference your infrastructure without hardcoding paths.
For example, in a template:
# The 'secrets_engines' parameter is templated by Terraform
# The {{ identity.entity.name }} directive is templated by Vault/OpenBao
path "${secrets_engines["kv"]["path"]}/data/users/{{ identity.entity.name }}" {
capabilities = ["create", "read", "update", "delete"]
}See terraform/main.tf#L66 for how these maps are constructed.
Before using vault-yaml, ensure you have:
- Terraform (v1.0+) installed and configured
- Vault or OpenBao server running and accessible
- Terraform Vault provider configured with authentication to your Vault/OpenBao instance
- Basic understanding of Vault concepts (secret engines, auth methods, policies)
If you're setting up a new Vault/OpenBao instance:
-
Create a Vault/OpenBao instance using the Hashicorp official Terraform provider.
-
Configure authentication and secrets engines using Terraform resources. For example:
- Mount a secrets engine:
vault_mount - Add Kubernetes integration:
vault_kubernetes_secret_backend - Configure LDAP authentication:
vault_ldap_auth_backend
- Mount a secrets engine:
-
Connect your mounts to
vault-yamlmodules by passing the mount path to the appropriate modules interraform/modules/secrets-enginesorterraform/modules/auth-methods. Seemain.tffor a complete example.
You can use vault-yaml with existing Vault/OpenBao instances - without replacing or disrupting your current configuration.
In this repository, the separation between vault-yaml and BYOV resources can be seen under terraform/ directory. The byov.tf files are meant to pre-date the vault-yaml files (main.tf, gateplane.tf)
vault-yaml works alongside your existing setup:
- Existing roles and policies are preserved -
vault-yamldoes not delete or modify your existing configuration (terraform planwill fail if resources with the same name exist) - New resources are added incrementally - your
accesses.yamlfile provisions new roles and policies defined in the YAML - No disruption to existing workflows - your current operations continue uninterrupted
Simply reference your existing Secrets Engines and Auth Methods when configuring vault-yaml modules:
-
For existing secrets engines: Connect an existing mount (e.g.,
/pkior/kubernetes) to the correspondingvault-yamlmodule using themountvariable. The module will create new roles defined in your YAML alongside any existing roles. -
For existing auth methods: Reference your existing auth methods in the configuration. This enables
vault-yamlto use principals (users, groups, entities) that are already authenticated through those auth methods.
If you have an existing Kubernetes engine referenced by Terraform with vault_kubernetes_secret_backend.kubernetes, that already contains legacy roles:
module "kubernetes" {
source = "github.com/gateplane-io/vault-yaml.git//terraform/modules/secrets-engines/kubernetes"
// Tying the vault-yaml configuration with existing Secrets Engine
mount = vault_kubernetes_secret_backend.kubernetes
// The directory where Kubernetes roles are defined (roles/kubernetes/<role_name>.yaml)
role_directory = "./roles/kubernetes"
// The `yamldecode` of your accessess.yaml file
accesses = local.accesses
}Your existing roles remain untouched, and the new roles defined in the accessess.yaml are added alongside them.
This approach allows you to gradually adopt vault-yaml's declarative management style without requiring a risky "big bang" migration.
The GatePlane Policy Gate plugin is free and fully included with vault-yaml under the Elastic V2 License, providing conditional access, approval workflows, and time-bound credentials.
For enhanced visibility, you can subscribe to GatePlane Services which adds real-time notifications (Slack, Teams, Discord) and access metrics/analytics to help you track usage patterns and identify bottlenecks.
To enable GatePlane Services, add/uncomment the gateplane_services module to your terraform/gateplane.tf file:
After applying the configuration, run terraform output gateplane_services_output and send the output to services@gateplane.io to activate your subscription.
vault-yaml focuses on access control and role management. Here's what's in scope:
β
Managed by vault-yaml:
- Secret engine roles (e.g., PKI roles, Kubernetes roles, SSH roles)
- Policies associated with those roles
- Role-to-principal assignments (who can use which role)
- Ad-hoc Vault policies defined in templates
- Conditional access using GatePlane plugins transparently
β Managed separately (not by vault-yaml):
- Vault/OpenBao server configuration
- Authentication method setup (LDAP, JWT, etc.)
- Secret engine mounts and basic configuration
- Identity entities and groups (these must exist first)
- Root tokens, recovery keys, and server certificates
- Audit logs and monitoring setup
Think of it this way: vault-yaml builds the access layer on top of infrastructure you've already configured. You set up the Vault/OpenBao server, auth methods, and secret engines - then vault-yaml helps you manage "who has what access to what" in a declarative, auditable way.
This project is licensed under the Elastic License v2.
This means:
- β You can use, fork, and modify it for yourself or within your company.
- β You can submit pull requests and redistribute modified versions (with the license attached).
- β You may not sell it, offer it as a paid product, or use it in a hosted service (e.g., SaaS).
- β You may not re-license it under a different license.
In short: You can use and extend the code freely, privately or inside your business - just donβt build a business around it without our permission. This FAQ by Elastic greatly summarizes things.
See the ./LICENSES/Elastic-2.0.txt file for full details.