From 58c69d0cb89fcf6053ce8c5e7c1122754e6d55ec Mon Sep 17 00:00:00 2001 From: Remigiusz Samborski Date: Wed, 25 Jan 2023 15:20:13 +0100 Subject: [PATCH] migrate code from googleapis/python-security-private-ca (#9028) - feat(samples): private CA python samples - chore(deps): update dependency google-cloud-private-ca to v1 (#89) - chore(deps): update dependency google-cloud-kms to v2.4.1 (#88) - chore(deps): update dependency google-cloud-kms to v2.4.2 (#93) - chore(deps): update dependency google-auth to v1.33.1 (#87) - chore(deps): update dependency google-auth to v1.34.0 (#94) - chore(deps): update dependency google-cloud-kms to v2.4.3 (#96) - chore(deps): update dependency google-cloud-private-ca to v1.0.3 (#97) - feat(samples): add local generation for crypto keys (#98) - chore(deps): update dependency google-cloud-kms to v2.5.0 (#101) - chore: generate python samples templates in owlbot.py (#108) - chore(deps): update dependency cryptography to v3.4.8 (#109) - chore(deps): update dependency google-auth to v2 (#107) - chore(deps): update dependency google-cloud-private-ca to v1.0.4 (#103) - chore(deps): update dependency google-auth to v2.0.2 (#116) - chore(deps): update dependency google-cloud-kms to v2.6.0 (#115) - chore(deps): update dependency pytest to v6.2.5 (#114) - chore: blacken samples noxfile template (#121) - chore: fail samples nox session if python version is missing (#128) - chore(python): Add kokoro configs for python 3.10 samples testing (#134) - chore(deps): update dependency google-auth to v2.1.0 (#120) - chore(deps): update all dependencies (#144) - chore(deps): update all dependencies (#146) - chore(deps): update dependency google-cloud-private-ca to v1.2.1 (#147) - chore(python): run blacken session for all directories with a noxfile (#150) - chore(deps): update dependency cryptography to v36 (#154) - feat(samples): add subordinate CA samples - chore(deps): update dependency cryptography to v36.0.1 (#158) - chore(samples): Add check for tests in directory (#164) - chore(python): Noxfile recognizes that tests can live in a folder (#169) - docs(samples): add sample to filter certificates (#160) - chore(deps): update dependency google-auth to v2.6.0 (#173) - docs(samples): add template/monitoring samples (#174) - chore(deps): update all dependencies (#178) - chore(deps): update all dependencies (#186) - chore: Adding support for pytest-xdist and pytest-parallel (#193) - chore(deps): update all dependencies (#196) - chore(deps): update dependency google-cloud-private-ca to v1.3.0 (#199) - chore(deps): update dependency pytest to v7.1.0 (#200) - chore(deps): update dependency cryptography to v36.0.2 (#201) - chore(deps): update all dependencies (#202) - chore(python): use black==22.3.0 (#204) - chore(deps): update dependency google-auth to v2.6.3 (#211) - chore(deps): update dependency google-auth to v2.6.4 (#215) - chore(deps): update dependency google-auth to v2.6.5 (#217) - chore(python): add nox session to sort python imports (#218) - chore(deps): update dependency google-auth to v2.6.6 (#221) - chore(deps): update dependency pytest to v7.1.2 (#222) - chore(deps): update dependency cryptography to v37 (#223) - chore(deps): update dependency cryptography to v37.0.1 (#225) - chore(deps): update dependency cryptography to v37.0.2 (#226) - chore(deps): update dependency google-cloud-private-ca to v1.3.1 (#230) - fix: require python 3.7+ (#267) - chore(deps): update all dependencies (#262) - chore(deps): update all dependencies (#273) - chore(deps): update all dependencies (#275) - chore(deps): update all dependencies (#276) - chore(deps): update dependency google-cloud-monitoring to v2.11.1 (#281) - chore(deps): update all dependencies (#283) - chore(deps): update dependency google-auth to v2.11.0 (#284) - chore(deps): update dependency pytest to v7.1.3 (#294) - chore: Bump gapic-generator-python version to 1.3.0 (#295) - chore: detect samples tests in nested directories (#299) - chore(deps): update dependency google-auth to v2.11.1 (#300) - chore(deps): update dependency google-auth to v2.12.0 (#302) - chore(deps): update all dependencies (#304) - chore(deps): update all dependencies (#307) - chore(deps): update dependency pytest to v7.2.0 (#308) - chore(deps): update dependency google-auth to v2.14.0 (#310) - chore(python): drop flake8-import-order in samples noxfile (#315) - chore(deps): update all dependencies (#311) - chore(deps): update dependency cryptography to v38.0.4 (#317) - chore(deps): update dependency google-auth to v2.15.0 (#318) - chore(deps): update all dependencies (#321) - chore(deps): update dependency google-cloud-private-ca to v1.5.0 (#322) - chore(deps): update dependency cryptography to v39 (#323) - chore(python): add support for python 3.11 (#324) - chore(deps): update dependency google-cloud-monitoring to v2.13.0 (#325) - chore(deps): update dependency google-auth to v2.16.0 (#326) - chore(deps): update all dependencies (#329) - chore(deps): update dependency pytest to v7.2.1 (#330) ## Description Sample Migration from googleapis/python-security-private-ca Note: Before submitting a pull request, please open an issue for discussion if you are not associated with Google. ## Checklist - [ ] I have followed [Sample Guidelines from AUTHORING_GUIDE.MD](https://togithub.com/GoogleCloudPlatform/python-docs-samples/blob/main/AUTHORING_GUIDE.md) - [ ] README is updated to include [all relevant information](https://togithub.com/GoogleCloudPlatform/python-docs-samples/blob/main/AUTHORING_GUIDE.md#readme-file) - [ ] **Tests** pass: `nox -s py-3.9` (see [Test Environment Setup](https://togithub.com/GoogleCloudPlatform/python-docs-samples/blob/main/AUTHORING_GUIDE.md#test-environment-setup)) - [ ] **Lint** pass: `nox -s lint` (see [Test Environment Setup](https://togithub.com/GoogleCloudPlatform/python-docs-samples/blob/main/AUTHORING_GUIDE.md#test-environment-setup)) - [ ] These samples need a new **API enabled** in testing projects to pass (let us know which ones) - [ ] These samples need a new/updated **env vars** in testing projects set to pass (let us know which ones) - [ ] Please **merge** this PR for me once it is approved. - [ ] This sample adds a new sample directory, and I updated the [CODEOWNERS file](https://togithub.com/GoogleCloudPlatform/python-docs-samples/blob/main/.github/CODEOWNERS) with the codeowners for this sample --- privateca/snippets/activate_subordinate_ca.py | 87 +++++++++++++ privateca/snippets/conftest.py | 83 ++++++++++++ privateca/snippets/create_ca_pool.py | 53 ++++++++ privateca/snippets/create_certificate.py | 102 +++++++++++++++ .../snippets/create_certificate_authority.py | 97 ++++++++++++++ privateca/snippets/create_certificate_csr.py | 74 +++++++++++ .../snippets/create_certificate_template.py | 82 ++++++++++++ privateca/snippets/create_subordinate_ca.py | 101 +++++++++++++++ privateca/snippets/delete_ca_pool.py | 45 +++++++ .../snippets/delete_certificate_authority.py | 76 +++++++++++ .../snippets/delete_certificate_template.py | 52 ++++++++ .../snippets/disable_certificate_authority.py | 58 +++++++++ .../snippets/enable_certificate_authority.py | 61 +++++++++ privateca/snippets/filter_certificates.py | 49 +++++++ privateca/snippets/list_ca_pools.py | 46 +++++++ .../snippets/list_certificate_authorities.py | 42 ++++++ .../snippets/list_certificate_templates.py | 44 +++++++ privateca/snippets/list_certificates.py | 46 +++++++ .../snippets/monitor_certificate_authority.py | 77 +++++++++++ privateca/snippets/noxfile_config.py | 38 ++++++ privateca/snippets/requirements-test.txt | 4 + privateca/snippets/requirements.txt | 3 + privateca/snippets/revoke_certificate.py | 65 ++++++++++ privateca/snippets/test_ca_pools.py | 85 ++++++++++++ .../snippets/test_certificate_authorities.py | 121 ++++++++++++++++++ privateca/snippets/test_certificates.py | 96 ++++++++++++++ .../test_crud_certificate_templates.py | 71 ++++++++++ privateca/snippets/test_subordinate_ca.py | 115 +++++++++++++++++ .../undelete_certificate_authority.py | 68 ++++++++++ .../update_ca_pool_issuance_policy.py | 97 ++++++++++++++ .../snippets/update_certificate_authority.py | 71 ++++++++++ .../snippets/update_certificate_template.py | 87 +++++++++++++ 32 files changed, 2196 insertions(+) create mode 100644 privateca/snippets/activate_subordinate_ca.py create mode 100644 privateca/snippets/conftest.py create mode 100644 privateca/snippets/create_ca_pool.py create mode 100644 privateca/snippets/create_certificate.py create mode 100644 privateca/snippets/create_certificate_authority.py create mode 100644 privateca/snippets/create_certificate_csr.py create mode 100644 privateca/snippets/create_certificate_template.py create mode 100644 privateca/snippets/create_subordinate_ca.py create mode 100644 privateca/snippets/delete_ca_pool.py create mode 100644 privateca/snippets/delete_certificate_authority.py create mode 100644 privateca/snippets/delete_certificate_template.py create mode 100644 privateca/snippets/disable_certificate_authority.py create mode 100644 privateca/snippets/enable_certificate_authority.py create mode 100644 privateca/snippets/filter_certificates.py create mode 100644 privateca/snippets/list_ca_pools.py create mode 100644 privateca/snippets/list_certificate_authorities.py create mode 100644 privateca/snippets/list_certificate_templates.py create mode 100644 privateca/snippets/list_certificates.py create mode 100644 privateca/snippets/monitor_certificate_authority.py create mode 100644 privateca/snippets/noxfile_config.py create mode 100644 privateca/snippets/requirements-test.txt create mode 100644 privateca/snippets/requirements.txt create mode 100644 privateca/snippets/revoke_certificate.py create mode 100644 privateca/snippets/test_ca_pools.py create mode 100644 privateca/snippets/test_certificate_authorities.py create mode 100644 privateca/snippets/test_certificates.py create mode 100644 privateca/snippets/test_crud_certificate_templates.py create mode 100644 privateca/snippets/test_subordinate_ca.py create mode 100644 privateca/snippets/undelete_certificate_authority.py create mode 100644 privateca/snippets/update_ca_pool_issuance_policy.py create mode 100644 privateca/snippets/update_certificate_authority.py create mode 100644 privateca/snippets/update_certificate_template.py diff --git a/privateca/snippets/activate_subordinate_ca.py b/privateca/snippets/activate_subordinate_ca.py new file mode 100644 index 000000000000..ad6d9b5593d9 --- /dev/null +++ b/privateca/snippets/activate_subordinate_ca.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python + +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START privateca_activate_subordinateca] +import google.cloud.security.privateca_v1 as privateca_v1 + + +def activate_subordinate_ca( + project_id: str, + location: str, + ca_pool_name: str, + subordinate_ca_name: str, + pem_ca_certificate: str, + ca_name: str, +) -> None: + """ + Activate a subordinate Certificate Authority (CA). + *Prerequisite*: Get the Certificate Signing Resource (CSR) of the subordinate CA signed by another CA. Pass in the signed + certificate and (issuer CA's name or the issuer CA's Certificate chain). + *Post*: After activating the subordinate CA, it should be enabled before issuing certificates. + Args: + project_id: project ID or project number of the Cloud project you want to use. + location: location you want to use. For a list of locations, see: https://cloud.google.com/certificate-authority-service/docs/locations. + ca_pool_name: set it to the CA Pool under which the CA should be created. + pem_ca_certificate: the signed certificate, obtained by signing the CSR. + subordinate_ca_name: the CA to be activated. + ca_name: The name of the certificate authority which signed the CSR. + If an external CA (CA not present in Google Cloud) was used for signing, + then use the CA's issuerCertificateChain. + """ + + ca_service_client = privateca_v1.CertificateAuthorityServiceClient() + + subordinate_ca_path = ca_service_client.certificate_authority_path( + project_id, location, ca_pool_name, subordinate_ca_name + ) + ca_path = ca_service_client.certificate_authority_path( + project_id, location, ca_pool_name, ca_name + ) + + # Set CA subordinate config. + subordinate_config = privateca_v1.SubordinateConfig( + # Follow one of the below methods: + # Method 1: If issuer CA is in Google Cloud, set the Certificate Authority Name. + certificate_authority=ca_path, + # Method 2: If issuer CA is external to Google Cloud, set the issuer's certificate chain. + # The certificate chain of the CA (which signed the CSR) from leaf to root. + # pem_issuer_chain=privateca_v1.SubordinateConfig.SubordinateConfigChain( + # pem_certificates=issuer_certificate_chain, + # ) + ) + + # Construct the "Activate CA Request". + request = privateca_v1.ActivateCertificateAuthorityRequest( + name=subordinate_ca_path, + # The signed certificate. + pem_ca_certificate=pem_ca_certificate, + subordinate_config=subordinate_config, + ) + + # Activate the CA + operation = ca_service_client.activate_certificate_authority(request=request) + result = operation.result() + + print("Operation result:", result) + + # The current state will be STAGED. + # The Subordinate CA has to be ENABLED before issuing certificates. + print( + f"Current state: {ca_service_client.get_certificate_authority(name=subordinate_ca_path).state}" + ) + + +# [END privateca_activate_subordinateca] diff --git a/privateca/snippets/conftest.py b/privateca/snippets/conftest.py new file mode 100644 index 000000000000..d958e01cafda --- /dev/null +++ b/privateca/snippets/conftest.py @@ -0,0 +1,83 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import uuid + +import google.auth +import pytest + +from create_ca_pool import create_ca_pool +from create_certificate_authority import create_certificate_authority +from create_certificate_template import create_certificate_template +from delete_ca_pool import delete_ca_pool +from delete_certificate_authority import delete_certificate_authority +from delete_certificate_template import delete_certificate_template + +PROJECT = google.auth.default()[1] +LOCATION = "us-central1" +COMMON_NAME = "COMMON_NAME" +ORGANIZATION = "ORGANIZATION" +CA_DURATION = 1000000 + + +def generate_name() -> str: + return "test-" + uuid.uuid4().hex[:10] + + +@pytest.fixture +def ca_pool(): + CA_POOL_NAME = generate_name() + + create_ca_pool(PROJECT, LOCATION, CA_POOL_NAME) + + yield CA_POOL_NAME + + delete_ca_pool(PROJECT, LOCATION, CA_POOL_NAME) + + +@pytest.fixture +def certificate_authority(ca_pool): + CA_NAME = generate_name() + + create_certificate_authority( + PROJECT, LOCATION, ca_pool, CA_NAME, COMMON_NAME, ORGANIZATION, CA_DURATION + ) + + yield ca_pool, CA_NAME + + delete_certificate_authority(PROJECT, LOCATION, ca_pool, CA_NAME) + + +@pytest.fixture +def deleted_certificate_authority(ca_pool): + CA_NAME = generate_name() + + create_certificate_authority( + PROJECT, LOCATION, ca_pool, CA_NAME, COMMON_NAME, ORGANIZATION, CA_DURATION + ) + + delete_certificate_authority(PROJECT, LOCATION, ca_pool, CA_NAME) + + yield ca_pool, CA_NAME + + +@pytest.fixture +def certificate_template(): + TEMPLATE_NAME = generate_name() + + create_certificate_template(PROJECT, LOCATION, TEMPLATE_NAME) + + yield TEMPLATE_NAME + + delete_certificate_template(PROJECT, LOCATION, TEMPLATE_NAME) diff --git a/privateca/snippets/create_ca_pool.py b/privateca/snippets/create_ca_pool.py new file mode 100644 index 000000000000..2b11785b039b --- /dev/null +++ b/privateca/snippets/create_ca_pool.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python + +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START privateca_create_ca_pool] +import google.cloud.security.privateca_v1 as privateca_v1 + + +def create_ca_pool(project_id: str, location: str, ca_pool_name: str) -> None: + """ + Create a Certificate Authority pool. All certificates created under this CA pool will + follow the same issuance policy, IAM policies,etc., + + Args: + project_id: project ID or project number of the Cloud project you want to use. + location: location you want to use. For a list of locations, see: https://cloud.google.com/certificate-authority-service/docs/locations. + ca_pool_name: a unique name for the ca pool. + """ + + caServiceClient = privateca_v1.CertificateAuthorityServiceClient() + + ca_pool = privateca_v1.CaPool( + # Set the tier (see: https://cloud.google.com/certificate-authority-service/docs/tiers). + tier=privateca_v1.CaPool.Tier.ENTERPRISE, + ) + location_path = caServiceClient.common_location_path(project_id, location) + + # Create the pool request. + request = privateca_v1.CreateCaPoolRequest( + parent=location_path, + ca_pool_id=ca_pool_name, + ca_pool=ca_pool, + ) + + # Create the CA pool. + operation = caServiceClient.create_ca_pool(request=request) + + print("Operation result:", operation.result()) + + +# [END privateca_create_ca_pool] diff --git a/privateca/snippets/create_certificate.py b/privateca/snippets/create_certificate.py new file mode 100644 index 000000000000..053305654296 --- /dev/null +++ b/privateca/snippets/create_certificate.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python + +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START privateca_create_certificate] +import google.cloud.security.privateca_v1 as privateca_v1 +from google.protobuf import duration_pb2 + + +def create_certificate( + project_id: str, + location: str, + ca_pool_name: str, + ca_name: str, + certificate_name: str, + common_name: str, + domain_name: str, + certificate_lifetime: int, + public_key_bytes: bytes, +) -> None: + """ + Create a Certificate which is issued by the Certificate Authority present in the CA Pool. + The key used to sign the certificate is created by the Cloud KMS. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + location: location you want to use. For a list of locations, see: https://cloud.google.com/certificate-authority-service/docs/locations. + ca_pool_name: set a unique name for the CA pool. + ca_name: the name of the certificate authority which issues the certificate. + certificate_name: set a unique name for the certificate. + common_name: a title for your certificate. + domain_name: fully qualified domain name for your certificate. + certificate_lifetime: the validity of the certificate in seconds. + public_key_bytes: public key used in signing the certificates. + """ + + caServiceClient = privateca_v1.CertificateAuthorityServiceClient() + + # The public key used to sign the certificate can be generated using any crypto library/framework. + # Also you can use Cloud KMS to retrieve an already created public key. + # For more info, see: https://cloud.google.com/kms/docs/retrieve-public-key. + + # Set the Public Key and its format. + public_key = privateca_v1.PublicKey( + key=public_key_bytes, + format_=privateca_v1.PublicKey.KeyFormat.PEM, + ) + + subject_config = privateca_v1.CertificateConfig.SubjectConfig( + subject=privateca_v1.Subject(common_name=common_name), + subject_alt_name=privateca_v1.SubjectAltNames(dns_names=[domain_name]), + ) + + # Set the X.509 fields required for the certificate. + x509_parameters = privateca_v1.X509Parameters( + key_usage=privateca_v1.KeyUsage( + base_key_usage=privateca_v1.KeyUsage.KeyUsageOptions( + digital_signature=True, + key_encipherment=True, + ), + extended_key_usage=privateca_v1.KeyUsage.ExtendedKeyUsageOptions( + server_auth=True, + client_auth=True, + ), + ), + ) + + # Create certificate. + certificate = privateca_v1.Certificate( + config=privateca_v1.CertificateConfig( + public_key=public_key, + subject_config=subject_config, + x509_config=x509_parameters, + ), + lifetime=duration_pb2.Duration(seconds=certificate_lifetime), + ) + + # Create the Certificate Request. + request = privateca_v1.CreateCertificateRequest( + parent=caServiceClient.ca_pool_path(project_id, location, ca_pool_name), + certificate_id=certificate_name, + certificate=certificate, + issuing_certificate_authority_id=ca_name, + ) + result = caServiceClient.create_certificate(request=request) + + print("Certificate creation result:", result) + + +# [END privateca_create_certificate] diff --git a/privateca/snippets/create_certificate_authority.py b/privateca/snippets/create_certificate_authority.py new file mode 100644 index 000000000000..2cb0c65ec17d --- /dev/null +++ b/privateca/snippets/create_certificate_authority.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python + +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START privateca_create_ca] +import google.cloud.security.privateca_v1 as privateca_v1 +from google.protobuf import duration_pb2 + + +def create_certificate_authority( + project_id: str, + location: str, + ca_pool_name: str, + ca_name: str, + common_name: str, + organization: str, + ca_duration: int, +) -> None: + """ + Create Certificate Authority which is the root CA in the given CA Pool. This CA will be + responsible for signing certificates within this pool. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + location: location you want to use. For a list of locations, see: https://cloud.google.com/certificate-authority-service/docs/locations. + ca_pool_name: set it to the CA Pool under which the CA should be created. + ca_name: unique name for the CA. + common_name: a title for your certificate authority. + organization: the name of your company for your certificate authority. + ca_duration: the validity of the certificate authority in seconds. + """ + + caServiceClient = privateca_v1.CertificateAuthorityServiceClient() + + # Set the types of Algorithm used to create a cloud KMS key. + key_version_spec = privateca_v1.CertificateAuthority.KeyVersionSpec( + algorithm=privateca_v1.CertificateAuthority.SignHashAlgorithm.RSA_PKCS1_4096_SHA256 + ) + + # Set CA subject config. + subject_config = privateca_v1.CertificateConfig.SubjectConfig( + subject=privateca_v1.Subject(common_name=common_name, organization=organization) + ) + + # Set the key usage options for X.509 fields. + x509_parameters = privateca_v1.X509Parameters( + key_usage=privateca_v1.KeyUsage( + base_key_usage=privateca_v1.KeyUsage.KeyUsageOptions( + crl_sign=True, + cert_sign=True, + ) + ), + ca_options=privateca_v1.X509Parameters.CaOptions( + is_ca=True, + ), + ) + + # Set certificate authority settings. + certificate_authority = privateca_v1.CertificateAuthority( + # CertificateAuthority.Type.SELF_SIGNED denotes that this CA is a root CA. + type_=privateca_v1.CertificateAuthority.Type.SELF_SIGNED, + key_spec=key_version_spec, + config=privateca_v1.CertificateConfig( + subject_config=subject_config, + x509_config=x509_parameters, + ), + lifetime=duration_pb2.Duration(seconds=ca_duration), + ) + + ca_pool_path = caServiceClient.ca_pool_path(project_id, location, ca_pool_name) + + # Create the CertificateAuthorityRequest. + request = privateca_v1.CreateCertificateAuthorityRequest( + parent=ca_pool_path, + certificate_authority_id=ca_name, + certificate_authority=certificate_authority, + ) + + operation = caServiceClient.create_certificate_authority(request=request) + result = operation.result() + + print("Operation result:", result) + + +# [END privateca_create_ca] diff --git a/privateca/snippets/create_certificate_csr.py b/privateca/snippets/create_certificate_csr.py new file mode 100644 index 000000000000..d3bc892507ce --- /dev/null +++ b/privateca/snippets/create_certificate_csr.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python + +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START privateca_create_certificate_csr] +import google.cloud.security.privateca_v1 as privateca_v1 +from google.protobuf import duration_pb2 + + +def create_certificate_csr( + project_id: str, + location: str, + ca_pool_name: str, + ca_name: str, + certificate_name: str, + certificate_lifetime: int, + pem_csr: str, +) -> None: + """ + Create a Certificate which is issued by the specified Certificate Authority (CA). + The certificate details and the public key is provided as a Certificate Signing Request (CSR). + Args: + project_id: project ID or project number of the Cloud project you want to use. + location: location you want to use. For a list of locations, see: https://cloud.google.com/certificate-authority-service/docs/locations. + ca_pool_name: set a unique name for the CA pool. + ca_name: the name of the certificate authority to sign the CSR. + certificate_name: set a unique name for the certificate. + certificate_lifetime: the validity of the certificate in seconds. + pem_csr: set the Certificate Issuing Request in the pem encoded format. + """ + + ca_service_client = privateca_v1.CertificateAuthorityServiceClient() + + # The public key used to sign the certificate can be generated using any crypto library/framework. + # Also you can use Cloud KMS to retrieve an already created public key. + # For more info, see: https://cloud.google.com/kms/docs/retrieve-public-key. + + # Create certificate with CSR. + # The pem_csr contains the public key and the domain details required. + certificate = privateca_v1.Certificate( + pem_csr=pem_csr, + lifetime=duration_pb2.Duration(seconds=certificate_lifetime), + ) + + # Create the Certificate Request. + # Set the CA which is responsible for creating the certificate with the provided CSR. + request = privateca_v1.CreateCertificateRequest( + parent=ca_service_client.ca_pool_path(project_id, location, ca_pool_name), + certificate_id=certificate_name, + certificate=certificate, + issuing_certificate_authority_id=ca_name, + ) + response = ca_service_client.create_certificate(request=request) + + print(f"Certificate created successfully: {response.name}") + + # Get the signed certificate and the issuer chain list. + print(f"Signed certificate: {response.pem_certificate}") + print(f"Issuer chain list: {response.pem_certificate_chain}") + + +# [END privateca_create_certificate_csr] diff --git a/privateca/snippets/create_certificate_template.py b/privateca/snippets/create_certificate_template.py new file mode 100644 index 000000000000..988ebfc364b4 --- /dev/null +++ b/privateca/snippets/create_certificate_template.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python + +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START privateca_create_certificate_template] +import google.cloud.security.privateca_v1 as privateca_v1 +from google.type import expr_pb2 + + +def create_certificate_template( + project_id: str, + location: str, + certificate_template_id: str, +) -> None: + """ + Create a Certificate template. These templates can be reused for common + certificate issuance scenarios. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + location: location you want to use. For a list of locations, see: https://cloud.google.com/certificate-authority-service/docs/locations. + certificate_template_id: set a unique name for the certificate template. + """ + + caServiceClient = privateca_v1.CertificateAuthorityServiceClient() + + # Describes any predefined X.509 values set by this template. + # The provided extensions are copied over to certificate requests that use this template. + x509_parameters = privateca_v1.X509Parameters( + key_usage=privateca_v1.KeyUsage( + base_key_usage=privateca_v1.KeyUsage.KeyUsageOptions( + digital_signature=True, + key_encipherment=True, + ), + extended_key_usage=privateca_v1.KeyUsage.ExtendedKeyUsageOptions( + server_auth=True, + ), + ), + ca_options=privateca_v1.X509Parameters.CaOptions( + is_ca=False, + ), + ) + + # CEL expression that is evaluated against the Subject and + # Subject Alternative Name of the certificate before it is issued. + expr = expr_pb2.Expr(expression="subject_alt_names.all(san, san.type == DNS)") + + # Set the certificate issuance schema. + certificate_template = privateca_v1.CertificateTemplate( + predefined_values=x509_parameters, + identity_constraints=privateca_v1.CertificateIdentityConstraints( + cel_expression=expr, + allow_subject_passthrough=False, + allow_subject_alt_names_passthrough=False, + ), + ) + + # Request to create a certificate template. + request = privateca_v1.CreateCertificateTemplateRequest( + parent=caServiceClient.common_location_path(project_id, location), + certificate_template=certificate_template, + certificate_template_id=certificate_template_id, + ) + operation = caServiceClient.create_certificate_template(request=request) + result = operation.result() + + print("Operation result:", result) + + +# [END privateca_create_certificate_template] diff --git a/privateca/snippets/create_subordinate_ca.py b/privateca/snippets/create_subordinate_ca.py new file mode 100644 index 000000000000..426a047dc980 --- /dev/null +++ b/privateca/snippets/create_subordinate_ca.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python + +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START privateca_create_subordinateca] +import google.cloud.security.privateca_v1 as privateca_v1 +from google.protobuf import duration_pb2 + + +def create_subordinate_ca( + project_id: str, + location: str, + ca_pool_name: str, + subordinate_ca_name: str, + common_name: str, + organization: str, + domain: str, + ca_duration: int, +) -> None: + """ + Create Certificate Authority (CA) which is the subordinate CA in the given CA Pool. + Args: + project_id: project ID or project number of the Cloud project you want to use. + location: location you want to use. For a list of locations, see: https://cloud.google.com/certificate-authority-service/docs/locations. + ca_pool_name: set it to the CA Pool under which the CA should be created. + subordinate_ca_name: unique name for the Subordinate CA. + common_name: a title for your certificate authority. + organization: the name of your company for your certificate authority. + domain: the name of your company for your certificate authority. + ca_duration: the validity of the certificate authority in seconds. + """ + + ca_service_client = privateca_v1.CertificateAuthorityServiceClient() + + # Set the type of Algorithm + key_version_spec = privateca_v1.CertificateAuthority.KeyVersionSpec( + algorithm=privateca_v1.CertificateAuthority.SignHashAlgorithm.RSA_PKCS1_4096_SHA256 + ) + + # Set CA subject config. + subject_config = privateca_v1.CertificateConfig.SubjectConfig( + subject=privateca_v1.Subject( + common_name=common_name, organization=organization + ), + # Set the fully qualified domain name. + subject_alt_name=privateca_v1.SubjectAltNames(dns_names=[domain]), + ) + + # Set the key usage options for X.509 fields. + x509_parameters = privateca_v1.X509Parameters( + key_usage=privateca_v1.KeyUsage( + base_key_usage=privateca_v1.KeyUsage.KeyUsageOptions( + crl_sign=True, + cert_sign=True, + ) + ), + ca_options=privateca_v1.X509Parameters.CaOptions( + is_ca=True, + ), + ) + + # Set certificate authority settings. + certificate_authority = privateca_v1.CertificateAuthority( + type_=privateca_v1.CertificateAuthority.Type.SUBORDINATE, + key_spec=key_version_spec, + config=privateca_v1.CertificateConfig( + subject_config=subject_config, + x509_config=x509_parameters, + ), + # Set the CA validity duration. + lifetime=duration_pb2.Duration(seconds=ca_duration), + ) + + ca_pool_path = ca_service_client.ca_pool_path(project_id, location, ca_pool_name) + + # Create the CertificateAuthorityRequest. + request = privateca_v1.CreateCertificateAuthorityRequest( + parent=ca_pool_path, + certificate_authority_id=subordinate_ca_name, + certificate_authority=certificate_authority, + ) + + operation = ca_service_client.create_certificate_authority(request=request) + result = operation.result() + + print(f"Operation result: {result}") + + +# [END privateca_create_subordinateca] diff --git a/privateca/snippets/delete_ca_pool.py b/privateca/snippets/delete_ca_pool.py new file mode 100644 index 000000000000..e90f89b00f8a --- /dev/null +++ b/privateca/snippets/delete_ca_pool.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python + +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START privateca_delete_ca_pool] +import google.cloud.security.privateca_v1 as privateca_v1 + + +def delete_ca_pool(project_id: str, location: str, ca_pool_name: str) -> None: + """ + Delete the CA pool as mentioned by the ca_pool_name. + Before deleting the pool, all CAs in the pool MUST BE deleted. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + location: location you want to use. For a list of locations, see: https://cloud.google.com/certificate-authority-service/docs/locations. + ca_pool_name: the name of the CA pool to be deleted. + """ + + caServiceClient = privateca_v1.CertificateAuthorityServiceClient() + + ca_pool_path = caServiceClient.ca_pool_path(project_id, location, ca_pool_name) + + # Create the Delete request. + request = privateca_v1.DeleteCaPoolRequest(name=ca_pool_path) + + # Delete the CA Pool. + caServiceClient.delete_ca_pool(request=request) + + print("Deleted CA Pool:", ca_pool_name) + + +# [END privateca_delete_ca_pool] diff --git a/privateca/snippets/delete_certificate_authority.py b/privateca/snippets/delete_certificate_authority.py new file mode 100644 index 000000000000..fc0b73e4c1cf --- /dev/null +++ b/privateca/snippets/delete_certificate_authority.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python + +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START privateca_delete_ca] +import google.cloud.security.privateca_v1 as privateca_v1 + + +def delete_certificate_authority( + project_id: str, location: str, ca_pool_name: str, ca_name: str +) -> None: + """ + Delete the Certificate Authority from the specified CA pool. + Before deletion, the CA must be disabled and must not contain any active certificates. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + location: location you want to use. For a list of locations, see: https://cloud.google.com/certificate-authority-service/docs/locations. + ca_pool_name: the name of the CA pool under which the CA is present. + ca_name: the name of the CA to be deleted. + """ + + caServiceClient = privateca_v1.CertificateAuthorityServiceClient() + ca_path = caServiceClient.certificate_authority_path( + project_id, location, ca_pool_name, ca_name + ) + + # Check if the CA is enabled. + ca_state = caServiceClient.get_certificate_authority(name=ca_path).state + print(ca_state) + if ca_state == privateca_v1.CertificateAuthority.State.ENABLED: + print( + "Please disable the Certificate Authority before deletion ! Current state:", + ca_state, + ) + + # Create the DeleteCertificateAuthorityRequest. + # Setting the ignore_active_certificates to True will delete the CA + # even if it contains active certificates. Care should be taken to re-anchor + # the certificates to new CA before deleting. + request = privateca_v1.DeleteCertificateAuthorityRequest( + name=ca_path, ignore_active_certificates=False + ) + + # Delete the Certificate Authority. + operation = caServiceClient.delete_certificate_authority(request=request) + result = operation.result() + + print("Operation result", result) + + # Get the current CA state. + ca_state = caServiceClient.get_certificate_authority(name=ca_path).state + + # Check if the CA has been deleted. + if ca_state == privateca_v1.CertificateAuthority.State.DELETED: + print("Successfully deleted Certificate Authority:", ca_name) + else: + print( + "Unable to delete Certificate Authority. Please try again ! Current state:", + ca_state, + ) + + +# [END privateca_delete_ca] diff --git a/privateca/snippets/delete_certificate_template.py b/privateca/snippets/delete_certificate_template.py new file mode 100644 index 000000000000..e44dca178c7c --- /dev/null +++ b/privateca/snippets/delete_certificate_template.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python + +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START privateca_delete_certificate_template] +import google.cloud.security.privateca_v1 as privateca_v1 + + +def delete_certificate_template( + project_id: str, + location: str, + certificate_template_id: str, +) -> None: + """ + Delete the certificate template present in the given project and location. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + location: location you want to use. For a list of locations, see: https://cloud.google.com/certificate-authority-service/docs/locations. + certificate_template_id: set a unique name for the certificate template. + """ + + caServiceClient = privateca_v1.CertificateAuthorityServiceClient() + + # Request to delete a certificate template. + request = privateca_v1.DeleteCertificateTemplateRequest( + name=caServiceClient.certificate_template_path( + project_id, + location, + certificate_template_id, + ) + ) + operation = caServiceClient.delete_certificate_template(request=request) + result = operation.result() + + print("Operation result", result) + print("Deleted certificate template:", certificate_template_id) + + +# [END privateca_delete_certificate_template] diff --git a/privateca/snippets/disable_certificate_authority.py b/privateca/snippets/disable_certificate_authority.py new file mode 100644 index 000000000000..5ec4e7c0c7a5 --- /dev/null +++ b/privateca/snippets/disable_certificate_authority.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python + +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START privateca_disable_ca] +import google.cloud.security.privateca_v1 as privateca_v1 + + +def disable_certificate_authority( + project_id: str, location: str, ca_pool_name: str, ca_name: str +) -> None: + """ + Disable a Certificate Authority which is present in the given CA pool. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + location: location you want to use. For a list of locations, see: https://cloud.google.com/certificate-authority-service/docs/locations. + ca_pool_name: the name of the CA pool under which the CA is present. + ca_name: the name of the CA to be disabled. + """ + + caServiceClient = privateca_v1.CertificateAuthorityServiceClient() + ca_path = caServiceClient.certificate_authority_path( + project_id, location, ca_pool_name, ca_name + ) + + # Create the Disable Certificate Authority Request. + request = privateca_v1.DisableCertificateAuthorityRequest(name=ca_path) + + # Disable the Certificate Authority. + operation = caServiceClient.disable_certificate_authority(request=request) + result = operation.result() + + print("Operation result:", result) + + # Get the current CA state. + ca_state = caServiceClient.get_certificate_authority(name=ca_path).state + + # Check if the CA is disabled. + if ca_state == privateca_v1.CertificateAuthority.State.DISABLED: + print("Disabled Certificate Authority:", ca_name) + else: + print("Cannot disable the Certificate Authority ! Current CA State:", ca_state) + + +# [END privateca_disable_ca] diff --git a/privateca/snippets/enable_certificate_authority.py b/privateca/snippets/enable_certificate_authority.py new file mode 100644 index 000000000000..a6ecd35580bc --- /dev/null +++ b/privateca/snippets/enable_certificate_authority.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python + +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START privateca_enable_ca] +import google.cloud.security.privateca_v1 as privateca_v1 + + +def enable_certificate_authority( + project_id: str, location: str, ca_pool_name: str, ca_name: str +) -> None: + """ + Enable the Certificate Authority present in the given ca pool. + CA cannot be enabled if it has been already deleted. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + location: location you want to use. For a list of locations, see: https://cloud.google.com/certificate-authority-service/docs/locations. + ca_pool_name: the name of the CA pool under which the CA is present. + ca_name: the name of the CA to be enabled. + """ + + caServiceClient = privateca_v1.CertificateAuthorityServiceClient() + ca_path = caServiceClient.certificate_authority_path( + project_id, location, ca_pool_name, ca_name + ) + + # Create the Enable Certificate Authority Request. + request = privateca_v1.EnableCertificateAuthorityRequest( + name=ca_path, + ) + + # Enable the Certificate Authority. + operation = caServiceClient.enable_certificate_authority(request=request) + result = operation.result() + + print("Operation result:", result) + + # Get the current CA state. + ca_state = caServiceClient.get_certificate_authority(name=ca_path).state + + # Check if the CA is enabled. + if ca_state == privateca_v1.CertificateAuthority.State.ENABLED: + print("Enabled Certificate Authority:", ca_name) + else: + print("Cannot enable the Certificate Authority ! Current CA State:", ca_state) + + +# [END privateca_enable_ca] diff --git a/privateca/snippets/filter_certificates.py b/privateca/snippets/filter_certificates.py new file mode 100644 index 000000000000..c9789dcc639c --- /dev/null +++ b/privateca/snippets/filter_certificates.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python + +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START privateca_filter_certificate] +import google.cloud.security.privateca_v1 as privateca_v1 + + +def filter_certificates( + project_id: str, location: str, ca_pool_name: str, filter_condition: str +) -> None: + """ + Filter certificates based on a condition and list them. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + location: location you want to use. For a list of locations, see: https://cloud.google.com/certificate-authority-service/docs/locations. + ca_pool_name: name of the CA pool which contains the certificates to be listed. + """ + + caServiceClient = privateca_v1.CertificateAuthorityServiceClient() + + ca_pool_path = caServiceClient.ca_pool_path(project_id, location, ca_pool_name) + + # Create the certificate request and set the filter condition. + request = privateca_v1.ListCertificatesRequest( + parent=ca_pool_path, + filter=filter_condition, + ) + + # Retrieve and print the certificate names. + print("Available certificates: ") + for cert in caServiceClient.list_certificates(request=request): + print(f"- {cert.name}") + + +# [END privateca_filter_certificate] diff --git a/privateca/snippets/list_ca_pools.py b/privateca/snippets/list_ca_pools.py new file mode 100644 index 000000000000..b072045e4345 --- /dev/null +++ b/privateca/snippets/list_ca_pools.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python + +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START privateca_list_ca_pool] +import google.cloud.security.privateca_v1 as privateca_v1 + + +def list_ca_pools(project_id: str, location: str) -> None: + """ + List all CA pools present in the given project and location. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + location: location you want to use. For a list of locations, see: https://cloud.google.com/certificate-authority-service/docs/locations. + """ + + caServiceClient = privateca_v1.CertificateAuthorityServiceClient() + + location_path = caServiceClient.common_location_path(project_id, location) + + request = privateca_v1.ListCaPoolsRequest(parent=location_path) + + print("Available CA pools:") + + for ca_pool in caServiceClient.list_ca_pools(request=request): + ca_pool_name = ca_pool.name + # ca_pool.name represents the full resource name of the + # format 'projects/{project-id}/locations/{location}/ca-pools/{ca-pool-name}'. + # Hence stripping it down to just pool name. + print(caServiceClient.parse_ca_pool_path(ca_pool_name)["ca_pool"]) + + +# [END privateca_list_ca_pool] diff --git a/privateca/snippets/list_certificate_authorities.py b/privateca/snippets/list_certificate_authorities.py new file mode 100644 index 000000000000..19fd37d9546b --- /dev/null +++ b/privateca/snippets/list_certificate_authorities.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python + +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START privateca_list_ca] +import google.cloud.security.privateca_v1 as privateca_v1 + + +def list_certificate_authorities( + project_id: str, location: str, ca_pool_name: str +) -> None: + """ + List all Certificate authorities present in the given CA Pool. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + location: location you want to use. For a list of locations, see: https://cloud.google.com/certificate-authority-service/docs/locations. + ca_pool_name: the name of the CA pool under which the CAs to be listed are present. + """ + + caServiceClient = privateca_v1.CertificateAuthorityServiceClient() + + ca_pool_path = caServiceClient.ca_pool_path(project_id, location, ca_pool_name) + + # List the CA name and its corresponding state. + for ca in caServiceClient.list_certificate_authorities(parent=ca_pool_path): + print(ca.name, "is", ca.state) + + +# [END privateca_list_ca] diff --git a/privateca/snippets/list_certificate_templates.py b/privateca/snippets/list_certificate_templates.py new file mode 100644 index 000000000000..8e8c4c7d5c0d --- /dev/null +++ b/privateca/snippets/list_certificate_templates.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python + +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START privateca_list_certificate_template] +import google.cloud.security.privateca_v1 as privateca_v1 + + +def list_certificate_templates(project_id: str, location: str) -> None: + """ + List the certificate templates present in the given project and location. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + location: location you want to use. For a list of locations, see: https://cloud.google.com/certificate-authority-service/docs/locations. + """ + + caServiceClient = privateca_v1.CertificateAuthorityServiceClient() + + # List Templates Request. + request = privateca_v1.ListCertificateTemplatesRequest( + parent=caServiceClient.common_location_path(project_id, location), + ) + + print("Available certificate templates:") + for certificate_template in caServiceClient.list_certificate_templates( + request=request + ): + print(certificate_template.name) + + +# [END privateca_list_certificate_template] diff --git a/privateca/snippets/list_certificates.py b/privateca/snippets/list_certificates.py new file mode 100644 index 000000000000..9c04ed93470f --- /dev/null +++ b/privateca/snippets/list_certificates.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python + +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START privateca_list_certificate] + +import google.cloud.security.privateca_v1 as privateca_v1 + + +def list_certificates( + project_id: str, + location: str, + ca_pool_name: str, +) -> None: + """ + List Certificates present in the given CA pool. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + location: location you want to use. For a list of locations, see: https://cloud.google.com/certificate-authority-service/docs/locations. + ca_pool_name: name of the CA pool which contains the certificates to be listed. + """ + + caServiceClient = privateca_v1.CertificateAuthorityServiceClient() + + ca_pool_path = caServiceClient.ca_pool_path(project_id, location, ca_pool_name) + + # Retrieve and print the certificate names. + print(f"Available certificates in CA pool {ca_pool_name}:") + for certificate in caServiceClient.list_certificates(parent=ca_pool_path): + print(certificate.name) + + +# [END privateca_list_certificate] diff --git a/privateca/snippets/monitor_certificate_authority.py b/privateca/snippets/monitor_certificate_authority.py new file mode 100644 index 000000000000..bac5e023b983 --- /dev/null +++ b/privateca/snippets/monitor_certificate_authority.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python + +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START privateca_monitor_ca_expiry] +import google.cloud.monitoring_v3 as monitoring_v3 + + +def create_ca_monitor_policy(project_id: str) -> None: + """ + Create a monitoring policy that notifies you 30 days before a managed CA expires. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + """ + + alertPolicyServiceClient = monitoring_v3.AlertPolicyServiceClient() + notificationChannelServiceClient = monitoring_v3.NotificationChannelServiceClient() + + # Query which indicates the resource to monitor and the constraints. + # Here, the alert policy notifies you 30 days before a managed CA expires. + # For more information on creating queries, see: https://cloud.google.com/monitoring/mql/alerts + query = ( + "fetch privateca.googleapis.com/CertificateAuthority" + "| metric 'privateca.googleapis.com/ca/cert_chain_expiration'" + "| group_by 5m," + "[value_cert_chain_expiration_mean: mean(value.cert_chain_expiration)]" + "| every 5m" + "| condition val() < 2.592e+06 's'" + ) + + # Create a notification channel. + notification_channel = monitoring_v3.NotificationChannel( + type_="email", + labels={"email_address": "python-docs-samples-testing@google.com"}, + ) + channel = notificationChannelServiceClient.create_notification_channel( + name=notificationChannelServiceClient.common_project_path(project_id), + notification_channel=notification_channel, + ) + + # Set the query and notification channel. + alert_policy = monitoring_v3.AlertPolicy( + display_name="policy-name", + conditions=[ + monitoring_v3.AlertPolicy.Condition( + display_name="ca-cert-chain-expiration", + condition_monitoring_query_language=monitoring_v3.AlertPolicy.Condition.MonitoringQueryLanguageCondition( + query=query, + ), + ) + ], + combiner=monitoring_v3.AlertPolicy.ConditionCombinerType.AND, + notification_channels=[channel.name], + ) + + policy = alertPolicyServiceClient.create_alert_policy( + name=notificationChannelServiceClient.common_project_path(project_id), + alert_policy=alert_policy, + ) + + print("Monitoring policy successfully created!", policy.name) + + +# [END privateca_monitor_ca_expiry] diff --git a/privateca/snippets/noxfile_config.py b/privateca/snippets/noxfile_config.py new file mode 100644 index 000000000000..4a4db8c2de30 --- /dev/null +++ b/privateca/snippets/noxfile_config.py @@ -0,0 +1,38 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Default TEST_CONFIG_OVERRIDE for python repos. + +# You can copy this file into your directory, then it will be inported from +# the noxfile.py. + +# The source of truth: +# https://github.com/GoogleCloudPlatform/python-docs-samples/blob/master/noxfile_config.py + +TEST_CONFIG_OVERRIDE = { + # You can opt out from the test for specific Python versions. + "ignored_versions": ["2.7"], + # Old samples are opted out of enforcing Python type hints + # All new samples should feature them + "enforce_type_hints": False, + # An envvar key for determining the project id to use. Change it + # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a + # build specific Cloud project. You can also use your own string + # to use your own Cloud project. + # "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", + "gcloud_project_env": "BUILD_SPECIFIC_GCLOUD_PROJECT", + # A dictionary you want to inject into your test. Don't put any + # secrets here. These values will override predefined values. + "envs": {}, +} diff --git a/privateca/snippets/requirements-test.txt b/privateca/snippets/requirements-test.txt new file mode 100644 index 000000000000..10a038a3a41c --- /dev/null +++ b/privateca/snippets/requirements-test.txt @@ -0,0 +1,4 @@ +pytest==7.2.1 +google-auth==2.16.0 +cryptography==39.0.0 +backoff==2.2.1 \ No newline at end of file diff --git a/privateca/snippets/requirements.txt b/privateca/snippets/requirements.txt new file mode 100644 index 000000000000..539381cc9c26 --- /dev/null +++ b/privateca/snippets/requirements.txt @@ -0,0 +1,3 @@ +google-cloud-private-ca==1.6.0 +google-cloud-kms==2.14.0 +google-cloud-monitoring==2.14.0 \ No newline at end of file diff --git a/privateca/snippets/revoke_certificate.py b/privateca/snippets/revoke_certificate.py new file mode 100644 index 000000000000..fa0d2f24e203 --- /dev/null +++ b/privateca/snippets/revoke_certificate.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python + +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys + +# isort: split +# [START privateca_revoke_certificate] + +import google.cloud.security.privateca_v1 as privateca_v1 + + +def revoke_certificate( + project_id: str, + location: str, + ca_pool_name: str, + certificate_name: str, +) -> None: + """ + Revoke an issued certificate. Once revoked, the certificate will become invalid and will expire post its lifetime. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + location: location you want to use. For a list of locations, see: https://cloud.google.com/certificate-authority-service/docs/locations. + ca_pool_name: name for the CA pool which contains the certificate. + certificate_name: name of the certificate to be revoked. + """ + + caServiceClient = privateca_v1.CertificateAuthorityServiceClient() + + # Create Certificate Path. + certificate_path = caServiceClient.certificate_path( + project_id, location, ca_pool_name, certificate_name + ) + + # Create Revoke Certificate Request and specify the appropriate revocation reason. + request = privateca_v1.RevokeCertificateRequest( + name=certificate_path, reason=privateca_v1.RevocationReason.PRIVILEGE_WITHDRAWN + ) + result = caServiceClient.revoke_certificate(request=request) + + print("Certificate revoke result:", result) + + +# [END privateca_revoke_certificate] + +if __name__ == "__main__": + revoke_certificate( + project_id=sys.argv[1], + location=sys.argv[2], + ca_pool_name=sys.argv[3], + certificate_name=sys.argv[4], + ) diff --git a/privateca/snippets/test_ca_pools.py b/privateca/snippets/test_ca_pools.py new file mode 100644 index 000000000000..5fc17a5d3082 --- /dev/null +++ b/privateca/snippets/test_ca_pools.py @@ -0,0 +1,85 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import re +import typing +import uuid + +import google.auth + +from create_ca_pool import create_ca_pool +from delete_ca_pool import delete_ca_pool +from list_ca_pools import list_ca_pools +from update_ca_pool_issuance_policy import update_ca_pool_issuance_policy + +PROJECT = google.auth.default()[1] +LOCATION = "us-central1" + + +def generate_name() -> str: + return "test-" + uuid.uuid4().hex[:10] + + +def test_create_ca_pool(ca_pool, capsys: typing.Any) -> None: + CA_POOL_NAME = generate_name() + + create_ca_pool(PROJECT, LOCATION, CA_POOL_NAME) + + out, _ = capsys.readouterr() + + assert re.search( + f'Operation result: name: "projects/{PROJECT}/locations/{LOCATION}/caPools/{CA_POOL_NAME}"', + out, + ) + + delete_ca_pool(PROJECT, LOCATION, CA_POOL_NAME) + + +def test_list_ca_pools(capsys: typing.Any) -> None: + CA_POOL_NAME_1 = generate_name() + CA_POOL_NAME_2 = generate_name() + + create_ca_pool(PROJECT, LOCATION, CA_POOL_NAME_1) + create_ca_pool(PROJECT, LOCATION, CA_POOL_NAME_2) + list_ca_pools(PROJECT, LOCATION) + + out, _ = capsys.readouterr() + + assert "Available CA pools:" in out + assert f"{CA_POOL_NAME_1}\n" in out + assert f"{CA_POOL_NAME_2}\n" in out + + delete_ca_pool(PROJECT, LOCATION, CA_POOL_NAME_1) + delete_ca_pool(PROJECT, LOCATION, CA_POOL_NAME_2) + + +def test_delete_ca_pool(capsys: typing.Any) -> None: + CA_POOL_NAME = generate_name() + + create_ca_pool(PROJECT, LOCATION, CA_POOL_NAME) + delete_ca_pool(PROJECT, LOCATION, CA_POOL_NAME) + + out, _ = capsys.readouterr() + + assert re.search(f"Deleted CA Pool: {CA_POOL_NAME}", out) + + +def test_update_ca_pool_issuance_policy(ca_pool, capsys: typing.Any) -> None: + CA_POOL_NAME = ca_pool + + update_ca_pool_issuance_policy(PROJECT, LOCATION, CA_POOL_NAME) + + out, _ = capsys.readouterr() + + assert "CA Pool Issuance policy has been updated successfully!" in out diff --git a/privateca/snippets/test_certificate_authorities.py b/privateca/snippets/test_certificate_authorities.py new file mode 100644 index 000000000000..e3ad215bcb09 --- /dev/null +++ b/privateca/snippets/test_certificate_authorities.py @@ -0,0 +1,121 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import re +import typing +import uuid + +import backoff +import google.auth + +from create_ca_pool import create_ca_pool +from create_certificate_authority import create_certificate_authority +from delete_ca_pool import delete_ca_pool +from delete_certificate_authority import delete_certificate_authority +from disable_certificate_authority import disable_certificate_authority +from enable_certificate_authority import enable_certificate_authority +from monitor_certificate_authority import create_ca_monitor_policy +from undelete_certificate_authority import undelete_certificate_authority +from update_certificate_authority import update_ca_label + +PROJECT = google.auth.default()[1] +LOCATION = "us-central1" +COMMON_NAME = "COMMON_NAME" +ORGANIZATION = "ORGANIZATION" +CA_DURATION = 1000000 + + +def generate_name() -> str: + return "i" + uuid.uuid4().hex[:10] + + +@backoff.on_exception(backoff.expo, Exception, max_tries=3) +def test_create_certificate(capsys: typing.Any) -> None: + CA_POOL_NAME = generate_name() + CA_NAME = generate_name() + + create_ca_pool(PROJECT, LOCATION, CA_POOL_NAME) + create_certificate_authority( + PROJECT, LOCATION, CA_POOL_NAME, CA_NAME, COMMON_NAME, ORGANIZATION, CA_DURATION + ) + + out, _ = capsys.readouterr() + + assert re.search( + f'Operation result: name: "projects/{PROJECT}/locations/{LOCATION}/caPools/{CA_POOL_NAME}/certificateAuthorities/{CA_NAME}"', + out, + ) + + delete_certificate_authority(PROJECT, LOCATION, CA_POOL_NAME, CA_NAME) + delete_ca_pool(PROJECT, LOCATION, CA_POOL_NAME) + + +def test_enable_and_disable_certificate_authority( + certificate_authority, capsys: typing.Any +) -> None: + CA_POOL_NAME, CA_NAME = certificate_authority + + enable_certificate_authority(PROJECT, LOCATION, CA_POOL_NAME, CA_NAME) + disable_certificate_authority(PROJECT, LOCATION, CA_POOL_NAME, CA_NAME) + + out, _ = capsys.readouterr() + + assert re.search( + f"Enabled Certificate Authority: {CA_NAME}", + out, + ) + assert re.search( + f"Disabled Certificate Authority: {CA_NAME}", + out, + ) + + +def test_undelete_certificate_authority( + deleted_certificate_authority, capsys: typing.Any +) -> None: + CA_POOL_NAME, CA_NAME = deleted_certificate_authority + + undelete_certificate_authority(PROJECT, LOCATION, CA_POOL_NAME, CA_NAME) + delete_certificate_authority(PROJECT, LOCATION, CA_POOL_NAME, CA_NAME) + delete_ca_pool(PROJECT, LOCATION, CA_POOL_NAME) + + out, _ = capsys.readouterr() + assert re.search( + f"Successfully undeleted Certificate Authority: {CA_NAME}", + out, + ) + assert re.search( + f"Successfully deleted Certificate Authority: {CA_NAME}", + out, + ) + + +def test_update_certificate_authority( + certificate_authority, capsys: typing.Any +) -> None: + CA_POOL_NAME, CA_NAME = certificate_authority + + update_ca_label(PROJECT, LOCATION, CA_POOL_NAME, CA_NAME) + + out, _ = capsys.readouterr() + + assert "Successfully updated the labels !" in out + + +def test_create_monitor_ca_policy(capsys: typing.Any) -> None: + create_ca_monitor_policy(PROJECT) + + out, _ = capsys.readouterr() + + assert "Monitoring policy successfully created!" in out diff --git a/privateca/snippets/test_certificates.py b/privateca/snippets/test_certificates.py new file mode 100644 index 000000000000..35fcac35008f --- /dev/null +++ b/privateca/snippets/test_certificates.py @@ -0,0 +1,96 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import re +import time +import typing +import uuid + +from cryptography.hazmat.backends.openssl.backend import backend +from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat +import google.auth + +from create_certificate import create_certificate +from disable_certificate_authority import disable_certificate_authority +from enable_certificate_authority import enable_certificate_authority +from filter_certificates import filter_certificates +from revoke_certificate import revoke_certificate + +PROJECT = google.auth.default()[1] +LOCATION = "us-central1" +COMMON_NAME = "COMMON_NAME" +ORGANIZATION = "ORGANIZATION" +CERTIFICATE_LIFETIME = 1000000 +DOMAIN_NAME = "domain.com" + + +def generate_name() -> str: + return "test-" + uuid.uuid4().hex[:10] + + +def test_create_and_revoke_certificate_authority( + certificate_authority, capsys: typing.Any +) -> None: + CERT_NAME = generate_name() + + CA_POOL_NAME, CA_NAME = certificate_authority + enable_certificate_authority(PROJECT, LOCATION, CA_POOL_NAME, CA_NAME) + + private_key = rsa.generate_private_key( + public_exponent=65537, key_size=2048, backend=backend + ) + + public_key_bytes = private_key.public_key().public_bytes( + Encoding.PEM, PublicFormat.SubjectPublicKeyInfo + ) + + # Wait while crypto key is generating + time.sleep(5) + + create_certificate( + PROJECT, + LOCATION, + CA_POOL_NAME, + CA_NAME, + CERT_NAME, + COMMON_NAME, + DOMAIN_NAME, + CERTIFICATE_LIFETIME, + public_key_bytes, + ) + + FILTER_CONDITION = ( + f"certificate_description.subject_description.subject.common_name={COMMON_NAME}" + ) + filter_certificates(PROJECT, LOCATION, CA_POOL_NAME, FILTER_CONDITION) + + revoke_certificate( + PROJECT, + LOCATION, + CA_POOL_NAME, + CERT_NAME, + ) + + disable_certificate_authority(PROJECT, LOCATION, CA_POOL_NAME, CA_NAME) + + out, _ = capsys.readouterr() + assert "Certificate creation result:" in out + assert "Available certificates:" in out + assert re.search( + f"- projects/.*/locations/{LOCATION}/caPools/{CA_POOL_NAME}/certificates/{CERT_NAME}", + out, + ) + assert "Certificate revoke result:" in out diff --git a/privateca/snippets/test_crud_certificate_templates.py b/privateca/snippets/test_crud_certificate_templates.py new file mode 100644 index 000000000000..8c2c94b86d8a --- /dev/null +++ b/privateca/snippets/test_crud_certificate_templates.py @@ -0,0 +1,71 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import re +import typing +import uuid + +import google.auth + +from create_certificate_template import create_certificate_template +from delete_certificate_template import delete_certificate_template +from list_certificate_templates import list_certificate_templates +from update_certificate_template import update_certificate_template + +PROJECT = google.auth.default()[1] +LOCATION = "us-central1" +COMMON_NAME = "COMMON_NAME" +ORGANIZATION = "ORGANIZATION" +CA_DURATION = 1000000 + + +def generate_name() -> str: + return "i" + uuid.uuid4().hex[:10] + + +def test_create_delete_certificate_template(capsys: typing.Any) -> None: + TEMPLATE_NAME = generate_name() + + create_certificate_template(PROJECT, LOCATION, TEMPLATE_NAME) + delete_certificate_template(PROJECT, LOCATION, TEMPLATE_NAME) + + out, _ = capsys.readouterr() + + assert re.search( + f'Operation result: name: "projects/{PROJECT}/locations/{LOCATION}/certificateTemplates/{TEMPLATE_NAME}"', + out, + ) + + assert re.search(f"Deleted certificate template: {TEMPLATE_NAME}", out) + + +def test_list_certificate_templates(certificate_template, capsys: typing.Any) -> None: + TEMPLATE_NAME = certificate_template + + list_certificate_templates(PROJECT, LOCATION) + + out, _ = capsys.readouterr() + + assert "Available certificate templates:" in out + assert f"{TEMPLATE_NAME}\n" in out + + +def test_update_certificate_template(certificate_template, capsys: typing.Any) -> None: + TEMPLATE_NAME = certificate_template + + update_certificate_template(PROJECT, LOCATION, TEMPLATE_NAME) + + out, _ = capsys.readouterr() + + assert "Successfully updated the certificate template!" in out diff --git a/privateca/snippets/test_subordinate_ca.py b/privateca/snippets/test_subordinate_ca.py new file mode 100644 index 000000000000..d90eecf4c52a --- /dev/null +++ b/privateca/snippets/test_subordinate_ca.py @@ -0,0 +1,115 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import re +import typing +import uuid + +import backoff +import google.auth +import google.cloud.security.privateca_v1 as privateca_v1 + +from activate_subordinate_ca import activate_subordinate_ca +from create_certificate_csr import create_certificate_csr +from create_subordinate_ca import create_subordinate_ca +from revoke_certificate import revoke_certificate + +PROJECT = google.auth.default()[1] +LOCATION = "us-central1" +COMMON_NAME = "COMMON_NAME" +ORGANIZATION = "ORGANIZATION" +CA_DURATION = CERTIFICATE_LIFETIME = 1000000 +DOMAIN_NAME = "domain.com" + + +def generate_name() -> str: + return "test-" + uuid.uuid4().hex[:10] + + +@backoff.on_exception(backoff.expo, Exception, max_tries=3) +def test_subordinate_certificate_authority( + certificate_authority, capsys: typing.Any +) -> None: + CSR_CERT_NAME = generate_name() + SUBORDINATE_CA_NAME = generate_name() + + CA_POOL_NAME, ROOT_CA_NAME = certificate_authority + + # 1. Create a Subordinate Certificate Authority. + create_subordinate_ca( + PROJECT, + LOCATION, + CA_POOL_NAME, + SUBORDINATE_CA_NAME, + COMMON_NAME, + ORGANIZATION, + DOMAIN_NAME, + CA_DURATION, + ) + + # 2. Fetch CSR of the given CA. + ca_service_client = privateca_v1.CertificateAuthorityServiceClient() + + ca_path = ca_service_client.certificate_authority_path( + PROJECT, LOCATION, CA_POOL_NAME, SUBORDINATE_CA_NAME + ) + response = ca_service_client.fetch_certificate_authority_csr(name=ca_path) + pem_csr = response.pem_csr + + # 3. Sign the CSR and create a certificate. + create_certificate_csr( + PROJECT, + LOCATION, + CA_POOL_NAME, + ROOT_CA_NAME, + CSR_CERT_NAME, + CERTIFICATE_LIFETIME, + pem_csr, + ) + + # 4. Get certificate PEM format + certificate_name = ca_service_client.certificate_path( + PROJECT, LOCATION, CA_POOL_NAME, CSR_CERT_NAME + ) + pem_certificate = ca_service_client.get_certificate( + name=certificate_name + ).pem_certificate + + # 5. Activate Subordinate CA + activate_subordinate_ca( + PROJECT, + LOCATION, + CA_POOL_NAME, + SUBORDINATE_CA_NAME, + pem_certificate, + ROOT_CA_NAME, + ) + + revoke_certificate( + PROJECT, + LOCATION, + CA_POOL_NAME, + CSR_CERT_NAME, + ) + + out, _ = capsys.readouterr() + + assert re.search( + f'Operation result: name: "projects/{PROJECT}/locations/{LOCATION}/caPools/{CA_POOL_NAME}/certificateAuthorities/{SUBORDINATE_CA_NAME}"', + out, + ) + + assert "Certificate created successfully" in out + assert f"Current state: {privateca_v1.CertificateAuthority.State.STAGED}" in out diff --git a/privateca/snippets/undelete_certificate_authority.py b/privateca/snippets/undelete_certificate_authority.py new file mode 100644 index 000000000000..f436f891cd38 --- /dev/null +++ b/privateca/snippets/undelete_certificate_authority.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python + +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START privateca_undelete_ca] +import google.cloud.security.privateca_v1 as privateca_v1 + + +def undelete_certificate_authority( + project_id: str, location: str, ca_pool_name: str, ca_name: str +) -> None: + """ + Restore a deleted CA, if still within the grace period of 30 days. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + location: location you want to use. For a list of locations, see: https://cloud.google.com/certificate-authority-service/docs/locations. + ca_pool_name: the name of the CA pool under which the deleted CA is present. + ca_name: the name of the CA to be restored (undeleted). + """ + + caServiceClient = privateca_v1.CertificateAuthorityServiceClient() + ca_path = caServiceClient.certificate_authority_path( + project_id, location, ca_pool_name, ca_name + ) + + # Confirm if the CA is in DELETED stage. + ca_state = caServiceClient.get_certificate_authority(name=ca_path).state + if ca_state != privateca_v1.CertificateAuthority.State.DELETED: + print("CA is not deleted !") + return + + # Create the Request. + request = privateca_v1.UndeleteCertificateAuthorityRequest(name=ca_path) + + # Undelete the CA. + operation = caServiceClient.undelete_certificate_authority(request=request) + result = operation.result() + + print("Operation result", result) + + # Get the current CA state. + ca_state = caServiceClient.get_certificate_authority(name=ca_path).state + + # CA state changes from DELETED to DISABLED if successfully restored. + # Confirm if the CA is DISABLED. + if ca_state == privateca_v1.CertificateAuthority.State.DISABLED: + print("Successfully undeleted Certificate Authority:", ca_name) + else: + print( + "Unable to restore the Certificate Authority! Please try again! Current state:", + ca_state, + ) + + +# [END privateca_undelete_ca] diff --git a/privateca/snippets/update_ca_pool_issuance_policy.py b/privateca/snippets/update_ca_pool_issuance_policy.py new file mode 100644 index 000000000000..750c6f36a320 --- /dev/null +++ b/privateca/snippets/update_ca_pool_issuance_policy.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python + +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START privateca_set_issuance_policy] +import google.cloud.security.privateca_v1 as privateca_v1 +from google.protobuf import field_mask_pb2 +from google.type import expr_pb2 + + +def update_ca_pool_issuance_policy( + project_id: str, + location: str, + ca_pool_name: str, +) -> None: + """ + Update the issuance policy for a CA Pool. All certificates issued from this CA Pool should + meet the issuance policy + + Args: + project_id: project ID or project number of the Cloud project you want to use. + location: location you want to use. For a list of locations, see: https://cloud.google.com/certificate-authority-service/docs/locations. + ca_pool_name: a unique name for the ca pool. + """ + + caServiceClient = privateca_v1.CertificateAuthorityServiceClient() + + ca_pool_path = caServiceClient.ca_pool_path(project_id, location, ca_pool_name) + + # Set the updated issuance policy for the CA Pool. + # This particular issuance policy allows only SANs that + # have DNS Names as "us.google.org" or ending in ".google.com". */ + expr = expr_pb2.Expr( + expression='subject_alt_names.all(san, san.type == DNS && (san.value == "us.google.org" || san.value.endsWith(".google.com")) )' + ) + + issuance_policy = privateca_v1.CaPool.IssuancePolicy( + identity_constraints=privateca_v1.CertificateIdentityConstraints( + allow_subject_passthrough=True, + allow_subject_alt_names_passthrough=True, + cel_expression=expr, + ), + ) + + ca_pool = privateca_v1.CaPool( + name=ca_pool_path, + issuance_policy=issuance_policy, + ) + + # 1. Set the CA pool with updated values. + # 2. Set the update mask to specify which properties of the CA Pool should be updated. + # Only the properties specified in the mask will be updated. Make sure that the mask fields + # match the updated issuance policy. + # For more info on constructing path for update mask, see: + # https://cloud.google.com/certificate-authority-service/docs/reference/rest/v1/projects.locations.caPools#issuancepolicy */ + request = privateca_v1.UpdateCaPoolRequest( + ca_pool=ca_pool, + update_mask=field_mask_pb2.FieldMask( + paths=[ + "issuance_policy.identity_constraints.allow_subject_alt_names_passthrough", + "issuance_policy.identity_constraints.allow_subject_passthrough", + "issuance_policy.identity_constraints.cel_expression", + ], + ), + ) + operation = caServiceClient.update_ca_pool(request=request) + result = operation.result() + + print("Operation result", result) + + # Get the CA Pool's issuance policy and verify if the fields have been successfully updated. + issuance_policy = caServiceClient.get_ca_pool(name=ca_pool_path).issuance_policy + + # Similarly, you can check for other modified fields as well. + if ( + issuance_policy.identity_constraints.allow_subject_passthrough + and issuance_policy.identity_constraints.allow_subject_alt_names_passthrough + ): + print("CA Pool Issuance policy has been updated successfully!") + return + + print("Error in updating CA Pool Issuance policy! Please try again!") + + +# [END privateca_set_issuance_policy] diff --git a/privateca/snippets/update_certificate_authority.py b/privateca/snippets/update_certificate_authority.py new file mode 100644 index 000000000000..9acd3f8b2eb0 --- /dev/null +++ b/privateca/snippets/update_certificate_authority.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python + +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START privateca_update_ca_label] +import google.cloud.security.privateca_v1 as privateca_v1 +from google.protobuf import field_mask_pb2 + + +def update_ca_label( + project_id: str, + location: str, + ca_pool_name: str, + ca_name: str, +) -> None: + """ + Update the labels in a certificate authority. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + location: location you want to use. For a list of locations, see: https://cloud.google.com/certificate-authority-service/docs/locations. + ca_pool_name: set it to the CA Pool under which the CA should be updated. + ca_name: unique name for the CA. + """ + + caServiceClient = privateca_v1.CertificateAuthorityServiceClient() + + # Set the parent path and the new labels. + ca_parent = caServiceClient.certificate_authority_path( + project_id, location, ca_pool_name, ca_name + ) + certificate_authority = privateca_v1.CertificateAuthority( + name=ca_parent, + labels={"env": "test"}, + ) + + # Create a request to update the CA. + request = privateca_v1.UpdateCertificateAuthorityRequest( + certificate_authority=certificate_authority, + update_mask=field_mask_pb2.FieldMask(paths=["labels"]), + ) + + operation = caServiceClient.update_certificate_authority(request=request) + result = operation.result() + + print("Operation result:", result) + + # Get the updated CA and check if it contains the new label. + + certificate_authority = caServiceClient.get_certificate_authority(name=ca_parent) + + if ( + "env" in certificate_authority.labels + and certificate_authority.labels["env"] == "test" + ): + print("Successfully updated the labels !") + + +# [END privateca_update_ca_label] diff --git a/privateca/snippets/update_certificate_template.py b/privateca/snippets/update_certificate_template.py new file mode 100644 index 000000000000..ac05be89bd97 --- /dev/null +++ b/privateca/snippets/update_certificate_template.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python + +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START privateca_update_certificate_template] +import google.cloud.security.privateca_v1 as privateca_v1 +from google.protobuf import field_mask_pb2 + + +def update_certificate_template( + project_id: str, + location: str, + certificate_template_id: str, +) -> None: + """ + Update an existing certificate template. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + location: location you want to use. For a list of locations, see: https://cloud.google.com/certificate-authority-service/docs/locations. + certificate_template_id: set a unique name for the certificate template. + """ + + caServiceClient = privateca_v1.CertificateAuthorityServiceClient() + + certificate_name = caServiceClient.certificate_template_path( + project_id, + location, + certificate_template_id, + ) + + # Set the parent name and the properties to be updated. + certificate_template = privateca_v1.CertificateTemplate( + name=certificate_name, + identity_constraints=privateca_v1.CertificateIdentityConstraints( + allow_subject_passthrough=False, + allow_subject_alt_names_passthrough=True, + ), + ) + + # Set the mask corresponding to the properties updated above. + field_mask = field_mask_pb2.FieldMask( + paths=[ + "identity_constraints.allow_subject_alt_names_passthrough", + "identity_constraints.allow_subject_passthrough", + ], + ) + + # Set the new template. + # Set the mask to specify which properties of the template should be updated. + request = privateca_v1.UpdateCertificateTemplateRequest( + certificate_template=certificate_template, + update_mask=field_mask, + ) + operation = caServiceClient.update_certificate_template(request=request) + result = operation.result() + + print("Operation result", result) + + # Get the updated certificate template and check if the properties have been updated. + cert_identity_constraints = caServiceClient.get_certificate_template( + name=certificate_name + ).identity_constraints + + if ( + not cert_identity_constraints.allow_subject_passthrough + and cert_identity_constraints.allow_subject_alt_names_passthrough + ): + print("Successfully updated the certificate template!") + return + + print("Error in updating certificate template!") + + +# [END privateca_update_certificate_template]