Token issuance

This guide describes how to set up the Salt master to orchestrate minion authentication by issuing Vault tokens. For an overview and help with deciding on an authentication flavor, see the Quickstart guide.

Prerequisites

Important

The following shows basic examples of how to create the necessary resources to get you rolling quickly. It does not necessarily represent recommended practices, specifically regarding token/SecretID validity.

A Vault server (cluster) is assumed to be available.

Token Role

By default, token issuance endpoints restrict assignment to only a subset of the requester’s policies and tie the child token’s validity to the parent token. This configuration requires the Salt master to possess all policies it assigns to minions. Additionally, it allows minions to potentially inherit token issuance authorizations.

To overcome these restrictions without relying on sudo capabilities, it is highly recommended to configure a Token Role. This allows for specifying assignable policies without these constraints and optionally enables the “orphaning” of child tokens, allowing them to remain valid beyond the Salt master token’s expiration.

vault write auth/token/roles/salt-master \
  orphan=true \
  allowed_policies=salt_minion \
  allowed_policies_glob='salt_minion_*,salt_role_*'  # Note: the (legacy) default policies need saltstack/*

Master policy

The Salt master needs access to the token issuance endpoints:

# This is the required Salt master policy for issuing Tokens.

# Issue tokens
path "auth/token/create" {
  capabilities = ["create", "read", "update"]
}

# Issue tokens with Token Roles
# Substitute `salt-master` with the role name the master is configured with
path "auth/token/create/salt-master" {
  capabilities = ["create", "read", "update"]
}

You can write it to a file (e.g. salt-master.hcl) and create the policy like this:

vault policy write salt-master salt-master.hcl

Master credentials

The Salt master itself needs statically configured authentication credentials, which should be associated with the salt-master policy created above. Create them as described in the Vault-side setup section of the local configuration guide.

Minion policies

Create policies for minions as needed. Examples are shown in the secrets setup section below.

Salt master configuration

Authentication

Configure the master’s own authentication, server connection and a persistent cache as described in the Node configuration section of the local configuration guide.

Credential orchestration

To allow minions to pull configuration and credentials from the Salt master, add this segment to the master configuration:

/etc/salt/master.d/peer_run.conf
peer_run:
  .*:
    - vault.get_config
    - vault.generate_new_token

Credential issuance

Reference the Token Role created during the prerequisites:

/etc/salt/master.d/vault.conf
vault:
  issue:
    type: token  # this is the default
    token:
      role_name: salt-master

Credential validity

For historical reasons, token issuance has very inefficient defaults. For each request to Vault, the minion requests a new token unless configured otherwise. It is generally recommended to raise the defaults:

vault:
  issue:
    token:
      params:
        explicit_max_ttl: 30  # Tokens are valid for 30s
        num_uses: 10          # Tokens are limited to 10 uses

Depending on how heavily you use Vault and whether you create dynamic leases such as database credentials, these parameters might have to be customized further. See issue:token:params for details.

Policies

Authenticated clients need associated authorizations to be useful. Policies describe the operations a client is allowed to perform.

By default, minions receive the following named policies:

  • saltstack/minions

  • saltstack/<minion_id>

Important

You need to create these policies yourself. Missing policies do not cause errors, but minions are left with the default permissions only if none of the assigned policies exist.

You can customize which policies are assigned to minions. They can be templated.

vault:
  policies:
    assign:
      - salt_minion
      - salt_minion_{minion}
      - salt_role_{pillar[roles]}
      # While it's theoretically possible to use {grains[roles]} here
      # for backwards-compatibility reasons, it's HIGHLY discouraged.
      # The minion reports grains itself, so a compromised minion would
      # be able to assign arbitrary roles to itself.

Complete example

/etc/salt/master.d/vault.conf
vault:
  auth:
    # This master authenticates with an AppRole, but
    # issues tokens
    method: approle
    role_id: e5a7b66e-5d08-da9c-7075-71984634b882
    secret_id: 841771dc-11c9-bbc7-bcac-6a3945a69cd9
  cache:
    backend: disk
  issue:
    type: token
    token:
      role_name: salt-master
      params:
        explicit_max_ttl: 30
        num_uses: 10
  policies:
    assign:
      - 'salt_minion'
      - 'salt_minion_{minion}'
      - 'salt_role_{pillar[roles]}'
  server:
    url: https://vault.example.com:8200

Secrets setup

Decide how you want to map minions to authorizations. A common pattern is to create policies based on minion IDs and minion roles, as shown in the complete example above. This example setup is continued here.

Mount the KV backend

Mount the Key/Value v2 backend to a path, e.g. salt:

vault secrets enable -path=salt -version=2 kv

Create secrets

Write a secret that is accessible to all minions:

vault kv put -mount=salt general/accessible_for_all_minions all_foo=bar

Write a secret that is accessible to any minion that has the db role:

vault kv put -mount=salt roles/db db_foo=baz

Write a secret that is accessible to a specific minion named elliott:

vault kv put -mount=salt minions/elliott minion_foo=quux

Create policies

Create the policies that map necessary authorizations.

Warning

If a secret path is used as a minion pillar, the minion must not have write access, otherwise a core security assumption in Salt is violated.

Important

Even if you only intend to use the secrets for minion pillars, you need to create minion policies. The master uses these policies to decide whether a minion should receive a specific pillar. The master token should not have access to secret paths itself. For details, see Pillar impersonation.

When issuing tokens, you cannot take advantage of minion metadata for templated Vault policies. You need to create all policies explicitly (consider automating this):

vault policy write salt_minion - <<'EOF'
path "salt/data/general/*" {
  capabilities = ["read"]
}
EOF

vault policy write salt_role_db - <<'EOF'
path "salt/data/roles/db" {
  capabilities = ["read"]
}
EOF
# + other roles as needed

vault policy write salt_minion_elliott - <<'EOF'
path "salt/data/minions/elliott" {
  capabilities = ["read"]
}
EOF
# + other minions as needed

Test access

Now you can test that the minion is able to read all secrets:

[root@master ~]# salt elliott vault_secret.read salt/general/accessible_for_all_minions
elliott:
    ----------
    all_foo: bar
[root@master ~]# salt elliott vault_secret.read salt/roles/db
elliott:
    ----------
    db_foo: baz
[root@master ~]# salt elliott vault_secret.read salt/minions/elliott
elliott:
    ----------
    minion_foo: quux

Also verify that minions without authorization to access these secrets can’t.

If reading fails, clear the minion’s cached Vault data, which forces new credentials to be requested, and try again:

salt elliott vault.clear_cache