Merge "Use secret_id's with vault-kv relation"

This commit is contained in:
Zuul 2018-05-15 09:57:12 +00:00 committed by Gerrit Code Review
commit 9c78a515e1
7 changed files with 77 additions and 10 deletions

View File

@ -6,3 +6,5 @@ authorize-charm:
description: Token to use to authorize charm
required:
- token
refresh-secrets:
description: Refresh secret_id's and re-issue retrieval tokens for secrets endpoints

View File

@ -29,6 +29,8 @@ import charm.vault as vault
import charms.reactive
from charms.reactive.flags import set_flag
def authorize_charm_action(*args):
"""Create a role allowing the charm to perform certain vault actions.
@ -39,10 +41,20 @@ def authorize_charm_action(*args):
role_id = vault.setup_charm_vault_access(action_config['token'])
hookenv.leader_set({vault.CHARM_ACCESS_ROLE_ID: role_id})
def refresh_secrets(*args):
"""Refresh secret_id's and re-issue tokens for secret_id retrieval
on secrets end-points"""
if not hookenv.is_leader():
hookenv.action_fail('Please run action on lead unit')
set_flag('secrets.refresh')
# Actions to function mapping, to allow for illegal python action names that
# can map to a python function.
ACTIONS = {
"authorize-charm": authorize_charm_action,
"refresh-secrets": refresh_secrets,
}

1
src/actions/refresh-secrets Symbolic link
View File

@ -0,0 +1 @@
actions.py

View File

@ -300,6 +300,22 @@ def configure_approle(client, name, cidr, policies):
token_ttl='60s',
token_max_ttl='60s',
policies=policies,
bind_secret_id='false',
bind_secret_id='true',
bound_cidr_list=cidr)
return client.get_role_id(name)
def generate_role_secret_id(client, name, cidr):
"""Generate a new secret_id for an AppRole
:param client: Vault client
:ptype client: hvac.Client
:param name: Name of role
:ptype name: str
:param cidr: Network address of remote unit
:ptype cidr: str
:returns: Vault token to retrieve the response-wrapped response
:rtype: str"""
response = client.write('auth/approle/role/{}/secret-id'.format(name),
wrap_ttl='1h', cidr_list=cidr)
return response['wrap_info']['token']

View File

@ -45,6 +45,7 @@ from charms.reactive import (
when,
when_file_changed,
when_not,
when_any,
)
from charms.reactive.relations import (
@ -371,7 +372,7 @@ def file_change_auto_unlock_mode():
@when('leadership.is_leader')
@when('endpoint.secrets.new-request')
@when_any('endpoint.secrets.new-request', 'secrets.refresh')
def configure_secrets_backend():
""" Process requests for setup and access to simple kv secret backends """
@tenacity.retry(wait=tenacity.wait_exponential(multiplier=1, max=10),
@ -401,7 +402,8 @@ def configure_secrets_backend():
return
client.auth_approle(charm_role_id)
secrets = endpoint_from_flag('endpoint.secrets.new-request')
secrets = (endpoint_from_flag('endpoint.secrets.new-request') or
endpoint_from_flag('secrets.connected'))
requests = secrets.requests()
# Configure KV secret backends
@ -412,6 +414,8 @@ def configure_secrets_backend():
continue
vault.configure_secret_backend(client, name=backend)
refresh_secrets = is_flag_set('secrets.refresh')
# Configure AppRoles for application unit access
for request in requests:
# NOTE: backends must start with charm-
@ -438,16 +442,27 @@ def configure_secrets_backend():
hostname=hostname)
)
cidr = '{}/32'.format(access_address)
new_role = (approle_name not in client.list_roles())
approle_id = vault.configure_approle(
client,
name=approle_name,
cidr='{}/32'.format(access_address),
cidr=cidr,
policies=[policy_name])
secrets.set_role_id(unit=unit,
role_id=approle_id)
if new_role or refresh_secrets:
wrapped_secret = vault.generate_role_secret_id(
client,
name=approle_name,
cidr=cidr
)
secrets.set_role_id(unit=unit,
role_id=approle_id,
token=wrapped_secret)
clear_flag('endpoint.secrets.new-request')
clear_flag('secrets.refresh')
@when('secrets.connected')

View File

@ -342,6 +342,20 @@ class TestLibCharmVault(unit_tests.test_utils.CharmTestCase):
vault.configure_secret_backend(hvac_client, 'secrets')
hvac_client.enable_secret_backend.assert_not_called()
def test_generate_role_secret_id(self):
hvac_client = mock.MagicMock()
hvac_client.write.return_value = {'wrap_info': {'token': 'foo'}}
self.assertEqual(
vault.generate_role_secret_id(hvac_client,
'testrole',
'10.5.10.10/32'),
'foo'
)
hvac_client.write.assert_called_with(
'auth/approle/role/testrole/secret-id',
wrap_ttl='1h', cidr_list='10.5.10.10/32'
)
def test_configure_policy(self):
hvac_client = mock.MagicMock()
vault.configure_policy(hvac_client, 'test-policy', 'test-hcl')
@ -365,7 +379,7 @@ class TestLibCharmVault(unit_tests.test_utils.CharmTestCase):
token_ttl='60s',
token_max_ttl='60s',
policies=['test-policy'],
bind_secret_id='false',
bind_secret_id='true',
bound_cidr_list='10.5.0.20/32'
)
hvac_client.get_role_id.assert_called_with('test-role')

View File

@ -535,6 +535,8 @@ class TestHandlers(unit_tests.test_utils.CharmTestCase):
_vault.configure_approle.side_effect = ['role_a', 'role_b']
self.is_flag_set.return_value = False
_vault.get_api_url.return_value = "http://vault:8200"
hvac_client.list_roles.return_value = []
_vault.generate_role_secret_id.return_value = 'mysecret'
handlers.configure_secrets_backend()
@ -560,12 +562,17 @@ class TestHandlers(unit_tests.test_utils.CharmTestCase):
secrets_interface.set_role_id.assert_has_calls([
mock.call(unit=mock.ANY,
role_id='role_a'),
role_id='role_a',
token='mysecret'),
mock.call(unit=mock.ANY,
role_id='role_b'),
role_id='role_b',
token='mysecret'),
])
self.clear_flag.assert_called_once_with('endpoint.secrets.new-request')
self.clear_flag.assert_has_calls([
mock.call('endpoint.secrets.new-request'),
mock.call('secrets.refresh'),
])
@mock.patch.object(handlers, 'vault')
def send_vault_url_and_ca(self, _vault):