Add action to generate certificate against the PKI.

Created action to utilize the existing
generate_certificate function for on demand
certificates agains the existing vault PKI.

Closes-Bug: #1948837
Change-Id: Ia1a169623c81d6aede7dc52eabd2de94007fde80
This commit is contained in:
Jeff Hillman 2021-10-26 12:53:42 -05:00
parent b797fcfcbf
commit d8bfff76e4
4 changed files with 119 additions and 1 deletions

View File

@ -143,3 +143,25 @@ reload:
description: >-
Reloads the vault unit. This allows for limited configuration options to be
re-read. Vault will not become sealed.
generate-certificate:
description: Generate a certificate agains the Vault PKI
properties:
ttl:
type: string
default: 87599h
description: >-
Specifies the Time To Live for the certificate
common-name:
type: string
description: >-
CN field of the new certificate
sans:
type: string
description: >-
Space delimited list of Subject Altername Name/IP addresse(s)
max-ttl:
type: string
default: 8760h
description: >-
Specifies the maximum Time To Live for generated certificates.

View File

@ -193,6 +193,32 @@ def reload(args):
host.service_reload(service_name='vault')
def generate_cert(*args):
"""Generates a certificate and sets it in the action output.
The certificate parameters are provided via the action parameters from
the user. If the current unit is not the leader or the vault calls fail,
this will result in a failed command.
"""
if not hookenv.is_leader():
hookenv.action_fail('Please run action on lead unit')
return
action_config = hookenv.action_get()
sans_list = action_config.get('sans')
try:
new_crt = vault_pki.generate_certificate(
cert_type='server',
common_name=action_config.get('common-name'),
sans=list(sans_list.split()),
ttl=action_config.get('ttl'),
max_ttl=action_config.get('max-ttl'))
hookenv.action_set({'output': new_crt})
except vault.VaultError as e:
hookenv.action_fail(str(e))
# Actions to function mapping, to allow for illegal python action names that
# can map to a python function.
ACTIONS = {
@ -207,7 +233,8 @@ ACTIONS = {
"pause": pause,
"resume": resume,
"restart": restart,
"reload": reload
"reload": reload,
"generate-certificate": generate_cert
}

View File

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

View File

@ -0,0 +1,68 @@
from unittest.mock import patch
import src.actions.actions as actions
import unit_tests.test_utils
class TestActions(unit_tests.test_utils.CharmTestCase):
def setUp(self):
super(TestActions, self).setUp()
self.patches = []
self.patch_all()
self.patch_object(actions, 'hookenv', name='mock_hookenv')
def test_generate_cert_not_leader(self):
"""Test when not leader, action fails"""
self.mock_hookenv.is_leader.return_value = False
actions.generate_cert()
# Action should fail
self.mock_hookenv.action_fail.assert_called_with(
'Please run action on lead unit'
)
self.mock_hookenv.action_set.assert_not_called()
@patch.object(actions, 'vault_pki')
def test_generate_cert(self, mock_vault_pki):
self.mock_hookenv.is_leader.return_value = True
self.mock_hookenv.action_get.return_value = {
'sans': 'foobar 1.2.3.4',
'common-name': 'bazbuz',
'ttl': '5m',
'max-ttl': '5y',
}
mock_vault_pki.generate_certificate.return_value = 'shiny-cert'
actions.generate_cert()
# Validate the request for the cert was called
mock_vault_pki.generate_certificate.assert_called_with(
cert_type='server', common_name='bazbuz',
sans=['foobar', '1.2.3.4'], ttl='5m', max_ttl='5y',
)
self.mock_hookenv.action_set.assert_called_with({
'output': 'shiny-cert',
})
@patch.object(actions, 'vault_pki')
def test_generate_cert_vault_failure(self, mock_vault_pki):
"""Test failure interacting with vault_pki"""
self.mock_hookenv.is_leader.return_value = True
self.mock_hookenv.action_get.return_value = {
'sans': 'foobar',
'common-name': 'bazbuz',
'ttl': '5m',
'max-ttl': '5y',
}
mock_vault_pki.generate_certificate.side_effect = \
actions.vault.VaultNotReady(1)
actions.generate_cert()
# Validate the request for the cert was called
self.mock_hookenv.action_set.assert_not_called
self.mock_hookenv.action_fail.assert_called_with(
'Vault is not ready (1)'
)