From 81159889e41230932b19a75e44198f538a16f68b Mon Sep 17 00:00:00 2001 From: AdrienDucourthial <113527328+AdrienDucourthial@users.noreply.github.com> Date: Wed, 21 Aug 2024 10:56:00 +0200 Subject: [PATCH] Horizon 2.5 (#14) * Add webra import workflow * improve error logs * add revoke by certificate id * prepare release * add tests for horizon_import * fix tests * update import tests * catch ssl error * improve bug fix of content-type in case of bad authentication * Improve integration tests * fix integration tests --- .github/workflows/run_test.yaml | 1 + README.md | 3 +- galaxy.yml | 2 +- plugins/action/horizon_import.py | 42 ++ plugins/action/horizon_revoke.py | 2 +- plugins/module_utils/horizon.py | 44 +- plugins/module_utils/horizon_errors.py | 3 + plugins/modules/horizon_import.py | 449 ++++++++++++++++++ plugins/modules/horizon_revoke.py | 4 + .../horizon_centralized_enroll/tasks/main.yml | 25 + .../horizon_certificate_feed/tasks/main.yml | 20 +- .../horizon_certificate_import/aliases | 0 .../horizon_certificate_import/tasks/main.yml | 67 +++ .../horizon_certificate_update/tasks/main.yml | 20 +- .../tasks/main.yml | 10 +- .../horizon_revoke_certificate/tasks/main.yml | 9 +- 16 files changed, 690 insertions(+), 11 deletions(-) create mode 100644 plugins/action/horizon_import.py create mode 100644 plugins/modules/horizon_import.py create mode 100644 tests/integration/targets/horizon_certificate_import/aliases create mode 100644 tests/integration/targets/horizon_certificate_import/tasks/main.yml diff --git a/.github/workflows/run_test.yaml b/.github/workflows/run_test.yaml index 74fc36c..1e31135 100644 --- a/.github/workflows/run_test.yaml +++ b/.github/workflows/run_test.yaml @@ -12,6 +12,7 @@ jobs: ansible-core-version: stable-2.14 target-python-version: 3.9 testing-type: integration + test-deps: community.crypto pre-test-cmd: >- echo "[endpoint: ${{ secrets.HORIZON_ENDPOINT }}, x_api_id: ${{ secrets.HORIZON_API_ID }}, x_api_key: ${{ secrets.HORIZON_API_KEY }}]" >> tests/integration/integration_config.yml diff --git a/README.md b/README.md index f217bc2..4c08f75 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,8 @@ This collection requires Python 3.6 or greater. It offers compatibility with the | Collection version | Horizon version | |--------------------|-----------------| -| 1.3.0 | 2.2.0+ | +| 1.4.0 | 2.2.0+ | +| 1.3.0 | 2.2.0 - 2.4.x | | 1.2.0 | 2.2.0 - 2.3.x | | 1.1.0 | 2.2.0 - 2.3.x | | 1.0.1 | 2.0.0 - 2.3.x | diff --git a/galaxy.yml b/galaxy.yml index 24a07f3..d20263d 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,6 +1,6 @@ namespace: evertrust name: horizon -version: 1.3.0 +version: 1.4.0 readme: README.md authors: - EverTrust R&D (@Evertrust) diff --git a/plugins/action/horizon_import.py b/plugins/action/horizon_import.py new file mode 100644 index 0000000..8d873b6 --- /dev/null +++ b/plugins/action/horizon_import.py @@ -0,0 +1,42 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Standard base includes and define this as a metaclass of type +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + +from ansible.errors import AnsibleError +from ansible_collections.evertrust.horizon.plugins.module_utils.horizon_action import HorizonAction +from ansible_collections.evertrust.horizon.plugins.module_utils.horizon_crypto import HorizonCrypto +from ansible_collections.evertrust.horizon.plugins.module_utils.horizon_errors import HorizonError + + +class ActionModule(HorizonAction): + TRANSFERS_FILES = True + + def _args(self): + return ['profile', 'certificate_id', 'certificate_pem', 'private_key', 'labels', 'metadata', 'owner', 'team', 'contact_email'] + + def run(self, tmp=None, task_vars=None): + result = super(ActionModule, self).run(tmp, task_vars) + + try: + client = self._get_client() + content = self._get_content() + response = client.webra_import(**content) + + if "certificate" in response: + result["certificate"] = response["certificate"] + result["chain"] = client.chain(result["certificate"]["certificate"]) + + if "pkcs12" in response.keys(): + result["p12"] = response["pkcs12"]["value"] + result["p12_password"] = response["password"]["value"] + result["key"] = HorizonCrypto.get_key_from_p12(response["pkcs12"]["value"], + response["password"]["value"]) + + except HorizonError as e: + raise AnsibleError(e.full_message) + + return result diff --git a/plugins/action/horizon_revoke.py b/plugins/action/horizon_revoke.py index 8964af4..bd9bc5c 100644 --- a/plugins/action/horizon_revoke.py +++ b/plugins/action/horizon_revoke.py @@ -15,7 +15,7 @@ class ActionModule(HorizonAction): TRANSFERS_FILES = True def _args(self): - return ["certificate_pem", "revocation_reason", "skip_already_revoked"] + return ["certificate_pem", "certificate_id", "revocation_reason", "skip_already_revoked"] def run(self, tmp=None, task_vars=None): result = super(ActionModule, self).run(tmp, task_vars) diff --git a/plugins/module_utils/horizon.py b/plugins/module_utils/horizon.py index 663eab8..b3a1346 100644 --- a/plugins/module_utils/horizon.py +++ b/plugins/module_utils/horizon.py @@ -176,7 +176,7 @@ def renew(self, certificate_pem, certificate_id, password=None): return self.post(self.REQUEST_SUBMIT_URL, json) - def revoke(self, certificate_pem, revocation_reason): + def revoke(self, certificate_pem, certificate_id, revocation_reason): """ Revoke a certificate :type certificate_pem: Union[str,dict] @@ -187,6 +187,7 @@ def revoke(self, certificate_pem, revocation_reason): json = { "workflow": "revoke", "certificatePem": self.__load_file_or_string(certificate_pem), + "certificateId": certificate_id, "revocationReason": revocation_reason, "template": { "revocationReason": revocation_reason @@ -230,6 +231,36 @@ def update(self, certificate_pem, labels=None, metadata=None, owner=None, team=N return self.post(self.REQUEST_SUBMIT_URL, json) + def webra_import(self, profile, certificate_pem, certificate_id, private_key, labels=None, metadata=None, owner=None, team=None, contact_email=None): + + if metadata is None: + metadata = {} + if labels is None: + labels = {} + + json = { + "workflow": "import", + "profile": profile, + "template": { + "privateKey": self.__load_file_or_string(private_key), + "metadata": self.__set_metadata(metadata), + "labels": self.__set_labels(labels) + }, + "certificateId": certificate_id, + "certificatePem": self.__load_file_or_string(certificate_pem) + } + + if owner is not None: + json["template"]["owner"] = {"value": owner} + if team is not None: + json["template"]["team"] = {"value": team} + if "contact_email" in metadata: + json["template"]["contactEmail"] = {"value": metadata["contact_email"]} + elif contact_email is not None: + json["template"]["contactEmail"] = {"value": contact_email} + + return self.post(self.REQUEST_SUBMIT_URL, json) + def search(self, query=None, fields=None): """ Search for certificates @@ -357,6 +388,8 @@ def __check_password_policy(password, template): password_mode = template["template"]["passwordMode"] if "passwordPolicy" in template["template"]: password_policy = template["template"]["passwordPolicy"] + else: + password_policy = -1 # Check if the password is needed and given if password_mode == "manual" and password is None: message = f'A password is required. ' @@ -441,9 +474,12 @@ def send(self, method, path, **kwargs): """ uri = self.endpoint + path method = method.upper() - response = requests.request(method, uri, cert=self.cert, verify=self.bundle, - headers=self.headers, **kwargs) - if 'Content-Type' in response.headers and response.headers['Content-Type'] == 'application/json': + try: + response = requests.request(method, uri, cert=self.cert, verify=self.bundle, headers=self.headers, **kwargs) + except requests.exceptions.SSLError: + raise AnsibleError("Got an SSL error try using the 'ca_bundle' paramater") + + if 'Content-Type' in response.headers and response.headers['Content-Type'] in ['application/json', 'application/problem+json']: content = response.json() else: content = response.content.decode() diff --git a/plugins/module_utils/horizon_errors.py b/plugins/module_utils/horizon_errors.py index 9b0e241..33d3d57 100644 --- a/plugins/module_utils/horizon_errors.py +++ b/plugins/module_utils/horizon_errors.py @@ -19,3 +19,6 @@ def __init__(self, code, message, response, detail=None): self.full_message = "Error %s : %s" % (self.code, self.message) if self.detail: self.full_message = "%s (%s)" % (self.full_message, self.detail) + + def __str__(self): + return self.full_message \ No newline at end of file diff --git a/plugins/modules/horizon_import.py b/plugins/modules/horizon_import.py new file mode 100644 index 0000000..ecbaccc --- /dev/null +++ b/plugins/modules/horizon_import.py @@ -0,0 +1,449 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# This is a virtual module that is entirely implemented as an action plugin and runs on the controller + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +# language=yaml +DOCUMENTATION = ''' +module: horizon_import +author: Evertrust R&D (@EverTrust) +short_description: Horizon import plugin +description: Import a certificate or a private key. +notes: Importing a certificate requires permissions on the related profile. +extends_documentation_fragment: evertrust.horizon.auth_options +options: + profile: + description: Name of the profile on which the certificate will be imported. + required: true + type: str + certificate_pem: + description: The PEM encoded certificate to import. + required: false + type: str + suboptions: + src: + description: The path to the PEM encoded certificate to import. + required: false + type: path + private_key: + description: The PEM encoded private key to import. + required: false + type: str + suboptions: + src: + description: The path to the PEM encoded private key to import. + required: false + type: path + certificate_id: + description: The ID of the certificate related to the key to import. + required: false + type: str + labels: + description: Labels of the certificate. + required: false + type: dict + metadata: + description: Technical metadata related to the certificate. + required: false + type: dict + suboptions: + gs_order_id: + type: str + renewed_certificate_id: + type: str + metapki_id: + type: str + pki_connector: + type: str + digicert_id: + type: str + entrust_id: + type: str + scep_transid: + type: str + fcms_id: + type: str + previous_certificate_id: + type: str + gsatlas_id: + type: str + certeurope_id: + type: str + digicert_order_id: + type: str + automation_policy: + type: str + owner: + description: Certificate's owner. + required: false + type: str + team: + description: Certificate's team. + required: false + type: str + contact_email: + description: + - Certificate's contact email. + - Default value will be the requester contact email adress. + required: false + type: str +''' + +# language=yaml +EXAMPLES = ''' +- name: Import a certificate + evertrust.horizon.horizon_import: + endpoint: "https://" + x_api_id: "" + x_api_key: "" + profile: "exampleProfile" + labels: + label1: "exampleLabel" + contact_email: "contact.email@example.fr" + owner: "exempleOwner" + team: "exampleTeam" + certificate_pem: "-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----" + +- name: Import a certificate by its file + evertrust.horizon.horizon_import: + endpoint: "https://" + x_api_id: "" + x_api_key: "" + profile: "exampleProfile" + labels: + label1: "exampleLabel" + certificate_pem: + src: /pem/file/path + private_key: + src: /key/file/path + +- name: Import a private key by its file + evertrust.horizon.horizon_import: + endpoint: "https://" + x_api_id: "" + x_api_key: "" + profile: "exampleProfile" + labels: + label1: "exampleLabel" + certificate_id: "" + private_key: + src: /key/file/path +''' + +# language=yaml +RETURN = ''' +certificate: + description: The certificate imported. This is only available after the request has been approved. + returned: Always + type: dict + contains: + metadata: + description: The certificate's technical metadata used internally. + type: list + elements: dict + returned: If specifically requested + contains: + key: + description: The metadata name. + type: string + returned: Always + value: + description: The metadata value + type: string + returned: Always + notAfter: + description: The certificate's expiration date in milliseconds since the epoch. + type: int + returned: If specifically requested + thumbprint: + description: The certificate's thumbprint. + type: str + returned: If specifically requested + revocationDate: + description: The certificate's revocation date in milliseconds since the epoch. This field is only present if the certificate is revoked. + type: int + returned: If present and specifically requested + certificate: + description: The certificate's PEM-encoded content. + type: str + returned: If specifically requested + dn: + description: The certificate's Distinguished Name. + type: str + returned: If specifically requested + grades: + description: The certificate's grades for the enabled grading policies. + type: list + elements: dict + returned: If specifically requested + contains: + name: + description: The name of the grading policy. + type: str + returned: always + grade: + description: The grade awarded by the grading policy. + type: str + returned: always + revoked: + description: Whether the certificate is revoked. + type: bool + returned: If present and specifically requested + issuer: + description: The certificate's issuer Distinguished Name. + type: str + returned: If specifically requested + notBefore: + description: The certificate's start date in milliseconds since the epoch. + type: int + returned: If specifically requested + crlSynchronized: + description: Whether the certificate's revocation status is synchronized with a CRL. + type: bool + returned: If present and specifically requested + selfSigned: + description: Whether the certificate is self-signed. + type: bool + returned: If specifically requested + discoveredTrusted: + description: If the certificate was discovered and is found to be issued by an existing trusted CA, this field will be set to true. If the certificate was discovered and is not found to be issued by an existing trusted CA, this field will be set to false. If the certificate was not discovered, this field will be null. + type: bool + returned: If present and specifically requested + keyType: + description: The certificate's key type. + type: str + returned: If specifically requested + thirdPartyData: + description: The certificate's information about synchronization with Horizon supported third parties. + type: list + elements: dict + returned: If present and specifically requested + contains: + connector: + description: The third party connector name on which this certificate is synchronized. + type: string + returned: Always + id: + description: The Id of this certificate on the third party. + type: string + returned: Always + fingerprint: + description: The fingerprint of this certificate on the third party. + type: string + returned: If present + pushDate: + description: The date when the certificate was pushed to this third party. + type: int + returned: If present + removeDate: + description: The date when the certificate was removed from this third party (in case of revocation). + type: int + returned: If present + owner: + description: The certificate's owner. This is a reference to a local identity identifier. + type: str + returned: If specifically requested + publicKeyThumbprint: + description: The certificate's public key thumbprint. + type: str + returned: If specifically requested + contactEmail: + description: The certificate's contact email. It will be used to send notifications about the certificate's expiration and revocation. + type: str + returned: If specifically requested + module: + description: The certificate's module. + type: str + returned: If specifically requested + profile: + description: The certificate's profile. + type: str + returned: If present and specifically requested + team: + description: The certificate's team. This is a reference to a team identifier. It will be used to determine the certificate's permissions and send notifications. + type: str + returned: If specifically requested + holderId: + description: The certificate's holder ID. This is a computed field that is used to count how many similar certificates are in use simultaneously by the same holder. + type: str + returned: If specifically requested + labels: + description: The certificate's labels. + type: list + elements: dict + returned: If present and specifically requested + contains: + key: + description: The label's name. + type: string + returned: Always + value: + description: The label's value. + type: string + returned: Always + discoveryInfo: + description: A list of metadata containing information on how and when the certificate was discovered. + type: list + elements: dict + returned: If present and specifically requested + contains: + campaign: + description: The discovery campaign's name. + type: string + returned: Always + lastDiscoveryDate: + description: When this certificate was discovered for the last time. + type: int + returned: Always + identifier: + description: Identifier of the user that discovered this certificate. + type: str + returned: If present + subjectAlternateNames: + description: The certificate's Subject Alternate Names. + type: list + elements: dict + returned: If specifically requested + contains: + sanType: + description: The type of the SAN + type: str + returned: Always + value: + description: The value of the SAN + type: str + returned: Always + triggerResults: + description: The result of the execution of triggers on this certificate. + type: list + elements: dict + returned: If present and specifically requested. + contains: + name: + description: The name of the trigger that was executed. + type: str + returned: Always + event: + description: The event that triggered the trigger. + type: str + returned: Always + triggerType: + description: The type of the trigger. + type: str + returned: Always + lastExecutionDate: + description: The last time this trigger was executed for this certificate and this event. + type: int + returned: Always + status: + description: The status of the trigger after its execution. + type: str + returned: Always + retries: + description: The number of remaining tries before the trigger is abandoned. + type: int + returned: If present + nextExecutionDate: + description: The next scheduled execution time for this trigger. + type: int + returned: If present + nextDelay: + description: Time that will be waited between the next and the next+1 execution of this trigger. + type: str + returned: If present + detail: + description: Contains details on this trigger's execution. + type: str + returned: If present + retryable: + description: Is this trigger manually retryable. + type: bool + returned: Always + extensions: + description: The certificate's extensions. + type: list + elements: dict + returned: If present and specifically requested + contains: + key: + description: The extension's type. + type: string + returned: Always + value: + description: The extension's value. + type: string + returned: Always + serial: + description: The certificate's serial number. + type: str + returned: If present and specifically requested + signingAlgorithm: + description: The certificate's signing algorithm. + type: str + returned: If specifically requested + discoveryData: + description: A list of metadata containing information on where the certificate was discovered. + type: list + elements: dict + returned: Only if the certificate was discovered + contains: + ip: + description: The certificate's host ip. + type: string + returned: Always + sources: + description: Information on the type of discovery that discovered this certificate. + type: list + elements: str + returned: Always + hostnames: + description: The certificate's host hostnames (netscan only). + type: list + elements: str + returned: If present + operatingSystems: + description: The certificate's host operating system (localscan only). + type: list + elements: str + returned: If present + paths: + description: The path to the certificate on the host machine (localscan only). + type: list + elements: str + returned: If present + usages: + description: The path of the configuration files that were used to find the certificates. + type: list + elements: str + returned: If present + tlsPorts: + description: The ports on which the certificate is exposed for https connexion. + type: list + elements: dict + returned: If present + contains: + port: + description: The number of the port. + type: int + returned: Always + version: + description: Protocol version used. + type: string + returned: Always + _id: + description: Horizon internal ID. + type: str + returned: If specifically requested + revocationReason: + description: The certificate's revocation reason. + type: str + returned: If specifically requested +chain: + description: Certificate's trust chain. + returned: Always + type: str +''' \ No newline at end of file diff --git a/plugins/modules/horizon_revoke.py b/plugins/modules/horizon_revoke.py index 1e0e4e6..1d7b6bd 100644 --- a/plugins/modules/horizon_revoke.py +++ b/plugins/modules/horizon_revoke.py @@ -25,6 +25,10 @@ description: The path to the PEM encoded certificate to revoke. required: false type: path + certificate_id: + description: The ID of the certificate to revoke. + required: false + type: str revocation_reason: description: The reason for revoking the certificate. required: false diff --git a/tests/integration/targets/horizon_centralized_enroll/tasks/main.yml b/tests/integration/targets/horizon_centralized_enroll/tasks/main.yml index 4e8dd5c..562e5d5 100644 --- a/tests/integration/targets/horizon_centralized_enroll/tasks/main.yml +++ b/tests/integration/targets/horizon_centralized_enroll/tasks/main.yml @@ -48,6 +48,14 @@ label-2: "ansible" ignore_errors: yes + register: error1 + +# Check error +- name: Check error1 + assert: + that: + - error1 is failed + - "'Profile does not exist or is disabled' in error1.msg" - name: Test centralize enrollment with wrong key_type @@ -70,6 +78,14 @@ label-2: "ansible" ignore_errors: yes + register: error2 + +# Check error +- name: Check error2 + assert: + that: + - error2 is failed + - "'Invalid key type' in error2.msg" - name: Test centralize enrollment with unexistant label @@ -93,3 +109,12 @@ label-2: "ansible" ignore_errors: yes + register: error3 + +# Check error +- name: Check error3 + assert: + that: + - error3 is failed + - "'Label element' in error3.msg" + - "'is not authorized' in error3.msg" diff --git a/tests/integration/targets/horizon_certificate_feed/tasks/main.yml b/tests/integration/targets/horizon_certificate_feed/tasks/main.yml index eac6d65..54e6c55 100644 --- a/tests/integration/targets/horizon_certificate_feed/tasks/main.yml +++ b/tests/integration/targets/horizon_certificate_feed/tasks/main.yml @@ -23,6 +23,15 @@ ip: 0.0.0.1 ignore_errors: yes + register: error1 + +# Check error +- name: Check error1 + assert: + that: + - error1 is failed + - "'Missing discovery campaign' in error1.msg" + - name: Feed a certificate without ip evertrust.horizon.horizon_feed: @@ -35,4 +44,13 @@ src: "/PEM.pem" campaign: "Ansible" - ignore_errors: yes \ No newline at end of file + ignore_errors: yes + register: error2 + +# Check error +- name: Check error2 + assert: + that: + - error2 is failed + - "'Missing certificate' in error2.msg" + - "'host ip' in error2.msg" \ No newline at end of file diff --git a/tests/integration/targets/horizon_certificate_import/aliases b/tests/integration/targets/horizon_certificate_import/aliases new file mode 100644 index 0000000..e69de29 diff --git a/tests/integration/targets/horizon_certificate_import/tasks/main.yml b/tests/integration/targets/horizon_certificate_import/tasks/main.yml new file mode 100644 index 0000000..e0d365d --- /dev/null +++ b/tests/integration/targets/horizon_certificate_import/tasks/main.yml @@ -0,0 +1,67 @@ +# Test import of certificate and key +- name: Create private key (RSA, 4096 bits) + community.crypto.openssl_privatekey: + path: /tmp/importcertandkey.key +- name: Create certificate signing request (CSR) for self-signed certificate + community.crypto.openssl_csr_pipe: + privatekey_path: /tmp/importcertandkey.key + common_name: test-import + register: csr +- name: Create simple self-signed certificate + community.crypto.x509_certificate: + path: /tmp/importcertandkey.pem + csr_content: "{{ csr.csr }}" + privatekey_path: /tmp/importcertandkey.key + provider: selfsigned + +- name: Import certificate and key by files + evertrust.horizon.horizon_import: + endpoint: "{{ endpoint }}" + x_api_id: "{{ x_api_id }}" + x_api_key: "{{ x_api_key }}" + profile: "Ansible" + certificate_pem: + src: /tmp/importcertandkey.pem + private_key: + src: /tmp/importcertandkey.key + + +# Test import of private key +- name: Create private key (RSA, 4096 bits) 2 + community.crypto.openssl_privatekey: + path: /tmp/importkey.key +- name: Create certificate signing request (CSR) for self-signed certificate 2 + community.crypto.openssl_csr_pipe: + privatekey_path: /tmp/importkey.key + common_name: test-import-2 + register: csr +- name: Create simple self-signed certificate 2 + community.crypto.x509_certificate: + path: /tmp/importkey.pem + csr_content: "{{ csr.csr }}" + privatekey_path: /tmp/importkey.key + provider: selfsigned + +- name: feed the certificate + evertrust.horizon.horizon_feed: + endpoint: "{{ endpoint }}" + x_api_id: "{{ x_api_id }}" + x_api_key: "{{ x_api_key }}" + certificate_pem: + src: /tmp/importkey.pem + campaign: "Ansible" + ip: 0.0.0.1 + +- name: Import a key by a file + evertrust.horizon.horizon_import: + endpoint: "{{ endpoint }}" + x_api_id: "{{ x_api_id }}" + x_api_key: "{{ x_api_key }}" + profile: "Ansible" + certificate_id: "{{ cert_id._id }}" + private_key: + src: /tmp/importkey.key + vars: + certificate: + src: '/tmp/importkey.pem' + cert_id: "{{ lookup('evertrust.horizon.horizon_lookup', endpoint=endpoint, x_api_id=x_api_id, x_api_key=x_api_key, certificate_pem=certificate, fields='_id', wantlist=True) }}" \ No newline at end of file diff --git a/tests/integration/targets/horizon_certificate_update/tasks/main.yml b/tests/integration/targets/horizon_certificate_update/tasks/main.yml index 155830c..78fc27a 100644 --- a/tests/integration/targets/horizon_certificate_update/tasks/main.yml +++ b/tests/integration/targets/horizon_certificate_update/tasks/main.yml @@ -30,6 +30,15 @@ team: "UnknownTeam" ignore_errors: yes + register: error1 + +# Check error +- name: Check error1 + assert: + that: + - error1 is failed + - "'Team' in error1.msg" + - "'is not authorized' in error1.msg" - name: Update with unexistant label @@ -47,4 +56,13 @@ team: "TeamB" - ignore_errors: yes \ No newline at end of file + ignore_errors: yes + register: error2 + +# Check error +- name: Check error2 + assert: + that: + - error2 is failed + - "'Label element' in error2.msg" + - "'is not authorized' in error2.msg" \ No newline at end of file diff --git a/tests/integration/targets/horizon_decentralized_enroll/tasks/main.yml b/tests/integration/targets/horizon_decentralized_enroll/tasks/main.yml index 78eb008..36c313d 100644 --- a/tests/integration/targets/horizon_decentralized_enroll/tasks/main.yml +++ b/tests/integration/targets/horizon_decentralized_enroll/tasks/main.yml @@ -79,4 +79,12 @@ wrongCSR -----END CERTIFICATE REQUEST----- - ignore_errors: yes \ No newline at end of file + ignore_errors: yes + register: error1 + +# Check error +- name: Check error1 + assert: + that: + - error1 is failed + - "'Could not parse provided PEM' in error1.msg" \ No newline at end of file diff --git a/tests/integration/targets/horizon_revoke_certificate/tasks/main.yml b/tests/integration/targets/horizon_revoke_certificate/tasks/main.yml index 56ce601..9eac9a6 100644 --- a/tests/integration/targets/horizon_revoke_certificate/tasks/main.yml +++ b/tests/integration/targets/horizon_revoke_certificate/tasks/main.yml @@ -24,4 +24,11 @@ revocation_reason: "unspecified" ignore_errors: yes - \ No newline at end of file + register: error1 + +# Check error +- name: Check error1 + assert: + that: + - error1 is failed + - "'Certificate is already revoked' in error1.msg" \ No newline at end of file