Skip to content
s1ns3nz0 | Known Unknowns
Go back

Identity Management Using Vault

7 min read

This project uses Vault to authenticate workloads and operators, issue Vault tokens, and control access to secrets and signing operations.

The underlying identities originate in Kubernetes, AWS, or GitHub. Vault decides what those identities may do inside Vault.

Repository inspection also revealed an important distinction: a GitHub-to-Vault JWT integration is documented, but the current release workflow obtains its AWS credentials directly from AWS STS.

1. Identities and Access Managed by Vault

Vault handles the following authentication and authorization paths. Some are connected to executable workflows; others remain configuration contracts or require live verification.

IdentityVault authenticationAccess grantedRepository status
Kubernetes application workloadsKubernetes authWorkload-specific runtime secretsManifests and bootstrap scripts
CodeBuild release signerAWS IAM authTransit signing and verificationUsed by the signer buildspec
Recovery operatorSeparate AWS IAM auth mountRestricted recovery ceremoniesConfiguration scripts; live setup unconfirmed
GitHub Actions release workflowJWT auth using GitHub OIDCRequest temporary AWS credentialsDefined contract; not used by the current release workflow

1.1 Kubernetes Workload Identities

Each application authenticates using its Kubernetes ServiceAccount token. Vault maps the verified identity to a role and policies.

For example, the Nethermind role contains:

{
  "bound_service_account_names": [
    "nethermind-execution"
  ],
  "bound_service_account_namespaces": [
    "node-operator"
  ],
  "audience": "vault",
  "token_policies": [
    "hoodi-engine-api"
  ],
  "token_ttl": "5m",
  "token_max_ttl": "10m",
  "token_no_default_policy": true
}

The role establishes which Kubernetes identity may authenticate and which Vault policy it receives.

The policy then defines the permitted secret operation:

path "node-operator-runtime/data/nodes/hoodi/engine-api-jwt" {
  capabilities = ["read"]
}

The project separates workload access by responsibility:

WorkloadIntended Vault access
NethermindShared Engine API JWT
Prysm BeaconShared Engine API JWT
Validator clientIts validator set’s client TLS material
Remote signerIts permitted keystore, password, signer TLS, and database credential
Slashing databaseIts database credential

For example, a validator client receives access to its client certificate material:

path "node-operator-runtime/data/validators/hoodi/REPLACE_WITH_VALIDATOR_SET/runtime/client-tls" {
  capabilities = ["read"]
}

It does not receive the remote signer’s keystore policy.

Here, Vault manages the mapping from a Kubernetes identity to secret-access permissions. Kubernetes still owns the ServiceAccount itself.

1.2 CodeBuild Release-Signer Identity

The CodeBuild signer authenticates to Vault using its AWS IAM identity.

The signer buildspec includes:

vault_token="$(vault login \
  -method=aws \
  -token-only \
  role="${VAULT_AUTH_ROLE}" \
  region="${AWS_REGION}")"

The corresponding Vault role template binds access to the dedicated signer role:

vault write auth/aws/role/release-signer \
  auth_type=iam \
  bound_iam_principal_arn="arn:aws:iam::<ACCOUNT_ID>:role/node-operator-baseline-release-signer" \
  resolve_aws_unique_ids=false \
  policies=release-signer \
  ttl=5m \
  max_ttl=10m

Its Vault policy allows signing and verification:

path "transit/sign/node-operator-release" {
  capabilities = ["update"]
}

path "transit/verify/node-operator-release" {
  capabilities = ["update"]
}

The shown policy grants no key export, rotation, deletion, or configuration permissions.

This creates a clear responsibility boundary:

1.3 Recovery-Operator Identity

Recovery operators use a separate AWS authentication mount:

vault auth enable -path=operator-aws aws

The configuration script creates a restricted role:

vault write auth/operator-aws/role/operator-recovery \
  auth_type=iam \
  bound_iam_principal_arn="$principal" \
  resolve_aws_unique_ids=true \
  token_policies=operator-recovery \
  token_ttl=5m \
  token_max_ttl=10m \
  token_explicit_max_ttl=10m \
  token_no_default_policy=true

The policy permits recovery-ceremony operations and self-revocation:

path "sys/generate-root/attempt" {
  capabilities = ["read", "update", "delete"]
}

path "sys/generate-root/update" {
  capabilities = ["update"]
}

path "auth/token/revoke-self" {
  capabilities = ["update"]
}

Authenticating as this operator does not automatically produce a root token. The required recovery shares must still be supplied.

The repository contains configuration and verification scripts, but its documentation records live setup as pending when that status was written. The inspection did not establish whether the deployed mount has since been configured.

1.4 GitHub Actions Identity: Defined Vault Integration

The repository defines a JWT authentication contract that trusts GitHub’s Actions issuer:

vault auth enable jwt

vault write auth/jwt/config \
  oidc_discovery_url="https://token.actions.githubusercontent.com" \
  bound_issuer="https://token.actions.githubusercontent.com"

This is authentication for a workflow using a GitHub-issued JWT. It is separate from interactive browser login for a human administrator. HashiCorp: JWT/OIDC authentication.

The role restricts the accepted identity claims:

{
  "role_type": "jwt",
  "bound_audiences": [
    "https://vault.node-operator.internal"
  ],
  "user_claim": "repository_id",
  "bound_claims_type": "glob",
  "bound_claims": {
    "repository": "s1ns3nz0/node-operator",
    "repository_owner": "s1ns3nz0",
    "ref_type": "tag",
    "ref": "refs/tags/v*",
    "workflow_ref": "s1ns3nz0/node-operator/.github/workflows/release.yml@refs/tags/v*"
  },
  "token_policies": [
    "release-runner-dynamic-aws"
  ],
  "token_ttl": "15m",
  "token_max_ttl": "15m",
  "token_type": "batch",
  "token_num_uses": 1,
  "token_no_default_policy": true
}

The intended checks restrict authentication to the expected audience, repository, owner, version-tag context, and release workflow.

user_claim: repository_id identifies the subject within the authentication flow. The bound_claims determine which claims are accepted.

There is one configuration inconsistency: the role requests both a batch token and a one-use limit. Vault batch tokens do not support use-count limits, so the documented “one-use batch token” behavior cannot be relied upon as written. HashiCorp: Token types.

1.5 Temporary AWS Credentials Through Vault

The intended GitHub role receives this policy:

path "aws/creds/release-runner" {
  capabilities = ["read"]
}

That operation requests temporary AWS credentials from Vault’s AWS secrets engine.

The engine role template specifies:

vault write aws/roles/release-runner \
  credential_type=assumed_role \
  role_arns="arn:aws:iam::REPLACE_WITH_ACCOUNT_ID:role/REPLACE_WITH_VAULT_DYNAMIC_AWS_ROLE" \
  policy_document=@deploy/vault/aws/release-runner-assumed-role-policy.json \
  default_sts_ttl=15m \
  max_sts_ttl=15m

The documented AWS permissions cover uploading signer input, starting and inspecting the approved CodeBuild signer, and reading its output.

The intended flow is:

flowchart LR
    GH["GitHub Actions<br/>OIDC JWT"]
    AUTH["Vault JWT auth"]
    TOKEN["Restricted Vault token"]
    ENGINE["Vault AWS secrets engine"]
    STS["AWS STS"]
    CREDS["Temporary AWS credentials"]

    GH -->|"Vault audience"| AUTH
    AUTH --> TOKEN
    TOKEN -->|"Read aws/creds/release-runner"| ENGINE
    ENGINE -->|"Assume approved IAM role"| STS
    STS --> CREDS

In this design, Vault would control access to credential issuance. AWS would still own the IAM role and issue the resulting STS credentials.

This remains the documented path; it is not the path used by the current release workflow.

2. Identities and Access Managed Outside Vault

Other systems remain responsible for creating identities, issuing their original credentials, and governing access to their own resources.

Identity or credentialResponsible systemVault’s relationship
GitHub users and repository membershipGitHubDoes not administer them
GitHub Actions OIDC identityGitHubCan verify its JWT through the defined auth contract
Current release runner’s AWS sessionAWS STS and IAMNot involved in issuing this session
Kubernetes ServiceAccounts and RBACKubernetesValidates tokens for Vault access
AWS IAM users, roles, and trust policiesAWS IAMCan authenticate selected principals
Vault server’s KMS identityEKS Pod Identity and AWS IAMConsumes the supplied AWS credentials
Vault listener certificate lifecyclecert-manager and its configured CAUses the delivered certificate
Human SSO and centralized identity groupsNo Vault integration established by the inspected configurationNot demonstrated in the repository

2.1 GitHub Accounts and Workflow Identity

GitHub manages users, repository membership, and the identity claims attached to workflow executions.

Even when the Vault JWT integration is enabled, GitHub remains the issuer. Vault checks the supplied identity evidence and decides whether to issue a Vault token.

That distinction separates two decisions:

Vault does not create GitHub accounts or manage repository membership.

2.2 The Current Release Runner’s AWS Identity

The checked-in release workflow requests a GitHub OIDC JWT with this audience:

sts.amazonaws.com

It then exchanges that token directly with AWS:

aws sts assume-role-with-web-identity \
  --role-arn "$AWS_ROLE_ARN" \
  --role-session-name "release-${GITHUB_RUN_ID}" \
  --web-identity-token "$web_token" \
  --duration-seconds 900

The implemented flow is:

flowchart LR
    GH["GitHub Actions<br/>OIDC JWT"]
    STS["AWS STS"]
    RUNNER["Release runner<br/>Temporary AWS credentials"]
    BUILD["CodeBuild signer"]
    AUTH["Vault AWS auth"]
    TRANSIT["Vault Transit<br/>Sign and verify"]

    GH -->|"Audience: sts.amazonaws.com"| STS
    STS --> RUNNER
    RUNNER -->|"Start signer build"| BUILD
    BUILD -->|"Authenticate with IAM identity"| AUTH
    AUTH --> TRANSIT

Vault participates when CodeBuild authenticates for signing. It does not mediate the release runner’s preceding GitHub-to-AWS credential exchange.

This is the principal mismatch found during inspection: the documented release contract places Vault in the AWS credential-issuance path, while the current workflow calls STS directly.

2.3 Kubernetes ServiceAccounts and RBAC

Kubernetes owns the workload’s ServiceAccount and its Kubernetes API permissions.

A workload can supply a projected token for Vault:

serviceAccountToken:
  audience: vault
  expirationSeconds: 600
  path: token

Vault uses that token to authenticate the workload, but a Vault policy does not grant permission to list Kubernetes pods or update Deployments.

The permissions remain separate:

Kubernetes Role / RoleBinding
    → Access to Kubernetes API resources

Vault role / ACL policy
    → Access to Vault secrets and operations

A workload can have minimal Kubernetes API permissions while still being allowed to read its application secret from Vault.

2.4 AWS IAM and Vault’s Own Cloud Identity

AWS manages the IAM principals that Vault trusts or uses.

For auto-unseal, the project associates Vault’s service account with an IAM role:

resource "aws_eks_pod_identity_association" "vault" {
  cluster_name    = aws_eks_cluster.private.name
  namespace       = "vault"
  service_account = "vault"
  role_arn        = aws_iam_role.vault.arn
}

This identity allows the Vault server to request authorized AWS operations, such as KMS access.

The trust direction differs from CodeBuild’s Vault login:

DirectionPurpose
Vault → AWS KMSVault uses an AWS identity to access its seal mechanism
CodeBuild → VaultCodeBuild presents its AWS identity to obtain Vault permissions

Both involve AWS identity, but only the second is authentication into Vault.

2.5 Certificates Stored in Vault Versus PKI Managed by Vault

Vault may store certificate and private-key material without acting as the certificate authority.

The inspected workload policies permit reading TLS material from KV paths. The Vault listener certificate is supplied through cert-manager and its configured CA.

These responsibilities should not be combined:

OperationWhat it establishes
Store a certificate in Vault KVProtected storage and controlled retrieval
Issue a certificate through Vault PKIVault participates in certificate issuance
Mount a certificate into a podDelivery to its consumer
Reload a renewed certificateApplication adoption of the replacement identity

Similarly, storing a database password in Vault does not establish that Vault creates the database account or rotates its password. Those behaviors require an explicit integration.

2.6 Human SSO and Central Identity Administration

The inspected configuration did not establish:

This does not exclude automatically created Vault identity records or manually configured live integrations. It means those capabilities are not demonstrated by the repository.

The project’s established pattern is external identity verification followed by restricted Vault access. Kubernetes supplies workload identities, AWS supplies IAM identities, and GitHub supplies workflow identities; Vault applies its own roles and policies where those identities enter Vault.


Share this post:

Previous Post
Hoodi Node Validator AWS Architecture Overview
Next Post
Kubernetes Namespace Design for a Hoodi Validator