Skip to content

Commit

Permalink
Merge pull request #59 from crayfishx/feature/approle
Browse files Browse the repository at this point in the history
Add AppRole Authentication
  • Loading branch information
bastelfreak authored Sep 27, 2022
2 parents ed23160 + 1a27318 commit d00dda5
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 14 deletions.
43 changes: 37 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ sees it. See [this blog
post](https://puppet.com/blog/secret-agents-man-secrets-store-integrations-puppet-6)
for more information and other secret store integrations.

Authentication with Vault is achieved via Puppet certificates. See the
Vault documentation for more information on setting up finer grained access
controls.
Authentication with Vault is achieved via Puppet certificates or by using the
Approle authentication method. See the Vault documentation for more information
on setting up finer grained access controls.

## Requirements

Expand All @@ -41,13 +41,13 @@ data.

## Setup

The `vault_lookup` function uses the Puppet agent's certificates in order to
### To set up Vault to use the Puppet Server CA cert:

The `vault_lookup` function can use the Puppet agent's certificates in order to
authenticate to the Vault server; this means that before any agents contact a
Vault server, you must configure the Vault server with the Puppet Server's CA
certificate, and Vault must be part of the same certificate infrastructure.

To set up Vault to use the Puppet Server CA cert:

1. Set up Vault using Puppet certs (if not already set up this way)
If the Vault host has a Puppet agent on it then you can just use the existing
certificates. Otherwise generate a new certificate with `puppetserver ca` and
Expand Down Expand Up @@ -84,6 +84,37 @@ $ vault write auth/cert/certs/puppetserver \
Once the certificate has been uploaded, any Puppet agent with a signed
certificate will be able to authenticate with Vault.

### To use AppRole Authentication

`vault_lookup` can also use AppRole authentication to authenticate against Vault with a valid `role_id` and `secret_id`. See [The Approle Vault Documentation](https://www.vaultproject.io/docs/auth/approle) for detailed explanations of creating and obtaining the security credentials. You will need the Role ID (non sensitive) and the Secret ID (sensitive!). The Secret ID can be provided as an argument to the `vault_lookup` function but it is recommended to pass this as an environment variable and not bake this into code.

Example:
```
# vault read auth/approle/role/puppet/role-id
Key Value
--- -----
role_id XXXXX-XXXX-XXX-XX-XXXXXXXXXX
```

```
# vault write -f auth/approle/role/puppet/secret-id
Key Value
--- -----
secret_id YYYYY-YYYY-YYY-YY-YYYYYYYYYYY
secret_id_accessor ZZZZZ-ZZZZZZ-ZZZZZZ-ZZZZZZZZ-ZZZZ
secret_id_ttl 0s
```

In order to use the AppRole auth engine you must set the `VAULT_AUTH_METHOD` environment variable (defaults to cert) to `approle`

```
export VAULT_AUTH_METHOD=approle
export VAULT_ROLE_ID=XXXXX-XXXX-XXX-XX-XXXXXXXXXX
export VAULT_SECRET_ID=YYYYY-YYYY-YYY-YY-YYYYYYYYYYY
```



## Usage

Install this module as you would in any other; the necessary code will
Expand Down
68 changes: 60 additions & 8 deletions lib/puppet/functions/vault_lookup/lookup.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,31 @@
optional_param 'String', :vault_cert_role
optional_param 'String', :vault_namespace
optional_param 'String', :vault_key
optional_param 'Enum["cert", "approle"]', :vault_auth_method
optional_param 'String', :vault_role_id
optional_param 'String', :vault_secret_id
optional_param 'Optional[String]', :vault_approle_path_segment
return_type 'Sensitive'
end

DEFAULT_CERT_PATH_SEGMENT = 'v1/auth/cert/'.freeze
DEFAULT_APPROLE_PATH_SEGMENT = 'v1/auth/approle/'.freeze

def lookup(path,
vault_url = nil,
vault_cert_path_segment = nil,
vault_cert_role = nil,
vault_namespace = nil,
vault_key = nil)
vault_key = nil,
vault_auth_method = nil,
vault_role_id = nil,
vault_secret_id = nil,
vault_approle_path_segment = nil)

if vault_auth_method.nil?
vault_auth_method = ENV['VAULT_AUTH_METHOD'] || 'cert'
end

if vault_url.nil?
Puppet.debug 'No Vault address was set on function, defaulting to value from VAULT_ADDR env value'
vault_url = ENV['VAULT_ADDR']
Expand All @@ -27,10 +41,21 @@ def lookup(path,
vault_namespace = ENV['VAULT_NAMESPACE']
end

if vault_role_id.nil?
vault_role_id = ENV['VAULT_ROLE_ID']
end

if vault_secret_id.nil?
vault_secret_id = ENV['VAULT_SECRET_ID']
end

if vault_cert_path_segment.nil?
vault_cert_path_segment = DEFAULT_CERT_PATH_SEGMENT
end

if vault_approle_path_segment.nil?
vault_approle_path_segment = DEFAULT_APPROLE_PATH_SEGMENT
end
vault_base_uri = URI(vault_url)
# URI is used here to parse the vault_url into a host string
# and port; it's possible to generate a URI::Generic when a scheme
Expand All @@ -39,11 +64,24 @@ def lookup(path,
raise Puppet::Error, "Unable to parse a hostname from #{vault_url}" unless vault_base_uri.hostname

client = Puppet.runtime[:http]
token = get_cert_auth_token(client,
vault_base_uri,
vault_cert_path_segment,
vault_cert_role,
vault_namespace)

case vault_auth_method
when 'cert'
token = get_cert_auth_token(client,
vault_base_uri,
vault_cert_path_segment,
vault_cert_role,
vault_namespace)
when 'approle'
raise Puppet::Error, 'vault_role_id and VAULT_ROLE_ID are both nil' if vault_role_id.nil?
raise Puppet::Error, 'vault_secret_id and VAULT_SECRET_ID are both nil' if vault_secret_id.nil?
token = get_approle_auth_token(client,
vault_base_uri,
vault_approle_path_segment,
vault_role_id,
vault_secret_id,
vault_namespace)
end

secret_uri = vault_base_uri + "/v1/#{path.delete_prefix('/')}"
data = get_secret(client,
Expand Down Expand Up @@ -86,15 +124,29 @@ def get_secret(client, uri, token, namespace, key)

def get_cert_auth_token(client, vault_url, vault_cert_path_segment, vault_cert_role, vault_namespace)
role_data = auth_login_body(vault_cert_role)
headers = { 'Content-Type' => 'application/json', 'X-Vault-Namespace' => vault_namespace }.delete_if { |_key, value| value.nil? }
segment = if vault_cert_path_segment.end_with?('/')
vault_cert_path_segment
else
vault_cert_path_segment + '/'
end
login_url = vault_url + segment + 'login'
get_token(client, login_url, role_data, vault_namespace)
end

def get_approle_auth_token(client, vault_url, path_segment, vault_role_id, vault_secret_id, vault_namespace)
vault_request_data = {
role_id: vault_role_id,
secret_id: vault_secret_id
}.to_json

login_url = vault_url + path_segment + 'login'
get_token(client, login_url, vault_request_data, vault_namespace)
end

def get_token(client, login_url, request_data, vault_namespace)
headers = { 'Content-Type' => 'application/json', 'X-Vault-Namespace' => vault_namespace }.delete_if { |_key, value| value.nil? }
response = client.post(login_url,
role_data,
request_data,
headers: headers,
options: { include_system_store: true })
unless response.success?
Expand Down

0 comments on commit d00dda5

Please sign in to comment.