From aa02567383117f98633828952c60792ee7ace1a1 Mon Sep 17 00:00:00 2001 From: selcem Date: Thu, 4 Jan 2024 21:31:16 +0300 Subject: [PATCH] Add crl-distribution-point to upload-signed-csr action New configuration parameter updates URI for CRL Distribution points inside Vault, to a publicly-accessible location. The purpose is not to impact all users, so I did not add a global configuration parameter. Instead, only 'upload_signed_csr' action was updated with an optional parameter introduced named 'crl-distribution-point'. Closes-bug: #2048237 Change-Id: I8dbfc0deb9f547100bb63bd6b20737734e97667b --- src/README.md | 6 ++-- src/actions.yaml | 24 ++++++++++++---- src/actions/actions.py | 3 +- src/lib/charm/vault_pki.py | 39 +++++++++++++++++--------- unit_tests/test_lib_charm_vault_pki.py | 19 ++++++++++++- 5 files changed, 67 insertions(+), 24 deletions(-) diff --git a/src/README.md b/src/README.md index c768416..a95828f 100644 --- a/src/README.md +++ b/src/README.md @@ -31,7 +31,7 @@ The `channel` option sets the snap channel to use for deployment (e.g. > 'Post-deployment tasks' covers this. Vault is often containerised. Here a single unit is deployed to a new -container on machine '1': +container on existing machine '1': juju deploy --to lxd:1 vault @@ -190,7 +190,7 @@ Sample output: This temporary token ('token') is then used to authorise the charm: - juju run-action --wait vault/leader authorize-charm token=s.QMhaOED3UGQ4MeH3fmGOpNED + juju run --wait vault/leader authorize-charm token=s.QMhaOED3UGQ4MeH3fmGOpNED After the action completes execution, the vault unit(s) will become active and any pending requests for secrets storage will be processed for consuming @@ -232,7 +232,7 @@ Actions allow specific operations to be performed on a per-unit basis. * `restart` To display action descriptions run `juju actions --schema vault`. If the charm -is not deployed then see file `actions.yaml`. +is not deployed then see file `actions.yaml`. Here is a sample command: ## High availability diff --git a/src/actions.yaml b/src/actions.yaml index a71ba7f..70e149f 100644 --- a/src/actions.yaml +++ b/src/actions.yaml @@ -7,11 +7,13 @@ authorize-charm: required: - token refresh-secrets: - description: Refresh secret_id's and re-issue retrieval tokens for secrets endpoints + description: Refresh secret_id's and re-issue retrieval tokens for secrets + endpoints get-csr: description: >- Get intermediate CA csr (DEPRECATED Please use regenerate-intermediate-ca). - WARNING Current certificates will be invalidated and will be recreated after the CSR is signed and uploaded. + WARNING Current certificates will be invalidated and will be recreated + after the CSR is signed and uploaded. properties: # Depending on the configuration of CA that will sign the CSRs it # may be necessary to ensure these fields match the CA @@ -47,7 +49,8 @@ get-csr: regenerate-intermediate-ca: description: >- Create a new intermediate CA and return a csr for it - WARNING Current certificates will be invalidated and will be recreated after the CSR is signed and uploaded. + WARNING Current certificates will be invalidated and will be recreated + after the CSR is signed and uploaded. properties: # Depending on the configuration of CA that will sign the CSRs it # may be necessary to ensure these fields match the CA @@ -107,6 +110,17 @@ upload-signed-csr: default: '8760h' description: >- Specifies the maximum Time To Live + crl-distribution-point: + type: string + default: '' + description: >- + Provide an alternative URL for the Certificate Revocation List (CRL) + distribution point that is included in all certificates issued by + Vault. This relies on an external process to synchronise certificates + revoked in Vault to this external distribution point and should only + be used when the Vault infrastructure is not generally accessible to + client endpoints used to access services secured by the Vault + Intermediate CA. root-ca: type: string description: >- @@ -184,8 +198,8 @@ restart: description: Restarts the vault unit. Vault will become sealed. reload: description: >- - Reloads the vault unit. This allows for limited configuration options to be - re-read. Vault will not become sealed. + 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: diff --git a/src/actions/actions.py b/src/actions/actions.py index b5a9083..0e038aa 100755 --- a/src/actions/actions.py +++ b/src/actions/actions.py @@ -131,7 +131,8 @@ def upload_signed_csr(*args): allow_subdomains=action_config.get('allow-subdomains'), enforce_hostnames=action_config.get('enforce-hostnames'), allow_any_name=action_config.get('allow-any-name'), - max_ttl=action_config.get('max-ttl')) + max_ttl=action_config.get('max-ttl'), + crl_distribution_point=action_config.get('crl-distribution-point')) set_flag('charm.vault.ca.ready') set_flag('pki.backend.tuned') # reissue any certificates we might previously have provided diff --git a/src/lib/charm/vault_pki.py b/src/lib/charm/vault_pki.py index 49f7fb2..c698402 100644 --- a/src/lib/charm/vault_pki.py +++ b/src/lib/charm/vault_pki.py @@ -23,7 +23,9 @@ def configure_pki_backend(client, name, ttl=None, max_ttl=None): :param name: Name of backend to enable :type name: str :param ttl: TTL - :type ttl: str + :type ttl: Optional[str] + :param max_ttl: max TTL + :type max_ttl: Optional[str] """ if not vault.is_backend_mounted(client, name): client.sys.enable_secrets_engine( @@ -53,7 +55,9 @@ def tune_pki_backend(ttl=None, max_ttl=None): """Assert tuning options for Charm PKI backend :param ttl: TTL - :type ttl: str + :type ttl: Optional[str] + :param max_ttl: max TTL + :type max_ttl: Optional[str] """ client = vault.get_local_client() if vault.is_backend_mounted(client, CHARM_PKI_MP): @@ -68,6 +72,10 @@ def is_ca_ready(client, name, role): :returns: Whether CA is ready :rtype: bool + :param name: Name of backend to enable + :type name: str + :param role: Name of role + :type role: str """ try: # read_role raises InvalidPath is the role is not available @@ -156,17 +164,17 @@ def get_csr(ttl=None, common_name=None, locality=None, fields embedded in the CSR may have to match the CA. :param ttl: TTL - :type ttl: string + :type ttl: Optional[string] :param country: The C (Country) values in the subject field of the CSR - :type country: string + :type country: Optional[string] :param province: The ST (Province) values in the subject field of the CSR. - :type province: string + :type province: Optional[string] :param organization: The O (Organization) values in the subject field of the CSR - :type organization: string + :type organization: Optional[string] :param organizational_unit: The OU (OrganizationalUnit) values in the subject field of the CSR. - :type organizational_unit: string + :type organizational_unit: Optional[string] :param common_name: The CN (Common_Name) values in the subject field of the CSR. :param locality: The L (Locality) values in the @@ -203,22 +211,24 @@ def get_csr(ttl=None, common_name=None, locality=None, def upload_signed_csr(pem, allowed_domains, allow_subdomains=True, enforce_hostnames=False, allow_any_name=True, - max_ttl=None): + max_ttl=None, crl_distribution_point=None): """Upload signed csr to intermediate pki :param pem: signed csr in pem format :type pem: string :param allow_subdomains: Specifies if clients can request certificates with CNs that are subdomains of the CNs: - :type allow_subdomains: bool + :type allow_subdomains: Optional[bool] :param enforce_hostnames: Specifies if only valid host names are allowed for CNs, DNS SANs, and the host part of email addresses. - :type enforce_hostnames: bool + :type enforce_hostnames: Optional[bool] :param allow_any_name: Specifies if clients can request any CN - :type allow_any_name: bool + :type allow_any_name:Optional[bool] :param max_ttl: Specifies the maximum Time To Live - :type max_ttl: str + :type max_ttl: Optional[str] + :param crl_distribution_point: Defines the CRL Distribution Point URI + :type crl_distribution_point: Optional[str] """ client = vault.get_local_client() # Set the intermediate certificate authorities signing certificate to the @@ -234,7 +244,8 @@ def upload_signed_csr(pem, allowed_domains, allow_subdomains=True, { "issuing_certificates": "{}/v1/{}/ca".format(addr, CHARM_PKI_MP), "crl_distribution_points": - "{}/v1/{}/crl".format(addr, CHARM_PKI_MP), + ("{}/v1/{}/crl".format(addr, CHARM_PKI_MP) + if not crl_distribution_point else crl_distribution_point), }, mount_point=CHARM_PKI_MP ) @@ -388,7 +399,7 @@ def is_cert_from_vault(cert, name=None): :param cert: the certificate in x509 form :type cert: str :param name: the mount point in value, default CHARM_PKI_MP - :type name: str + :type name: Optional[str] :returns: True if issued by vault, False if unknown. :raises VaultDown: if vault is down. :raises VaultNotReady: if vault is sealed. diff --git a/unit_tests/test_lib_charm_vault_pki.py b/unit_tests/test_lib_charm_vault_pki.py index f1c2bfb..0c73f2e 100644 --- a/unit_tests/test_lib_charm_vault_pki.py +++ b/unit_tests/test_lib_charm_vault_pki.py @@ -260,6 +260,23 @@ class TestLibCharmVaultPKI(unit_tests.test_utils.CharmTestCase): 'MYPEM', mount_point=vault_pki.CHARM_PKI_MP ) + @patch.object(vault_pki.vault, 'get_access_address') + @patch.object(vault_pki.vault, 'get_local_client') + def test_upload_signed_csr_cdp(self, get_local_client, get_access_address): + 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' + vault_pki.upload_signed_csr( + 'MYPEM', 'example.com', crl_distribution_point='https://cdp.com' + ) + client_mock.secrets.pki.set_urls.assert_called_once_with( + { + 'issuing_certificates': '{}/ca'.format(local_url), + 'crl_distribution_points': 'https://cdp.com', + }, mount_point=vault_pki.CHARM_PKI_MP + ) + @patch.object(vault_pki.vault, 'get_access_address') @patch.object(vault_pki.vault, 'get_local_client') def test_upload_signed_csr_ipv4( @@ -335,7 +352,7 @@ class TestLibCharmVaultPKI(unit_tests.test_utils.CharmTestCase): allow_subdomains=False, enforce_hostnames=True, allow_any_name=False, - max_ttl='42h') + max_ttl='42h', crl_distribution_point=None) client_mock.secrets.pki.set_signed_intermediate.\ assert_called_once_with( 'MYPEM', mount_point='charm-pki-local'