Merge "Use secret_id's with vault-kv relation"
This commit is contained in:
@@ -6,3 +6,5 @@ authorize-charm:
|
|||||||
description: Token to use to authorize charm
|
description: Token to use to authorize charm
|
||||||
required:
|
required:
|
||||||
- token
|
- token
|
||||||
|
refresh-secrets:
|
||||||
|
description: Refresh secret_id's and re-issue retrieval tokens for secrets endpoints
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ import charm.vault as vault
|
|||||||
|
|
||||||
import charms.reactive
|
import charms.reactive
|
||||||
|
|
||||||
|
from charms.reactive.flags import set_flag
|
||||||
|
|
||||||
|
|
||||||
def authorize_charm_action(*args):
|
def authorize_charm_action(*args):
|
||||||
"""Create a role allowing the charm to perform certain vault actions.
|
"""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'])
|
role_id = vault.setup_charm_vault_access(action_config['token'])
|
||||||
hookenv.leader_set({vault.CHARM_ACCESS_ROLE_ID: role_id})
|
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
|
# Actions to function mapping, to allow for illegal python action names that
|
||||||
# can map to a python function.
|
# can map to a python function.
|
||||||
ACTIONS = {
|
ACTIONS = {
|
||||||
"authorize-charm": authorize_charm_action,
|
"authorize-charm": authorize_charm_action,
|
||||||
|
"refresh-secrets": refresh_secrets,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
1
src/actions/refresh-secrets
Symbolic link
1
src/actions/refresh-secrets
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
actions.py
|
||||||
@@ -300,6 +300,22 @@ def configure_approle(client, name, cidr, policies):
|
|||||||
token_ttl='60s',
|
token_ttl='60s',
|
||||||
token_max_ttl='60s',
|
token_max_ttl='60s',
|
||||||
policies=policies,
|
policies=policies,
|
||||||
bind_secret_id='false',
|
bind_secret_id='true',
|
||||||
bound_cidr_list=cidr)
|
bound_cidr_list=cidr)
|
||||||
return client.get_role_id(name)
|
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']
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ from charms.reactive import (
|
|||||||
when,
|
when,
|
||||||
when_file_changed,
|
when_file_changed,
|
||||||
when_not,
|
when_not,
|
||||||
|
when_any,
|
||||||
)
|
)
|
||||||
|
|
||||||
from charms.reactive.relations import (
|
from charms.reactive.relations import (
|
||||||
@@ -371,7 +372,7 @@ def file_change_auto_unlock_mode():
|
|||||||
|
|
||||||
|
|
||||||
@when('leadership.is_leader')
|
@when('leadership.is_leader')
|
||||||
@when('endpoint.secrets.new-request')
|
@when_any('endpoint.secrets.new-request', 'secrets.refresh')
|
||||||
def configure_secrets_backend():
|
def configure_secrets_backend():
|
||||||
""" Process requests for setup and access to simple kv secret backends """
|
""" Process requests for setup and access to simple kv secret backends """
|
||||||
@tenacity.retry(wait=tenacity.wait_exponential(multiplier=1, max=10),
|
@tenacity.retry(wait=tenacity.wait_exponential(multiplier=1, max=10),
|
||||||
@@ -401,7 +402,8 @@ def configure_secrets_backend():
|
|||||||
return
|
return
|
||||||
client.auth_approle(charm_role_id)
|
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()
|
requests = secrets.requests()
|
||||||
|
|
||||||
# Configure KV secret backends
|
# Configure KV secret backends
|
||||||
@@ -412,6 +414,8 @@ def configure_secrets_backend():
|
|||||||
continue
|
continue
|
||||||
vault.configure_secret_backend(client, name=backend)
|
vault.configure_secret_backend(client, name=backend)
|
||||||
|
|
||||||
|
refresh_secrets = is_flag_set('secrets.refresh')
|
||||||
|
|
||||||
# Configure AppRoles for application unit access
|
# Configure AppRoles for application unit access
|
||||||
for request in requests:
|
for request in requests:
|
||||||
# NOTE: backends must start with charm-
|
# NOTE: backends must start with charm-
|
||||||
@@ -438,16 +442,27 @@ def configure_secrets_backend():
|
|||||||
hostname=hostname)
|
hostname=hostname)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
cidr = '{}/32'.format(access_address)
|
||||||
|
new_role = (approle_name not in client.list_roles())
|
||||||
|
|
||||||
approle_id = vault.configure_approle(
|
approle_id = vault.configure_approle(
|
||||||
client,
|
client,
|
||||||
name=approle_name,
|
name=approle_name,
|
||||||
cidr='{}/32'.format(access_address),
|
cidr=cidr,
|
||||||
policies=[policy_name])
|
policies=[policy_name])
|
||||||
|
|
||||||
secrets.set_role_id(unit=unit,
|
if new_role or refresh_secrets:
|
||||||
role_id=approle_id)
|
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('endpoint.secrets.new-request')
|
||||||
|
clear_flag('secrets.refresh')
|
||||||
|
|
||||||
|
|
||||||
@when('secrets.connected')
|
@when('secrets.connected')
|
||||||
|
|||||||
@@ -342,6 +342,20 @@ class TestLibCharmVault(unit_tests.test_utils.CharmTestCase):
|
|||||||
vault.configure_secret_backend(hvac_client, 'secrets')
|
vault.configure_secret_backend(hvac_client, 'secrets')
|
||||||
hvac_client.enable_secret_backend.assert_not_called()
|
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):
|
def test_configure_policy(self):
|
||||||
hvac_client = mock.MagicMock()
|
hvac_client = mock.MagicMock()
|
||||||
vault.configure_policy(hvac_client, 'test-policy', 'test-hcl')
|
vault.configure_policy(hvac_client, 'test-policy', 'test-hcl')
|
||||||
@@ -365,7 +379,7 @@ class TestLibCharmVault(unit_tests.test_utils.CharmTestCase):
|
|||||||
token_ttl='60s',
|
token_ttl='60s',
|
||||||
token_max_ttl='60s',
|
token_max_ttl='60s',
|
||||||
policies=['test-policy'],
|
policies=['test-policy'],
|
||||||
bind_secret_id='false',
|
bind_secret_id='true',
|
||||||
bound_cidr_list='10.5.0.20/32'
|
bound_cidr_list='10.5.0.20/32'
|
||||||
)
|
)
|
||||||
hvac_client.get_role_id.assert_called_with('test-role')
|
hvac_client.get_role_id.assert_called_with('test-role')
|
||||||
|
|||||||
@@ -535,6 +535,8 @@ class TestHandlers(unit_tests.test_utils.CharmTestCase):
|
|||||||
_vault.configure_approle.side_effect = ['role_a', 'role_b']
|
_vault.configure_approle.side_effect = ['role_a', 'role_b']
|
||||||
self.is_flag_set.return_value = False
|
self.is_flag_set.return_value = False
|
||||||
_vault.get_api_url.return_value = "http://vault:8200"
|
_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()
|
handlers.configure_secrets_backend()
|
||||||
|
|
||||||
@@ -560,12 +562,17 @@ class TestHandlers(unit_tests.test_utils.CharmTestCase):
|
|||||||
|
|
||||||
secrets_interface.set_role_id.assert_has_calls([
|
secrets_interface.set_role_id.assert_has_calls([
|
||||||
mock.call(unit=mock.ANY,
|
mock.call(unit=mock.ANY,
|
||||||
role_id='role_a'),
|
role_id='role_a',
|
||||||
|
token='mysecret'),
|
||||||
mock.call(unit=mock.ANY,
|
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')
|
@mock.patch.object(handlers, 'vault')
|
||||||
def send_vault_url_and_ca(self, _vault):
|
def send_vault_url_and_ca(self, _vault):
|
||||||
|
|||||||
Reference in New Issue
Block a user