From c2f9e04be557cea4fee9a6da5634061100fa3b13 Mon Sep 17 00:00:00 2001 From: AdrienDucourthial <113527328+AdrienDucourthial@users.noreply.github.com> Date: Tue, 12 Nov 2024 15:48:07 +0100 Subject: [PATCH] feat(horizon_template): implem (#17) * Add action horizon_template * Fix decentralized renew * Add samples with the new action horizon_template * prepare release 1.5.0 --- README.md | 11 +- galaxy.yml | 2 +- plugins/action/horizon_enroll.py | 6 +- plugins/action/horizon_renew.py | 2 +- plugins/action/horizon_template.py | 33 ++ plugins/module_utils/horizon.py | 13 +- plugins/modules/horizon_renew.py | 18 + plugins/modules/horizon_template.py | 453 ++++++++++++++++++ .../playbook-deploy-apache-decentralized.yaml | 118 +++++ .../playbook-deploy-nginx-decentralized.yaml | 123 +++++ .../tomcat.server.xml.decentralized.j2 | 188 ++++++++ .../playbook-deploy-tomcat-decentralized.yaml | 122 +++++ .../targets/horizon_certificate_renew/aliases | 0 .../horizon_certificate_renew/tasks/main.yml | 52 ++ 14 files changed, 1129 insertions(+), 12 deletions(-) create mode 100644 plugins/action/horizon_template.py create mode 100644 plugins/modules/horizon_template.py create mode 100644 samples/apache/playbook-deploy-apache-decentralized.yaml create mode 100644 samples/nginx/playbook-deploy-nginx-decentralized.yaml create mode 100644 samples/templates/tomcat.server.xml.decentralized.j2 create mode 100644 samples/tomcat/playbook-deploy-tomcat-decentralized.yaml create mode 100644 tests/integration/targets/horizon_certificate_renew/aliases create mode 100644 tests/integration/targets/horizon_certificate_renew/tasks/main.yml diff --git a/README.md b/README.md index 4c08f75..7682166 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,12 @@ This collection requires Python 3.6 or greater. It offers compatibility with the | Collection version | Horizon version | |--------------------|-----------------| -| 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 | +| 1.5 | 2.2.0+ | +| 1.4 | 2.2.0+ | +| 1.3 | 2.2.0 - 2.4.x | +| 1.2 | 2.2.0 - 2.3.x | +| 1.1 | 2.2.0 - 2.3.x | +| 1.0 | 2.0.0 - 2.3.x | ### Ansible Galaxy diff --git a/galaxy.yml b/galaxy.yml index 3f9e2c8..2c550a3 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,6 +1,6 @@ namespace: evertrust name: horizon -version: 1.4.2 +version: 1.5.0 readme: README.md authors: - EverTrust R&D (@Evertrust) diff --git a/plugins/action/horizon_enroll.py b/plugins/action/horizon_enroll.py index 3c01d15..f34f87c 100644 --- a/plugins/action/horizon_enroll.py +++ b/plugins/action/horizon_enroll.py @@ -24,7 +24,6 @@ def run(self, tmp=None, task_vars=None): try: client = self._get_client() content = self._get_content() - should_generate_csr = content["mode"] == "decentralized" and content['csr'] is None if content["subject"] == None: raise AnsibleError("The subject parameter is mandatory.") @@ -44,6 +43,11 @@ def run(self, tmp=None, task_vars=None): else: key_type = None + mode = client.check_mode(template, content["mode"]) + content["mode"] = mode + should_generate_csr = content["mode"] == "decentralized" and content['csr'] is None + + # Generate a key pair and CSR if none was provided if should_generate_csr: try: diff --git a/plugins/action/horizon_renew.py b/plugins/action/horizon_renew.py index fc36edd..898cc5d 100644 --- a/plugins/action/horizon_renew.py +++ b/plugins/action/horizon_renew.py @@ -16,7 +16,7 @@ class ActionModule(HorizonAction): TRANSFERS_FILES = True def _args(self): - return ['certificate_id', 'certificate_pem', 'password'] + return ['certificate_id', 'certificate_pem', 'password', 'csr'] def run(self, tmp=None, task_vars=None): result = super(ActionModule, self).run(tmp, task_vars) diff --git a/plugins/action/horizon_template.py b/plugins/action/horizon_template.py new file mode 100644 index 0000000..4e76a87 --- /dev/null +++ b/plugins/action/horizon_template.py @@ -0,0 +1,33 @@ +#!/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) +from re import M + +__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", "workflow"] + + 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.get_template(**content, module="webra") + + except HorizonError as e: + raise AnsibleError(e.full_message) + + return response["template"] \ No newline at end of file diff --git a/plugins/module_utils/horizon.py b/plugins/module_utils/horizon.py index 28d26cf..e753d59 100644 --- a/plugins/module_utils/horizon.py +++ b/plugins/module_utils/horizon.py @@ -80,7 +80,6 @@ def enroll(self, profile, template, mode=None, csr=None, password=None, key_type if metadata is None: metadata = {} - mode = self.__check_mode(template, mode=mode) csr = self.__load_file_or_string(csr) if mode == "decentralized": @@ -156,19 +155,25 @@ def recover(self, certificate_pem, password): return self.post(self.REQUEST_SUBMIT_URL, json) - def renew(self, certificate_pem, certificate_id, password=None): + def renew(self, certificate_pem, certificate_id, password=None, csr=None): """ Renew a certificate :type certificate_pem: Union[str,dict] :type certificate_id: str :rtype: dict """ + csr = self.__load_file_or_string(csr) + json = { "module": "webra", "workflow": "renew", "certificateId": certificate_id, - "certificatePem": self.__load_file_or_string(certificate_pem) + "certificatePem": self.__load_file_or_string(certificate_pem), + "template": { + "csr": csr + } } + if password is not None: json["password"] = {} json["password"]["value"] = password @@ -660,7 +665,7 @@ def __set_subject(subject, template): return my_subject @staticmethod - def __check_mode(template, mode=None): + def check_mode(template, mode=None): """ :param template: the template of the request :param mode: mode precised in the playbook diff --git a/plugins/modules/horizon_renew.py b/plugins/modules/horizon_renew.py index f74f503..fc55e40 100644 --- a/plugins/modules/horizon_renew.py +++ b/plugins/modules/horizon_renew.py @@ -28,6 +28,15 @@ description: The ID of the certificate to renew. required: false type: str + csr: + description: A certificate signing request, or the path to the CSR file. Required for decentralized renew. + required: false + type: str + suboptions: + src: + description: The path to a CSR file + required: false + type: path ''' # language=yaml @@ -46,6 +55,15 @@ x_api_id: "" x_api_key: "" certificate_id: + +- name: decentralized renew with csr + evertrust.horizon.horizon_renew: + endpoint: "https://" + x_api_id: "" + x_api_key: "" + certificate_id: + csr: + src: csr/file/path ''' diff --git a/plugins/modules/horizon_template.py b/plugins/modules/horizon_template.py new file mode 100644 index 0000000..0d3d391 --- /dev/null +++ b/plugins/modules/horizon_template.py @@ -0,0 +1,453 @@ +#!/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_template +author: Evertrust R&D (@EverTrust) +short_description: Horizon template plugin +description: Performs a get template request against the Horizon API. +extends_documentation_fragment: evertrust.horizon.auth_options +options: + profile: + description: Name of the profile. + required: true + type: str + workflow: + description: Workflow of the template + required: true + choices: + - enroll + - recover + - renew + - revoke + - update +''' + +# language=yaml +EXAMPLES = ''' +- name: Get webra enroll template + evertrust.horizon.horizon_get_template: + endpoint: "https://" + x_api_id: "" + x_api_key: "" + profile: "exampleProfile" + workflow: "enroll" + +- name: Get webra renew template + evertrust.horizon.horizon_get_template: + endpoint: "https://" + x_api_id: "" + x_api_key: "" + profile: "exampleProfile" + workflow: "renew" +''' + +# language=yaml +RETURN = ''' +capabilites: + description: Describes how certificates will be enrolled on this profile. + returned: If present + type: dict + contains: + centralized: + description: Whether this profile supports centralized enrollment. + type: bool + returned: Always + decentralized: + description: Whether this profile supports decentralized enrollment. + type: bool + returned: Always + defaultKeyType: + description: Default key type used for centralized enrollment. + type: string + returned: If present + authorizedKeyTypes: + description: List of authorized key types for enrollment. + type: list + elements: string + returned: If present + preferredEnrollmentMode: + description: If both centralized and decentralized enrollment are supported, this is the preferred mode. + type: string + returned: If present + escrow: + description: Whether this profile will escrow the certificate private keys. + type: bool + returned: Always + p12passwordPolicy: + description: Password policy for the P12 file. + type: string + returned: If present + p12passwordMode: + description: Whether the user will be required to input their PKCS#12 password upon enrollment. + type: string + returned: If present + p12storeEncryptionType: + description: Encryption type for the P12 file. + type: string + returned: If present + showP12PasswordOnEnroll: + description: Whether the PKCS#12 password will be displayed to the user upon enrollment. + type: bool + returned: If present + showP12OnEnroll: + description: Whether the PKCS#12 file will be displayed to the user upon enrollment. + type: bool + returned: If present + showP12PasswordOnRecover: + description: Whether the PKCS#12 password will be displayed to the user upon recovery. + type: bool + returned: If present + showP12OnRecover: + description: Whether the PKCS#12 file will be displayed to the user upon recovery. + type: bool + returned: If present +subject: + description: List of DN elements that will be used to build the certificate's Distinguished Name. + type: list + returned: If present + contains: + element: + description: The element type and index. + type: string + returned: Always + type: + description: The formatted element type. + type: string + returned: If present + value: + description: The element value. + type: string + returned: If present + computationRule: + description: Computation rule input will be evaluated and will override all other inputs. + type: string + returned: If present + mandatory: + description: Whether the field is mandatory or not. + type: bool + returned: If present + editable: + description: Whether the field is editable or not for the currently authenticated user. + type: bool + returned: If present + regex: + description: A regular expression that will be used to validate the element's value. + type: string + returned: If present +sans: + description: List of SAN elements that will be used to build the certificate's Subject Alternative Name. + type: list + returned: If present + contains: + type: + description: SAN type. + type: string + returned: Always + value: + description: SAN value. + type: list + elements: string + returned: If present + computationRule: + description: Computation rule input will be evaluated and will override all other inputs. + type: string + returned: If present + editable: + description: Whether the field is editable or not for the currently authenticated user. + type: bool + returned: If present + regex: + description: A regular expression that will be used to validate the element's value. + type: string + returned: If present + min: + description: The minimum number of SAN elements that must be provided. + type: int + returned: If present + max: + description: The maximum number of SAN elements that must be provided. + type: int + returned: If present +extensions: + description: Information about the certificate's extensions and how to edit them. + type: list + returned: If present + contains: + type: + description: The type of the extension element. + type: string + returned: Always + value: + description: The value of the extension element. + type: string + returned: If present + computationRule: + description: Computation rule input will be evaluated and will override all other inputs. + type: string + returned: If present + editable: + description: Whether the extension element is editable by the requester. + type: bool + returned: If present + regex: + description: The regular expression to validate the extension element. + type: string + returned: If present + mandatory: + description: Whether the extension element is mandatory to submit this request. + type: bool + returned: If present +labels: + description: List of labels used internally to tag and group certificates. + type: list + returned: If present + contains: + label: + description: The name of the label. + type: string + returned: Always + displayName: + desciption: The display name of the label element. + type: list + elements: string + returned: If present + contains: + lang: + description: The ISO 3166-1 (2-letters) code of the language used for the value. + type: string + return: Always + value: + description: The localized value. + type: string + return Always + description: + description: The description of the label element. + type: list + elements: string + returned: If present + contains: + lang: + description: The ISO 3166-1 (2-letters) code of the language used for the value. + type: string + return: Always + value: + description: The localized value. + type: string + return Always + value: + description: The value of the label element. + type: string + returned: If present + computationRule: + description: The computation rule of the label element. + type: string + returned: If present + mandatory: + description: Whether the label element is mandatory to submit this request. + type: bool + returned: If present + editable: + description: Whether the label is editable. + type: bool + returned: If present + regex: + description: The regex used to validate the label element. + type: string + returned: If present + enum: + description: The enum used to validate the label element. + type: list + returned: If present + suggestions: + description: The suggestions used to recommend the label element values. + type: list + returned: If present +contactEmail: + description: Information about the certificate's contact email and how to edit it. + type: dict + returned: If present + contains: + value: + description: The contact email. + type: string + returned: If present + computationRule: + description: Computation rule input will be evaluated and will override all other inputs. + type: string + returned: If present + editable: + description: Whether the contact email is editable by the requester. + type: bool + returned: If present + mandatory: + description: Whether the contact email is mandatory to submit this request. + type: bool + returned: If present + regex: + description: The regex used to validate the contact email. + type: string + returned: If present + whitelist: + description: The list of allowed contact emails. + type: list + elements: string + returned: If present + description: + description: The description of the contact email. + type: list + elements: string + returned: If present + contains: + lang: + description: The ISO 3166-1 (2-letters) code of the language used for the value. + type: string + return: Always + value: + description: The localized value. + type: string + return Always +owner: + description: Information about the certificate's owner and how to edit it. + type: dict + returned: If present + contains: + value: + description: The value of the owner element. This should be a principal identifier. + type: string + returned: If present + computationRule: + description: Computation rule input will be evaluated and will override all other inputs. + type: string + returned: If present + editable: + description: Whether the owner element is editable by the requester. + type: bool + returned: If present + mandatory: + description: Whether the owner element is mandatory to submit this request. + type: bool + returned: If present + description: + description: The description of the owner element. + type: list + elements: string + returned: If present + contains: + lang: + description: The ISO 3166-1 (2-letters) code of the language used for the value. + type: string + return: Always + value: + description: The localized value. + type: string + return Always +team: + description: Information about the certificate's team and how to edit it. + type: dict + returned: If present + contains: + value: + description: The value of the team element. This should be a team identifier. + type: string + returned: If present + computationRule: + description: Computation rule input will be evaluated and will override all other inputs. + type: string + returned: If present + editable: + description: Whether the team element is editable by the requester. + type: bool + returned: If present + mandatory: + description: Whether the team element is mandatory to submit this request. + type: bool + returned: If present + description: + description: The description of the team element. + type: list + elements: string + returned: If present + contains: + lang: + description: The ISO 3166-1 (2-letters) code of the language used for the value. + type: string + return: Always + value: + description: The localized value. + type: string + return Always +passwordPolicy: + description: The password policy that will be used to generate the certificate's PKCS#12 password. + type: dict + returned: If present + contains: + _id: + description: The internal ID of the password policy. + type: string + returned: Always + name: + description: The name of the password policy. + type: string + returned: Always + minChar: + description: The minimum number of characters of the password. + type: int + returned: Always + maxChar: + description: The maximum number of characters of the password. + type: int + returned: If present + minUpChar: + description: The minimum number of uppercase characters of the password. + type: int + returned: If present + minLoChar: + description: The minimum number of lowercase characters of the password. + type: int + returned: If present + minDiChar: + description: The minimum number of digits of the password. + type: int + returned: If present + spChar: + description: The special characters of the password accepted by the password policy. + type: int + returned: If present + minSpChar: + description: The minimum number of special characters of the password. + type: int + returned: If present +revocationReason: + description: The reason for revoking the certificate + type: string + returned: If present (revocation only) +metadata: + description: Information about the certificate's metadata and how to edit them. + type list: + returned: If present + contains: + metadata: + description: Technical metadata related to the certificate. + type: string + returned: Always + value: + description: The value of the metadata element + type: string + returned: If present + editable: + description: Whether the metadata element is editable by the requester. + type: bool + returned: If present +passwordMode: + description: The password mode of the certificate + type: string + returned: If present (recover only) +''' \ No newline at end of file diff --git a/samples/apache/playbook-deploy-apache-decentralized.yaml b/samples/apache/playbook-deploy-apache-decentralized.yaml new file mode 100644 index 0000000..1a97a25 --- /dev/null +++ b/samples/apache/playbook-deploy-apache-decentralized.yaml @@ -0,0 +1,118 @@ +- hosts: webserver + become: yes + tasks: + - name: Updating APT cache + apt: update_cache=yes + + - name: Installing apache + apt: + name: apache2 + state: latest + + - name: Enabling the SSL module + command: "a2enmod ssl" + become: true + become_user: root + + - name: Get webra enroll template + evertrust.horizon.horizon_template: + endpoint: "{{ horizon_endpoint }}" + x_api_id: "{{ api_id }}" + x_api_key: "{{ api_key }}" + profile: "{{ profile }}" + workflow: "enroll" + register: template + + - name: Generate an OpenSSL private RSA key with size-2048 bits + openssl_privatekey: + path: /tmp/ansible_key + type: "{{ template.capabilities.defaultKeyType | split('-') | first | upper }}" + size: "{{ template.capabilities.defaultKeyType | split('-') | last }}" + return_content: true + register: privatekey + + - name: Generate an OpenSSL certificate signing request file bases on input key values + openssl_csr: + path: /tmp/ansible_csr + privatekey_path: "{{ privatekey.filename }}" + register: csr + + - name: Enrolling a certificate on Horizon for domain {{ domain }} + evertrust.horizon.horizon_enroll: + endpoint: "{{ horizon_endpoint }}" + mode: "centralized" + key_type: "{{ key_type }}" + x_api_id: "{{ api_id }}" + x_api_key: "{{ api_key }}" + profile: "{{ profile }}" + subject: + cn.1: "{{ domain }}" + sans: + dnsname: + - "{{ domain }}" + - "www.{{ domain }}" + labels: + business_unit: "{{ business_unit }}" + ansible_host: "{{ ansible_host }}" + contact_email: "{{ contact_email }}" + csr: + src: "{{csr.filename}}" + register: enrolled_certificate + + - name: Creating the SSL directory + file: + path: /etc/apache2/ssl + state: directory + mode: '0775' + owner: "www-data" + group: "www-data" + + - name: Uploading the {{ domain }} certificate + copy: + content: "{{ enrolled_certificate['certificate']['certificate'] }}" + dest: "/etc/apache2/ssl/{{ domain }}.crt" + mode: '0775' + owner: "www-data" + group: "www-data" + + - name: Uploading the {{ domain }} private key + copy: + content: "{{ privatekey.privatekey }}" + dest: "/etc/apache2/ssl/{{ domain }}.key" + mode: '0660' + owner: "www-data" + group: "www-data" + + - name: Creating the {{ domain }} webroot directory + file: + path: /var/www/{{ domain }} + state: directory + mode: '0775' + owner: "www-data" + group: "www-data" + + - name: Deploying the {{ domain }} index page + template: + src: ../templates/index.html.j2 + dest: "/var/www/{{ domain }}/index.html" + owner: "www-data" + group: "www-data" + mode: '0644' + + - name: Uploading the {{ domain }} site config + template: + src: ../templates/apache.conf.j2 + dest: "/etc/apache2/sites-available/{{ domain }}.conf" + owner: root + group: root + mode: '0644' + + - name: Activating SSL for {{ domain }} + command: "a2ensite {{ domain }}" + become: true + become_user: root + + - name: Restarting Apache + service: + name: apache2 + state: restarted \ No newline at end of file diff --git a/samples/nginx/playbook-deploy-nginx-decentralized.yaml b/samples/nginx/playbook-deploy-nginx-decentralized.yaml new file mode 100644 index 0000000..3d3c8ba --- /dev/null +++ b/samples/nginx/playbook-deploy-nginx-decentralized.yaml @@ -0,0 +1,123 @@ +- hosts: webserver + become: yes + tasks: + - name: Updating APT cache + apt: update_cache=yes + + - name: Installing nginx + apt: + name: nginx + state: latest + + - name: Get webra enroll template + evertrust.horizon.horizon_template: + endpoint: "{{ horizon_endpoint }}" + x_api_id: "{{ api_id }}" + x_api_key: "{{ api_key }}" + profile: "{{ profile }}" + workflow: "enroll" + register: template + + - name: Generate an OpenSSL private RSA key with size-2048 bits + openssl_privatekey: + path: /tmp/ansible_key + type: "{{ template.capabilities.defaultKeyType | split('-') | first | upper }}" + size: "{{ template.capabilities.defaultKeyType | split('-') | last }}" + return_content: true + register: privatekey + + - name: Generate an OpenSSL certificate signing request file bases on input key values + openssl_csr: + path: /tmp/ansible_csr + privatekey_path: "{{ privatekey.filename }}" + register: csr + + - name: Enrolling a certificate on Horizon for domain {{ domain }} + evertrust.horizon.horizon_enroll: + endpoint: "{{ horizon_endpoint }}" + mode: "decentralized" + key_type: "{{ key_type }}" + x_api_id: "{{ api_id }}" + x_api_key: "{{ api_key }}" + profile: "{{ profile }}" + subject: + cn.1: "{{ domain }}" + ou.1: "Web Server" + sans: + dnsname: + - "{{ domain }}" + - "www.{{ domain }}" + labels: + business_unit: "{{ business_unit }}" + ansible_host: "{{ ansible_host }}" + contact_email: "{{ contact_email }}" + csr: + src: "{{csr.filename}}" + register: enrolled_certificate + + - name: Creating the SSL directory + file: + path: /etc/nginx/ssl + state: directory + mode: '0775' + owner: "www-data" + group: "www-data" + + - name: Uploading the {{ domain }} certificate + copy: + content: "{{ enrolled_certificate['certificate']['certificate'] }}" + dest: "/etc/nginx/ssl/{{ domain }}.crt" + mode: '0660' + owner: "www-data" + group: "www-data" + + - name: Uploading the {{ domain }} private key + copy: + content: "{{ privatekey.privatekey }}" + dest: "/etc/nginx/ssl/{{ domain }}.key" + mode: '0660' + owner: "www-data" + group: "www-data" + + - name: Creating the {{ domain }} webroot directory + file: + path: /var/www/{{ domain }} + state: directory + mode: '0775' + owner: "www-data" + group: "www-data" + + - name: Deploying the {{ domain }} index page + template: + src: ../templates/index.html.j2 + dest: "/var/www/{{ domain }}/index.html" + owner: "www-data" + group: "www-data" + mode: '0644' + + - name: Removing the default nginx site + file: + path: /etc/nginx/sites-enabled/default + state: absent + + - name: Uploading the {{ domain }} site config + template: + src: ../templates/nginx.conf.j2 + dest: "/etc/nginx/sites-available/{{ domain }}" + owner: "www-data" + group: "www-data" + mode: '0644' + + - name: Ensuring that {{ domain }} site exists + stat: + path: "/etc/nginx/sites-enabled/{{domain}}" + register: stat_result + + - name: Enabling the {{ domain }} site + command: "ln -s /etc/nginx/sites-available/{{ domain }} /etc/nginx/sites-enabled" + when: not stat_result.stat.exists + + - name: Restarting nginx + service: + name: nginx + state: restarted diff --git a/samples/templates/tomcat.server.xml.decentralized.j2 b/samples/templates/tomcat.server.xml.decentralized.j2 new file mode 100644 index 0000000..f60eda5 --- /dev/null +++ b/samples/templates/tomcat.server.xml.decentralized.j2 @@ -0,0 +1,188 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/tomcat/playbook-deploy-tomcat-decentralized.yaml b/samples/tomcat/playbook-deploy-tomcat-decentralized.yaml new file mode 100644 index 0000000..a9d8e0a --- /dev/null +++ b/samples/tomcat/playbook-deploy-tomcat-decentralized.yaml @@ -0,0 +1,122 @@ +- hosts: webserver + become: yes + tasks: + - name: Updating APT cache + apt: + update_cache: yes + + - name: Creating group "tomcat" + group: + name: tomcat + + - name: Creating user "tomcat" + user: + name: tomcat + group: tomcat + createhome: yes + become: true + + - name: Installing tomcat + apt: + name: tomcat10 + state: latest + + - name: Get webra enroll template + evertrust.horizon.horizon_template: + endpoint: "{{ horizon_endpoint }}" + x_api_id: "{{ api_id }}" + x_api_key: "{{ api_key }}" + profile: "{{ profile }}" + workflow: "enroll" + register: template + + - name: Generate an OpenSSL private RSA key with size-2048 bits + openssl_privatekey: + path: /tmp/ansible_key + type: "{{ template.capabilities.defaultKeyType | split('-') | first | upper }}" + size: "{{ template.capabilities.defaultKeyType | split('-') | last }}" + register: privatekey + + - name: Generate an OpenSSL certificate signing request file bases on input key values + openssl_csr: + path: /tmp/ansible_csr + privatekey_path: "{{ privatekey.filename }}" + register: csr + + - name: Enrolling a certificate on Horizon for domain {{ domain }} + evertrust.horizon.horizon_enroll: + endpoint: "{{ horizon_endpoint }}" + mode: "decentralized" + key_type: "{{ key_type }}" + x_api_id: "{{ api_id }}" + x_api_key: "{{ api_key }}" + profile: "{{ profile }}" + subject: + cn.1: "{{ domain }}" + ou.1: "Web Server" + labels: + business_unit: "{{ business_unit }}" + ansible_host: "{{ ansible_host }}" + contact_email: "{{ contact_email }}" + csr: + src: "{{csr.filename}}" + register: enrolled_certificate + + - name: Creating the tomcat directory + file: + path: /etc/tomcat10 + state: directory + mode: '0775' + owner: "tomcat" + group: "tomcat" + + - name: Creating the SSL directory + file: + path: /etc/tomcat10/ssl + state: directory + mode: '0775' + owner: "tomcat" + group: "tomcat" + + - name: Uploading the {{ domain }} certificate + copy: + content: "{{ enrolled_certificate['certificate']['certificate'] }}" + dest: "/etc/tomcat10/ssl/{{ domain }}.crt" + mode: '0660' + owner: tomcat + group: tomcat + + - name: Building the {{ domain }} PKCS12 + openssl_pkcs12: + action: export + path: "/etc/tomcat10/ssl/{{ domain }}.p12" + privatekey_path: /tmp/ansible_key + certificate_path: "/etc/tomcat10/ssl/{{ domain }}.crt" + friendly_name: "{{ domain }}" + passphrase: "Thisis@securep4ssw0rd" + state: present + register: p12 + + - name: Deploying the {{ domain }} index page + template: + src: ../templates/index.html.j2 + dest: "/var/lib/tomcat10/webapps/ROOT/index.html" + owner: "www-data" + group: "www-data" + mode: '0644' + + - name: Uploading the tomcat configuration + template: + src: ../templates/tomcat.server.xml.decentralized.j2 + dest: /etc/tomcat10/server.xml + + - name: Restarting tomcat + service: + name: tomcat10 + state: restarted + + - name: Waiting for the tomcat service to start + wait_for: + port: 8443 + timeout: 600 + diff --git a/tests/integration/targets/horizon_certificate_renew/aliases b/tests/integration/targets/horizon_certificate_renew/aliases new file mode 100644 index 0000000..e69de29 diff --git a/tests/integration/targets/horizon_certificate_renew/tasks/main.yml b/tests/integration/targets/horizon_certificate_renew/tasks/main.yml new file mode 100644 index 0000000..7442dea --- /dev/null +++ b/tests/integration/targets/horizon_certificate_renew/tasks/main.yml @@ -0,0 +1,52 @@ +- name: centralize enroll + evertrust.horizon.horizon_enroll: + + endpoint: "{{ endpoint }}" + x_api_id: "{{ x_api_id }}" + x_api_key: "{{ x_api_key }}" + + profile: "Ansible" + mode: "centralized" + key_type: "rsa-2048" + + subject: + cn.1: "IntegrationTestCI-certificate-to-renew" + o.1: "Evertrust" + ou.1: "R&D" + + register: data + +- name: Test centralized renew + evertrust.horizon.horizon_renew: + + endpoint: "{{ endpoint }}" + x_api_id: "{{ x_api_id }}" + x_api_key: "{{ x_api_key }}" + + certificate_id: "{{ data.certificate._id }}" + +- name: Test decentralized renew + evertrust.horizon.horizon_renew: + + endpoint: "{{ endpoint }}" + x_api_id: "{{ x_api_id }}" + x_api_key: "{{ x_api_key }}" + + certificate_id: "{{ data.certificate._id }}" + csr: | + -----BEGIN CERTIFICATE REQUEST----- + MIICfzCCAWcCAQAwOjELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCUV2ZXJ0cnVzdDEX + MBUGA1UEAwwOTVlBTlNJQkxFQ0lDU1IwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw + ggEKAoIBAQDXYHbD54XThxsD01uRZVYKRJpdyvQtd9EGMXXhiEKljQ2cXuFUHT/F + MYTr15gF1T6zF0XSKXVYI3Q98WseU2b/obKa3fxDaIDMh5ARJJnFxfaYw6v8m7Wn + iJCYlu8jQRmfxcPBRbX0r6GQtwMI9ge38goBGh7kwnG1Shuqxb1XjBwFouSZ+yL2 + kFonhFYsFCDcRaUIWJfGSgpAzdK66w2SnKBYGjFn/LOh/tybY0QE/7sMSKtYOvUj + YVNyxLPCWFLNkl1UV7I2c7eWuCDDwvUGI1sbpydFAewEn805zWapZRDHfFX/06YW + 9BzaxZmO1Zs4tGj/GZOXG21NM9aT0FDdAgMBAAGgADANBgkqhkiG9w0BAQsFAAOC + AQEABXD15Bb/jq0+/3zZl63i2RaLob4LVNk1/yII0HPgr4OYCMPcILpJPb9/GzeH + 3CCzGuFuDr8kBHumyA24YbdtNQ46iM2HUmw4JaFlzTYmo0zMsa5MbQhmKfym0k9v + XcO4CHnP9TBCh/yWSlagqSUrQVN0mxHQrzVUGsM5KMjW83xnQhuvGDZ8Ke9Vjvyv + KXsklafhHYh4DCGAlfElS0Ye/TP+L5m0jS7IigrsrtqDeK8I+MBnQpyHUAZUhENp + att/xqOGwLPrhZDgSQVtGnVFW2848ZCB4MjkvQFmF+rnqKP8DEDkjDujEPC27f/7 + UOZa8OXHqLGN6HsCwd7gcJYwAA== + -----END CERTIFICATE REQUEST----- \ No newline at end of file