Elegant Cert Governance with Vault Identity and Sentinel Policy
Learn how using policy as code to enforce governance for certificate creation inside HashiCorp Vault reduces cost of ownership and lowers risk.
Sentinel is HashiCorp’s policy as code solution. It provides targeted, shift-left policy enforcement to ensure that organizational security, financial, and operational requirements are met across all workflows. While Sentinel is best known for its use with HashiCorp Terraform, it is embedded in all of HashiCorp’s enterprise offerings.
Sentinel can be used to create many custom guardrails. Examples include:
- Requiring network access control lists (ACLs) on cloud storage created by Terraform Enterprise and Cloud.
- Restricting access to specific secrets in Vault Enterprise to the corporate network.
- Enforcing service naming standards in Consul Enterprise.
- Restricting Nomad Enterprise jobs created by specific groups to the Docker driver.
In this blog post we are going to use Sentinel and Vault’s identity management system to enforce governance for certificate creation. This post assumes a general knowledge of HashiCorp Vault.
» The Problem
Here’s a hypothetical scenario: Acme Corp. wants to allow its applications in Microsoft Azure to generate short-lived certificates using HashiCorp Vault Enterprise. Each application should get a certificate with a common name of <application name>.acme-app.com
. Applications should not be able to get certificates in the acme-app.com domain for other common names.
Acme’s security team wants to avoid giving developers credentials to the secrets management platform, which could result in frequent issues around the secret zero problem. The security team also wants to minimize the amount of administration required on Vault.
» The Solution
One solution to this problem is to build a Vault PKI secrets engine role for each application. Each role would control which certificate common names are allowed for that role. Applications would be granted rights to their respective PKI role through Vault security policy.
However, scalability is a challenge for that approach. A thousand applications would mean a thousand roles, with each role being identical except for the allowed common name. That’s a lot of duplication, onboarding overhead, and administrative complexity, even using templated Vault policies.
A more elegant solution is to use Vault’s identity system and Sentinel to govern access to a single endpoint which can create certificates for any child domain of acme-app.com
.
» Workflow
This solution combines the PKI secrets engine, Azure authentication, Vault identity, and Sentinel. The workflow looks like this:
- The workload uses the Vault SDK to authenticate with Vault using the Azure managed identity of the virtual machine.
- Vault verifies access to a PKI secrets engine endpoint using Vault ACL policies.
- Sentinel verifies that the identity for the workload is authorized to use the common name that it requested.
Let’s dig in.
» Vault PKI
Vault’s PKI secrets engine will be used to issue certificates to the client workloads. For this example we will configure Vault to be a root certificate authority (CA).
vault secrets enable pki
vault write pki/root/generate/internal \
common_name=acme.com \
ttl=8760h
vault write pki/config/urls \
issuing_certificates="http://127.0.0.1:8200/v1/pki/ca" \
crl_distribution_points="http://127.0.0.1:8200/v1/pki/crl"
One of the solution’s goals is to simplify management of Vault endpoints. The API endpoint to issue certificates to our workloads is configured to issue certificates to any subdomain of acme-app.com
. This endpoint meets our requirement for management simplicity but not our common-name requirements. That will be addressed later:
vault write pki/roles/app \
allowed_domains=acme-app.com \
allow_subdomains=true \
max_ttl=2h
A Vault ACL policy called pki-local
policy grants access to our certificate endpoint. It looks like this:
path "pki/issue/app" {
capabilities = ["create", "update"]
}
For more information about using the PKI secrets engine, refer to the HashiCorp Learn topics on intermediate and root CA use cases.
» Azure Authentication
Secret zero, also called secure introduction, is a challenge in secrets management. How can a workload access secrets without having to first pass that workload a credential, itself a secret, to access the secrets management platform? Our problem statement identifies secret zero as a problem to be solved.
HashiCorp Vault provides authentication methods that solve the secret zero problem. One such method is the Azure authentication method. The Azure authentication method uses an Azure managed identity assigned to the workload to authenticate with Vault. An Azure resource such as a virtual machine (VM) or Azure Function has the managed identity built or assigned at creation. Vault SDKs allow developers to use the managed identity to authenticate with Vault without needing access to a password or any other kind of secret.
(Note: Rob Barnes’ blog post on Integrating Azure AD Identity with HashiCorp Vault — Part 3: Azure Managed Identity Auth via Azure Auth Method is an excellent examination of the Azure authentication method.)
We need to create a role within the Azure authentication endpoint in Vault. The role can filter by a number of Azure attributes. For this demonstration we are going to create a role called appcert
and limit access to the role by subscription id and resource group of the managed identity.
vault write auth/azure/role/appcert \
token_policies="default,pki-local" \
bound_subscription_ids=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \
bound_resource_groups=Csmith-proj-sentinel-resources
This authentication endpoint allows any Azure workload running within the given subscription and the Csmith-proj-sentinel-resources
resource group to authenticate. Workloads authenticating to this endpoint are given a token with the pki-local
policy. With this policy, the workloads will be able to generate certificates for child domains of the acme-app.com
domain.
Next, we build infrastructure to run our workload. For our demo we will create a virtual machine. We will also create a user-defined managed identity and assign it to the VM. The identity can be used on multiple VMs or other Azure resources. The important thing to remember is that the identity is tied to the workload, not to the specific Azure resource.
A snippet of the relevant Terraform code looks like this:
resource "azurerm_user_assigned_identity" "ident_demo_app" {
resource_group_name = azurerm_resource_group.proj-rg.name
location = azurerm_resource_group.proj-rg.location
name = "demo_app_csmith_sentinel"
}
// Bastion Host Compute
resource "azurerm_linux_virtual_machine" "bastion" {
name = "${var.prefix}-bastion-machine"
resource_group_name = azurerm_resource_group.proj-rg.name
location = azurerm_resource_group.proj-rg.location
size = "Standard_F2"
admin_username = "adminuser"
network_interface_ids = [
azurerm_network_interface.bastion-nic.id,
]
admin_ssh_key {
username = "adminuser"
public_key = var.ssh_public_key
}
os_disk {
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
}
identity {
type = "UserAssigned"
identity_ids = [azurerm_user_assigned_identity.ident_demo_app.id]
}
source_image_reference {
publisher = var.linux-info.publisher
offer = var.linux-info.offer
sku = var.linux-info.sku
version = var.linux-info.version
}
}
output "identity_id" {
value = azurerm_user_assigned_identity.ident_demo_app.principal_id
}
» Vault Identity
So far we have:
- A Vault endpoint to distribute certificates to child domains of
acme-app.com
. - An Azure authentication endpoint that will authenticate Azure managed identities within a specific resource group and subscription.
- A policy allowing workloads authenticated by the Azure endpoint to generate certificates using the PKI secrets endpoint.
- A VM running under a user-defined managed identity that meets the criteria of the Azure endpoint.
If we stopped here, workloads on the VM would be able to generate certificates without secret zero challenges, but we have not completely solved our problem. The workload has the ability to create a certificate for a child domain of acme-app.com
with any common name. We need to restrict the common name allowed by the application.
To restrict by common name we will build an entity in Vault for the workload. The entity will combine the Azure managed identity with user-defined metadata. Conceptually, it will look like this:
In this entity:
- The entity is the name of the workload or application.
- The alias name is the Azure object ID for the managed identity.
- The metadata
allowed_app
is the common name that we want the Azure managed identity to be able to use for certificates, in this casealpha
.
The entity can be created in the same Terraform code used to create the workload VM:
resource "azurerm_user_assigned_identity" "ident_demo_app" {
resource_group_name = azurerm_resource_group.proj-rg.name
location = azurerm_resource_group.proj-rg.location
name = "demo_app_csmith_sentinel"
tags = local.common_tags
}
/*
SNIP...other Terraform removed
*/
provider "vault" {
# This will default to using $VAULT_ADDR
# But can be set explicitly
address = var.vault_addr
token = var.token
}
data "vault_auth_backend" "azure_auth" {
path = "azure"
}
resource "vault_identity_entity" "demo_app" {
name = "demo_app_csmith_sentinel"
policies = ["default"]
metadata = {
allowed_app = "alpha"
}
}
resource "vault_identity_entity_alias" "demo_alias" {
name = azurerm_user_assigned_identity.ident_demo_app.principal_id
mount_accessor = data.vault_auth_backend.azure_auth.accessor
canonical_id = vault_identity_entity.demo_app.id
}
Alternatively, it can be created from the Vault CLI:
#!/usr/bin/env bash
set -euo pipefail
readonly ALIAS_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
ACCESSOR_ID=$(vault auth list -format=json | jq -r '.["azure/"].accessor')
ENTITY_ID=$(vault write -format=json identity/entity name="demo_app_csmith_sentinel" policies="default" \
metadata=allowed_app="alpha" \
| jq -r ".data.id")
vault write identity/entity-alias name="$ALIAS_ID" \
canonical_id=$ENTITY_ID \
mount_accessor=$ACCESSOR_ID > /dev/null
echo "Complete"
» Sentinel Policy
The final step is to attach a Sentinel policy to govern the common names that a workload can use. Sentinel policies in Vault come in two different flavors. The first type of policy is the role governing policy (RGP). RGPs are attached directly to Vault entities or authentication aliases. These policies are evaluated when authentication occurs.
The other type of Sentinel policy in Vault is an endpoint governing policy (EGP). EGPs are attached to a Vault path. These policies activate when an authenticated and authorized user or workload attempts to access secrets at the targeted path. EGP policies can use metadata from Vault entities as part of their access calculations. This provides a more finely tuned access policy than what is provided by standard Vault ACL policies. Our policy is an EGP:
import "strings"
id_value = identity.entity.metadata.allowed_app + ".acme-app.com"
host_req = request.data["common_name"]
cert_ident = rule {
host_req == id_value
}
main = rule {
cert_ident
}
This policy:
- Builds an acceptable common name from the entity metadata.
- Compares the acceptable common name to the name in the request for a certificate.
- Allows work to go ahead if they match.
The policy is attached to the pki/issue/app
endpoint.
» Testing
Let’s put it all together from the developer’s viewpoint. The developer builds an application on .NET Core using the VaultSharp SDK. The workload will run on Azure compute using the managed identity and Vault entity that we configured. The developer does not need to pass any credentials to Vault. The SDK does that using the managed identity of the VM.
Let’s use this sample application to test our solution. The application requests a certificate from the pki/issue/app
endpoint with the common name of the VAULT_COMMON_NAME
environmental variable prepended to .acme-app.com
.
Vault builds a cert when the developer asks for a common name of alpha
:
But when the developer asks for beta
:
We can see that the EGP policy pki-protect
has been tripped and no certificate has been generated.
» Conclusion
Sentinel is a key part of enforcing corporate governance in all HashiCorp Enterprise products. Identity is at the heart of Vault. Pairing Sentinel with Vault’s entity model enables more granular authorization and allows a simplified secrets architecture — one that doesn’t require the creation and management of hundreds or thousands of roles.
» Resources
Sign up for the latest HashiCorp news
More blog posts like this one
Vault integrations with MongoDB, Private Machines, and walt.id strengthen customer security
Three new HashiCorp Vault ecosystem integrations extend security use cases for customers.
HashiCorp at re:Invent 2024: Security Lifecycle Management with AWS
A recap of HashiCorp security news and developments on AWS from the past year, for your security management playbook.
HCP Vault Dedicated adds secrets sync, cross-region DR, EST PKI, and more
The newest HCP Vault Dedicated 1.18 upgrade includes a range of new features that include expanding DR region coverage, syncing secrets across providers, and adding PKI EST among other key features.