Vault: enabling unauthenticated "public" resources

607 views Asked by At

I am creating a transit RSA key for signing JWTs and would like to make this policy available to any caller who can connect to the vault instance, without authentication (assuming TLS is already working on all clients, that is).

path "transit/keys/jwt-signing" {
  capabilities = ["read"]
}

What combination of policies and auth backends are required to mimic the behavior present in some of the endpoints managed by the standard pki secrets engine in vault?

Taken from: https://www.vaultproject.io/api-docs/secret/pki#read-ca-certificate


»Read CA Certificate

This endpoint retrieves the CA certificate [...] in PEM format.

This is an unauthenticated endpoint. [emphasis mine]

»Sample Request

$ curl \
   http://127.0.0.1:8200/v1/pki/ca/pem

I would like to expose my signing token's public part in a similar way, to construct a typical .well-known/jkws endpoint in an API.

1

There are 1 answers

1
yurisich On

Completely unauthenticated access isn't supported in vault. Tokens are fundamentally tied to how vault is exposed to end users, including operators.

There is no way around it, a token will be involved in the final result, and therefore, token expiration, revocation, and renewal. Using a periodic token is the simplest approach possible.


»Periodic Tokens

In some cases, having a token be revoked would be problematic [...] over a long period of time. In this scenario, a periodic token can be used. Periodic tokens can be created in a few ways:

  1. By [...] a root token with the auth/token/create endpoint

[...] as long as the token is successfully renewed within each of these periods of time, it will never expire. Outside of root tokens, it is currently the only way for a token in Vault to have an unlimited lifetime.


Given that a root token is going to be used for this, creating a token includes one last quality of life feature for "public" access, which is the ability to set the token id of a created token.


»Create Token

Creates a new token. Certain options are only available when called by a root token.

»Parameters

  • id (string: "") – The ID of the client token. Can only be specified by a root token. The ID provided may not contain a . character. Otherwise, the token ID is a randomly generated value.

When a token gets created, it needs a policy attached to it, otherwise it inherits the scope of the token which created it. For a root token, this is not desirable. Creating a policy and binding it to a token includes a special built-in policy to simplify token lifecycle tasks, the default token policy.


»Default Policy

The default policy is a built-in Vault policy that cannot be removed. By default, it is attached to all tokens, but may be explicitly excluded at token creation time by supporting authentication methods.

[...]

To view all permissions granted by the default policy on your Vault installation, run:

$ vault read sys/policy/default

To disable attachment of the default policy:

$ vault token create -no-default-policy

This default policy is going to be needed disabled, since it allows for owners of the token to access auth/token/revoke-self, and destroy the token's ability to be used by everyone.

$ vault read sys/policy/default -format=table
Key      Value                                                                                            
---      -----                                                                                            
name     default                                                                                          
rules    # Allow tokens to look up their own properties
path "auth/token/lookup-self" {
    capabilities = ["read"]                                                                                                                                                                                         
}                       
                                                     
# Allow tokens to renew themselves
path "auth/token/renew-self" {                
    capabilities = ["update"]     
}                                            
                                                     
# Allow tokens to revoke themselves
path "auth/token/revoke-self" {
    capabilities = ["update"]
}

Using these facts, setting up access to a vault endpoint for reading the public part of a jwt signing key boils down to using the root token to:

  • create a vault token
    • with a publicly shared id value, "jwk"
    • as a periodic token, with no parent
    • with the highest possible value for the period's TTL
    • without the default policy attached to it
  • bound to a custom policy
    • which can read the public jwt signing key
    • can lookup-self on its token
    • can renew-self on its token

Here's a script to demonstrate how all this might fit together:

#!/bin/bash

mkdir -p /vault/config/

cat <<EOT > /vault/config/init.hcl
storage "inmem" {}
disable_mlock = "true"

listener "tcp" {
  tls_disable = "true"
  address = "0.0.0.0:8200"
}

api_addr = "http://127.0.0.1:8200"
EOT

nohup vault server --config /vault/config/init.hcl > /vault/nohup.log 2>&1 &
echo $! > /vault/nohup_pid.txt
sleep 1

VAULT_INIT=$(vault operator init -key-shares=1 -key-threshold=1 2>&1)
VAULT_ROOT=$(echo "$VAULT_INIT" 2>&1 | jq -r ".root_token")
VAULT_UNSEAL=$(echo "$VAULT_INIT" 2>&1 | jq -r ".unseal_keys_hex[0]")
UNSEAL_OPERATION=$(vault operator unseal "$VAULT_UNSEAL" 2>&1)
LOGIN_OPERATION=$(printf "$VAULT_ROOT" | vault login - 2>&1)

vault secrets enable transit 2>&1 >/dev/null
vault write "transit/keys/jwt-signing" type=rsa-4096 exportable=false 2>&1 >/dev/null

cat <<EOT > /vault/config/jwk.policy.hcl
path "transit/keys/jwt-signing" {
  capabilities = ["read"]
}

path "/auth/token/lookup-self" {
  capabilities = ["read"]
}

path "/auth/token/renew-self" {
  capabilities = ["update"]
}
EOT
vault policy write "jwk" /vault/config/jwk.policy.hcl 2>&1 >/dev/null
vault write auth/token/create-orphan id="jwk" no_default_policy="true" policies="jwk" ttl="0" explicit_max_ttl="0" num_uses="0" period="2764800" 2>&1 >/dev/null

curl -s -H "X-Vault-Token: jwk" "http://localhost:8200/v1/transit/keys/jwt-signing" | jq -r '.data.keys["1"].public_key'

Which prints:

-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxoZ3HfPOTtcKGXpPSg15
l/ElL4NzNr4YjQrMlRCxw3LPfum1VIyAXupvFb6+dIsmyF9kEE8kXCePpioaEZwo
hlqrqTOrrJRzbsA5znnJPW/S8BuMm1o44x4EXQuc8cWTFc3aP2IgNnMSP31urIpy
/gFoHlYkNCydHSMzCB/oQ33IwpB6KCy7c8ChB2ZqTSlfiXHYkCA4QVLxY/Aoitqz
pAN988QwFDtXwzFxaayT/awS6pSgH75Twrs5K7JnC/97uEl2IQDYrB+DXugGvoHa
ZFi1KqdA2sCRW70ephox0+byLjAYsYI55eWdMtOdCcLfDoIHt/V5YaolzqJgNMSv
t6VfIL139xnnX2L66P/BJ2MOr/BtDlb7bUhVoGFojBoZd2UvNBSK+b1Jgydy+ZNy
5XIDtEnW+TzEHhgMyjLnBl4r54ncDsF++2qqRoyxjFQNBLxTNgxLj6joxfu3OvOP
MQOpBlYwHIYpsvImY5tV2XVioT8VTVeHmZfL52H7K+CkIoP5rebX6R+JdrIuvFht
Gn9GpMsJBJe+g0zCwM1tEfWct/vkIMNcOp4gwhUI129tLOeEep0sbAQCj5Ee+U9U
1M4yh4U1yvRIK2Y5jjZnr7JeW+jf2jHbpKmunFq3s5HMTjxOTmfM84cvdt5bqbzB
ly1rdyPowulT/l7qQtUJCi8CAwEAAQ==
-----END PUBLIC KEY-----

This is as close as I can get to "unauthenticated" access.

Don't forget to periodically renew this token at least once a month!

curl -s -X POST -H "X-Vault-Token: jwk" http://localhost:8200/v1/auth/token/renew-self