diff --git a/privateca/snippets/conftest.py b/privateca/snippets/conftest.py index 6c11fc7cadcd..5e9f943d4abf 100644 --- a/privateca/snippets/conftest.py +++ b/privateca/snippets/conftest.py @@ -20,8 +20,10 @@ 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 = "europe-west1" @@ -69,3 +71,14 @@ def deleted_certificate_authority(ca_pool): 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_certificate_template.py b/privateca/snippets/create_certificate_template.py new file mode 100644 index 000000000000..b508bd8b12ad --- /dev/null +++ b/privateca/snippets/create_certificate_template.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_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/delete_certificate_template.py b/privateca/snippets/delete_certificate_template.py new file mode 100644 index 000000000000..8d1a5ad5f7de --- /dev/null +++ b/privateca/snippets/delete_certificate_template.py @@ -0,0 +1,48 @@ +#!/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/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/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/requirements.txt b/privateca/snippets/requirements.txt index bc4aa3ab46cc..b51179d3cf7f 100644 --- a/privateca/snippets/requirements.txt +++ b/privateca/snippets/requirements.txt @@ -1,2 +1,3 @@ google-cloud-private-ca==1.2.1 google-cloud-kms==2.10.1 +google-cloud-monitoring==2.8.0 \ No newline at end of file diff --git a/privateca/snippets/test_ca_pools.py b/privateca/snippets/test_ca_pools.py index 2f7921c5a055..c0775d124294 100644 --- a/privateca/snippets/test_ca_pools.py +++ b/privateca/snippets/test_ca_pools.py @@ -21,6 +21,7 @@ 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 = "europe-west1" @@ -72,3 +73,13 @@ def test_delete_ca_pool(capsys: typing.Any) -> None: 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 index 128cf7e79868..c596fbea8e99 100644 --- a/privateca/snippets/test_certificate_authorities.py +++ b/privateca/snippets/test_certificate_authorities.py @@ -24,7 +24,9 @@ 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] @@ -84,3 +86,23 @@ def test_undelete_certificate_authority( 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_crud_certificate_templates.py b/privateca/snippets/test_crud_certificate_templates.py new file mode 100644 index 000000000000..6ecd752ad106 --- /dev/null +++ b/privateca/snippets/test_crud_certificate_templates.py @@ -0,0 +1,72 @@ +# 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 = "europe-west1" +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/update_ca_pool_issuance_policy.py b/privateca/snippets/update_ca_pool_issuance_policy.py new file mode 100644 index 000000000000..05a8c9cddd98 --- /dev/null +++ b/privateca/snippets/update_ca_pool_issuance_policy.py @@ -0,0 +1,92 @@ +#!/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..13620ab74d2f --- /dev/null +++ b/privateca/snippets/update_certificate_authority.py @@ -0,0 +1,67 @@ +#!/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..e39c09a4ee8b --- /dev/null +++ b/privateca/snippets/update_certificate_template.py @@ -0,0 +1,81 @@ +#!/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]