Update hvac library to latest version

Update deprecated method calls where possible,
and use new methods instead of lower level read/write calls.

Change-Id: I991435cdf8d36016e75c46823ec47f3290a42fe4
This commit is contained in:
Samuel Walladge 2022-06-30 11:39:18 +09:30
parent 4fccd71076
commit 68fecd9ba8
9 changed files with 342 additions and 349 deletions

1
.gitignore vendored
View File

@ -1,6 +1,7 @@
*~
build/*
.tox/*
src/.tox/*
.stestr/*
__pycache__
.unit-state.db

View File

@ -179,8 +179,8 @@ def enable_approle_auth(client):
:param client: Vault client
:type client: hvac.Client"""
if 'approle/' not in client.list_auth_backends():
client.enable_auth_backend('approle')
if 'approle/' not in client.sys.list_auth_methods():
client.sys.enable_auth_method('approle')
def create_local_charm_access_role(client, policies):
@ -192,14 +192,15 @@ def create_local_charm_access_role(client, policies):
:type policies: [str, str, ...]
:returns: Id of created role
:rtype: str"""
client.create_role(
client.auth.approle.create_or_update_approle(
CHARM_ACCESS_ROLE,
token_ttl='60s',
token_max_ttl='60s',
policies=policies,
token_policies=policies,
bind_secret_id='false',
bound_cidr_list='127.0.0.1/32')
return client.get_role_id(CHARM_ACCESS_ROLE)
token_bound_cidrs=['127.0.0.1/32'])
response = client.auth.approle.read_role_id(CHARM_ACCESS_ROLE)
return response["data"]["role_id"]
def setup_charm_vault_access(token=None):
@ -216,7 +217,7 @@ def setup_charm_vault_access(token=None):
token=token)
enable_approle_auth(client)
policies = [CHARM_POLICY_NAME]
client.set_policy(CHARM_POLICY_NAME, CHARM_POLICY)
client.sys.create_or_update_policy(CHARM_POLICY_NAME, CHARM_POLICY)
return create_local_charm_access_role(client, policies=policies)
@ -254,8 +255,7 @@ def get_local_client():
if not app_role_id:
hookenv.log('Could not retrieve app_role_id', level=hookenv.DEBUG)
raise VaultNotReady("Cannot initialise local client")
client = hvac.Client(url=VAULT_LOCALHOST_URL)
client.auth_approle(app_role_id)
client.auth.approle.login(app_role_id)
return client
@ -312,7 +312,7 @@ def initialize_vault(shares=1, threshold=1):
:type threshold: int
"""
client = get_client(url=VAULT_LOCALHOST_URL)
result = client.initialize(shares, threshold)
result = client.sys.initialize(shares, threshold)
client.token = result['root_token']
hookenv.leader_set(
root_token=result['root_token'],
@ -326,7 +326,7 @@ def unseal_vault(keys=None):
if not keys:
keys = json.loads(hookenv.leader_get()['keys'])
for key in keys:
client.unseal(key)
client.sys.submit_unseal_key(key)
def can_restart():
@ -342,9 +342,9 @@ def can_restart():
safe_restart = True
else:
client = get_client(url=VAULT_LOCALHOST_URL)
if not client.is_initialized():
if not client.sys.is_initialized():
safe_restart = True
elif client.is_sealed():
elif client.sys.is_sealed():
safe_restart = True
hookenv.log(
"Safe to restart: {}".format(safe_restart),
@ -359,11 +359,13 @@ def configure_secret_backend(client, name):
:ptype client: hvac.Client
:param name: Name of backend to enable
:ptype name: str"""
if '{}/'.format(name) not in client.list_secret_backends():
client.enable_secret_backend(backend_type='kv',
description='Charm created KV backend',
mount_point=name,
options={'version': 1})
if '{}/'.format(name) not in client.sys.list_mounted_secrets_engines():
client.sys.enable_secrets_engine(
backend_type='kv',
description='Charm created KV backend',
path=name,
options={'version': 1}
)
def configure_policy(client, name, hcl):
@ -375,7 +377,7 @@ def configure_policy(client, name, hcl):
:ptype name: str
:param hcl: Vault policy HCL
:ptype hcl: str"""
client.set_policy(name, hcl)
client.sys.create_or_update_policy(name, hcl)
def configure_approle(client, name, cidr, policies):
@ -391,14 +393,14 @@ def configure_approle(client, name, cidr, policies):
:ptype policies: [str, str, ...]
:returns: Id of created role
:rtype: str"""
client.create_role(
client.auth.approle.create_or_update_approle(
name,
token_ttl='60s',
token_max_ttl='60s',
policies=policies,
token_policies=policies,
bind_secret_id='true',
bound_cidr_list=cidr)
return client.get_role_id(name)
token_bound_cidrs=[cidr])
return client.auth.approle.read_role_id(name)["data"]["role_id"]
def generate_role_secret_id(client, name, cidr):
@ -412,6 +414,9 @@ def generate_role_secret_id(client, name, cidr):
:ptype cidr: str
:returns: Vault token to retrieve the response-wrapped response
:rtype: str"""
# NOTE: cannot use client.auth.approle.generate_secret_id()
# because that doesn't support wrap_ttl
# https://github.com/hvac/hvac/issues/683
response = client.write('auth/approle/role/{}/secret-id'.format(name),
wrap_ttl='1h', cidr_list=cidr)
return response['wrap_info']['token']
@ -423,7 +428,7 @@ def is_backend_mounted(client, name):
:returns: Whether mount point is in use
:rtype: bool
"""
return '{}/'.format(name) in client.list_secret_backends()
return '{}/'.format(name) in client.sys.list_mounted_secrets_engines()
def vault_ready_for_clients():
@ -433,8 +438,8 @@ def vault_ready_for_clients():
reraise=True)
def _check_vault_status(client):
if (not host.service_running('vault') or
not client.is_initialized() or
client.is_sealed()):
not client.sys.is_initialized() or
client.sys.is_sealed()):
return False
return True

View File

@ -28,10 +28,10 @@ def configure_pki_backend(client, name, ttl=None, max_ttl=None):
:type ttl: str
"""
if not vault.is_backend_mounted(client, name):
client.enable_secret_backend(
client.sys.enable_secrets_engine(
backend_type='pki',
description='Charm created PKI backend',
mount_point=name,
path=name,
# Default ttl to 10 years
config={
'default_lease_ttl': ttl or '8759h',
@ -43,12 +43,12 @@ def disable_pki_backend():
"""
client = vault.get_local_client()
if vault.is_backend_mounted(client, CHARM_PKI_MP):
client.delete('{}/root'.format(CHARM_PKI_MP))
client.delete('{}/roles/{}'.format(CHARM_PKI_MP,
CHARM_PKI_ROLE_CLIENT))
client.delete('{}/roles/{}'.format(CHARM_PKI_MP,
CHARM_PKI_ROLE))
client.disable_secret_backend(CHARM_PKI_MP)
client.secrets.pki.delete_root(mount_point=CHARM_PKI_MP)
client.secrets.pki.delete_role(CHARM_PKI_ROLE_CLIENT,
mount_point=CHARM_PKI_MP)
client.secrets.pki.delete_role(CHARM_PKI_ROLE,
mount_point=CHARM_PKI_MP)
client.sys.disable_secrets_engine(CHARM_PKI_MP)
def tune_pki_backend(ttl=None, max_ttl=None):
@ -59,9 +59,8 @@ def tune_pki_backend(ttl=None, max_ttl=None):
"""
client = vault.get_local_client()
if vault.is_backend_mounted(client, CHARM_PKI_MP):
client.tune_secret_backend(
backend_type='pki',
mount_point=CHARM_PKI_MP,
client.sys.tune_mount_configuration(
path=CHARM_PKI_MP,
default_lease_ttl=ttl or '8759h',
max_lease_ttl=max_ttl or '87600h')
@ -72,7 +71,7 @@ def is_ca_ready(client, name, role):
:returns: Whether CA is ready
:rtype: bool
"""
return client.read('{}/roles/{}'.format(name, role)) is not None
return client.secrets.pki.read_role(role, mount_point=name) is not None
def get_chain(name=None):
@ -84,7 +83,9 @@ def get_chain(name=None):
client = vault.get_local_client()
if not name:
name = CHARM_PKI_MP
return client.read('{}/cert/ca_chain'.format(name))['data']['certificate']
response = client.secrets.pki.read_certificate('ca_chain',
mount_point=name)
return response['data']['certificate']
def get_ca():
@ -120,9 +121,7 @@ def generate_certificate(cert_type, common_name, sans, ttl, max_ttl):
else:
raise vault.VaultInvalidRequest('Unsupported cert_type: '
'{}'.format(cert_type))
config = {
'common_name': common_name,
}
config = {}
if sans:
ip_sans, alt_names = sort_sans(sans)
if ip_sans:
@ -130,8 +129,12 @@ def generate_certificate(cert_type, common_name, sans, ttl, max_ttl):
if alt_names:
config['alt_names'] = ','.join(alt_names)
try:
response = client.write('{}/issue/{}'.format(CHARM_PKI_MP, role),
**config)
response = client.secrets.pki.generate_certificate(
role,
common_name,
extra_params=config,
mount_point=CHARM_PKI_MP,
)
if not response['data']:
raise vault.VaultError(response.get('warnings', 'unknown error'))
except hvac.exceptions.InvalidRequest as e:
@ -168,6 +171,11 @@ def get_csr(ttl=None, common_name=None, locality=None,
"""
client = vault.get_local_client()
configure_pki_backend(client, CHARM_PKI_MP)
if common_name is None:
common_name = (
"Vault Intermediate Certificate Authority "
"({})".format(CHARM_PKI_MP)
)
config = {
# Year - 1 hour
'ttl': ttl or '87599h',
@ -175,14 +183,14 @@ def get_csr(ttl=None, common_name=None, locality=None,
'province': province,
'ou': organizational_unit,
'organization': organization,
'common_name': common_name or ("Vault Intermediate Certificate "
"Authority " "({})".format(CHARM_PKI_MP)
),
'locality': locality}
config = {k: v for k, v in config.items() if v}
csr_info = client.write(
'{}/intermediate/generate/internal'.format(CHARM_PKI_MP),
**config)
csr_info = client.secrets.pki.generate_intermediate(
'internal',
common_name,
extra_params=config,
mount_point=CHARM_PKI_MP,
)
if not csr_info['data']:
raise vault.VaultError(csr_info.get('warnings', 'unknown error'))
return csr_info['data']['csr']
@ -210,17 +218,20 @@ def upload_signed_csr(pem, allowed_domains, allow_subdomains=True,
client = vault.get_local_client()
# Set the intermediate certificate authorities signing certificate to the
# signed certificate.
# (hvac module doesn't expose a method for this, hence the _post call)
client._post(
'v1/{}/intermediate/set-signed'.format(CHARM_PKI_MP),
json={'certificate': pem.rstrip()})
client.secrets.pki.set_signed_intermediate(
pem.rstrip(),
mount_point=CHARM_PKI_MP
)
# Generated certificates can have the CRL location and the location of the
# issuing certificate encoded.
addr = vault.get_access_address()
client.write(
'{}/config/urls'.format(CHARM_PKI_MP),
issuing_certificates="{}/v1/{}/ca".format(addr, CHARM_PKI_MP),
crl_distribution_points="{}/v1/{}/crl".format(addr, CHARM_PKI_MP)
client.secrets.pki.set_urls(
{
"issuing_certificates": "{}/v1/{}/ca".format(addr, CHARM_PKI_MP),
"crl_distribution_points":
"{}/v1/{}/crl".format(addr, CHARM_PKI_MP),
},
mount_point=CHARM_PKI_MP
)
# Configure a role which maps to a policy for accessing this pki
if not max_ttl:
@ -271,23 +282,28 @@ def generate_root_ca(ttl='87599h', allow_any_name=True, allowed_domains=None,
if is_ca_ready(client, CHARM_PKI_MP, CHARM_PKI_ROLE):
raise vault.VaultError('PKI CA already configured')
config = {
'common_name': ("Vault Root Certificate Authority "
"({})".format(CHARM_PKI_MP)),
'ttl': ttl,
}
csr_info = client.write(
'{}/root/generate/internal'.format(CHARM_PKI_MP),
**config)
common_name = "Vault Root Certificate Authority ({})".format(CHARM_PKI_MP)
csr_info = client.secrets.pki.generate_root(
'internal',
common_name,
extra_params=config,
mount_point=CHARM_PKI_MP,
)
if not csr_info['data']:
raise vault.VaultError(csr_info.get('warnings', 'unknown error'))
cert = csr_info['data']['certificate']
# Generated certificates can have the CRL location and the location of the
# issuing certificate encoded.
addr = vault.get_access_address()
client.write(
'{}/config/urls'.format(CHARM_PKI_MP),
issuing_certificates="{}/v1/{}/ca".format(addr, CHARM_PKI_MP),
crl_distribution_points="{}/v1/{}/crl".format(addr, CHARM_PKI_MP)
client.secrets.pki.set_urls(
{
"issuing_certificates": "{}/v1/{}/ca".format(addr, CHARM_PKI_MP),
"crl_distribution_points":
"{}/v1/{}/crl".format(addr, CHARM_PKI_MP),
},
mount_point=CHARM_PKI_MP
)
write_roles(client,
@ -318,23 +334,33 @@ def sort_sans(sans):
def write_roles(client, **kwargs):
# Configure a role for using this PKI to issue server certs
client.write(
'{}/roles/{}'.format(CHARM_PKI_MP, CHARM_PKI_ROLE),
server_flag=True,
**kwargs)
client.secrets.pki.create_or_update_role(
CHARM_PKI_ROLE,
extra_params={
'server_flag': True,
**kwargs,
},
mount_point=CHARM_PKI_MP,
)
# Configure a role for using this PKI to issue client-only certs
client.write(
'{}/roles/{}'.format(CHARM_PKI_MP, CHARM_PKI_ROLE_CLIENT),
server_flag=False, # client certs cannot be used as server certs
**kwargs)
client.secrets.pki.create_or_update_role(
CHARM_PKI_ROLE_CLIENT,
extra_params={
# client certs cannot be used as server certs
'server_flag': False,
**kwargs,
},
mount_point=CHARM_PKI_MP,
)
def update_roles(**kwargs):
client = vault.get_local_client()
# local and local-client contain the same data except for server_flag,
# so we only need to read one, but update both
local = client.read(
'{}/roles/{}'.format(CHARM_PKI_MP, CHARM_PKI_ROLE))['data']
local = client.secrets.pki.read_role(
CHARM_PKI_ROLE, mount_point=CHARM_PKI_MP
)['data']
# the reason we handle as kwargs here is because updating n-1 fields
# causes all the others to reset. Therefore we always need to read what
# the current values of all fields are, and apply all of them as well

View File

@ -505,8 +505,8 @@ def configure_secrets_backend():
reraise=True)
def _check_vault_status(client):
if (not service_running('vault') or
not client.is_initialized() or
client.is_sealed()):
not client.sys.is_initialized() or
client.sys.is_sealed()):
return False
return True
@ -525,7 +525,7 @@ def configure_secrets_backend():
log('Charm access to vault not configured, deferring'
' secrets backend setup', level=DEBUG)
return
client.auth_approle(charm_role_id)
client.auth.approle.login(charm_role_id)
secrets = (endpoint_from_flag('endpoint.secrets.new-request') or
endpoint_from_flag('secrets.connected'))
@ -568,7 +568,7 @@ def configure_secrets_backend():
)
cidr = '{}/32'.format(access_address)
new_role = (approle_name not in client.list_roles())
new_role = (approle_name not in client.auth.approle.list_roles())
approle_id = vault.configure_approle(
client,
@ -965,7 +965,7 @@ def publish_ca_info():
return
client = vault.get_client(url=vault.VAULT_LOCALHOST_URL)
tls = endpoint_from_flag('certificates.available')
if client.is_sealed():
if client.sys.is_sealed():
log("Unable to publish ca info, service sealed.")
else:
tls.set_ca(vault_pki.get_ca())
@ -1165,7 +1165,7 @@ def tune_pki_backend_config_changed():
set_flag('failed.to.start')
return
client = vault.get_client(url=vault.VAULT_LOCALHOST_URL)
if client.is_sealed():
if client.sys.is_sealed():
log("Unable to tune pki backend, service sealed.")
else:
ttl = config()['default-ttl']

View File

@ -1,5 +1,5 @@
netifaces
hvac<0.7.0
hvac<0.12.0
# for xenial support, tenacity 8.0.0+ drops support for py35
tenacity<8.0.0
pbr

View File

@ -50,5 +50,5 @@ tenacity # vault
pbr==5.6.0 # vault
cryptography<3.4 # vault, keystone-saml-mellon
lxml # keystone-saml-mellon
hvac # vault, barbican-vault
hvac<0.12.0 # vault, barbican-vault
psutil # cinder-lvm

View File

@ -25,9 +25,9 @@ class TestLibCharmVault(unit_tests.test_utils.CharmTestCase):
def test_enable_approle_auth(self):
client_mock = mock.MagicMock()
client_mock.list_auth_backends.return_value = []
client_mock.sys.list_auth_methods.return_value = []
vault.enable_approle_auth(client_mock)
client_mock.enable_auth_backend.assert_called_once_with('approle')
client_mock.sys.enable_auth_method.assert_called_once_with('approle')
def test_enable_approle_auth_mounted(self):
client_mock = mock.MagicMock()
@ -37,17 +37,19 @@ class TestLibCharmVault(unit_tests.test_utils.CharmTestCase):
def test_create_local_charm_access_role(self):
client_mock = mock.MagicMock()
client_mock.get_role_id.return_value = '123'
client_mock.auth.approle.read_role_id.return_value = {
'data': {'role_id': '123'}}
policies = ['policy1', 'pilicy2']
role_id = vault.create_local_charm_access_role(client_mock, policies)
self.assertEqual(role_id, '123')
client_mock.create_role.assert_called_once_with(
'local-charm-access',
bind_secret_id='false',
bound_cidr_list='127.0.0.1/32',
policies=['policy1', 'pilicy2'],
token_max_ttl='60s',
token_ttl='60s')
client_mock.auth.approle.create_or_update_approle.\
assert_called_once_with(
'local-charm-access',
bind_secret_id='false',
token_bound_cidrs=['127.0.0.1/32'],
token_policies=['policy1', 'pilicy2'],
token_max_ttl='60s',
token_ttl='60s')
@patch.object(vault.hvac, 'Client')
@patch.object(vault, 'get_api_url')
@ -64,7 +66,7 @@ class TestLibCharmVault(unit_tests.test_utils.CharmTestCase):
mock_enable_approle_auth.assert_called_once_with(client_mock)
policy_calls = [
mock.call('local-charm-policy', mock.ANY)]
client_mock.set_policy.assert_has_calls(policy_calls)
client_mock.sys.create_or_update_policy.assert_has_calls(policy_calls)
mock_create_local_charm_access_role.assert_called_once_with(
client_mock,
policies=['local-charm-policy'])
@ -164,10 +166,10 @@ class TestLibCharmVault(unit_tests.test_utils.CharmTestCase):
config.return_value = False
service_running.return_value = True
hvac_mock = mock.MagicMock()
hvac_mock.is_initialized.return_value = False
hvac_mock.sys.is_initialized.return_value = False
get_client.return_value = hvac_mock
self.assertTrue(vault.can_restart())
hvac_mock.is_initialized.assert_called_once_with()
hvac_mock.sys.is_initialized.assert_called_once_with()
@patch.object(vault.host, 'service_running')
@patch.object(vault.hookenv, 'config')
@ -176,12 +178,12 @@ class TestLibCharmVault(unit_tests.test_utils.CharmTestCase):
config.return_value = False
service_running.return_value = True
hvac_mock = mock.MagicMock()
hvac_mock.is_initialized.return_value = True
hvac_mock.is_sealed.return_value = True
hvac_mock.sys.is_initialized.return_value = True
hvac_mock.sys.is_sealed.return_value = True
get_client.return_value = hvac_mock
self.assertTrue(vault.can_restart())
hvac_mock.is_initialized.assert_called_once_with()
hvac_mock.is_sealed.assert_called_once_with()
hvac_mock.sys.is_initialized.assert_called_once_with()
hvac_mock.sys.is_sealed.assert_called_once_with()
@patch.object(vault.host, 'service_running')
@patch.object(vault.hookenv, 'config')
@ -190,8 +192,8 @@ class TestLibCharmVault(unit_tests.test_utils.CharmTestCase):
config.return_value = False
service_running.return_value = True
hvac_mock = mock.MagicMock()
hvac_mock.is_initialized.return_value = True
hvac_mock.is_sealed.return_value = False
hvac_mock.sys.is_initialized.return_value = True
hvac_mock.sys.is_sealed.return_value = False
get_client.return_value = hvac_mock
self.assertFalse(vault.can_restart())
@ -328,15 +330,15 @@ class TestLibCharmVault(unit_tests.test_utils.CharmTestCase):
@patch.object(vault, 'get_client')
def test_initialize_vault(self, get_client, leader_set):
hvac_mock = mock.MagicMock()
hvac_mock.is_initialized.return_value = True
hvac_mock.initialize.return_value = {
hvac_mock.sys.is_initialized.return_value = True
hvac_mock.sys.initialize.return_value = {
'keys': ['c579a143d55423483b9076ea7bba49b63ae432bf74729f77afb4e'],
'keys_base64': ['xX35oUPVVCNIO5B26nu6SbY65DK/dHKfd6+05y1Afcw='],
'root_token': 'dee94df7-23a3-9bf2-cb96-e943537c2b76'
}
get_client.return_value = hvac_mock
vault.initialize_vault()
hvac_mock.initialize.assert_called_once_with(1, 1)
hvac_mock.sys.initialize.assert_called_once_with(1, 1)
leader_set.assert_called_once_with(
keys='["c579a143d55423483b9076ea7bba49b63ae432bf74729f77afb4e"]',
root_token='dee94df7-23a3-9bf2-cb96-e943537c2b76')
@ -351,7 +353,7 @@ class TestLibCharmVault(unit_tests.test_utils.CharmTestCase):
'keys': '["c579a143d55423483b9076ea7bba49b63ae432bf74729f77afb4e"]'
}
vault.unseal_vault()
hvac_mock.unseal.assert_called_once_with(
hvac_mock.sys.submit_unseal_key.assert_called_once_with(
'c579a143d55423483b9076ea7bba49b63ae432bf74729f77afb4e')
@patch.object(vault.hookenv, 'log')
@ -373,12 +375,13 @@ class TestLibCharmVault(unit_tests.test_utils.CharmTestCase):
def test_configure_secret_backend(self):
hvac_client = mock.MagicMock()
hvac_client.list_secret_backends.return_value = ['secrets/']
hvac_client.sys.list_mounted_secrets_engines.return_value = [
'secrets/']
vault.configure_secret_backend(hvac_client, 'test')
hvac_client.enable_secret_backend.assert_called_once_with(
hvac_client.sys.enable_secrets_engine.assert_called_once_with(
backend_type='kv',
description=mock.ANY,
mount_point='test',
path='test',
options={'version': 1})
def test_configure_secret_backend_noop(self):
@ -404,14 +407,15 @@ class TestLibCharmVault(unit_tests.test_utils.CharmTestCase):
def test_configure_policy(self):
hvac_client = mock.MagicMock()
vault.configure_policy(hvac_client, 'test-policy', 'test-hcl')
hvac_client.set_policy.assert_called_once_with(
hvac_client.sys.create_or_update_policy.assert_called_once_with(
'test-policy',
'test-hcl',
)
def test_configure_approle(self):
hvac_client = mock.MagicMock()
hvac_client.get_role_id.return_value = 'some-UUID'
hvac_client.auth.approle.read_role_id.return_value = {
'data': {'role_id': 'some-UUID'}}
self.assertEqual(
vault.configure_approle(hvac_client,
'test-role',
@ -419,12 +423,34 @@ class TestLibCharmVault(unit_tests.test_utils.CharmTestCase):
['test-policy']),
'some-UUID'
)
hvac_client.create_role.assert_called_once_with(
'test-role',
token_ttl='60s',
token_max_ttl='60s',
policies=['test-policy'],
bind_secret_id='true',
bound_cidr_list='10.5.0.20/32'
)
hvac_client.get_role_id.assert_called_with('test-role')
hvac_client.auth.approle.create_or_update_approle.\
assert_called_once_with(
'test-role',
token_ttl='60s',
token_max_ttl='60s',
token_policies=['test-policy'],
bind_secret_id='true',
token_bound_cidrs=['10.5.0.20/32']
)
hvac_client.auth.approle.read_role_id.assert_called_with('test-role')
@patch.object(vault.hookenv, 'leader_get')
@patch.object(vault, 'get_client')
def test_get_local_client(self, get_client, mock_leader_get):
leader_db = {'local-charm-access-id': '12'}
mock_leader_get.side_effect = lambda x: leader_db[x]
hvac_client = mock.MagicMock()
get_client.return_value = hvac_client
client = vault.get_local_client()
self.assertEqual(client, hvac_client)
hvac_client.auth.approle.login.assert_called_once_with('12')
@patch.object(vault.hookenv, 'leader_get')
@patch.object(vault, 'get_client')
def test_get_local_client_not_ready(self, get_client, mock_leader_get):
leader_db = {'local-charm-access-id': None}
mock_leader_get.side_effect = lambda x: leader_db[x]
hvac_client = mock.MagicMock()
get_client.return_value = hvac_client
with self.assertRaises(vault.VaultNotReady):
vault.get_local_client()

View File

@ -24,13 +24,13 @@ class TestLibCharmVaultPKI(unit_tests.test_utils.CharmTestCase):
client_mock,
'my_backend',
ttl=42, max_ttl=42)
client_mock.enable_secret_backend.assert_called_once_with(
client_mock.sys.enable_secrets_engine.assert_called_once_with(
backend_type='pki',
config={
'default_lease_ttl': 42,
'max_lease_ttl': 42},
description='Charm created PKI backend',
mount_point='my_backend')
path='my_backend')
@patch.object(vault_pki.vault, 'is_backend_mounted')
def test_configure_pki_backend_default_ttl(self, is_backend_mounted):
@ -39,13 +39,13 @@ class TestLibCharmVaultPKI(unit_tests.test_utils.CharmTestCase):
vault_pki.configure_pki_backend(
client_mock,
'my_backend')
client_mock.enable_secret_backend.assert_called_once_with(
client_mock.sys.enable_secrets_engine.assert_called_once_with(
backend_type='pki',
config={
'default_lease_ttl': '8759h',
'max_lease_ttl': '87600h'},
description='Charm created PKI backend',
mount_point='my_backend')
path='my_backend')
@patch.object(vault_pki.vault, 'is_backend_mounted')
def test_configure_pki_backend_noop(self, is_backend_mounted):
@ -59,34 +59,42 @@ class TestLibCharmVaultPKI(unit_tests.test_utils.CharmTestCase):
def test_is_ca_ready(self):
client_mock = mock.MagicMock()
vault_pki.is_ca_ready(client_mock, 'my_backend', 'local')
client_mock.read.assert_called_once_with('my_backend/roles/local')
def read_role(role, mount_point=None):
if role == "role":
return "role info"
client_mock.secrets.pki.read_role.side_effect = read_role
self.assertTrue(vault_pki.is_ca_ready(client_mock, 'mp', 'role'))
self.assertFalse(
vault_pki.is_ca_ready(client_mock, 'mp', 'doesnotexist')
)
@patch.object(vault_pki.vault, 'get_local_client')
def test_get_chain(self, get_local_client):
client_mock = mock.MagicMock()
client_mock.read.return_value = {
client_mock.secrets.pki.read_certificate.return_value = {
'data': {
'certificate': 'somecert'}}
get_local_client.return_value = client_mock
self.assertEqual(
vault_pki.get_chain('my_backend'),
'somecert')
client_mock.read.assert_called_once_with(
'my_backend/cert/ca_chain')
client_mock.secrets.pki.read_certificate.assert_called_once_with(
'ca_chain', mount_point='my_backend')
@patch.object(vault_pki.vault, 'get_local_client')
def test_get_chain_default_pki(self, get_local_client):
client_mock = mock.MagicMock()
client_mock.read.return_value = {
client_mock.secrets.pki.read_certificate.return_value = {
'data': {
'certificate': 'somecert'}}
get_local_client.return_value = client_mock
self.assertEqual(
vault_pki.get_chain(),
'somecert')
client_mock.read.assert_called_once_with(
'charm-pki-local/cert/ca_chain')
client_mock.secrets.pki.read_certificate.assert_called_once_with(
'ca_chain', mount_point=vault_pki.CHARM_PKI_MP)
@patch.object(vault_pki.hookenv, 'leader_get')
def test_get_ca(self, leader_get):
@ -102,28 +110,11 @@ class TestLibCharmVaultPKI(unit_tests.test_utils.CharmTestCase):
is_ca_ready,
sort_sans):
client_mock = mock.MagicMock()
client_mock.write.return_value = {'data': 'data'}
client_mock.secrets.pki.generate_certificate.return_value = {
'data': 'data'}
get_local_client.return_value = client_mock
is_ca_ready.return_value = True
sort_sans.side_effect = lambda l: (l[0], l[1])
write_calls = [
mock.call(
'charm-pki-local/issue/local',
common_name='example.com',
),
mock.call(
'charm-pki-local/issue/local',
common_name='example.com',
ip_sans='ip1',
alt_names='alt1',
),
mock.call(
'charm-pki-local/issue/local-client',
common_name='example.com',
ip_sans='ip1,ip2',
alt_names='alt1,alt2',
),
]
vault_pki.generate_certificate('server',
'example.com',
([], []),
@ -136,7 +127,29 @@ class TestLibCharmVaultPKI(unit_tests.test_utils.CharmTestCase):
'example.com',
(['ip1', 'ip2'], ['alt1', 'alt2']),
ttl='3456h', max_ttl='3456h')
client_mock.write.assert_has_calls(write_calls)
client_mock.secrets.pki.generate_certificate.assert_has_calls([
mock.call(
vault_pki.CHARM_PKI_ROLE, 'example.com',
mount_point=vault_pki.CHARM_PKI_MP,
extra_params={},
),
mock.call(
vault_pki.CHARM_PKI_ROLE, 'example.com',
mount_point=vault_pki.CHARM_PKI_MP,
extra_params={
'ip_sans': 'ip1',
'alt_names': 'alt1',
}
),
mock.call(
vault_pki.CHARM_PKI_ROLE_CLIENT, 'example.com',
mount_point=vault_pki.CHARM_PKI_MP,
extra_params={
'ip_sans': 'ip1,ip2',
'alt_names': 'alt1,alt2',
}
),
])
@patch.object(vault_pki, 'is_ca_ready')
@patch.object(vault_pki, 'configure_pki_backend')
@ -173,7 +186,9 @@ class TestLibCharmVaultPKI(unit_tests.test_utils.CharmTestCase):
client_mock = mock.MagicMock()
get_local_client.return_value = client_mock
is_ca_ready.return_value = True
client_mock.write.side_effect = hvac.exceptions.InvalidRequest
client_mock.secrets.pki.generate_certificate.side_effect = (
hvac.exceptions.InvalidRequest
)
with self.assertRaises(vault_pki.vault.VaultInvalidRequest):
vault_pki.generate_certificate('server', 'example.com', [],
ttl='3456h', max_ttl='3456h')
@ -183,24 +198,23 @@ class TestLibCharmVaultPKI(unit_tests.test_utils.CharmTestCase):
def test_get_csr(self, get_local_client, configure_pki_backend):
client_mock = mock.MagicMock()
get_local_client.return_value = client_mock
client_mock.write.return_value = {
client_mock.secrets.pki.generate_intermediate.return_value = {
'data': {
'csr': 'somecert'}}
self.assertEqual(vault_pki.get_csr(), 'somecert')
client_mock.write.assert_called_once_with(
'charm-pki-local/intermediate/generate/internal',
common_name=('Vault Intermediate Certificate Authority'
' (charm-pki-local)'),
ttl='87599h')
client_mock.secrets.pki.generate_intermediate.assert_called_once_with(
'internal',
'Vault Intermediate Certificate Authority (charm-pki-local)',
extra_params={'ttl': '87599h'},
mount_point=vault_pki.CHARM_PKI_MP)
@patch.object(vault_pki, 'configure_pki_backend')
@patch.object(vault_pki.vault, 'get_local_client')
def test_get_csr_explicit(self, get_local_client, configure_pki_backend):
client_mock = mock.MagicMock()
get_local_client.return_value = client_mock
client_mock.write.return_value = {
'data': {
'csr': 'somecert'}}
client_mock.secrets.pki.generate_intermediate.return_value = {
'data': {'csr': 'somecert'}}
self.assertEqual(
vault_pki.get_csr(
ttl='2h',
@ -210,16 +224,19 @@ class TestLibCharmVaultPKI(unit_tests.test_utils.CharmTestCase):
organizational_unit='My Department',
organization='My Company'),
'somecert')
client_mock.write.assert_called_once_with(
'charm-pki-local/intermediate/generate/internal',
common_name=('Vault Intermediate Certificate Authority '
'(charm-pki-local)'),
country='GB',
locality='here',
organization='My Company',
ou='My Department',
province='Kent',
ttl='2h')
client_mock.secrets.pki.generate_intermediate.assert_called_once_with(
'internal',
'Vault Intermediate Certificate Authority (charm-pki-local)',
extra_params=dict(
country='GB',
locality='here',
organization='My Company',
ou='My Department',
province='Kent',
ttl='2h'
),
mount_point=vault_pki.CHARM_PKI_MP
)
@patch.object(vault_pki.vault, 'get_access_address')
@patch.object(vault_pki.vault, 'get_local_client')
@ -227,36 +244,11 @@ class TestLibCharmVaultPKI(unit_tests.test_utils.CharmTestCase):
get_access_address.return_value = 'https://vault.local:8200'
client_mock = mock.MagicMock()
get_local_client.return_value = client_mock
local_url = 'https://vault.local:8200/v1/charm-pki-local'
write_calls = [
mock.call(
'charm-pki-local/config/urls',
issuing_certificates='{}/ca'.format(local_url),
crl_distribution_points='{}/crl'.format(local_url)),
mock.call(
'charm-pki-local/roles/local',
allowed_domains='example.com',
allow_subdomains=True,
enforce_hostnames=False,
allow_any_name=True,
max_ttl='87598h',
server_flag=True,
client_flag=True),
mock.call(
'charm-pki-local/roles/local-client',
allowed_domains='example.com',
allow_subdomains=True,
enforce_hostnames=False,
allow_any_name=True,
max_ttl='87598h',
server_flag=False,
client_flag=True),
]
vault_pki.upload_signed_csr('MYPEM', 'example.com')
client_mock._post.assert_called_once_with(
'v1/charm-pki-local/intermediate/set-signed',
json={'certificate': 'MYPEM'})
client_mock.write.assert_has_calls(write_calls)
client_mock.secrets.pki.set_signed_intermediate.\
assert_called_once_with(
'MYPEM', mount_point=vault_pki.CHARM_PKI_MP
)
@patch.object(vault_pki.vault, 'get_access_address')
@patch.object(vault_pki.vault, 'get_local_client')
@ -267,35 +259,43 @@ class TestLibCharmVaultPKI(unit_tests.test_utils.CharmTestCase):
client_mock = mock.MagicMock()
get_local_client.return_value = client_mock
local_url = 'https://127.0.0.1:8200/v1/charm-pki-local'
write_calls = [
mock.call(
'charm-pki-local/config/urls',
issuing_certificates='{}/ca'.format(local_url),
crl_distribution_points='{}/crl'.format(local_url)),
mock.call(
'charm-pki-local/roles/local',
allowed_domains='example.com',
allow_subdomains=True,
enforce_hostnames=False,
allow_any_name=True,
max_ttl='87598h',
server_flag=True,
client_flag=True),
mock.call(
'charm-pki-local/roles/local-client',
allowed_domains='example.com',
allow_subdomains=True,
enforce_hostnames=False,
allow_any_name=True,
max_ttl='87598h',
server_flag=False,
client_flag=True),
]
vault_pki.upload_signed_csr('MYPEM', 'example.com')
client_mock._post.assert_called_once_with(
'v1/charm-pki-local/intermediate/set-signed',
json={'certificate': 'MYPEM'})
client_mock.write.assert_has_calls(write_calls)
client_mock.secrets.pki.set_signed_intermediate.\
assert_called_once_with(
'MYPEM', mount_point=vault_pki.CHARM_PKI_MP
)
client_mock.secrets.pki.set_urls.assert_called_once_with({
'issuing_certificates': '{}/ca'.format(local_url),
'crl_distribution_points': '{}/crl'.format(local_url),
}, mount_point=vault_pki.CHARM_PKI_MP)
client_mock.secrets.pki.create_or_update_role.assert_has_calls([
mock.call(
vault_pki.CHARM_PKI_ROLE,
extra_params=dict(
allowed_domains='example.com',
allow_subdomains=True,
enforce_hostnames=False,
allow_any_name=True,
max_ttl='87598h',
server_flag=True,
client_flag=True,
),
mount_point=vault_pki.CHARM_PKI_MP,
),
mock.call(
vault_pki.CHARM_PKI_ROLE_CLIENT,
extra_params=dict(
allowed_domains='example.com',
allow_subdomains=True,
enforce_hostnames=False,
allow_any_name=True,
max_ttl='87598h',
server_flag=False,
client_flag=True,
),
mount_point=vault_pki.CHARM_PKI_MP,
),
])
@patch.object(vault_pki.vault, 'get_access_address')
@patch.object(vault_pki.vault, 'get_local_client')
@ -305,36 +305,11 @@ class TestLibCharmVaultPKI(unit_tests.test_utils.CharmTestCase):
get_access_address.return_value = 'https://[::1]:8200'
client_mock = mock.MagicMock()
get_local_client.return_value = client_mock
local_url = 'https://[::1]:8200/v1/charm-pki-local'
write_calls = [
mock.call(
'charm-pki-local/config/urls',
issuing_certificates='{}/ca'.format(local_url),
crl_distribution_points='{}/crl'.format(local_url)),
mock.call(
'charm-pki-local/roles/local',
allowed_domains='example.com',
allow_subdomains=True,
enforce_hostnames=False,
allow_any_name=True,
max_ttl='87598h',
server_flag=True,
client_flag=True),
mock.call(
'charm-pki-local/roles/local-client',
allowed_domains='example.com',
allow_subdomains=True,
enforce_hostnames=False,
allow_any_name=True,
max_ttl='87598h',
server_flag=False,
client_flag=True),
]
vault_pki.upload_signed_csr('MYPEM', 'example.com')
client_mock._post.assert_called_once_with(
'v1/charm-pki-local/intermediate/set-signed',
json={'certificate': 'MYPEM'})
client_mock.write.assert_has_calls(write_calls)
client_mock.secrets.pki.set_signed_intermediate.\
assert_called_once_with(
'MYPEM', mount_point=vault_pki.CHARM_PKI_MP
)
@patch.object(vault_pki.vault, 'get_access_address')
@patch.object(vault_pki.vault, 'get_local_client')
@ -344,30 +319,6 @@ class TestLibCharmVaultPKI(unit_tests.test_utils.CharmTestCase):
get_access_address.return_value = 'https://vault.local:8200'
get_local_client.return_value = client_mock
local_url = 'https://vault.local:8200/v1/charm-pki-local'
write_calls = [
mock.call(
'charm-pki-local/config/urls',
issuing_certificates='{}/ca'.format(local_url),
crl_distribution_points='{}/crl'.format(local_url)),
mock.call(
'charm-pki-local/roles/local',
allowed_domains='example.com',
allow_subdomains=False,
enforce_hostnames=True,
allow_any_name=False,
max_ttl='42h',
server_flag=True,
client_flag=True),
mock.call(
'charm-pki-local/roles/local-client',
allowed_domains='example.com',
allow_subdomains=False,
enforce_hostnames=True,
allow_any_name=False,
max_ttl='42h',
server_flag=False,
client_flag=True),
]
vault_pki.upload_signed_csr(
'MYPEM',
'example.com',
@ -375,10 +326,17 @@ class TestLibCharmVaultPKI(unit_tests.test_utils.CharmTestCase):
enforce_hostnames=True,
allow_any_name=False,
max_ttl='42h')
client_mock._post.assert_called_once_with(
'v1/charm-pki-local/intermediate/set-signed',
json={'certificate': 'MYPEM'})
client_mock.write.assert_has_calls(write_calls)
client_mock.secrets.pki.set_signed_intermediate.\
assert_called_once_with(
'MYPEM', mount_point='charm-pki-local'
)
client_mock.secrets.pki.set_urls.assert_called_once_with(
{
'issuing_certificates': '{}/ca'.format(local_url),
'crl_distribution_points': '{}/crl'.format(local_url),
},
mount_point=vault_pki.CHARM_PKI_MP
)
@patch.object(vault_pki.vault, 'get_access_address')
@patch.object(vault_pki, 'is_ca_ready')
@ -390,7 +348,8 @@ class TestLibCharmVaultPKI(unit_tests.test_utils.CharmTestCase):
is_ca_ready,
get_access_address):
mock_client = get_local_client.return_value
mock_client.write.return_value = {'data': {'certificate': 'cert'}}
mock_client.secrets.pki.generate_root.return_value = {
'data': {'certificate': 'cert'}}
is_ca_ready.return_value = False
get_access_address.return_value = 'addr'
rv = vault_pki.generate_root_ca(ttl='0h',
@ -402,35 +361,6 @@ class TestLibCharmVaultPKI(unit_tests.test_utils.CharmTestCase):
enforce_hostnames=True,
max_ttl='0h')
self.assertEqual(rv, 'cert')
mock_client.write.assert_has_calls([
mock.call('charm-pki-local/root/generate/internal',
common_name='Vault Root Certificate Authority '
'(charm-pki-local)',
ttl='0h'),
mock.call('charm-pki-local/config/urls',
issuing_certificates='addr/v1/charm-pki-local/ca',
crl_distribution_points='addr/v1/charm-pki-local/crl'),
mock.call('charm-pki-local/roles/local',
allow_any_name=True,
allowed_domains='domains',
allow_bare_domains=True,
allow_subdomains=True,
allow_glob_domains=False,
enforce_hostnames=True,
max_ttl='0h',
server_flag=True,
client_flag=True),
mock.call('charm-pki-local/roles/local-client',
allow_any_name=True,
allowed_domains='domains',
allow_bare_domains=True,
allow_subdomains=True,
allow_glob_domains=False,
enforce_hostnames=True,
max_ttl='0h',
server_flag=False,
client_flag=True),
])
@patch.object(vault_pki.vault, 'get_access_address')
@patch.object(vault_pki, 'is_ca_ready')
@ -467,9 +397,8 @@ class TestLibCharmVaultPKI(unit_tests.test_utils.CharmTestCase):
vault_pki.tune_pki_backend(ttl='3456h', max_ttl='3456h')
is_backend_mounted.assert_called_with(mock_client,
vault_pki.CHARM_PKI_MP)
mock_client.tune_secret_backend.assert_called_with(
backend_type='pki',
mount_point=vault_pki.CHARM_PKI_MP,
mock_client.sys.tune_mount_configuration.assert_called_with(
path=vault_pki.CHARM_PKI_MP,
max_lease_ttl='3456h',
default_lease_ttl='3456h'
)
@ -478,7 +407,7 @@ class TestLibCharmVaultPKI(unit_tests.test_utils.CharmTestCase):
def test_update_roles(self, get_local_client):
mock_client = mock.MagicMock()
get_local_client.return_value = mock_client
mock_client.read.return_value = {
mock_client.secrets.pki.read_role.return_value = {
'data': {
'allow_any_name': True,
'allowed_domains': 'domains',
@ -492,29 +421,35 @@ class TestLibCharmVaultPKI(unit_tests.test_utils.CharmTestCase):
}
}
vault_pki.update_roles(max_ttl='20h')
mock_client.write.assert_has_calls([
mock.call('{}/roles/{}'.format(
vault_pki.CHARM_PKI_MP, vault_pki.CHARM_PKI_ROLE),
allow_any_name=True,
allowed_domains='domains',
allow_bare_domains=True,
allow_subdomains=True,
allow_glob_domains=False,
enforce_hostnames=True,
max_ttl='20h',
server_flag=True,
client_flag=True),
mock.call('{}/roles/{}'.format(
vault_pki.CHARM_PKI_MP, vault_pki.CHARM_PKI_ROLE_CLIENT),
allow_any_name=True,
allowed_domains='domains',
allow_bare_domains=True,
allow_subdomains=True,
allow_glob_domains=False,
enforce_hostnames=True,
max_ttl='20h',
server_flag=False,
client_flag=True),
mock_client.secrets.pki.create_or_update_role.assert_has_calls([
mock.call(
vault_pki.CHARM_PKI_ROLE,
mount_point=vault_pki.CHARM_PKI_MP,
extra_params=dict(
allow_any_name=True,
allowed_domains='domains',
allow_bare_domains=True,
allow_subdomains=True,
allow_glob_domains=False,
enforce_hostnames=True,
max_ttl='20h',
server_flag=True,
client_flag=True)
),
mock.call(
vault_pki.CHARM_PKI_ROLE_CLIENT,
mount_point=vault_pki.CHARM_PKI_MP,
extra_params=dict(
allow_any_name=True,
allowed_domains='domains',
allow_bare_domains=True,
allow_subdomains=True,
allow_glob_domains=False,
enforce_hostnames=True,
max_ttl='20h',
server_flag=False,
client_flag=True)
),
])
@patch.object(vault_pki.hookenv, 'leader_get')

View File

@ -670,8 +670,8 @@ class TestHandlers(unit_tests.test_utils.CharmTestCase):
hvac_client = mock.MagicMock()
_vault.get_client.return_value = hvac_client
# Vault is up and running, init'ed and unsealed
hvac_client.is_initialized.return_value = True
hvac_client.is_sealed.return_value = False
hvac_client.sys.is_initialized.return_value = True
hvac_client.sys.is_sealed.return_value = False
self.service_running.return_value = True
_vault.get_local_charm_access_role_id.return_value = 'local-approle'
@ -687,7 +687,7 @@ class TestHandlers(unit_tests.test_utils.CharmTestCase):
handlers.configure_secrets_backend()
hvac_client.auth_approle.assert_called_once_with('local-approle')
hvac_client.auth.approle.login.assert_called_once_with('local-approle')
_vault.configure_secret_backend.assert_has_calls([
mock.call(hvac_client, name='charm-vaultlocker'),
mock.call(hvac_client, name='charm-supersecrets'),
@ -883,7 +883,7 @@ class TestHandlers(unit_tests.test_utils.CharmTestCase):
def _set_sealed(self, _vault, status):
hvac_client = mock.MagicMock()
_vault.get_client.return_value = hvac_client
hvac_client.is_sealed.return_value = status
hvac_client.sys.is_sealed.return_value = status
@mock.patch.object(handlers, 'client_approle_authorized')
@mock.patch.object(handlers, 'vault')