├── examples ├── f5_bigip │ ├── hosts │ ├── venafi_ansible_role.png │ ├── variables.yaml │ ├── f5_delete_playbook.yaml │ ├── f5_create_playbook.yaml │ └── README.md ├── citrix_adc │ ├── hosts │ ├── venafi_ansible_role.png │ ├── variables.yaml │ ├── citrix_delete_playbook.yaml │ ├── citrix_create_playbook.yaml │ └── README.md ├── logo_tile_f5.png ├── logo_tile_citrix.png └── README.md ├── requirements.txt ├── vars └── main.yml ├── handlers └── main.yml ├── ansible.cfg ├── Venafi_logo.png ├── SECURITY.md ├── tests ├── Dockerfile ├── inventory ├── assets │ ├── valid_ec_key.pem │ ├── invalid_date_rsa2048_cert.pem │ ├── valid_rsa2048_key.pem │ ├── valid_alt_rsa2048_key.pem │ ├── valid_rsa2048_chain.pem │ ├── invalid_cert.pem │ ├── valid_rsa2048_cert.pem │ ├── invalid_cn_rsa2048_cert.pem │ └── valid_alt_rsa2048_cert.pem ├── example-vars.yml ├── test_venafi_certificate.py ├── original-ansible-crypto-playbook-example.yml ├── venafi-role-playbook-example.yml └── venafi-playbook-example.yml ├── molecule └── default │ ├── Dockerfile.j2 │ ├── INSTALL.rst │ ├── molecule.yml │ └── playbook.yml ├── .gitignore ├── .github └── ISSUE_TEMPLATE │ ├── config.yml │ ├── ask_a_question.md │ ├── request_a_feature.md │ └── report_a_bug.md ├── .yamllint ├── tasks ├── main.yml ├── remote-certificate.yml └── local-certificate.yml ├── defaults └── main.yml ├── meta └── main.yml ├── Makefile ├── LICENSE ├── README.md └── library └── venafi_certificate.py /examples/f5_bigip/hosts: -------------------------------------------------------------------------------- 1 | localhost 2 | -------------------------------------------------------------------------------- /examples/citrix_adc/hosts: -------------------------------------------------------------------------------- 1 | localhost 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | vcert~=0.10.0 2 | ansible 3 | cryptography -------------------------------------------------------------------------------- /vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # vars file for venafi-certificate 3 | -------------------------------------------------------------------------------- /handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # handlers file for venafi-certificate 3 | -------------------------------------------------------------------------------- /ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | roles_path = ../:../tests/roles 3 | library = ./library 4 | -------------------------------------------------------------------------------- /Venafi_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Venafi/ansible-role-venafi/HEAD/Venafi_logo.png -------------------------------------------------------------------------------- /examples/logo_tile_f5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Venafi/ansible-role-venafi/HEAD/examples/logo_tile_f5.png -------------------------------------------------------------------------------- /examples/logo_tile_citrix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Venafi/ansible-role-venafi/HEAD/examples/logo_tile_citrix.png -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | If you believe you have found a security issue, please report it to opensource@venafi.com. Venafi takes security _very_ seriously. 2 | -------------------------------------------------------------------------------- /examples/citrix_adc/venafi_ansible_role.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Venafi/ansible-role-venafi/HEAD/examples/citrix_adc/venafi_ansible_role.png -------------------------------------------------------------------------------- /examples/f5_bigip/venafi_ansible_role.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Venafi/ansible-role-venafi/HEAD/examples/f5_bigip/venafi_ansible_role.png -------------------------------------------------------------------------------- /tests/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.7.10 2 | RUN apt-get update && apt-get install -y --no-install-recommends python-pip python-setuptools python3-pip python3-setuptools 3 | -------------------------------------------------------------------------------- /tests/inventory: -------------------------------------------------------------------------------- 1 | [robots] 2 | venafi-cert-python3 image="local-ansible-test" 3 | venafi-cert-python2 image="local-ansible-test" ansible_python_interpreter="/usr/bin/python2.7" 4 | -------------------------------------------------------------------------------- /molecule/default/Dockerfile.j2: -------------------------------------------------------------------------------- 1 | FROM python:3.7.10 2 | RUN apt-get update && apt-get install -y --no-install-recommends python-pip python-setuptools python3-pip python3-setuptools 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | .idea 3 | venv 4 | venv27 5 | .venv37 6 | venafi_certificate_*.json 7 | *_credentials.yml 8 | credentials.yml 9 | vault-password.txt 10 | tests/library 11 | tests/roles/ 12 | *.retry 13 | .cache 14 | -------------------------------------------------------------------------------- /tests/assets/valid_ec_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MHcCAQEEIHxXL1YaMh+1kb8F7U+3PB3jqMGSWZHzrnUrl2T8JG4koAoGCCqGSM49 3 | AwEHoUQDQgAEBXh8ZfUyKSLhJlRBrcJxmHOBc4HGDLLjZe9F+NppFiajSmZooro3 4 | iyv/TSCJZUxG1x8rg3jQOi6We4kf29y68Q== 5 | -----END EC PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Join us on Slack! 4 | url: https://join.slack.com/t/venafi-integrations/shared_invite/zt-i8fwc379-kDJlmzU8OiIQOJFSwiA~dg 5 | about: Interact with Venafi and the customer community in real-time! 6 | -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | extends: default 2 | 3 | rules: 4 | braces: 5 | max-spaces-inside: 1 6 | level: error 7 | brackets: 8 | max-spaces-inside: 1 9 | level: error 10 | line-length: disable 11 | # NOTE(retr0h): Templates no longer fail this lint rule. 12 | # Uncomment if running old Molecule templates. 13 | # truthy: disable 14 | -------------------------------------------------------------------------------- /molecule/default/INSTALL.rst: -------------------------------------------------------------------------------- 1 | *********************************** 2 | Docker driver installation guide 3 | *********************************** 4 | 5 | Requirements 6 | ============ 7 | 8 | * General molecule dependencies (see https://molecule.readthedocs.io/en/latest/installation.html) 9 | * Docker Engine 10 | * docker-py 11 | * docker 12 | 13 | Install 14 | ======= 15 | 16 | $ sudo pip install docker-py 17 | -------------------------------------------------------------------------------- /tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Include vars of {{ credentials_file }} into the venafi variable." 3 | include_vars: 4 | file: "{{ credentials_file }}" 5 | name: venafi 6 | 7 | - name: "show execution mode" 8 | debug: 9 | msg: "certificate_remote_execution is {{ certificate_remote_execution }}" 10 | 11 | - import_tasks: local-certificate.yml 12 | when: not certificate_remote_execution 13 | 14 | - import_tasks: remote-certificate.yml 15 | when: certificate_remote_execution 16 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Venafi Ansible Role examples 2 | 3 | In this section you'll find functional examples you can use to better secure application delivery using the _Venafi Ansible Role_ with F5 BIG-IP or Citrix ADC. 4 | 5 | Adding Venafi enables you to manage certificates more securely as part of the TLS termination process on your application delivery controller (ADC). 6 | 7 | **Examples**: 8 | 9 | [SSL Termination with F5 BIG-IP](f5_bigip) 10 | 11 | [SSL Termination with Citrix ADC](citrix_adc) 12 | -------------------------------------------------------------------------------- /molecule/default/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | molecule: 3 | ignore_paths: 4 | - ../../.git 5 | - ../../.venv37 6 | - ../../venv27 7 | - ../../venv 8 | - ../../.molecule 9 | dependency: 10 | name: galaxy 11 | driver: 12 | name: docker 13 | lint: 14 | name: yamllint 15 | platforms: 16 | - name: instance 17 | image: python:3.7.10 18 | provisioner: 19 | name: ansible 20 | log: true 21 | lint: 22 | name: ansible-lint 23 | scenario: 24 | name: default 25 | verifier: 26 | name: testinfra 27 | lint: 28 | name: flake8 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/ask_a_question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F4AC Ask a question" 3 | about: How can we help you? 4 | 5 | --- 6 | 7 | Please send your questions about setup and general usage to opensource@venafi.com rather 8 | than creating GitHub issues. 9 | 10 | The Venafi team is constantly monitoring the opensource@venafi.com inbox so you'll generally 11 | receive a faster response with more detail than if you were to post an issue here on GitHub. 12 | It also allows for you to share information that you might not be comfortable sharing in a 13 | public forum. 14 | 15 | Thanks! 16 | -------------------------------------------------------------------------------- /examples/citrix_adc/variables.yaml: -------------------------------------------------------------------------------- 1 | adc_address: "192.168.5.188" 2 | adc_username: "youruser" 3 | adc_password: "youtpassword" 4 | 5 | test_site: 6 | name: "demo-citrix" 7 | domain: "venafi.example" 8 | 9 | adc_virtual_ip: "192.168.3.167" 10 | adc_virtual_port: "443" 11 | 12 | http_service: 192.168.6.201 13 | port1: 8001 14 | port2: 8002 15 | port3: 8003 16 | 17 | cert_name: "{{ test_site.name }}.crt" 18 | key_name: "{{ test_site.name }}.key" 19 | chain_name: "{{ test_site.name }}-ca-bundle.crt" 20 | 21 | adc_provider: 22 | nsip: "{{ adc_address }}" 23 | nitro_user: "{{ adc_username }}" 24 | nitro_pass: "{{ adc_password }}" 25 | -------------------------------------------------------------------------------- /examples/f5_bigip/variables.yaml: -------------------------------------------------------------------------------- 1 | f5_address: "yourf5bigip" 2 | f5_username: "youruser" 3 | f5_password: "yourpassword" 4 | 5 | test_site: 6 | name: "demo-f5" 7 | domain: "venafi.example" 8 | 9 | f5_partition: "Demo" 10 | f5_virtual_ip: "192.168.7.68" 11 | f5_virtual_port: "443" 12 | f5_pool_members: 13 | - host: 192.168.6.201 14 | port: 8001 15 | - host: 192.168.6.201 16 | port: 8002 17 | - host: 192.168.6.201 18 | port: 8003 19 | 20 | cert_name: "{{ test_site.name }}.crt" 21 | key_name: "{{ test_site.name }}.key" 22 | chain_name: "{{ test_site.name }}-ca-bundle.crt" 23 | 24 | f5_provider: 25 | server: "{{ f5_address }}" 26 | server_port: 443 27 | user: "{{ f5_username }}" 28 | password: "{{ f5_password }}" 29 | validate_certs: no 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/request_a_feature.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F680 Request a feature" 3 | about: Tell us about your idea for improvement! 4 | labels: enhancement 5 | 6 | --- 7 | 8 | 14 | 15 | **BUSINESS PROBLEM** 16 | 17 | 18 | **PROPOSED SOLUTION** 19 | 20 | 21 | **CURRENT ALTERNATIVES** 22 | 23 | 24 | **VENAFI EXPERIENCE** 25 | 26 | -------------------------------------------------------------------------------- /tests/assets/invalid_date_rsa2048_cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC9jCCAd6gAwIBAgIJAIH9NNbfhhIoMA0GCSqGSIb3DQEBCwUAMCUxIzAhBgNV 3 | BAMMGnRlc3QxMjMudmVuYWZpLmV4YW1wbGUuY29tMB4XDTE4MDEzMDE4MjMyM1oX 4 | DTE4MDMwMTE4MjMyM1owJTEjMCEGA1UEAwwadGVzdDEyMy52ZW5hZmkuZXhhbXBs 5 | ZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDjQoGFPysXp15K 6 | QTERbffhiLBaWekmN/NFsXrjTU2b/6RVxl8bbQiI2hjXuuUJwEc3HpAoGo/g307W 7 | YyZXRDQUJe6y88+wmrKZ1+f0QkvEFIVUuWGZy4KZvXRjDv+5j1XJUpbbQK/LV2vd 8 | I2BdNAjSw46Sb5Ik+ESIVS5efAxK28GaoVuOnHzp0sKfKbBIMTy+C9ScACTYwXq8 9 | qx/NzWqqKrQ4i9XQUPXawfZRuKrVE0o9iTNs2ffzZ9Us4/f5UGdR7O5LLdV5a4/d 10 | rk+UFMhRpAsfDcsq/QdX14pGwFBqAw42npLxD4bgQB2x2MwmqagkbskxTT9Vjdwh 11 | QUxNmEIDAgMBAAGjKTAnMCUGA1UdEQQeMByCGnRlc3QxMjMudmVuYWZpLmV4YW1w 12 | bGUuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQDYR6+aWO6aHZ0jHcLQ2t3rxE1rYREM 13 | CrIwM7/0kW2CKxkpF+XEPkz/QKovVoll8TM3xYAtODD31tLvT8WNCzulm6LMqL3K 14 | Q64JYAD5O7GHjn16cDLZJpYOvmsKtWbsZwGG8AjOAlHnG8dhZFHRN3ytiV1NxY7n 15 | czjURe7KNa+QjvISd2Cq0o+Ra+AbS5Z083r0MAEQDeVgWFAfO6QyepHSypbZ1GdR 16 | xlZLa9QIhjOZj2kbC3MsDsv2CARl3ocSiHSnrFTqrXZV8ciYtuiM4WhdxbVd4MaZ 17 | joZPR+GhLd9YWiQq7Zt/A8h9Z4DNsF5gsX+zDn6XTMWEJCzRrEGUWo7y 18 | -----END CERTIFICATE----- 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/report_a_bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41B Report a bug" 3 | about: Let us know what isn't working right! 4 | labels: bug 5 | 6 | --- 7 | 8 | 14 | 15 | **PROBLEM SUMMARY** 16 | 17 | 18 | **STEPS TO REPRODUCE** 19 | 20 | 21 | **EXPECTED RESULTS** 22 | 23 | 24 | **ACTUAL RESULTS** 25 | 26 | 27 | **ENVIRONMENT DETAILS** 28 | 29 | 30 | **COMMENTS/WORKAROUNDS** 31 | 32 | -------------------------------------------------------------------------------- /tasks/remote-certificate.yml: -------------------------------------------------------------------------------- 1 | # Generates certificates on remote host 2 | --- 3 | - name: "Enroll Venafi certificate on remote host" 4 | venafi_certificate: 5 | url: "{{ venafi.url | default(omit) }}" 6 | token: "{{ venafi.token | default(omit) }}" 7 | zone: "{{ venafi.zone | default(omit) }}" 8 | test_mode: "{{ venafi.test_mode if venafi.test_mode is defined else 'false' }}" 9 | user: "{{ venafi.user | default(omit) }}" 10 | password: "{{ venafi.password | default(omit) }}" 11 | access_token: "{{ venafi.access_token | default(omit) }}" 12 | trust_bundle: "{{ venafi.trust_bundle | default(omit) }}" 13 | cert_path: "{{ certificate_cert_path }}" 14 | chain_path: "{{ certificate_chain_path | default(omit) }}" 15 | privatekey_path: "{{ certificate_privatekey_path | default(omit) }}" 16 | privatekey_type: "{{ certificate_privatekey_type | default(omit) }}" 17 | privatekey_size: "{{ certificate_privatekey_size | default(omit) }}" 18 | common_name: "{{ certificate_common_name }}" 19 | alt_name: "{{ certificate_alt_name | default([]) }}" 20 | before_expired_hours: "{{ certificate_before_expired_hours | default(omit) }}" 21 | force: "{{ certificate_force if certificate_force is defined else false }}" 22 | register: certout 23 | - name: "dump test output" 24 | debug: 25 | msg: "{{ certout }}" 26 | -------------------------------------------------------------------------------- /defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | credentials_file: credentials.yml 3 | # Use Ansible host FQDN for certificate common name 4 | certificate_common_name: "{{ ansible_fqdn }}" 5 | 6 | # Directory where to place certificates 7 | certificate_cert_dir: "/etc/ssl/{{ certificate_common_name }}" 8 | # Paths for certificate and keys 9 | certificate_cert_path: "{{ certificate_cert_dir }}/{{ certificate_common_name }}.pem" 10 | certificate_chain_path: "{{ certificate_cert_dir }}/{{ certificate_common_name }}.chain.pem" 11 | certificate_privatekey_path: "{{ certificate_cert_dir }}/{{ certificate_common_name }}.key" 12 | certificate_csr_path: "{{ certificate_cert_dir }}/{{ certificate_common_name }}.csr" 13 | 14 | # Where to execute venafi_certificate module. If set to false, certificate will be 15 | # created on ansible master host and then copied to the remote server 16 | certificate_remote_execution: false 17 | # remote location where to place the certificate_ 18 | certificate_remote_cert_path: "{{ certificate_cert_dir }}/{{ certificate_common_name }}.pem" 19 | certificate_remote_chain_path: "{{ certificate_cert_dir }}/{{ certificate_common_name }}.chain.pem" 20 | certificate_remote_privatekey_path: "{{ certificate_cert_dir }}/{{ certificate_common_name }}.key" 21 | # Set to false, if you don't want to copy private key to remote location 22 | certificate_copy_private_key_to_remote: true 23 | -------------------------------------------------------------------------------- /tests/assets/valid_rsa2048_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEA0MILue7W4ZcQZbe7wHc1CjTsunsyuWGIU6tNxDH4PW4RyclJ 3 | jQb0jgPTJOxTZ23r8LmnXv7luOyQt09K9bMpEta+925SpILSSVzOXqUKvDxznGd6 4 | LyAP4Md5Yre7g05XJV1pdv7bysbMd2zs8uyYW2n7lacNny9674yXKZ3qcf9FxWEJ 5 | C4RPTd8rDrqg7NXK7wJ3dg9tzVmDKrL6NEbE6cjFNyypWApMvh1Hneed6qeVL1xX 6 | TnQjbJ2vRVI0qJzhm3TgnhaDj1xHFQPDA4UOn1PprAjPiNptmE7OHFB63uoyCSIs 7 | O+yMRQCFDxH77yV8Js3b8IYn3TryahxuDlTlEQIDAQABAoIBADvNOrq61setFL9u 8 | 0NQj0gfofWA2ZqOAcyM00YRApFJEs8fQZ8eElI4SPmw3XvUkIhDFvlKSRpChBBvf 9 | FSQpfLyu0+nhqr8B0ue9NEkckmS2FJBbfD7/uky0F+vVolvNF13W0p2KCMCgDnav 10 | t3knmcWmKLIINvjBC0CQT8VWPZEGAQeBTR14DSefZ5nYAecpJS8ovIgT+9zNkN4R 11 | NWYHv1tJuykEXLC+dnxFrqhDH5KLW6+gCk/SAAPW7n+501SbcEU1NH+XG1OS6Vpr 12 | hDQv+4rs7dgboRNvwwJ9sAPvJvMXurKP5KwMzVz9Rvt/pSTtDZpVAN27y/L1YDVx 13 | /FrT9nkCgYEA8m0L14tm+u3C4OSs2lrxXqL+6xKQFuNNL+73wL6fD2QpE8+xlkna 14 | sbI3w5hUY3wBR2cRb3LE8XBuan+3NTRpbPCwDU7/v1j1dfFPSyQjjQcbJWtK7KDz 15 | KIqr/ja9+KZmB7CR0jrPmC/E9M4era97GiKxEcSxMkSSrTf0Q4om/qMCgYEA3HJm 16 | a7AY0j0x7UJ3pejf+RjMb7cGFw9yDaQLdxqpMKrHxr9wTls9f1sJcnyejFxyWRY2 17 | NRD/E5RLKbyS9y9GV9T+IJ0QdEULFuHp4pdMkLDXek4Vtmk97uz0dPNbG5EH+QHi 18 | 2+2DCiYM9NlBL16I1NarYJ6VF93a3upm+cVFzLsCgYA/BWQXs5Cg2OBZcHkTBqNK 19 | s9rLJ3c3y/1L4bacb7GP4bzf/pUu+aIVobvnaBlAB4OZmUyqCU7zaQP7QZpSBX8G 20 | qAdMXmYTyz98Wq//W7S2O8utzZebrjeoKWyO2JJIKpHJm/g8i7dd28U60r6c7kp1 21 | P+GCOfzKkZMD3tDsIeUDOwKBgQDS5NrZAQHt979Q1Pq0DUJgAvppkbXQz+Tz6dFS 22 | I35i4a73k2O0gpMlyIkULuZbL4Hxek9nmxf7ui3iAtayhVaNZmWr+7anFPpT7NKd 23 | BNOpxJSQHC2nca1fau8/ByVNDQWMkeJfNizbw6U3hLRYxd6vh6MybuQBjiv6gFL9 24 | dl2dSwKBgEGzBxA9Ju1CoA1HMn1yefuIeO5T3BfCHRrcYV9+mngUHuAVyqvXiGKx 25 | ItkNpLzL1XnPirgWpuLLeoJFvgBLEnVmKfe3NjkBauiyHrmonwKzeSXcQH8U/qGT 26 | 2b66skeb3XSNGjsIl3bJ07p9y0uSPFwduOPhi6EzdVPWQpbHiy3v 27 | -----END RSA PRIVATE KEY----- -------------------------------------------------------------------------------- /tests/assets/valid_alt_rsa2048_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEAwwwRWMAHM58oKfdpKmZDOOKEV2BY6ApyuQGi2EpSX0ErXJj2 3 | bBGdQ3oq25Gpm7qAUH3jxbxq8FOl+47iVq9GLQtYii9D14QrXk6TehBkR7DEyPMO 4 | 26zvcz7kN3tHxIdsL1JsZto6/va1+rukcFGxNBUnASbqPwK9GYDyf9GpOglVewxx 5 | fjmDoxlp+LIzQnT5wO0XB00k2sNC4smvvh58TLjS0VyMmIJpiKz5l1QRk77X6aXA 6 | 7Mkv5wuDz6tDrMsCOOXidAUepSarNjYeA5/K3Q+Yr/M30ZbfDIp3WPcZQ2xLVdS7 7 | VLxTy2Z3WptBiunfr9L/Teb6/IJJM5942jZyUQIDAQABAoIBAFWskFYOi1tGdsrw 8 | TTZ9ktuCzCThQkwygq/3IwOkqcgDhTt4SQ2xMwX3vKmTsw+ffUtd8NXjbInCBG+a 9 | FJuFA+s3R53zIvagrLgRBcChQJso71dhRE6ECO3zWyVvtleCMKgcqVcIVs+1qLvI 10 | /nMDvzWkcOyVnmEtH8j7FgnFG3NM7rVVv5uFmvYWp9t6x7d6kiIRd46aMk4xPq/7 11 | zVKIsUrjKnsc03X9YDcmNGB9CH72g8vzyAX6ob3Gfro2ZIdgPBBlyok7yAK2FAS8 12 | /UaF+UXI8+bPnCs71h+YPQJ0P5abWDxIB8mLB4P3gIw7jCh2ePM43r1K+JvEbnQ4 13 | E3PCP90CgYEA+mltmYc9HGwTFbah990rIQZIyg+Wzu7FpYTAoEnToanf93T27xOD 14 | 5Ksti3k5yrGqTxcoE0hyMNn4DciUgNWVaa2aIcI1YmTPyjaRq7MeHrHv4nbndAJ0 15 | CiyXWKRH+Dthyn4Rw8bRfi6R2Bsn7+TSL5NLci4TWuOIkoYl2tc51wsCgYEAx2ZZ 16 | FpPK6O32yzDHYS3mocYt8H+keHnoWlShmP0ptjSkZPVVmBZr2v0Cr0maY2BC8ttn 17 | xnkbCK/3GNv+KmaMGiTHoaY0EmIz58/sSIWsy8jtKtZrSulX6PyZcoq75hu7AtMn 18 | F3K3LxWK7AgS0Y4qPmaOPTPNfXrVJN0i5YVeRZMCgYEA9uKXAjVJ1QngzxmfGudd 19 | rFOr1DwGbcMP7p6x49al5s+7VxhklVXiRcNXRhmhFuyPgybLhid5Hhzo6X4Gm/b4 20 | NpbITdxSEc5e53lhqa0RVyYL4nVkwQXiLl3EYcqmgmDZi3E8Ro9w4D094Zj0iRpK 21 | +Ej6q3ot7wBCGGRWUiq8hf0CgYBaZbT3vlLcHJ2o6llJXjTTnHPRNxzKHYJQCVQl 22 | dohFeUIaHvsJ8wg8hD2GWBjs+oP/c6ZtXRP7cULVe06TzF+xroDucNnkh66+Zg3Z 23 | pvh6foG+zOxhTr4y+ulZ+zlKDcJPeoibYb9YUizj6pkVdZ0DIx1S87wyKdCdYL9k 24 | TH07jwKBgQDD0kyBbe8T9G/AgJbxdAL1wmwzQaO85m1rwHbBDB0cUfYaU4H+PYII 25 | O7VMsNS7LtwVfQ5oUnD/u5z9wUzFnB6RFwYCwvOa4xESXcRucO06VqLBRqwb3dXW 26 | 3dSjPLXgus9GfbUze3bgBlpSKbqhzr6Uo/45g3zsJeKypMnedvmjnQ== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /tests/example-vars.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Credentials. 3 | venafi: 4 | # Venafi Platform connection parameters 5 | user: 'admin' 6 | password: 'secret' 7 | url: 'https://venafi.example.com/vedsdk' 8 | zone: "devops\\vcert" 9 | # Venafi Cloud connection parameters 10 | # token: 'enter-cloud-api-token-here' 11 | # zone: 'Default' 12 | # Test mode parameter 13 | # test_mode: true 14 | 15 | # Certificate parameters. These are just examples. 16 | certificate_common_name: "{{ ansible_fqdn }}" 17 | certificate_alt_name: "IP:192.168.1.1,DNS:www.venafi.example.com,DNS:m.venafi.example.com,email:e@venafi.com,email:e2@venafi.com,IP Address:192.168.2.2" 18 | 19 | certificate_privatekey_type: "RSA" 20 | certificate_privatekey_size: "2048" 21 | certificate_privatekey_curve: "P251" 22 | certificate_privatekey_passphrase: "password" 23 | certificate_chain_option: "last" 24 | 25 | certificate_cert_dir: "/etc/ssl/{{ certificate_common_name }}" 26 | certificate_cert_path: "{{ certificate_cert_dir }}/{{ certificate_common_name }}.pem" 27 | certificate_chain_path: "{{ certificate_cert_dir }}/{{ certificate_common_name }}.chain.pem" 28 | certificate_privatekey_path: "{{ certificate_cert_dir }}/{{ certificate_common_name }}.key" 29 | certificate_csr_path: "{{ certificate_cert_dir }}/{{ certificate_common_name }}.csr" 30 | 31 | # Where to execute venafi_certificate module. If set to false certificate will be 32 | # created on ansible master host and then copied to the remote server 33 | certificate_remote_execution: false 34 | # remote location where to place the certificate_ 35 | certificate_remote_cert_path: "{{ certificate_cert_dir }}/{{ certificate_common_name }}.pem" 36 | certificate_remote_chain_path: "{{ certificate_cert_dir }}/{{ certificate_common_name }}.chain.pem" 37 | certificate_remote_privatekey_path: "{{ certificate_cert_dir }}/{{ certificate_common_name }}.key" 38 | # Set to false if you don't want to copy private key to remote location 39 | certificate_copy_private_key_to_remote: true 40 | -------------------------------------------------------------------------------- /examples/f5_bigip/f5_delete_playbook.yaml: -------------------------------------------------------------------------------- 1 | - name: Remove F5 Application 2 | hosts: localhost 3 | connection: local 4 | 5 | vars_files: 6 | - variables.yaml 7 | 8 | tasks: 9 | - name: Remove Virtual Server from F5 BIG-IP {{ f5_address }} 10 | bigip_virtual_server: 11 | state: absent 12 | provider: "{{ f5_provider }}" 13 | name: "vs_{{ test_site.name }}" 14 | partition: "{{ f5_partition }}" 15 | delegate_to: localhost 16 | 17 | - name: Remove Pool from F5 BIG-IP {{ f5_address }} 18 | bigip_pool: 19 | state: absent 20 | provider: "{{ f5_provider }}" 21 | name: "pool_{{ test_site.name }}" 22 | partition: "{{ f5_partition }}" 23 | delegate_to: localhost 24 | 25 | - name: Remove Client SSL Profile from F5 BIG-IP {{ f5_address }} 26 | bigip_profile_client_ssl: 27 | state: absent 28 | provider: "{{ f5_provider }}" 29 | name: "clientssl_{{ test_site.name }}" 30 | partition: "{{ f5_partition }}" 31 | delegate_to: localhost 32 | 33 | - name: Remove Private Key from F5 BIG-IP {{ f5_address }} 34 | bigip_ssl_key: 35 | state: absent 36 | provider: "{{ f5_provider }}" 37 | name: "{{ key_name }}" 38 | partition: "{{ f5_partition }}" 39 | delegate_to: localhost 40 | 41 | - name: Remove Certificate from F5 BIG-IP {{ f5_address }} 42 | bigip_ssl_certificate: 43 | state: absent 44 | provider: "{{ f5_provider }}" 45 | name: "{{ cert_name }}" 46 | partition: "{{ f5_partition }}" 47 | delegate_to: localhost 48 | 49 | - name: Remove CA Bundle from F5 BIG-IP {{ f5_address }} 50 | bigip_ssl_certificate: 51 | state: absent 52 | provider: "{{ f5_provider }}" 53 | name: "{{ chain_name }}" 54 | partition: "{{ f5_partition }}" 55 | delegate_to: localhost 56 | 57 | - name: Delete Local Crypto Assets 58 | file: 59 | state: absent 60 | path: "./tmp/" 61 | -------------------------------------------------------------------------------- /meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | galaxy_info: 3 | role_name: ansible_role_venafi 4 | author: venafi 5 | description: Streamline machine identity (certificate and key) acquisition using Venafi vcert. 6 | company: Venafi, Inc. 7 | 8 | license: Apache License 2.0 9 | 10 | min_ansible_version: 2.4 11 | 12 | # If this a Container Enabled role, provide the minimum Ansible Container version. 13 | # min_ansible_container_version: 14 | 15 | # Optionally specify the branch Galaxy will use when accessing the GitHub 16 | # repo for this role. During role install, if no tags are available, 17 | # Galaxy will use this branch. During import, Galaxy will access files on 18 | # this branch. If Travis integration is configured, only notifications for this 19 | # branch will be accepted. Otherwise, in all cases, the repo's default branch 20 | # (usually master) will be used. 21 | # github_branch: 22 | 23 | # 24 | # Provide a list of supported platforms, and for each platform a list of versions. 25 | # If you don't wish to enumerate all versions for a particular platform, use 'all'. 26 | # To view available platforms and versions (or releases), visit: 27 | # https://galaxy.ansible.com/api/v1/platforms/ 28 | # 29 | platforms: 30 | - name: EL 31 | versions: 32 | - all 33 | - name: Fedora 34 | versions: 35 | - all 36 | - name: Ubuntu 37 | versions: 38 | - all 39 | - name: Debian 40 | versions: 41 | - all 42 | 43 | galaxy_tags: ['certificates', 'ssl', 'security', 'venafi'] 44 | # List tags for your role here, one per line. A tag is a keyword that describes 45 | # and categorizes the role. Users find roles by searching for tags. Be sure to 46 | # remove the '[]' above, if you add tags to this list. 47 | # 48 | # NOTE: A tag is limited to a single word comprised of alphanumeric characters. 49 | # Maximum 20 tags per role. 50 | 51 | dependencies: [] 52 | # List your role dependencies here, one per line. Be sure to remove the '[]' above, 53 | # if you add dependencies to this list. 54 | -------------------------------------------------------------------------------- /tests/assets/valid_rsa2048_chain.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFpjCCA46gAwIBAgIQPY6aY41C6JxH4BxIUMuftTANBgkqhkiG9w0BAQsFADBb 3 | MRMwEQYKCZImiZPyLGQBGRYDY29tMRYwFAYKCZImiZPyLGQBGRYGdmVuYWZpMRUw 4 | EwYKCZImiZPyLGQBGRYFdmVucWExFTATBgNVBAMTDFFBIFZlbmFmaSBDQTAeFw0x 5 | NjExMjExMzU4NTVaFw0zNjExMjExNDA4NTRaMFsxEzARBgoJkiaJk/IsZAEZFgNj 6 | b20xFjAUBgoJkiaJk/IsZAEZFgZ2ZW5hZmkxFTATBgoJkiaJk/IsZAEZFgV2ZW5x 7 | YTEVMBMGA1UEAxMMUUEgVmVuYWZpIENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A 8 | MIICCgKCAgEA3M53kteYHPX83uDvxd25NBNfokj4FBGXhrrAuUbiZFu2oolu9W9y 9 | KD/7OwlivxnEkEbUcdaLvKJmlmVUqZqmPATATsvU1RVuv6P2e7BT/C9ErSPQOkUW 10 | XkKBfZtOJufHs0FSwUa+AUm6Kd+bkOEZIbAmMNuip5aC7HDfmN77cSksXRNX/UjU 11 | W5B5y/0aV58p32GGCySr9gBqYwYHX3pPCUl+rnf/+hEMViI1TWlLaVa77uodCfD8 12 | b7hNopVk8KAnNlNhEYNVIQnfKC/OsNGP63FYqDswS0SRr/M6XmoMHZSr6MEXCz9m 13 | MQLeft/nR8llcvB+CnfuzEUWWj2zgBzsCwvBZ6vUrz0ziZmUODqek9oQ+6L9HOJn 14 | nBATIOLMYfDX0kYvfnvVnA2b4ugdrD/PpYOnKHW3twpxVJ2HplRX4dAZ2TXJs7tU 15 | EYgAcYAJzk1rE/yBEgY0Z6Wj8WlBj7PzTxWs8NUhEvrpPNCus2ARz8Xx8IE6A9cI 16 | 87U0BRISiFFtd0BFG0EF4C6vZaBtXK049swsVu+2f2Q9mzxskcUcThxVGHqNLYBY 17 | Zadwjq/+O8/OLG6mpu9d1TpF7TSFmFd2Mc0tqm9ROthtKXRPahVQSmXTYhSrCLRj 18 | /GMCS2+zR6rv3y5K/YBEaskpM0/wZHygFbucjfFizgGxZbvk3NYxLP0CAwEAAaNm 19 | MGQwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQF 20 | MAMBAf8wHQYDVR0OBBYEFDysnKYNoTDUVqc9eLwjG+y0e011MBAGCSsGAQQBgjcV 21 | AQQDAgEAMA0GCSqGSIb3DQEBCwUAA4ICAQDb6m9/c694TIo/V2Bowq0iei5f0TKJ 22 | Cc0X4+jUGa3ivkQRB0EgKFbXUHtP52Pribi2OeGeLJibMDcB6sfiOjSmun83Pe8D 23 | pOAAq+YlKRiUTF4qb8SD5iJPPTTL/KaRxisBLcUGxOvhBVJcm5rQ9crowE5RN9qm 24 | YVGVG73T9Y+p9GgLZUz3v1YTZ89LubLfiW6x8Q8jyzjkgfKY49oxGf/DrWp9y6gt 25 | TBFcG4pQNOC7AIVYj5UTPxZqbuuJTkwADdRwElSvzHxceHvICJaSbSNiHhX4XsrQ 26 | FMajGG3AZC879wcPW1pejPN4A2705WPZ/8mMVuYJDadQ6Pt8+PUXJDcmKGtVv+1E 27 | d7AVpYqhgWwze+V+eRgI5rTPr0ijFXX8VGFUcJl5JwUwPLrUNA45UMA7V5qgjb9+ 28 | k+GXaoC9l4PyiSdEm/vR0+Vbj/ZB7sgU9XlFe8D8e3c2bdvg2Iwjjx4RBQffnoWl 29 | vc/Ofw9Hbk3LUsn7k4GOrQNpMlz14tpY3pPi6qrZFH/RabZngL5Tog9mszcqgMBv 30 | 9FyPr0ubOaCXBXJzRjVQjHV0YOGwFeLvQAohFIAdMlCVRVx+rIzupEskGgAMnKtG 31 | QXe+VMF9FXaRqDI/cCNsBnR++USinZvwGY6SecfDtHA7x65yJol7Y8YtURNfyDfg 32 | yVzOWlPcu2gJaw== 33 | -----END CERTIFICATE----- 34 | -------------------------------------------------------------------------------- /tests/assets/invalid_cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIHBTCCBO2gAwIBAgITbQB2yTTOt8JP17HI7QAAAHbJNDANBgkqhkiG9w0BAQsF 3 | ADBbMRMwEQYKCZImiZPyLGQBGRYDY29tMRYwFAYKCZImiZPyLGQBGRYGdmVuYWZp 4 | MRUwEwYKCZImiZPyLGQBGRYFdmVucWExFTATBgNVBAMTDFFBIFZlbmFmaSBDQTAe 5 | Fw0xOTAxMjgxMjAyMzNaFw0yMTAxMjcxMjAyMzNaMCUxIzAhBgNVBAMTGnRlc3Qx 6 | MTEudmVuYWZpLmV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB 7 | CgKCAQEA40KBhT8rF6deSkExEW334YiwWlnpJjfzRbF6401Nm/+kVcZfG20IiNoY 8 | 17rlCcBHNx6QKBqP4N9O1mMmV0Q0FCXusvPPsJqymdfn9EJLxBSFVLlhmcuCmb10 9 | Yw7/uY9VyVKW20Cvy1dr3SNgXTQI0sOOkm+SJPhEiFUuXnwMStvBmqFbjpx86dLC 10 | nymwSDE8vgvUnAAk2MF6vKsfzc1qqiq0OIvV0FD12sH2Ubiq1RNKPYkzbNn382fV 11 | LOP3+VBnUezuSy3VeWuP3a5PlBTIUaQLHw3LKv0HV9eKRsBQagMONp6S8Q+G4EAd 12 | sdjMJqmoJG7JMU0/VY3cIUFMTZhCAwIDAQABo4IC9jCCAvIwCQYDVR0RBAIwADAd 13 | BgNVHQ4EFgQUIFA2UfU9Kn2htZ2nobCp2UlOqWowHwYDVR0jBBgwFoAUPKycpg2h 14 | MNRWpz14vCMb7LR7TXUwggEiBgNVHR8EggEZMIIBFTCCARGgggENoIIBCYZCaHR0 15 | cDovL3FhdmVuYWZpY2EudmVucWEudmVuYWZpLmNvbS9DZXJ0RW5yb2xsL1FBJTIw 16 | VmVuYWZpJTIwQ0EuY3JshoHCbGRhcDovLy9DTj1RQSUyMFZlbmFmaSUyMENBLENO 17 | PXFhdmVuYWZpY2EsQ049Q0RQLENOPVB1YmxpYyUyMEtleSUyMFNlcnZpY2VzLENO 18 | PVNlcnZpY2VzLENOPUNvbmZpZ3VyYXRpb24sREM9dmVucWEsREM9dmVuYWZpLERD 19 | PWNvbT9jZXJ0aWZpY2F0ZVJldm9jYXRpb25MaXN0P2Jhc2U/b2JqZWN0Q2xhc3M9 20 | AQUFBzAChl5odHRwOi8vcWF2ZW5hZmljYS52ZW5xYS52ZW5hZmkuY29tL0NlcnRF 21 | bnJvbGwvcWF2ZW5hZmljYS52ZW5xYS52ZW5hZmkuY29tX1FBJTIwVmVuYWZpJTIw 22 | Q0EuY3J0MIG3BggrBgEFBQcwAoaBqmxkYXA6Ly8vQ049UUElMjBWZW5hZmklMjBD 23 | QSxDTj1BSUEsQ049UHVibGljJTIwS2V5JTIwU2VydmljZXMsQ049U2VydmljZXMs 24 | Q049Q29uZmlndXJhdGlvbixEQz12ZW5xYSxEQz12ZW5hZmksREM9Y29tP2NBQ2Vy 25 | dGlmaWNhdGU/YmFzZT9vYmplY3RDbGFzcz1jZXJ0aWZpY2F0aW9uQXV0aG9yaXR5 26 | MCEGCSsGAQQBgjcUAgQUHhIAVwBlAGIAUwBlAHIAdgBlAHIwCwYDVR0PBAQDAgWg 27 | MBMGA1UdJQQMMAoGCCsGAQUFBwMBMA0GCSqGSIb3DQEBCwUAA4ICAQBBv4wemLIT 28 | nibcOBnYCKhkJj97i/EVcOWALXi9iVoKtVUuLFTHjLxyzwHobM8ndRJ9Ard/F9Uk 29 | UqEsqf+9SWdZwMWTGmNTdWjywabSP508LdkSz9Ffpmz/Ysw8smbQfFPxbpxqCin8 30 | j5QX2QHeA3+OZvX7/4fc4Xog/19skRrLR/O65IEO+gd5MZjsf3A9mvEtAJB05uiF 31 | L1szUAeKMYFmlVkdcA6C2xxxai049sdkgUwHJ+eqbF43Ko2g6s1kfGtXZI26B5kx 32 | Obqf3KUONRhrUA3NM6LO9A/io5oxweFjvofsFA/QuoU5y4xXzdB8bAS+FzWug/Ve 33 | 6/RT7Xw6RsCDreBFdLd+Xe5vrzq6/duHWWnBTDd23DAlTWrD3RKD16OwnJppaG31 34 | YB0/j+O5dLOqkvh1OTNUAaxOTeE7K/X9s1aIZoeb79W26DMDKJNJJ5/djqNImylU 35 | ZIf77mYIDX+yDCYsrXG5XzecC6HXpCEkJtBEbBuIuW/nwfiuECbiHIqMgStavZzd 36 | 2URasPdBG2vquDxDRgpXYavSgod0a/QjJ+kDXVvHEAyFyruyDcV3DXi+Tk1DcWtV 37 | B2Axcb0wRYYfFFTWrfP74Zgse4hpKA/DHSLxQyzZ7dI2xQjowm6ugAx8K8v/FlhO 38 | 129nilQZ1FsjrhKgFp6OBNc+4ceFTOjDdA== 39 | -----END CERTIFICATE----- -------------------------------------------------------------------------------- /tests/assets/valid_rsa2048_cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIHtjCCBZ6gAwIBAgITbQB6PPaudu2pxon+EAAAAHo89jANBgkqhkiG9w0BAQsF 3 | ADBbMRMwEQYKCZImiZPyLGQBGRYDY29tMRYwFAYKCZImiZPyLGQBGRYGdmVuYWZp 4 | MRUwEwYKCZImiZPyLGQBGRYFdmVucWExFTATBgNVBAMTDFFBIFZlbmFmaSBDQTAe 5 | Fw0xOTExMjYxMjQxMDJaFw0yNzExMjQxMjQxMDJaMIGCMQswCQYDVQQGEwJVUzEN 6 | MAsGA1UECBMEVXRhaDESMBAGA1UEBxMJU2FsdCBMYWtlMRQwEgYDVQQKEwtWZW5h 7 | ZmkgSW5jLjEVMBMGA1UECxMMSW50ZWdyYXRpb25zMSMwIQYDVQQDExp0ZXN0MTEx 8 | LnZlbmFmaS5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC 9 | ggEBANDCC7nu1uGXEGW3u8B3NQo07Lp7MrlhiFOrTcQx+D1uEcnJSY0G9I4D0yTs 10 | U2dt6/C5p17+5bjskLdPSvWzKRLWvvduUqSC0klczl6lCrw8c5xnei8gD+DHeWK3 11 | u4NOVyVdaXb+28rGzHds7PLsmFtp+5WnDZ8veu+Mlymd6nH/RcVhCQuET03fKw66 12 | oOzVyu8Cd3YPbc1Zgyqy+jRGxOnIxTcsqVgKTL4dR53nneqnlS9cV050I2ydr0VS 13 | NKic4Zt04J4Wg49cRxUDwwOFDp9T6awIz4jabZhOzhxQet7qMgkiLDvsjEUAhQ8R 14 | ++8lfCbN2/CGJ9068mocbg5U5RECAwEAAaOCA0kwggNFMB0GA1UdDgQWBBQjNgT2 15 | sOHXIGWWs1pycaSrqngw2jAfBgNVHSMEGDAWgBQ8rJymDaEw1FanPXi8IxvstHtN 16 | dTCCASIGA1UdHwSCARkwggEVMIIBEaCCAQ2gggEJhkJodHRwOi8vcWF2ZW5hZmlj 17 | YS52ZW5xYS52ZW5hZmkuY29tL0NlcnRFbnJvbGwvUUElMjBWZW5hZmklMjBDQS5j 18 | cmyGgcJsZGFwOi8vL0NOPVFBJTIwVmVuYWZpJTIwQ0EsQ049cWF2ZW5hZmljYSxD 19 | Tj1DRFAsQ049UHVibGljJTIwS2V5JTIwU2VydmljZXMsQ049U2VydmljZXMsQ049 20 | Q29uZmlndXJhdGlvbixEQz12ZW5xYSxEQz12ZW5hZmksREM9Y29tP2NlcnRpZmlj 21 | YXRlUmV2b2NhdGlvbkxpc3Q/YmFzZT9vYmplY3RDbGFzcz1jUkxEaXN0cmlidXRp 22 | b25Qb2ludDCCATgGCCsGAQUFBwEBBIIBKjCCASYwagYIKwYBBQUHMAKGXmh0dHA6 23 | Ly9xYXZlbmFmaWNhLnZlbnFhLnZlbmFmaS5jb20vQ2VydEVucm9sbC9xYXZlbmFm 24 | aWNhLnZlbnFhLnZlbmFmaS5jb21fUUElMjBWZW5hZmklMjBDQS5jcnQwgbcGCCsG 25 | AQUFBzAChoGqbGRhcDovLy9DTj1RQSUyMFZlbmFmaSUyMENBLENOPUFJQSxDTj1Q 26 | dWJsaWMlMjBLZXklMjBTZXJ2aWNlcyxDTj1TZXJ2aWNlcyxDTj1Db25maWd1cmF0 27 | aW9uLERDPXZlbnFhLERDPXZlbmFmaSxEQz1jb20/Y0FDZXJ0aWZpY2F0ZT9iYXNl 28 | P29iamVjdENsYXNzPWNlcnRpZmljYXRpb25BdXRob3JpdHkwCwYDVR0PBAQDAgWg 29 | MDsGCSsGAQQBgjcVBwQuMCwGJCsGAQQBgjcVCIGPiXKEhLBq9Z0Qh5b/fMSJFWCE 30 | z5pjhfuuQwIBZAIBBDATBgNVHSUEDDAKBggrBgEFBQcDATAbBgkrBgEEAYI3FQoE 31 | DjAMMAoGCCsGAQUFBwMBMCUGA1UdEQQeMByCGnRlc3QxMTEudmVuYWZpLmV4YW1w 32 | bGUuY29tMA0GCSqGSIb3DQEBCwUAA4ICAQALQbEtiVp/+gVyTS6sS+Ipj54GHJgs 33 | iIsmQCS2YxuUppf7K6kpfMdEgeQZ9UAtPSgTy7fxERjWdrs1ZXHkjeb2LSdP0HdC 34 | /nIwdUkCDzFgOTg/s6T2A/Z200hsp/td+t9JwY1lctZefv+5EgRCU4L4c5zseqpq 35 | /mrLtUBGmFJa2qAeoIAH0wKYcGExcerWlU/e9S7qKSzmoGgUQPsrChJCSClxSTYK 36 | 4HUO05whP6NRbyYhfH3K7O8UEdlFe0H0pKf9v/SrwRTIsG9tnU+P3Rp7o/S15mYz 37 | k61UBmdHa0NJXEFUQy3JZcGW0aC85ovPwBMSqExHuFzGR0rPCRYsPVAOwtWZaLiK 38 | 9IunCh+qFMcdP1nPngnCpV2TnXNyFxCKFwtkTpTcoQvdj5oAaRpcr3RLVd0nwrmj 39 | 8g6Ys8XgTBwziQ2WcEThvp4r5t+CjAlT9n2kIqtBOx3/Q9v3eN+FVA1kdEmWJPig 40 | WJE4dRtxMgHk07Kog4Z5tyvoag8D3gPANNzGF82oIGUyJfD8m0tm9lOBmdgxuJjj 41 | jdVJ7gQSmyszBvJ6vYOOt1rHGfUsCHS01bjO5bgjzProI0k4Rm+8DCVrO2PKnGGx 42 | WgFWkdNhDPzvjx+BD3xTNGDhxlY0ikkNMxs62UeZUp+V0iYSCb6DzrEV/1n6G7yA 43 | Ky4JV+em9M+5mg== 44 | -----END CERTIFICATE----- 45 | -------------------------------------------------------------------------------- /tests/assets/invalid_cn_rsa2048_cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIHtjCCBZ6gAwIBAgITbQB6PP8ap5kdHJfdbAAAAHo8/zANBgkqhkiG9w0BAQsF 3 | ADBbMRMwEQYKCZImiZPyLGQBGRYDY29tMRYwFAYKCZImiZPyLGQBGRYGdmVuYWZp 4 | MRUwEwYKCZImiZPyLGQBGRYFdmVucWExFTATBgNVBAMTDFFBIFZlbmFmaSBDQTAe 5 | Fw0xOTExMjYxMjU2MzRaFw0yNzExMjQxMjU2MzRaMIGCMQswCQYDVQQGEwJVUzEN 6 | MAsGA1UECBMEVXRhaDESMBAGA1UEBxMJU2FsdCBMYWtlMRQwEgYDVQQKEwtWZW5h 7 | ZmkgSW5jLjEVMBMGA1UECxMMSW50ZWdyYXRpb25zMSMwIQYDVQQDExp0ZXN0MTIz 8 | LnZlbmFmaS5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC 9 | ggEBANvK+i5BjzuqiOOWdoF2FKn+s1ZatlSKSlUOzrN/qvk/6zmTPF7gQbzZDFk+ 10 | tAo/3RkrFgy2plYBA8o92uHdUBlw8xel076Ku6wVu5G942Ip4FUfacXGIgWU7dqO 11 | U1rq6XUFU+Gpr93v6VoDiY1lGav4htdtDryfSz4UX40jVgnbTMP+FxLQiV6xSYUo 12 | tYpKWvV9PWshmao3q1mJUMTtOpijhFqk9YF/xUPCSLHym87u7eOD7AEUn2KRDve8 13 | UCBYY2znCwiQrRefmUhDaJeqOy6akrO5A+hUzT2Ue7j6LPQ1cdc9nBEmelFojFXo 14 | RV42zJPSHrtENsBg0EdViw/4VjECAwEAAaOCA0kwggNFMB0GA1UdDgQWBBTOX1Gn 15 | 0XFCQ/cm4ZhV5puMrxEPCDAfBgNVHSMEGDAWgBQ8rJymDaEw1FanPXi8IxvstHtN 16 | dTCCASIGA1UdHwSCARkwggEVMIIBEaCCAQ2gggEJhkJodHRwOi8vcWF2ZW5hZmlj 17 | YS52ZW5xYS52ZW5hZmkuY29tL0NlcnRFbnJvbGwvUUElMjBWZW5hZmklMjBDQS5j 18 | cmyGgcJsZGFwOi8vL0NOPVFBJTIwVmVuYWZpJTIwQ0EsQ049cWF2ZW5hZmljYSxD 19 | Tj1DRFAsQ049UHVibGljJTIwS2V5JTIwU2VydmljZXMsQ049U2VydmljZXMsQ049 20 | Q29uZmlndXJhdGlvbixEQz12ZW5xYSxEQz12ZW5hZmksREM9Y29tP2NlcnRpZmlj 21 | YXRlUmV2b2NhdGlvbkxpc3Q/YmFzZT9vYmplY3RDbGFzcz1jUkxEaXN0cmlidXRp 22 | b25Qb2ludDCCATgGCCsGAQUFBwEBBIIBKjCCASYwagYIKwYBBQUHMAKGXmh0dHA6 23 | Ly9xYXZlbmFmaWNhLnZlbnFhLnZlbmFmaS5jb20vQ2VydEVucm9sbC9xYXZlbmFm 24 | aWNhLnZlbnFhLnZlbmFmaS5jb21fUUElMjBWZW5hZmklMjBDQS5jcnQwgbcGCCsG 25 | AQUFBzAChoGqbGRhcDovLy9DTj1RQSUyMFZlbmFmaSUyMENBLENOPUFJQSxDTj1Q 26 | dWJsaWMlMjBLZXklMjBTZXJ2aWNlcyxDTj1TZXJ2aWNlcyxDTj1Db25maWd1cmF0 27 | aW9uLERDPXZlbnFhLERDPXZlbmFmaSxEQz1jb20/Y0FDZXJ0aWZpY2F0ZT9iYXNl 28 | P29iamVjdENsYXNzPWNlcnRpZmljYXRpb25BdXRob3JpdHkwCwYDVR0PBAQDAgWg 29 | MDsGCSsGAQQBgjcVBwQuMCwGJCsGAQQBgjcVCIGPiXKEhLBq9Z0Qh5b/fMSJFWCE 30 | z5pjhfuuQwIBZAIBBDATBgNVHSUEDDAKBggrBgEFBQcDATAbBgkrBgEEAYI3FQoE 31 | DjAMMAoGCCsGAQUFBwMBMCUGA1UdEQQeMByCGnRlc3QxMjMudmVuYWZpLmV4YW1w 32 | bGUuY29tMA0GCSqGSIb3DQEBCwUAA4ICAQCRZFKkhCUTbrM+raD40bNuASrdCViV 33 | Ot9U18PD65IaZSN9l3EQvOjCQRstp9oH4APO7gClb/XEGoGuHUKO8QZPFm9i4SXB 34 | OrCso9dN94x2mvmcyZ/jBEm3Fsfhsm1gZqG41WZVTeUxVOTX4NEep4D/OQbPLs9k 35 | TUXO8mzvh+mFZrA4hkicieIuetGyoNmIZtdC2QgIdjyje7OwUoBQebDcFDVUsqPq 36 | abBNOzJXr+QX8cFeh/tUScHHDbk9vatBEMvZ4pbINKUI2fHEaz82ZzXfhuofagkr 37 | 4CDazfEVXv8EW/Dr+f9fDwrgvc1zZ+nxaQWiKgJYe2vija8Asn6LK1RvauV0i1bH 38 | YegPAUOBhLx6IgM5PmSSoQ+nYokgB28bQ0d/4tvvkBp8vjSbrHxgOesbhJFQALfc 39 | ez/y7ipoUYyECYUn6EAaIVgRDXIdCFrUj0BPYwBzEKcC5qbNo+Ef+ppASTPkaf1V 40 | VL2KgVlXz6Wfk/4SMbBfbn+ZczIaMhPwRWfTulDOEJFSmZ2OqED69/fv64ue5MoB 41 | zoxnz53OQ23Ect2VjjyNjMTrFYM+Kwy+zj6CJn2icsJ/p++GUJ3InWZB74+23Ool 42 | 154qJ8a7X2vVzDXexBMrQaxe0fjhi6+eEKBlcHoIM1YPO0RqsnqIMHm7gx71LPz9 43 | UC6vVjMVQ5/0tg== 44 | -----END CERTIFICATE----- 45 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | pep8: 2 | pycodestyle --first ./library/venafi_certificate.py 3 | 4 | yamllint: 5 | yamllint `git ls-files *.yml | grep -v ISSUE_TEMPLATE` 6 | 7 | lint: yamllint pep8 8 | ansible-lint -x 106,204,504 ./tasks/* 9 | ansible-lint ./meta/* 10 | ansible-lint ./defaults/* 11 | 12 | ansible-molecule: 13 | docker build ./tests --tag local-ansible-test 14 | ANSIBLE_VAULT_PASSWORD_FILE=${PWD}/vault-password.txt molecule converge 15 | 16 | #Testing ansible crypto modules for examples and compability checks 17 | test-crypto-playbook: 18 | ansible-playbook -i tests/inventory tests/original-ansible-crypto-playbook-example.yml 19 | 20 | #test Ansible playbook with venafi certificate module 21 | 22 | test-vcert-playbook-tpp: 23 | # #have to copy library to test our module, otherwise test playbook will not 24 | docker build ./tests --tag local-ansible-test 25 | rm -rvf tests/library 26 | cp -rv library tests/ 27 | ansible-playbook -i tests/inventory tests/venafi-playbook-example.yml \ 28 | --vault-password-file vault-password.txt \ 29 | --extra-vars "credentials_file=../tpp_credentials.yml docker_demo=true" 30 | 31 | #test Ansible role with venafi_Certificate module 32 | test-vcert-role-tpp: 33 | # #have to copy library to test our module, otherwise test playbook will not 34 | docker build ./tests --tag local-ansible-test 35 | rm -rvf tests/library 36 | cp -rv library tests/ 37 | ansible-playbook -i tests/inventory tests/venafi-role-playbook-example.yml \ 38 | --vault-password-file vault-password.txt \ 39 | --extra-vars "credentials_file=tpp_credentials.yml docker_demo=true" 40 | 41 | test-vcert-role-cloud: 42 | # #have to copy library to test our module, otherwise test playbook will not 43 | docker build ./tests --tag local-ansible-test 44 | rm -rvf tests/library 45 | cp -rv library tests/ 46 | ansible-playbook -i tests/inventory tests/venafi-role-playbook-example.yml \ 47 | --vault-password-file vault-password.txt \ 48 | --extra-vars "credentials_file=cloud_credentials.yml docker_demo=true" 49 | 50 | test-vcert-role-fake: 51 | # #have to copy library to test our module, otherwise test playbook will not 52 | docker build ./tests --tag local-ansible-test 53 | rm -rvf tests/library 54 | cp -rv library tests/ 55 | ansible-playbook -i tests/inventory tests/venafi-role-playbook-example.yml \ 56 | --vault-password-file vault-password.txt \ 57 | --extra-vars "credentials_file=fake_credentials.yml docker_demo=true" 58 | 59 | #test module with python using json for args 60 | test-python-module: test-python-module-fake test-python-module-tpp test-python-module-cloud 61 | 62 | test-python-module-tpp: 63 | python3 library/venafi_certificate.py venafi_certificate_tpp.json 64 | 65 | test-python-module-fake: 66 | python3 ./library/venafi_certificate.py venafi_certificate_fake.json 67 | 68 | test-python-module-cloud: 69 | python3 ./library/venafi_certificate.py venafi_certificate_cloud.json 70 | 71 | unit-test: 72 | rm -rvf tests/library 73 | cp -rv library tests/ 74 | PYTHONPATH=./:$PYTHONPATH pytest tests/test_venafi_certificate.py 75 | -------------------------------------------------------------------------------- /tasks/local-certificate.yml: -------------------------------------------------------------------------------- 1 | # Locally generate certificate 2 | # Copy files to remote host from inventory 3 | # Generates certificates on remote host 4 | # TODO: maybe rewrite to delegate to be able to register certificate from any host? 5 | --- 6 | - name: "Create directory {{ certificate_cert_dir }}" 7 | file: 8 | path: "{{ certificate_cert_dir }}" 9 | state: directory 10 | mode: 0755 11 | delegate_to: localhost 12 | 13 | - name: "Enroll Venafi certificate on local host" 14 | venafi_certificate: 15 | url: "{{ venafi.url | default(omit) }}" 16 | token: "{{ venafi.token | default(omit) }}" 17 | zone: "{{ venafi.zone | default(omit) }}" 18 | test_mode: "{{ venafi.test_mode if venafi.test_mode is defined else 'false' }}" 19 | user: "{{ venafi.user | default(omit) }}" 20 | password: "{{ venafi.password | default(omit) }}" 21 | access_token: "{{ venafi.access_token | default(omit) }}" 22 | trust_bundle: "{{ venafi.trust_bundle | default(omit) }}" 23 | cert_path: "{{ certificate_cert_path }}" 24 | chain_path: "{{ certificate_chain_path | default(omit) }}" 25 | privatekey_path: "{{ certificate_privatekey_path | default(omit) }}" 26 | privatekey_type: "{{ certificate_privatekey_type | default(omit) }}" 27 | privatekey_size: "{{ certificate_privatekey_size | default(omit) }}" 28 | privatekey_curve: "{{ certificate_privatekey_curve | default(omit) }}" 29 | common_name: "{{ certificate_common_name }}" 30 | alt_name: "{{ certificate_alt_name | default([]) }}" 31 | before_expired_hours: "{{ certificate_before_expired_hours if certificate_before_expired_hours is defined else 72 }}" 32 | force: "{{ certificate_force if certificate_force is defined else false }}" 33 | delegate_to: localhost 34 | register: certout 35 | - name: "dump test output" 36 | debug: 37 | msg: "{{ certout }}" 38 | 39 | - name: "Copy Venafi certificate file to remote location {{ certificate_remote_cert_path if certificate_remote_cert_path is defined else certificate_cert_path }}" 40 | copy: 41 | src: "{{ certificate_cert_path }}" 42 | dest: "{{ certificate_remote_cert_path if certificate_remote_cert_path is defined else certificate_cert_path }}" 43 | mode: 0644 44 | 45 | - name: "Copy Venafi private key file to remote location {{ certificate_remote_privatekey_path if certificate_remote_privatekey_path else certificate_privatekey_path }}" 46 | copy: 47 | src: "{{ certificate_privatekey_path }}" 48 | dest: "{{ certificate_remote_privatekey_path if certificate_remote_privatekey_path else certificate_privatekey_path }}" 49 | mode: 0600 50 | when: certificate_copy_private_key_to_remote 51 | 52 | - name: "Copy Venafi certificate chain file to remote location {{ certificate_remote_chain_path if certificate_remote_chain_path else certificate_chain_path }}" 53 | copy: 54 | src: "{{ certificate_chain_path }}" 55 | dest: "{{ certificate_remote_chain_path if certificate_remote_chain_path else certificate_chain_path }}" 56 | mode: 0644 57 | when: certificate_chain_path is defined 58 | -------------------------------------------------------------------------------- /tests/assets/valid_alt_rsa2048_cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIIDzCCBfegAwIBAgITbQB6PP3/whfpBt6RngAAAHo8/TANBgkqhkiG9w0BAQsF 3 | ADBbMRMwEQYKCZImiZPyLGQBGRYDY29tMRYwFAYKCZImiZPyLGQBGRYGdmVuYWZp 4 | MRUwEwYKCZImiZPyLGQBGRYFdmVucWExFTATBgNVBAMTDFFBIFZlbmFmaSBDQTAe 5 | Fw0xOTExMjYxMjU0NDNaFw0yNzExMjQxMjU0NDNaMIGCMQswCQYDVQQGEwJVUzEN 6 | MAsGA1UECBMEVXRhaDESMBAGA1UEBxMJU2FsdCBMYWtlMRQwEgYDVQQKEwtWZW5h 7 | ZmkgSW5jLjEVMBMGA1UECxMMSW50ZWdyYXRpb25zMSMwIQYDVQQDExp0ZXN0MTIz 8 | LnZlbmFmaS5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC 9 | ggEBAMMMEVjABzOfKCn3aSpmQzjihFdgWOgKcrkBothKUl9BK1yY9mwRnUN6KtuR 10 | qZu6gFB948W8avBTpfuO4lavRi0LWIovQ9eEK15Ok3oQZEewxMjzDtus73M+5Dd7 11 | R8SHbC9SbGbaOv72tfq7pHBRsTQVJwEm6j8CvRmA8n/RqToJVXsMcX45g6MZafiy 12 | M0J0+cDtFwdNJNrDQuLJr74efEy40tFcjJiCaYis+ZdUEZO+1+mlwOzJL+cLg8+r 13 | Q6zLAjjl4nQFHqUmqzY2HgOfyt0PmK/zN9GW3wyKd1j3GUNsS1XUu1S8U8tmd1qb 14 | QYrp36/S/03m+vyCSTOfeNo2clECAwEAAaOCA6IwggOeMH4GA1UdEQR3MHWBDWVl 15 | QHZlbmFmaS5jb22BDmVlMkB2ZW5hZmkuY29tghZ3d3cudmVuYWZpLmV4YW1wbGUu 16 | Y29tghRtLnZlbmFmaS5leGFtcGxlLmNvbYIadGVzdDEyMy52ZW5hZmkuZXhhbXBs 17 | ZS5jb22HBMCoAQGHBMCoAgIwHQYDVR0OBBYEFIQvRi2dz7DrUDnzYJKGSQvsHNMB 18 | MB8GA1UdIwQYMBaAFDysnKYNoTDUVqc9eLwjG+y0e011MIIBIgYDVR0fBIIBGTCC 19 | ARUwggERoIIBDaCCAQmGQmh0dHA6Ly9xYXZlbmFmaWNhLnZlbnFhLnZlbmFmaS5j 20 | b20vQ2VydEVucm9sbC9RQSUyMFZlbmFmaSUyMENBLmNybIaBwmxkYXA6Ly8vQ049 21 | UUElMjBWZW5hZmklMjBDQSxDTj1xYXZlbmFmaWNhLENOPUNEUCxDTj1QdWJsaWMl 22 | MjBLZXklMjBTZXJ2aWNlcyxDTj1TZXJ2aWNlcyxDTj1Db25maWd1cmF0aW9uLERD 23 | PXZlbnFhLERDPXZlbmFmaSxEQz1jb20/Y2VydGlmaWNhdGVSZXZvY2F0aW9uTGlz 24 | dD9iYXNlP29iamVjdENsYXNzPWNSTERpc3RyaWJ1dGlvblBvaW50MIIBOAYIKwYB 25 | BQUHAQEEggEqMIIBJjBqBggrBgEFBQcwAoZeaHR0cDovL3FhdmVuYWZpY2EudmVu 26 | cWEudmVuYWZpLmNvbS9DZXJ0RW5yb2xsL3FhdmVuYWZpY2EudmVucWEudmVuYWZp 27 | LmNvbV9RQSUyMFZlbmFmaSUyMENBLmNydDCBtwYIKwYBBQUHMAKGgapsZGFwOi8v 28 | L0NOPVFBJTIwVmVuYWZpJTIwQ0EsQ049QUlBLENOPVB1YmxpYyUyMEtleSUyMFNl 29 | cnZpY2VzLENOPVNlcnZpY2VzLENOPUNvbmZpZ3VyYXRpb24sREM9dmVucWEsREM9 30 | dmVuYWZpLERDPWNvbT9jQUNlcnRpZmljYXRlP2Jhc2U/b2JqZWN0Q2xhc3M9Y2Vy 31 | dGlmaWNhdGlvbkF1dGhvcml0eTALBgNVHQ8EBAMCBaAwOwYJKwYBBAGCNxUHBC4w 32 | LAYkKwYBBAGCNxUIgY+JcoSEsGr1nRCHlv98xIkVYITPmmOF+65DAgFkAgEEMBMG 33 | A1UdJQQMMAoGCCsGAQUFBwMBMBsGCSsGAQQBgjcVCgQOMAwwCgYIKwYBBQUHAwEw 34 | DQYJKoZIhvcNAQELBQADggIBABq6Jp+bURVxNqIQ3TikvxCut3Hj6D0LrDolU/c5 35 | SVz21I0gONdvJiwqSQmYVkiVHIvFvbaWBEhoJa5UuUY12wcTT7IE+VFzM6w6lyud 36 | PPy14q9PX6RRNUzzvRRlW0TTsB6OyRHF+TqlNuxiCcLub3ZPaDlQGaa5lA1o9t0g 37 | CNOEbiYicSCKYADNal03uH/j3GG1Uwc7Y44vage616sJdreogT7m1XR0aJvwFzVo 38 | ftZ1nu7qcPGhXEvQ4T3g8tBabAYS+EoNBVb738D8togNPB8MUCiyZ5GL5fFITpLL 39 | qDKZW2yVoFz0fVqjy6cdxw6N46nCaIkrLbOs5KenlWo95RIZNCQYqbw/ScVmZCQB 40 | LWpHdVrIq44O80mNLF6GZmLVTqhOaNKhxelx15+6WiBJJThhHO5i5DM5ipfH3Sbc 41 | 0sijw5fHdEqQzfK8X00u4XE5jTqoTFJN1hq5YFKcrxOB9vcpB49aQeCe1Uel9Tog 42 | 1nPCfKfKu6jqyciT8kB7CdT/gSyr5DeQ2BCV/X5WG+7cCYktP19F3LbS61JhwDkR 43 | RYWy1JMWopKvJzDvfJ1jsBEzwJAnz1eHTJMuHcSlxUemQm+DDAHV8Bz4z5sye5m4 44 | qXHfNeNyU3Ldus8YTeayNZIPf5ChdPZ5l5Pr5WDzs4QW8yHq9480dLWH3mMHNJku 45 | dbrs 46 | -----END CERTIFICATE----- 47 | -------------------------------------------------------------------------------- /examples/f5_bigip/f5_create_playbook.yaml: -------------------------------------------------------------------------------- 1 | - name: Create F5 Application 2 | hosts: localhost 3 | connection: local 4 | 5 | vars_files: 6 | - variables.yaml 7 | 8 | roles: 9 | - role: venafi.ansible_role_venafi 10 | 11 | certificate_common_name: "{{ test_site.name }}.{{ test_site.domain }}" 12 | certificate_alt_name: "DNS:{{ test_site.name }}.{{ test_site.domain }}" 13 | certificate_privatekey_type: "RSA" 14 | certificate_privatekey_size: "2048" 15 | certificate_chain_option: "last" 16 | 17 | certificate_cert_dir: "./tmp" 18 | certificate_cert_path: "./tmp/{{ cert_name }}" 19 | certificate_chain_path: "./tmp/{{ chain_name }}" 20 | certificate_privatekey_path: "./tmp/{{ key_name }}" 21 | certificate_copy_private_key_to_remote: false 22 | 23 | certificate_remote_execution: false 24 | certificate_remote_privatekey_path: "./tmp/{{ key_name }}.remote" 25 | certificate_remote_cert_path: "./tmp/{{ cert_name }}.remote" 26 | certificate_remote_chain_path: "./tmp/{{ chain_name }}.remote" 27 | 28 | tasks: 29 | - name: Create Private Key on F5 BIG-IP {{ f5_address }} 30 | bigip_ssl_key: 31 | state: present 32 | provider: "{{ f5_provider }}" 33 | name: "{{ key_name }}" 34 | partition: "{{ f5_partition }}" 35 | content: "{{ lookup('file', './tmp/' + key_name) }}" 36 | delegate_to: localhost 37 | 38 | - name: Create Certificate on F5 BIG-IP {{ f5_address }} 39 | bigip_ssl_certificate: 40 | state: present 41 | provider: "{{ f5_provider }}" 42 | name: "{{ cert_name }}" 43 | partition: "{{ f5_partition }}" 44 | content: "{{ lookup('file', './tmp/' + cert_name + '.remote') }}" 45 | delegate_to: localhost 46 | 47 | - name: Create CA Bundle on F5 BIG-IP {{ f5_address }} 48 | bigip_ssl_certificate: 49 | state: present 50 | provider: "{{ f5_provider }}" 51 | name: "{{ chain_name }}" 52 | partition: "{{ f5_partition }}" 53 | content: "{{ lookup('file', './tmp/' + chain_name + '.remote') }}" 54 | delegate_to: localhost 55 | 56 | - name: Create Client SSL Profile on F5 BIG-IP {{ f5_address }} 57 | bigip_profile_client_ssl: 58 | state: present 59 | provider: "{{ f5_provider }}" 60 | name: "clientssl_{{ test_site.name }}" 61 | partition: "{{ f5_partition }}" 62 | parent: "clientssl" 63 | cert_key_chain: 64 | - cert: "{{ cert_name }}" 65 | key: "{{ key_name }}" 66 | chain: "{{ chain_name }}" 67 | delegate_to: localhost 68 | 69 | - name: Create Pool on F5 BIG-IP {{ f5_address }} 70 | bigip_pool: 71 | state: present 72 | provider: "{{ f5_provider }}" 73 | name: "pool_{{ test_site.name }}" 74 | partition: "{{ f5_partition }}" 75 | lb_method: round-robin 76 | delegate_to: localhost 77 | 78 | - name: Add Pool Members on F5 BIG-IP {{ f5_address }} 79 | bigip_pool_member: 80 | state: present 81 | provider: "{{ f5_provider }}" 82 | partition: "{{ f5_partition }}" 83 | host: "{{ item.host }}" 84 | port: "{{ item.port }}" 85 | pool: "pool_{{ test_site.name }}" 86 | with_items: "{{ f5_pool_members }}" 87 | delegate_to: localhost 88 | 89 | - name: Create Virtual Server on F5 BIG-IP {{ f5_address }} 90 | bigip_virtual_server: 91 | state: present 92 | provider: "{{ f5_provider }}" 93 | name: "vs_{{ test_site.name }}" 94 | partition: "{{ f5_partition }}" 95 | description: "Provisioned by Ansible" 96 | destination: "{{ f5_virtual_ip }}" 97 | port: "{{ f5_virtual_port }}" 98 | snat: Automap 99 | pool: "pool_{{ test_site.name }}" 100 | profiles: 101 | - "clientssl_{{ test_site.name }}" 102 | delegate_to: localhost 103 | -------------------------------------------------------------------------------- /tests/test_venafi_certificate.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import shutil 3 | import os 4 | from collections import namedtuple, defaultdict 5 | from library.venafi_certificate import VCertificate 6 | 7 | CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) 8 | 9 | 10 | testAsset = namedtuple("testAssert", "is_valid cert chain private_key password common_name alt_name id") 11 | 12 | CERT_PATH = "/tmp/cert.pem" 13 | CHAIN_PATH = "/tmp/chain.pem" 14 | PRIV_PATH = "/tmp/priv.pem" 15 | 16 | class Fail(Exception): 17 | pass 18 | 19 | class FakeModule(object): 20 | def __init__(self, asset): 21 | self.fail_code = None 22 | self.exit_code = None 23 | self.warn = str 24 | self.params = defaultdict(lambda: None) 25 | self.params["cert_path"] = CERT_PATH 26 | self.params["chain_path"] = CHAIN_PATH 27 | self.params["privatekey_path"] = PRIV_PATH 28 | self.params["common_name"] = asset.common_name 29 | self.params["before_expired_hours"] = 72 30 | if asset.alt_name: 31 | self.params["alt_name"] = [x.strip() for x in asset.alt_name.split(',')] 32 | self.params["test_mode"] = True 33 | 34 | def exit_json(self, **kwargs): 35 | self.exit_code = kwargs 36 | 37 | def fail_json(self, **kwargs): 38 | self.fail_code = kwargs 39 | raise Fail(self.fail_code['msg']) 40 | 41 | 42 | class TestVcertificate(unittest.TestCase): 43 | def test_validate(self): 44 | for asset in TEST_ASSETS: 45 | print("testing asset id %s" % asset.id) 46 | create_testfiles(asset) 47 | module = FakeModule(asset) 48 | vcert = VCertificate(module) 49 | if asset.is_valid: 50 | vcert.validate() 51 | self.assertIsNone(module.fail_code) 52 | else: 53 | self.assertRaises(Fail, vcert.validate) 54 | 55 | 56 | def create_testfiles(asset): 57 | """ 58 | :param testAsset asset: 59 | """ 60 | for p, v in ((CERT_PATH, asset.cert), (CHAIN_PATH, asset.chain), (PRIV_PATH, asset.private_key)): 61 | 62 | shutil.copy(CURRENT_DIR + "/assets/" + v, p) 63 | 64 | 65 | TEST_ASSETS = [ 66 | # TODO check error message, not just valid\invalid 67 | #simple valid 68 | testAsset(is_valid=True, cert="valid_rsa2048_cert.pem", chain="valid_rsa2048_chain.pem", 69 | private_key="valid_rsa2048_key.pem", password=None, common_name="test111.venafi.example.com", 70 | alt_name=None,id=1), 71 | #another cn 72 | testAsset(is_valid=False, cert="valid_rsa2048_cert.pem", chain="valid_rsa2048_chain.pem", 73 | private_key="valid_rsa2048_key.pem", password=None, common_name="test1111.venafi.example.com", alt_name=None,id=2), 74 | #corrupted file 75 | testAsset(is_valid=False, cert="invalid_cert.pem", chain="valid_rsa2048_chain.pem", 76 | private_key="valid_rsa2048_key.pem", password=None, common_name="test111.venafi.example.com", alt_name=None,id=3), 77 | #unmactched cn 78 | testAsset(is_valid=False, cert="invalid_cn_rsa2048_cert.pem", chain="valid_rsa2048_chain.pem", 79 | private_key="valid_rsa2048_key.pem", password=None, common_name="test111.venafi.example.com", alt_name=None,id=4), 80 | # unmatched key type 81 | testAsset(is_valid=False, cert="valid_rsa2048_cert.pem", chain="valid_rsa2048_chain.pem", 82 | private_key="valid_ec_key.pem", password=None, common_name="test1111.venafi.example.com", alt_name=None,id=5), 83 | #valid with dns 84 | testAsset(is_valid=True, cert="valid_alt_rsa2048_cert.pem", chain="valid_rsa2048_chain.pem", 85 | private_key="valid_alt_rsa2048_key.pem", password=None, common_name="test123.venafi.example.com", 86 | alt_name="IP:192.168.1.1,DNS:www.venafi.example.com,DNS:m.venafi.example.com,email:e@venafi.com," 87 | "email:e2@venafi.com,IP Address:192.168.2.2",id=6), 88 | #invalid with dns 89 | testAsset(is_valid=False, cert="valid_alt_rsa2048_cert.pem", chain="valid_rsa2048_chain.pem", 90 | private_key="valid_alt_rsa2048_key.pem", password=None, common_name="test123.venafi.example.com", 91 | alt_name="IP:192.168.1.1,DNS:www.venafi.example.com,DNS:m.venafi.example.com,email:e@venafi.com," 92 | "email:e2@venafi.com",id=7), 93 | #expired 94 | testAsset(is_valid=False, cert="invalid_date_rsa2048_cert.pem", chain="valid_rsa2048_chain.pem", 95 | private_key="valid_rsa2048_key.pem", password=None, common_name="test123.venafi.example.com", 96 | alt_name=None,id=8) 97 | ] 98 | 99 | -------------------------------------------------------------------------------- /tests/original-ansible-crypto-playbook-example.yml: -------------------------------------------------------------------------------- 1 | # Clone https://github.com/chrismeyersfsu/provision_docker.git to /etc/ansible/roles before run 2 | --- 3 | - name: "Bring up Docker containers for Docker connection inventory" 4 | hosts: localhost 5 | roles: 6 | - role: provision_docker 7 | provision_docker_privileged: true, 8 | provision_docker_inventory_group: "{{ groups['robots'] }}" 9 | provision_docker_use_docker_connection: true 10 | 11 | - hosts: robots 12 | vars: 13 | ca: 14 | local_dir: /tmp/ansible-ca 15 | remote_dir: /etc/ssl 16 | cert_file: ca.pem 17 | key_file: ca.key 18 | csr_file: ca.csr 19 | country_name: US 20 | organization_name: Example 21 | email_address: ca@example.com 22 | common_name: ca 23 | cert: 24 | dir: /etc/ssl 25 | cert_file: example.com.pem 26 | key_file: example.com.key 27 | csr_file: example.com.csr 28 | country_name: US 29 | organization_name: Example 30 | email_address: ca@example.com 31 | common_name: cert.example.com 32 | 33 | tasks: 34 | - name: "Say hello to my new containers" 35 | ping: 36 | 37 | - name: "Create directories for local CA files" 38 | local_action: 39 | module: file 40 | path: "{{ ca.local_dir }}" 41 | state: directory 42 | mode: 0755 43 | 44 | - name: "Generate an OpenSSL private key with the default values (4096 bits, RSA) for local CA" 45 | local_action: 46 | module: openssl_privatekey 47 | path: "{{ ca.local_dir }}/{{ ca.key_file }}" 48 | 49 | - name: "Generate an OpenSSL Certificate Signing Request with Subject information for local CA" 50 | local_action: 51 | module: openssl_csr 52 | path: "{{ ca.local_dir }}/{{ ca.csr_file }}" 53 | privatekey_path: "{{ ca.local_dir }}/{{ ca.key_file }}" 54 | country_name: "{{ ca.country_name }}" 55 | organization_name: "{{ ca.organization_name }}" 56 | email_address: "{{ ca.email_address }}" 57 | common_name: "{{ ca.common_name }}" 58 | basic_constraints: "CA:true" 59 | 60 | - name: "Generate a Self Signed local CA certificate" 61 | local_action: 62 | module: openssl_certificate 63 | path: "{{ ca.local_dir }}/{{ ca.cert_file }}" 64 | privatekey_path: "{{ ca.local_dir }}/{{ ca.key_file }}" 65 | csr_path: "{{ ca.local_dir }}/{{ ca.csr_file }}" 66 | provider: selfsigned 67 | 68 | - name: "Copy CA file to remote location" 69 | copy: 70 | src: "{{ ca.local_dir }}/{{ item.file }}" 71 | dest: "{{ cert.dir }}" 72 | with_items: 73 | - { file: "{{ ca.cert_file }}" } 74 | - { file: "{{ ca.key_file }}" } 75 | 76 | - name: "Install required pip packages" 77 | pip: 78 | name: 79 | - pyOpenSSL 80 | 81 | - name: "Create directories" 82 | file: 83 | path: "{{ cert.dir }}/{{ item.dir }}" 84 | state: directory 85 | mode: 0755 86 | with_items: 87 | - { dir: 'csr' } 88 | - { dir: 'crt' } 89 | - { dir: 'private' } 90 | 91 | 92 | # Testing original crypto modules 93 | 94 | - name: "Generate an OpenSSL private key with the default values (4096 bits, RSA)" 95 | openssl_privatekey: 96 | path: "{{ cert.dir }}/private/{{ cert.key_file }}" 97 | 98 | - name: "Generate an OpenSSL Certificate Signing Request with Subject information" 99 | openssl_csr: 100 | path: "{{ cert.dir }}/csr/{{ cert.csr_file }}" 101 | privatekey_path: "{{ cert.dir }}/private/{{ cert.key_file }}" 102 | country_name: "{{ ca.country_name }}" 103 | organization_name: "{{ ca.organization_name }}" 104 | email_address: "{{ cert.email_address }}" 105 | common_name: "{{ cert.common_name }}" 106 | 107 | - name: "Sign certificate with local CA" 108 | openssl_certificate: 109 | path: "{{ cert.dir }}/crt/{{ cert.cert_file }}" 110 | privatekey_path: "{{ cert.dir }}/private/{{ cert.key_file }}" 111 | csr_path: "{{ cert.dir }}/csr/{{ cert.csr_file }}" 112 | ownca_path: "{{ cert.dir }}/{{ ca.cert_file }}" 113 | ownca_privatekey_path: "{{ cert.dir }}/{{ ca.key_file }}" 114 | provider: ownca 115 | 116 | - name: "Verify certificate" 117 | openssl_certificate: 118 | path: "{{ cert.dir }}/crt/{{ cert.cert_file }}" 119 | privatekey_path: "{{ cert.dir }}/private/{{ cert.key_file }}" 120 | issuer: 121 | CN: ca 122 | has_expired: false 123 | provider: assertonly 124 | -------------------------------------------------------------------------------- /examples/citrix_adc/citrix_delete_playbook.yaml: -------------------------------------------------------------------------------- 1 | - name: Remove Citrix ADC Application 2 | hosts: localhost 3 | connection: local 4 | collections: citrix.adc 5 | 6 | vars_files: 7 | - variables.yaml 8 | 9 | tasks: 10 | - name: Remove ssl binding from Citrix ADC {{ adc_address }} 11 | citrix_adc_nitro_resource: 12 | nsip: "{{ adc_address }}" 13 | nitro_user: "{{ adc_username }}" 14 | nitro_pass: "{{ adc_password }}" 15 | nitro_protocol: http 16 | validate_certs: false 17 | state: absent 18 | workflow: 19 | lifecycle: binding 20 | endpoint: sslvserver_sslcertkey_binding 21 | bound_resource_missing_errorcode: 461 22 | primary_id_attribute: vservername 23 | delete_id_attributes: 24 | - certkeyname 25 | - crlcheck 26 | - ocspcheck 27 | - ca 28 | - snicert 29 | resource: 30 | vservername: "vs-{{ test_site.name }}.{{ test_site.domain }}" 31 | certkeyname: "{{ test_site.name }}.{{ test_site.domain }}_certkey" 32 | snicert: true 33 | delegate_to: localhost 34 | 35 | - name: Remove lb vserver from Citrix ADC {{ adc_address }} 36 | citrix_adc_service: 37 | nsip: "{{ adc_address }}" 38 | nitro_user: "{{ adc_username }}" 39 | nitro_pass: "{{ adc_password }}" 40 | nitro_protocol: http 41 | validate_certs: false 42 | state: absent 43 | name: "vs-{{ test_site.name }}.{{ test_site.domain }}" 44 | delegate_to: localhost 45 | 46 | - name: Remove service-http-1 from Citrix ADC {{ adc_address }} 47 | citrix_adc_service: 48 | nsip: "{{ adc_address }}" 49 | nitro_user: "{{ adc_username }}" 50 | nitro_pass: "{{ adc_password }}" 51 | nitro_protocol: http 52 | validate_certs: false 53 | state: absent 54 | name: service-http-1 55 | delegate_to: localhost 56 | 57 | - name: Remove service-http-2 from Citrix ADC {{ adc_address }} 58 | citrix_adc_service: 59 | nsip: "{{ adc_address }}" 60 | nitro_user: "{{ adc_username }}" 61 | nitro_pass: "{{ adc_password }}" 62 | nitro_protocol: http 63 | validate_certs: false 64 | state: absent 65 | name: service-http-2 66 | delegate_to: localhost 67 | 68 | - name: Remove service-http-3 from Citrix ADC {{ adc_address }} 69 | citrix_adc_service: 70 | nsip: "{{ adc_address }}" 71 | nitro_user: "{{ adc_username }}" 72 | nitro_pass: "{{ adc_password }}" 73 | nitro_protocol: http 74 | validate_certs: false 75 | state: absent 76 | name: service-http-3 77 | delegate_to: localhost 78 | 79 | - name: Remove Certkey from Citrix ADC {{ adc_address }} 80 | citrix_adc_ssl_certkey: 81 | nsip: "{{ adc_address }}" 82 | nitro_user: "{{ adc_username }}" 83 | nitro_pass: "{{ adc_password }}" 84 | nitro_protocol: http 85 | validate_certs: false 86 | state: absent 87 | certkey: "{{ test_site.name }}.{{ test_site.domain }}_certkey" 88 | delegate_to: localhost 89 | 90 | - name: Remove Private Key from Citrix ADC {{ adc_address }} 91 | citrix_adc_system_file: 92 | nsip: "{{ adc_address }}" 93 | nitro_user: "{{ adc_username }}" 94 | nitro_pass: "{{ adc_password }}" 95 | nitro_protocol: http 96 | validate_certs: false 97 | state: absent 98 | filelocation: "/nsconfig/ssl" 99 | filename: "{{ key_name }}" 100 | delegate_to: localhost 101 | 102 | - name: Remove Certificate from Citrix ADC {{ adc_address }} 103 | citrix_adc_system_file: 104 | nsip: "{{ adc_address }}" 105 | nitro_user: "{{ adc_username }}" 106 | nitro_pass: "{{ adc_password }}" 107 | nitro_protocol: http 108 | validate_certs: false 109 | state: absent 110 | filelocation: "/nsconfig/ssl" 111 | filename: "{{ cert_name }}" 112 | delegate_to: localhost 113 | 114 | - name: Remove CA Bundle from Citrix ADC {{ adc_address }} 115 | citrix_adc_system_file: 116 | nsip: "{{ adc_address }}" 117 | nitro_user: "{{ adc_username }}" 118 | nitro_pass: "{{ adc_password }}" 119 | nitro_protocol: http 120 | validate_certs: false 121 | state: absent 122 | filelocation: "/nsconfig/ssl" 123 | filename: "{{ chain_name }}" 124 | 125 | - name: Delete Local Crypto Assets 126 | file: 127 | state: absent 128 | path: "./tmp/" 129 | -------------------------------------------------------------------------------- /examples/citrix_adc/citrix_create_playbook.yaml: -------------------------------------------------------------------------------- 1 | - name: Create Critx ADC Application 2 | hosts: localhost 3 | connection: local 4 | collections: citrix.adc 5 | 6 | vars_files: 7 | - variables.yaml 8 | 9 | roles: 10 | - role: venafi.ansible_role_venafi 11 | 12 | certificate_common_name: "{{ test_site.name }}.{{ test_site.domain }}" 13 | certificate_alt_name: "DNS:{{ test_site.name }}.{{ test_site.domain }}" 14 | certificate_privatekey_type: "RSA" 15 | certificate_privatekey_size: "2048" 16 | certificate_chain_option: "last" 17 | 18 | certificate_cert_dir: "./tmp" 19 | certificate_cert_path: "./tmp/{{ cert_name }}" 20 | certificate_chain_path: "./tmp/{{ chain_name }}" 21 | certificate_privatekey_path: "./tmp/{{ key_name }}" 22 | certificate_copy_private_key_to_remote: false 23 | 24 | certificate_remote_execution: false 25 | certificate_remote_privatekey_path: "./tmp/{{ key_name }}.remote" 26 | certificate_remote_cert_path: "./tmp/{{ cert_name }}.remote" 27 | certificate_remote_chain_path: "./tmp/{{ chain_name }}.remote" 28 | 29 | tasks: 30 | - name: Copy Private Key to Citrix ADC {{ adc_address }} 31 | citrix_adc_system_file: 32 | nsip: "{{ adc_address }}" 33 | nitro_user: "{{ adc_username }}" 34 | nitro_pass: "{{ adc_password }}" 35 | nitro_protocol: http 36 | validate_certs: false 37 | state: present 38 | filename: "{{ key_name }}" 39 | filelocation: "/nsconfig/ssl/" 40 | filecontent: "{{ lookup('file', './tmp/' + key_name) }}" 41 | delegate_to: localhost 42 | 43 | - name: Copy Certificate to Citrix ADC {{ adc_address }} 44 | citrix_adc_system_file: 45 | nsip: "{{ adc_address }}" 46 | nitro_user: "{{ adc_username }}" 47 | nitro_pass: "{{ adc_password }}" 48 | nitro_protocol: http 49 | validate_certs: false 50 | state: present 51 | filename: "{{ cert_name }}" 52 | filelocation: "/nsconfig/ssl/" 53 | filecontent: "{{ lookup('file', './tmp/' + cert_name + '.remote') }}" 54 | delegate_to: localhost 55 | 56 | - name: Copy CA Bundle to Citrix ADC {{ adc_address }} 57 | citrix_adc_system_file: 58 | nsip: "{{ adc_address }}" 59 | nitro_user: "{{ adc_username }}" 60 | nitro_pass: "{{ adc_password }}" 61 | nitro_protocol: http 62 | validate_certs: false 63 | state: present 64 | filename: "{{ chain_name }}" 65 | filelocation: "/nsconfig/ssl" 66 | filecontent: "{{ lookup('file', './tmp/' + chain_name + '.remote') }}" 67 | delegate_to: localhost 68 | 69 | - name: Create Certkey on Citrix ADC {{ adc_address }} 70 | citrix_adc_ssl_certkey: 71 | nsip: "{{ adc_address }}" 72 | nitro_user: "{{ adc_username }}" 73 | nitro_pass: "{{ adc_password }}" 74 | nitro_protocol: http 75 | validate_certs: false 76 | state: present 77 | certkey: "{{ test_site.name }}.{{ test_site.domain }}_certkey" 78 | cert: "/nsconfig/ssl/{{ cert_name }}" 79 | key: "/nsconfig/ssl/{{ key_name }}" 80 | 81 | - name: Create service-http-1 on Citrix ADC {{ adc_address }} 82 | citrix_adc_service: 83 | nsip: "{{ adc_address }}" 84 | nitro_user: "{{ adc_username }}" 85 | nitro_pass: "{{ adc_password }}" 86 | nitro_protocol: http 87 | validate_certs: false 88 | state: present 89 | name: service-http-1 90 | servicetype: HTTP 91 | ip: "{{ http_service }}" 92 | ipaddress: "{{ http_service }}" 93 | port: "{{ port1 }}" 94 | delegate_to: localhost 95 | 96 | - name: Create service-http-2 on Citrix ADC {{ adc_address }} 97 | citrix_adc_service: 98 | nsip: "{{ adc_address }}" 99 | nitro_user: "{{ adc_username }}" 100 | nitro_pass: "{{ adc_password }}" 101 | nitro_protocol: http 102 | validate_certs: false 103 | state: present 104 | name: service-http-2 105 | servicetype: HTTP 106 | ip: "{{ http_service }}" 107 | ipaddress: "{{ http_service }}" 108 | port: "{{ port2 }}" 109 | delegate_to: localhost 110 | 111 | - name: Create service-http-3 on Citrix ADC {{ adc_address }} 112 | citrix_adc_service: 113 | nsip: "{{ adc_address }}" 114 | nitro_user: "{{ adc_username }}" 115 | nitro_pass: "{{ adc_password }}" 116 | nitro_protocol: http 117 | validate_certs: false 118 | state: present 119 | name: service-http-3 120 | servicetype: HTTP 121 | ip: "{{ http_service }}" 122 | ipaddress: "{{ http_service }}" 123 | port: "{{ port3 }}" 124 | delegate_to: localhost 125 | 126 | - name: Create lb vserver on Citrix ADC {{ adc_address }} 127 | citrix_adc_lb_vserver: 128 | nsip: "{{ adc_address }}" 129 | nitro_user: "{{ adc_username }}" 130 | nitro_pass: "{{ adc_password }}" 131 | nitro_protocol: http 132 | validate_certs: false 133 | state: present 134 | name: "vs-{{ test_site.name }}.{{ test_site.domain }}" 135 | servicetype: SSL 136 | timeout: 2 137 | ipv46: "{{ adc_virtual_ip }}" 138 | port: "{{ adc_virtual_port }}" 139 | lbmethod: ROUNDROBIN 140 | ssl_certkey: "{{ test_site.name }}.{{ test_site.domain }}_certkey" 141 | servicebindings: 142 | - servicename: service-http-1 143 | weight: 80 144 | - servicename: service-http-2 145 | weight: 60 146 | - servicename: service-http-3 147 | weight: 40 148 | disabled: no 149 | delegate_to: localhost 150 | 151 | 152 | -------------------------------------------------------------------------------- /tests/venafi-role-playbook-example.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # We need Docker provisioning only for demo purpose 3 | - name: "Bring up Docker containers for Docker connection inventory iface" 4 | hosts: localhost 5 | roles: 6 | - role: provision_docker 7 | provision_docker_privileged: true 8 | provision_docker_inventory_group: "{{ groups['robots'] }}" 9 | provision_docker_use_docker_connection: true 10 | when: docker_demo is defined 11 | 12 | - name: Prepare 13 | hosts: all 14 | gather_facts: false 15 | tasks: 16 | - name: "Set CN fact" 17 | set_fact: 18 | cn: "{{ 10000|random }}" 19 | 20 | - hosts: robots 21 | vars: 22 | certificate_common_name: "{{ ansible_fqdn }}-{{ cn }}.venafi.example.com" 23 | certificate_alt_name: "IP:{{ansible_default_ipv4.address}},DNS:{{ ansible_fqdn }}-{{ cn }}-alt.venafi.example.com" 24 | certificate_cert_dir: "/tmp/ansible/etc/ssl/{{ certificate_common_name }}" 25 | certificate_cert_path: "{{ certificate_cert_dir }}/{{ certificate_common_name }}.pem" 26 | certificate_chain_path: "{{ certificate_cert_dir }}/{{ certificate_common_name }}.chain.pem" 27 | certificate_privatekey_path: "{{ certificate_cert_dir }}/{{ certificate_common_name }}.key" 28 | certificate_csr_path: "{{ certificate_cert_dir }}/{{ certificate_common_name }}.csr" 29 | 30 | # Where to execute venafi_certificate module. If set to false certificate will be 31 | # created on Ansible master host and then copied to the remote server 32 | certificate_remote_execution: false 33 | # remote location where to place the certificate. 34 | certificate_remote_cert_dir: "/etc/ssl" 35 | certificate_remote_cert_path: "{{ certificate_remote_cert_dir }}/{{ certificate_common_name }}.pem" 36 | certificate_remote_chain_path: "{{ certificate_remote_cert_dir }}/{{ certificate_common_name }}.chain.pem" 37 | certificate_remote_privatekey_path: "{{ certificate_remote_cert_dir }}/{{ certificate_common_name }}.key" 38 | # Set to false if you don't want to copy private key to remote location 39 | certificate_copy_private_key_to_remote: true 40 | 41 | roles: 42 | - role: "ansible-role-venafi" 43 | 44 | # This tasks needed only for certificate verification 45 | tasks: 46 | - name: "Install vcert for verification" 47 | pip: 48 | name: 49 | - vcert 50 | - name: "Verify Venafi certificate on remote host" 51 | venafi_certificate: 52 | url: "{{ venafi.url | default(omit) }}" 53 | token: "{{ venafi.token | default(omit) }}" 54 | zone: "{{ venafi.zone | default(omit) }}" 55 | test_mode: "{{ venafi.test_mode if venafi.test_mode is defined else 'false' }}" 56 | user: "{{ venafi.user | default(omit) }}" 57 | password: "{{ venafi.password | default(omit) }}" 58 | trust_bundle: "{{ venafi.trust_bundle | default(omit) }}" 59 | cert_path: "{{ certificate_remote_cert_path if certificate_remote_cert_path is defined else certificate_cert_path }}" 60 | chain_path: "{{ certificate_remote_chain_path if certificate_remote_chain_path else certificate_chain_path }}" 61 | privatekey_path: "{{ certificate_remote_privatekey_path if certificate_remote_privatekey_path else certificate_privatekey_path }}" 62 | common_name: "{{ certificate_common_name }}" 63 | alt_name: "{{ certificate_alt_name }}" 64 | check_mode: true 65 | register: cert_validation 66 | 67 | - debug: 68 | msg: "Certificate {{ certificate_common_name }} is not in valid state: {{ cert_validation.changed_msg }}" 69 | when: cert_validation is changed 70 | 71 | - name: "Example verification which will always fail with debug message" 72 | venafi_certificate: 73 | url: "{{ venafi.url | default(omit) }}" 74 | token: "{{ venafi.token | default(omit) }}" 75 | zone: "{{ venafi.zone | default(omit) }}" 76 | test_mode: "{{ venafi.test_mode if venafi.test_mode is defined else 'false' }}" 77 | user: "{{ venafi.user | default(omit) }}" 78 | password: "{{ venafi.password | default(omit) }}" 79 | trust_bundle: "{{ venafi.trust_bundle | default(omit) }}" 80 | cert_path: "{{ certificate_remote_cert_path if certificate_remote_cert_path is defined else certificate_cert_path }}" 81 | chain_path: "{{ certificate_remote_chain_path if certificate_remote_chain_path else certificate_chain_path }}" 82 | privatekey_path: "{{ certificate_remote_privatekey_path if certificate_remote_privatekey_path else certificate_privatekey_path }}" 83 | common_name: "{{ certificate_common_name }}-fail-check" 84 | alt_name: "{{ certificate_alt_name }}" 85 | check_mode: true 86 | register: cert_validation_failed 87 | 88 | - debug: 89 | msg: "Certificate {{ certificate_common_name }} is not in valid state: {{ cert_validation_failed.changed_msg }}" 90 | when: cert_validation_failed is changed 91 | 92 | - name: "This one shouldn't enroll new Venafi certificate on remote host because it's valid" 93 | venafi_certificate: 94 | url: "{{ venafi.url | default(omit) }}" 95 | token: "{{ venafi.token | default(omit) }}" 96 | zone: "{{ venafi.zone | default(omit) }}" 97 | test_mode: "{{ venafi.test_mode if venafi.test_mode is defined else 'false' }}" 98 | user: "{{ venafi.user | default(omit) }}" 99 | password: "{{ venafi.password | default(omit) }}" 100 | trust_bundle: "{{ venafi.trust_bundle | default(omit) }}" 101 | cert_path: "{{ certificate_remote_cert_path if certificate_remote_cert_path is defined else certificate_cert_path }}" 102 | chain_path: "{{ certificate_remote_chain_path if certificate_remote_chain_path else certificate_chain_path }}" 103 | privatekey_path: "{{ certificate_remote_privatekey_path if certificate_remote_privatekey_path else certificate_privatekey_path }}" 104 | common_name: "{{ certificate_common_name }}" 105 | register: result 106 | 107 | - name: "Certificate is in following state:" 108 | debug: 109 | msg: "{{ result }}" 110 | -------------------------------------------------------------------------------- /molecule/default/playbook.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Prepare 3 | hosts: all 4 | gather_facts: false 5 | tasks: 6 | - name: "Set CN fact" 7 | set_fact: 8 | cn: "{{ 10000|random }}" 9 | # TODO: make test cases of ECDSA, minimum variables, maximum variables 10 | - name: Converge 11 | hosts: all 12 | vars: 13 | tpp_alt_names: "email:e@venafi.com,IP:192.168.0.15,DNS:{{ ansible_fqdn }}-{{ cn }}-alt.venafi.example.com" 14 | cloud_alt_names: "DNS:{{ ansible_fqdn }}-{{ cn }}-alt.venafi.example.com" 15 | certificate_common_name: "{{ ansible_fqdn }}-{{ cn }}.venafi.example.com" 16 | certificate_alt_name: "{{ cloud_alt_names if venafi.token is defined else tpp_alt_names }}" 17 | certificate_cert_dir: "/tmp/ansible/etc/ssl/{{ certificate_common_name }}" 18 | certificate_cert_path: "{{ certificate_cert_dir }}/{{ certificate_common_name }}.pem" 19 | certificate_chain_path: "{{ certificate_cert_dir }}/{{ certificate_common_name }}.chain.pem" 20 | certificate_privatekey_path: "{{ certificate_cert_dir }}/{{ certificate_common_name }}.key" 21 | certificate_privatekey_type: "RSA" 22 | certificate_privatekey_size: 4096 23 | certificate_csr_path: "{{ certificate_cert_dir }}/{{ certificate_common_name }}.csr" 24 | 25 | # Where to execute venafi_certificate module. If set to false certificate will be 26 | # created on Ansible master host and then copied to the remote server 27 | certificate_remote_execution: false 28 | # remote location where to place the certificate. 29 | certificate_remote_cert_dir: "/etc/ssl" 30 | certificate_remote_cert_path: "{{ certificate_remote_cert_dir }}/{{ certificate_common_name }}.pem" 31 | certificate_remote_chain_path: "{{ certificate_remote_cert_dir }}/{{ certificate_common_name }}.chain.pem" 32 | certificate_remote_privatekey_path: "{{ certificate_remote_cert_dir }}/{{ certificate_common_name }}.key" 33 | # Set to false if you don't want to copy private key to remote location 34 | certificate_copy_private_key_to_remote: true 35 | 36 | roles: 37 | - role: ansible-role-venafi 38 | 39 | # This tasks needed only for certificate verification 40 | tasks: 41 | - name: "Install future library" 42 | pip: 43 | name: 44 | - future 45 | - name: "Install vcert for verification" 46 | pip: 47 | name: 48 | - vcert==0.10.0 49 | 50 | - name: "Verify Venafi certificate on remote host" 51 | venafi_certificate: 52 | url: "{{ venafi.url | default(omit) }}" 53 | token: "{{ venafi.token | default(omit) }}" 54 | zone: "{{ venafi.zone | default(omit) }}" 55 | test_mode: "{{ venafi.test_mode if venafi.test_mode is defined else 'false' }}" 56 | user: "{{ venafi.user | default(omit) }}" 57 | password: "{{ venafi.password | default(omit) }}" 58 | access_token: "{{ venafi.access_token | default(omit) }}" 59 | trust_bundle: "{{ venafi.trust_bundle | default(omit) }}" 60 | cert_path: "{{ certificate_remote_cert_path if certificate_remote_cert_path is defined else certificate_cert_path }}" 61 | chain_path: "{{ certificate_remote_chain_path if certificate_remote_chain_path else certificate_chain_path }}" 62 | privatekey_path: "{{ certificate_remote_privatekey_path if certificate_remote_privatekey_path else certificate_privatekey_path }}" 63 | common_name: "{{ certificate_common_name }}" 64 | alt_name: "{{ certificate_alt_name }}" 65 | check_mode: true 66 | register: cert_validation 67 | 68 | - debug: 69 | msg: "Certificate {{ certificate_common_name }} is not in valid state: {{ cert_validation.changed_msg }}" 70 | when: cert_validation is changed 71 | 72 | - name: "Example verification which will always fail with debug message" 73 | venafi_certificate: 74 | url: "{{ venafi.url | default(omit) }}" 75 | token: "{{ venafi.token | default(omit) }}" 76 | zone: "{{ venafi.zone | default(omit) }}" 77 | test_mode: "{{ venafi.test_mode if venafi.test_mode is defined else 'false' }}" 78 | user: "{{ venafi.user | default(omit) }}" 79 | password: "{{ venafi.password | default(omit) }}" 80 | access_token: "{{ venafi.access_token | default(omit) }}" 81 | trust_bundle: "{{ venafi.trust_bundle | default(omit) }}" 82 | cert_path: "{{ certificate_remote_cert_path if certificate_remote_cert_path is defined else certificate_cert_path }}" 83 | chain_path: "{{ certificate_remote_chain_path if certificate_remote_chain_path else certificate_chain_path }}" 84 | privatekey_path: "{{ certificate_remote_privatekey_path if certificate_remote_privatekey_path else certificate_privatekey_path }}" 85 | common_name: "{{ certificate_common_name }}-fail-check" 86 | alt_name: "{{ certificate_alt_name }}" 87 | check_mode: true 88 | register: cert_validation_failed 89 | 90 | - debug: 91 | msg: "Certificate {{ certificate_common_name }} is not in valid state: {{ cert_validation_failed.changed_msg }}" 92 | when: cert_validation_failed is changed 93 | 94 | - name: "Fail playbook if cert_validation_failed is not in changed state" 95 | fail: 96 | msg: "Certificate should be in changed stage but it is not" 97 | when: cert_validation_failed is not changed 98 | 99 | - name: "This one shouldn't enroll new Venafi certificate on remote host because it's valid" 100 | venafi_certificate: 101 | url: "{{ venafi.url | default(omit) }}" 102 | token: "{{ venafi.token | default(omit) }}" 103 | zone: "{{ venafi.zone | default(omit) }}" 104 | test_mode: "{{ venafi.test_mode if venafi.test_mode is defined else 'false' }}" 105 | user: "{{ venafi.user | default(omit) }}" 106 | password: "{{ venafi.password | default(omit) }}" 107 | access_token: "{{ venafi.access_token | default(omit) }}" 108 | trust_bundle: "{{ venafi.trust_bundle | default(omit) }}" 109 | cert_path: "{{ certificate_remote_cert_path if certificate_remote_cert_path is defined else certificate_cert_path }}" 110 | chain_path: "{{ certificate_remote_chain_path if certificate_remote_chain_path else certificate_chain_path }}" 111 | privatekey_path: "{{ certificate_remote_privatekey_path if certificate_remote_privatekey_path else certificate_privatekey_path }}" 112 | common_name: "{{ certificate_common_name }}" 113 | alt_name: "{{ certificate_alt_name }}" 114 | register: cert_is_valid 115 | 116 | - name: "Certificate is in following state:" 117 | debug: 118 | msg: "{{ cert_is_valid }}" 119 | 120 | - name: "Fail playbook if cert_is_valid is in changed state" 121 | fail: 122 | msg: "Certificate should not be in changed stage but it is" 123 | when: cert_is_valid is changed 124 | -------------------------------------------------------------------------------- /tests/venafi-playbook-example.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # We need Docker provisioning only for demo purpose 3 | - name: "Bring up Docker containers for Docker connection inventory iface" 4 | hosts: localhost 5 | roles: 6 | - role: provision_docker 7 | provision_docker_privileged: true 8 | provision_docker_inventory_group: "{{ groups['robots'] }}" 9 | provision_docker_use_docker_connection: true 10 | when: docker_demo is defined 11 | 12 | - name: Prepare 13 | hosts: all 14 | gather_facts: false 15 | tasks: 16 | - name: "Set random CN fact" 17 | set_fact: 18 | cn: "{{ 10000|random }}" 19 | 20 | - hosts: robots 21 | vars: 22 | credentials_file: "../credentials.yml" 23 | # Use Ansible host FQDN for certificate common name 24 | certificate_common_name: "{{ ansible_fqdn }}-{{ cn }}.venafi.example.com" 25 | # Use ansible default IP for DNS 26 | certificate_alt_name: "IP:{{ansible_default_ipv4.address}},DNS:{{ ansible_fqdn }}-{{ cn }}-alt.venafi.example.com" 27 | 28 | # Directory where to place certificates 29 | certificate_cert_dir: "/tmp/ansible/etc/ssl/{{ certificate_common_name }}" 30 | # Paths for certficaite and keys 31 | certificate_cert_path: "{{ certificate_cert_dir }}/{{ certificate_common_name }}.pem" 32 | certificate_chain_path: "{{ certificate_cert_dir }}/{{ certificate_common_name }}.chain.pem" 33 | certificate_privatekey_path: "{{ certificate_cert_dir }}/{{ certificate_common_name }}.key" 34 | certificate_csr_path: "{{ certificate_cert_dir }}/{{ certificate_common_name }}.csr" 35 | 36 | # Where to execute venafi_certificate module. If set to false, certificate will be 37 | # created on ansible master host and then copied to the remote server 38 | certificate_remote_execution: false 39 | # remote location where to place the certificate_ 40 | certificate_remote_cert_path: "/etc/ssl/{{ certificate_common_name }}.pem" 41 | certificate_remote_chain_path: "/etc/ssl/{{ certificate_common_name }}.chain.pem" 42 | certificate_remote_privatekey_path: "/etc/ssl/{{ certificate_common_name }}.key" 43 | # Set to false, if you don't want to copy private key to remote location 44 | certificate_copy_private_key_to_remote: true 45 | 46 | # Modify default before expire hours variable 47 | certificate_before_expired_hours: 100 48 | 49 | # Set to true if you want forcly renew certificate 50 | certificate_force: false 51 | 52 | tasks: 53 | - name: "Include vars of {{ credentials_file }} into the venafi variable." 54 | include_vars: 55 | file: "{{ credentials_file }}" 56 | name: venafi 57 | 58 | - name: "Create directory {{ certificate_cert_dir }}" 59 | local_action: 60 | module: file 61 | path: "{{ certificate_cert_dir }}" 62 | state: directory 63 | 64 | - name: "Enroll Venafi certificate on local host" 65 | local_action: 66 | module: venafi_certificate 67 | url: "{{ venafi.url | default(omit) }}" 68 | token: "{{ venafi.token | default(omit) }}" 69 | zone: "{{ venafi.zone | default(omit) }}" 70 | test_mode: "{{ venafi.test_mode if venafi.test_mode is defined else 'false' }}" 71 | user: "{{ venafi.user | default(omit) }}" 72 | password: "{{ venafi.password | default(omit) }}" 73 | trust_bundle: "{{ venafi.trust_bundle | default(omit) }}" 74 | cert_path: "{{ certificate_cert_path }}" 75 | chain_path: "{{ certificate_chain_path | default(omit) }}" 76 | privatekey_path: "{{ certificate_privatekey_path | default(omit) }}" 77 | privatekey_size: "{{ certificate_privatekey_size | default(omit) }}" 78 | common_name: "{{ certificate_common_name }}" 79 | register: certout 80 | - name: "Certificate is in following state:" 81 | debug: 82 | msg: "{{ certout }}" 83 | 84 | 85 | - name: "Copy Venafi certificate file to remote location {{ certificate_remote_cert_path if certificate_remote_cert_path is defined else certificate_cert_path }}" 86 | copy: 87 | src: "{{ certificate_cert_path }}" 88 | dest: "{{ certificate_remote_cert_path if certificate_remote_cert_path is defined else certificate_cert_path }}" 89 | 90 | - name: "Copy Venafi private key file to remote location {{ certificate_remote_privatekey_path if certificate_remote_privatekey_path else certificate_privatekey_path }}" 91 | copy: 92 | src: "{{ certificate_privatekey_path }}" 93 | dest: "{{ certificate_remote_privatekey_path if certificate_remote_privatekey_path else certificate_privatekey_path }}" 94 | when: certificate_copy_private_key_to_remote 95 | 96 | - name: "Copy Venafi certificate chain file to remote location {{ certificate_remote_chain_path if certificate_remote_chain_path else certificate_chain_path }}" 97 | copy: 98 | src: "{{ certificate_chain_path }}" 99 | dest: "{{ certificate_remote_chain_path if certificate_remote_chain_path else certificate_chain_path }}" 100 | when: certificate_chain_path is defined 101 | 102 | - name: "Install vcert for verification" 103 | pip: 104 | name: 105 | - vcert 106 | 107 | - name: "Verify Venafi certificate on remote host" 108 | venafi_certificate: 109 | url: "{{ venafi.url | default(omit) }}" 110 | token: "{{ venafi.token | default(omit) }}" 111 | zone: "{{ venafi.zone | default(omit) }}" 112 | test_mode: "{{ venafi.test_mode if venafi.test_mode is defined else 'false' }}" 113 | user: "{{ venafi.user | default(omit) }}" 114 | password: "{{ venafi.password | default(omit) }}" 115 | trust_bundle: "{{ venafi.trust_bundle | default(omit) }}" 116 | cert_path: "{{ certificate_remote_cert_path if certificate_remote_cert_path is defined else certificate_cert_path }}" 117 | chain_path: "{{ certificate_remote_chain_path if certificate_remote_chain_path else certificate_chain_path }}" 118 | privatekey_path: "{{ certificate_remote_privatekey_path if certificate_remote_privatekey_path else certificate_privatekey_path }}" 119 | common_name: "{{ certificate_common_name }}" 120 | check_mode: true 121 | register: cert_validation 122 | 123 | - debug: 124 | msg: "Certificate {{ certificate_common_name }} is not in valid state: {{ cert_validation.changed_msg }}" 125 | when: cert_validation is changed 126 | 127 | - name: "Example verification which will always fail with debug message" 128 | venafi_certificate: 129 | url: "{{ venafi.url | default(omit) }}" 130 | token: "{{ venafi.token | default(omit) }}" 131 | zone: "{{ venafi.zone | default(omit) }}" 132 | test_mode: "{{ venafi.test_mode if venafi.test_mode is defined else 'false' }}" 133 | user: "{{ venafi.user | default(omit) }}" 134 | password: "{{ venafi.password | default(omit) }}" 135 | trust_bundle: "{{ venafi.trust_bundle | default(omit) }}" 136 | cert_path: "{{ certificate_remote_cert_path if certificate_remote_cert_path is defined else certificate_cert_path }}" 137 | chain_path: "{{ certificate_remote_chain_path if certificate_remote_chain_path else certificate_chain_path }}" 138 | privatekey_path: "{{ certificate_remote_privatekey_path if certificate_remote_privatekey_path else certificate_privatekey_path }}" 139 | common_name: "{{ certificate_common_name }}-fail-check" 140 | check_mode: true 141 | register: cert_validation_failed 142 | 143 | - debug: 144 | msg: "Certificate {{ certificate_common_name }} is not in valid state: {{ cert_validation_failed.changed_msg }}" 145 | when: cert_validation_failed is changed 146 | 147 | - name: "Fail playbook if cert_validation_failed is not in changed state" 148 | fail: 149 | msg: "Certificate should be in changed stage but it is not" 150 | when: cert_validation_failed is not changed 151 | 152 | - name: "This one shouldn't enroll new Venafi certificate on remote host because it's valid" 153 | venafi_certificate: 154 | url: "{{ venafi.url | default(omit) }}" 155 | token: "{{ venafi.token | default(omit) }}" 156 | zone: "{{ venafi.zone | default(omit) }}" 157 | test_mode: "{{ venafi.test_mode if venafi.test_mode is defined else 'false' }}" 158 | user: "{{ venafi.user | default(omit) }}" 159 | password: "{{ venafi.password | default(omit) }}" 160 | trust_bundle: "{{ venafi.trust_bundle | default(omit) }}" 161 | cert_path: "{{ certificate_remote_cert_path if certificate_remote_cert_path is defined else certificate_cert_path }}" 162 | chain_path: "{{ certificate_remote_chain_path if certificate_remote_chain_path else certificate_chain_path }}" 163 | privatekey_path: "{{ certificate_remote_privatekey_path if certificate_remote_privatekey_path else certificate_privatekey_path }}" 164 | common_name: "{{ certificate_common_name }}" 165 | register: result 166 | 167 | - name: "Certificate is in following state:" 168 | debug: 169 | msg: "{{ result }}" 170 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2019 Venafi Inc. 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Venafi](Venafi_logo.png) 2 | [![Apache 2.0 License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 3 | ![Community Supported](https://img.shields.io/badge/Support%20Level-Community-brightgreen) 4 | ![Compatible with TPP 17.3+ & VaaS](https://img.shields.io/badge/Compatibility-TPP%2017.3+%20%26%20VaaS-f9a90c) 5 | _:warning: **This community-supported open source project has reached its END-OF-LIFE, and as of May 30th 2025, this project is deprecated and will no longer be maintained**. Please use **[Ansible Collection for Venafi](https://github.com/Venafi/ansible-collection-venafi)** instead. Switching over is easy, simply install the `venafi.machine_identity` collection using Ansible Galaxy and replace `role: venafi.ansible_role_venafi` with `role: venafi.machine_identity.certificate` in your playbooks._ 6 | 7 | # Venafi Role for Ansible 8 | 9 | This solution adds certificate enrollment capabilities to [Red Hat Ansible](https://www.ansible.com/) by seamlessly 10 | integrating with the [Venafi Trust Protection Platform](https://www.venafi.com/platform/trust-protection-platform) 11 | or [Venafi as a Service](https://www.venafi.com/venaficloud) in a manner that ensures compliance with corporate 12 | security policy and provides visibility into certificate issuance enterprise wide. 13 | 14 | >:red_car: **Test drive our integration examples today** 15 | > 16 | >Let us show you _step-by-step_ how to add certificates to your _Infrastucture as Code_ automation with Ansible. 17 | > 18 | >Products | Available integration examples... 19 | >:------: | -------- 20 | >[F5 BIG-IP](examples/f5_bigip/README.md) | [How to configure secure application delivery using F5 BIG-IP and the Venafi Ansible role](examples/f5_bigip/README.md) 21 | >[Citrix ADC](examples/citrix_adc/README.md) | [How to configure secure application delivery using Citrix ADC and the Venafi Ansible role](examples/citrix_adc/README.md) 22 | > 23 | >**NOTE** If you don't see an example for a product you use, check back later. We're working hard to add more integration examples. 24 | 25 | ## Requirements 26 | 27 | Review the [Venafi](https://github.com/Venafi/vcert-python#prerequisites-for-using-with-trust-protection-platform) 28 | prerequisites, then install Ansible and [VCert-Python](https://github.com/Venafi/vcert-python) (v0.10.0 or higher) using `pip`: 29 | ```sh 30 | pip install ansible vcert --upgrade 31 | ``` 32 | 33 | ## Using with Ansible Galaxy 34 | 35 | For more information about Ansible Galaxy, go to https://galaxy.ansible.com/docs/using/installing.html 36 | 37 | 1. Install the [Venafi Role for Ansible](https://galaxy.ansible.com/venafi/ansible_role_venafi) from Ansible Galaxy: 38 | 39 | ```sh 40 | ansible-galaxy install venafi.ansible_role_venafi 41 | ``` 42 | 43 | 1. Create the `credentials.yml` and populate it with connection parameters: 44 | 45 | **Trust Protection Platform**: 46 | 47 | ```sh 48 | cat <>credentials.yml 49 | access_token: 'p0WTt3sDPbzm2BDIkoJROQ==' 50 | url: 'https://tpp.venafi.example' 51 | zone: "DevOps\\Ansible" 52 | trust_bundle: "/path/to/bundle.pem" 53 | EOF 54 | ``` 55 | 56 | **Venafi as a Service**: 57 | 58 | ```sh 59 | cat <>credentials.yml 60 | token: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' 61 | zone: 'Business App\\Enterprise CIT' 62 | EOF 63 | ``` 64 | 65 | The Venafi Role for Ansible supports the following connection and credential settings: 66 | 67 | | Variable Name | Description | 68 | | -------------- | ------------------------------------------------------------ | 69 | | `access_token` | Trust Protection Platform access token for the "ansible-by-venafi" API Application | 70 | | `password` | **[DEPRECATED]** Trust Protection Platform WebSDK password, use `access_token` if possible | 71 | | `test_mode` | When "true", the role operates without connecting to Trust Protection Platform or Venafi as a Service | 72 | | `token` | Venafi as a Service API key | 73 | | `trust_bundle` | Text file containing trust anchor certificates in PEM (text) format, generally required for Trust Protection Platform | 74 | | `url` | Venafi service URL (e.g. "https://tpp.venafi.example"), generally only applicable to Trust Protection Platform | 75 | | `user` | **[DEPRECATED]** Trust Protection Platform WebSDK username, use `access_token` if possible | 76 | | `zone` | Policy folder for TPP or Application name and Issuing Template API Alias for VaaS (e.g. "Business App\Enterprise CIT") | 77 | 78 | 1. Use `ansible-vault` to encrypt the `credentials.yml` file using a password. This is optional but highly recommended. 79 | As long as you know the password you can always decrypt the file to make changes and then re-encrypt it. 80 | Go to https://docs.ansible.com/ansible/latest/user_guide/vault.html for more information. 81 | 82 | ```sh 83 | ansible-vault encrypt credentials.yml 84 | ``` 85 | 86 | 1. Write a simple playbook called, for example, `sample.yml`. 87 | 88 | ```yaml 89 | - hosts: localhost 90 | roles: 91 | - role: venafi.ansible_role_venafi 92 | certificate_cert_dir: "/tmp/etc/ssl/{{ certificate_common_name }}" 93 | ``` 94 | 95 | 1. Run the playbook. 96 | 97 | ```sh 98 | ansible-playbook sample.yml --ask-vault-pass 99 | ``` 100 | 101 | Running the playbook will generate a certificate and place it into folder in /tmp/etc/ssl/ directory. 102 | The `--ask-vault-pass` parameter is needed if you encrypted the `credentials.yml` file. Additional 103 | playbook variables can be added to specify properties of the certificate and key pair, file locations, 104 | and to override default behaviors. 105 | 106 | | Variable Name | Description | 107 | | ---------------------------------------- | ------------------------------------------------------------ | 108 | | `credentials_file` | Name of the file containing Venafi credentials and connection settings
Default: `credentials.yml` | 109 | | `certificate_common_name` | *Common Name* to request for the certificate.
Default: `"{{ ansible_fqdn }}"` | 110 | | `certificate_alt_name` | Comma separated list of *Subject Alternative Names* to request for the certificate. Prefix each value with the SAN type.
Example: `"DNS:host.example.com,IP:10.20.30.40,email:me@example.com"` | | 111 | | `certificate_privatekey_type` | Key algorithm, "RSA" or "ECDSA"
Default: `"RSA"` (from VCert) | 112 | | `certificate_privatekey_size` | Key size in bits for RSA keys
Default: `"2048"` (from VCert) | 113 | | `certificate_privatekey_curve` | Elliptic Curve for ECDSA keys
Default: `"P251"` (from VCert) | 114 | | `certificate_privatekey_passphrase` | Password to use for encrypting the private key | 115 | | `certificate_chain_option` | Specifies whether the root CA certificate appears `"last"` (default) or `"first"` in the chain file | 116 | | `certificate_cert_dir` | Local parent directory where the cryptographic assets will be stored
Default: `"/etc/ssl/{{ certificate_common_name }}"` | 117 | | `certificate_cert_path` | Local directory where certificate files will be stored
Default: `{{ certificate_cert_dir }}/{{ certificate_common_name }}.pem"` | 118 | | `certificate_chain_path` | Local directory where certificate chain files will be stored
Default: `"{{ certificate_cert_dir }}/{{ certificate_common_name }}.chain.pem"` | 119 | | `certificate_privatekey_path` | Local directory where private key files will be stored
Default: `"{{ certificate_cert_dir }}/{{ certificate_common_name }}.key"` | 120 | | `certificate_csr_path` | Local directory where certificate signing request files will be stored
Default: `"{{ certificate_cert_dir }}/{{ certificate_common_name }}.csr"` | 121 | | `certificate_remote_execution` | Specifies whether cryptographic assets will be generated remotely, or locally and then provisioned to the remote host
Default: `false` | 122 | | `certificate_remote_cert_path` | Directory on remote host where certificate files will be stored
Default: `"{{ certificate_cert_dir }}/{{ certificate_common_name }}.pem"` | 123 | | `certificate_remote_chain_path` | Directory on remote host where certificate chain files will be stored
Default: `"{{ certificate_cert_dir }}/{{ certificate_common_name }}.chain.pem"` | 124 | | `certificate_remote_privatekey_path` | Directory on remote host where private key files will be stored
Default: `"{{ certificate_cert_dir }}/{{ certificate_common_name }}.key"` | 125 | | `certificate_copy_private_key_to_remote` | Specifies whether to copy the private key file to the remote host
Default: `true` | 126 | | `certificate_before_expired_hours` | Number of hours prior to the expiration of the certificate before it can be renewed
Default: `72` | 127 | | `certificate_renew` | Specifies whether to renew the certificate if it is within the "before_expired_hours" window when the playbook is run
Default: `true` | 128 | | `certificate_force` | Specifies whether to request a new certificate every time the playbook is run
Default: `false` | 129 | 130 | Defaults are defined in the [defaults/main.yml](defaults/main.yml) file. 131 | 132 | ## Preparing a Docker demo environment for running Ansible 133 | 134 | 1. (Optional) Prepare the demo environment. If you want to use your own inventory, update the tests/inventory file. 135 | 136 | 1. To run our test/demo playbook you'll need the Docker provisioning role. 137 | Download it into the `tests/roles/provision_docker` directory: 138 | 139 | ```sh 140 | git clone https://github.com/chrismeyersfsu/provision_docker.git tests/roles/provision_docker 141 | ``` 142 | 143 | 1. Then build the Docker images needed for the demo playbook: 144 | 145 | ```sh 146 | docker build ./tests --tag local-ansible-test 147 | ``` 148 | 149 | Demo certificates will be placed in the `/tmp/ansible/etc/ssl` directory on the Ansible host. 150 | From there they will be distributed to the `/etc/ssl/` directory of remote hosts. 151 | 152 | 1. Generate a credentials file for either Trust Protection Platform or Venafi as a Service as described in the above section. 153 | 154 | 1. Run the Ansible playbook (remove `docker_demo=true` if you want to use your own inventory). 155 | The contents of `credentials.yml` will be used to decide whether Trust Protection Platform or Venafi as a Service is used. 156 | If you set the `token` parameter, the playbook assumes you are using Venafi as a Service. If you set the `access_token` or 157 | `password` parameters, the playbook assumes you are using Trust Protection Platform. 158 | 159 | ```sh 160 | ansible-playbook -i tests/inventory \ 161 | tests/venafi-playbook-example.yml \ 162 | --extra-vars "credentials_file=credentials.yml docker_demo=true" \ 163 | --ask-vault-pass 164 | ``` 165 | 166 | You will be prompted for the password for decrypting the `credentials.yml` as before. The source file for the 167 | credentials can be overridden using the *credentials_file* variable and this can be specified on the command line 168 | using the `--extra-vars` parameter as shown. 169 | 170 | ## Sample Playbook 171 | 172 | ```yaml 173 | - hosts: servers 174 | roles: 175 | - role: "ansible-role-venafi" 176 | certificate_common_name: "{{ ansible_fqdn }}.venafi.example.com" 177 | certificate_cert_dir: "/tmp/ansible/etc/ssl/{{ certificate_common_name }}" 178 | certificate_cert_path: "{{ certificate_cert_dir }}/{{ certificate_common_name }}.pem" 179 | certificate_chain_path: "{{ certificate_cert_dir }}/{{ certificate_common_name }}.chain.pem" 180 | certificate_privatekey_path: "{{ certificate_cert_dir }}/{{ certificate_common_name }}.key" 181 | certificate_csr_path: "{{ certificate_cert_dir }}/{{ certificate_common_name }}.csr" 182 | 183 | # Where to execute venafi_certificate module. If set to false, certificate will be 184 | # created on ansible master host and then copied to the remote server. 185 | certificate_remote_execution: false 186 | # Remote location where to place the certificate. 187 | certificate_remote_cert_dir: "/etc/ssl" 188 | certificate_remote_cert_path: "{{ certificate_remote_cert_dir }}/{{ certificate_common_name }}.pem" 189 | certificate_remote_chain_path: "{{ certificate_remote_cert_dir }}/{{ certificate_common_name }}.chain.pem" 190 | certificate_remote_privatekey_path: "{{ certificate_remote_cert_dir }}/{{ certificate_common_name }}.key" 191 | # Set to false if you don't want to copy private key to remote location. 192 | certificate_copy_private_key_to_remote: true 193 | ``` 194 | 195 | For playbook examples look into [venafi-playbook-example.yml](tests/venafi-playbook-example.yml) file. 196 | For role examples look into [venafi-role-playbook-example.yml](tests/venafi-role-playbook-example.yml) file 197 | 198 | For more information about using roles go to https://docs.ansible.com/ansible/latest/user_guide/playbooks_reuse_roles.html 199 | 200 | ## License 201 | 202 | Copyright © Venafi, Inc. All rights reserved. 203 | 204 | This solution is licensed under the Apache License, Version 2.0. See `LICENSE` for the full license text. 205 | 206 | Please direct questions/comments to opensource@venafi.com. 207 | -------------------------------------------------------------------------------- /examples/f5_bigip/README.md: -------------------------------------------------------------------------------- 1 | # Configuring secure application delivery using F5 BIG-IP and the Venafi Ansible Role 2 | 3 | In this example, we'll show you how to better secure application delivery using the Venafi Ansible Role with your [F5 BIG-IP](https://www.f5.com/products/big-ip-services) instance. 4 | Adding Venafi enables you to manage certificates more securely as part of the [TLS termination](https://www.f5.com/services/resources/glossary/ssl-termination) process on your load balancer. 5 | 6 | ## Who should use this example? 7 | 8 | The steps described in this document are typically performed by a _DevOps engineers_ or a _system administrators_. Generally, you'll need a basic undestanding of F5 BIG-IP, Venafi Trust Protection Platform or Venafi Cloud, and the required permissions for completing the tasks described in the example. 9 | 10 | ## About this example 11 | 12 | An _application delivery controller_ (ADC) is used to increase capacity and reliability of applications. ADC improves the performance of applications by decreasing the load on associated servers while managing and maintaining application and network sessions. But ADC configuration can become a lengthy process. However, you can actually automate the process by using a configuration management tool. 13 | 14 | In this example we use [RedHat Ansible](https://www.ansible.com/) with the _Venafi Ansible Role_ to automate the process of requesting, retrieving and installing a certificate as part of SSL termination on an ADC (specifically, F5 BIG-IP) for load balancing web traffic. We'll also utilize three HTTP servers contained in a cluster as the endpoints that are sending and receiving web traffic and being managed by F5 BIG-IP. 15 | 16 | Later in this example, you'll generate a certificate for the `demo-f5.venafi.example` domain using the Venafi Ansible Role to request and retrieve it from either _Venafi Trust Protection Platform_ or _Venafi Cloud_ services. Then you'll copy the certificate files (certificate, private key, chain bundle) to the F5 BIG-IP. Finally, you'll configure F5 BIG-IP to distribute the traffic between three HTTP servers using the round-robin load balancing method. Take a look at the diagram below for an overview of what we're going to create. 17 | 18 | > **NOTE** In our example, we suggest that you use the round-robin balancing method. But keep in mind that there are [other methods](https://www.f5.com/services/resources/glossary/load-balancer) that might be more suitable for your specific use case. 19 | 20 | ![AnsibleVenafi](venafi_ansible_role.png) 21 | 22 | ## Prerequisites 23 | 24 | To perform the tasks described in this example, you'll need: 25 | 26 | - The Venafi Ansible Role installed on your machine, you can install it using `ansible-galaxy` [as described here](https://github.com/Venafi/ansible-role-venafi#using-with-ansible-galaxy) 27 | - Access to either **Venafi Trust Protection Platform** or **Venafi Cloud** services (the `credentials.yml` [file](https://github.com/Venafi/ansible-role-venafi#using-with-ansible-galaxy) is used in this example). 28 | - If you are working with **Venafi Trust Protection Platform** obtain the `access_token` and `refresh_token` using the [VCert CLI](https://github.com/Venafi/vcert/blob/master/README-CLI-PLATFORM.md#obtaining-an-authorization-token). 29 | - Administration access to the F5 BIG-IP instance. 30 | - A set of 3 HTTP servers running your application. 31 | 32 | ## Getting started 33 | 34 | Here are the steps we'll take as we go trough this example: 35 | 36 | 1. Retrieve a certificate using the Venafi Ansible Role 37 | 2. Copy the retrieved certificate files to F5 BIG-IP 38 | 3. Create a Client SSL Profile on F5 BIG-IP 39 | 4. Create Pool on F5 BIG-IP 40 | 5. Add pool members on F5 BIG-IP 41 | 6. Create a virtual server on F5 BIG-IP 42 | 7. Execute the playbook 43 | 44 | > **NOTE** Credentials used in this example are for demonstration purposes only. You should use stronger credentials. 45 | 46 | > **BEST PRACTICES** In general, be careful when using self-signed certificates because of the inherent risks of no identity verification or trust control. The public and private keys are both held by the same entity. Also, self-signed certificates cannot be revoked; they can only be replaced. If an attacker has already gained access to a system, the attacker can spoof the identity of the subject. Of course, CAs can revoke a certificate only when they discover the compromise. 47 | 48 | ## Step 1: Retrieve a certificate using Venafi Ansible Role 49 | 50 | ### Step 1a: Creating variables file 51 | 52 | The first step is to create the `variables.yaml` file. This file defines the variables used during the execution of the playbook, which include: 53 | 54 | - The F5 BIG-IP management IP address 55 | - The credentials used to manage the F5 BIG-IP 56 | - The CN needed to generate the certificate 57 | - The partition in which all the information will be stored 58 | - The Virtual IP and port on which all the HTTPS traffic will be handled 59 | - The pool members (the NGINX servers running the application) 60 | - The name for the certificate files that will be copied to the F5 BIG-IP 61 | - To facilitate the connection with the device, the connection parameters can be also specified in this file. 62 | - The pattern used for this is called a _provider_; the provider is a dictionary that includes sub-keys such as *password*, *server*, etc. 63 | - In the following steps, the provider dictionary will be passed as a parameter to the tasks so that they can connect to the F5 BIG-IP. 64 | 65 | ```yaml 66 | f5_address: "yourf5bigip" 67 | f5_username: "youruser" 68 | f5_password: "yourpassword" 69 | 70 | test_site: 71 | name: "demo-f5" 72 | domain: "venafi.example" 73 | 74 | f5_partition: "Demo" 75 | f5_virtual_ip: "192.168.7.68" 76 | f5_virtual_port: "443" 77 | f5_pool_members: 78 | - host: 192.168.6.201 79 | port: 8001 80 | - host: 192.168.6.201 81 | port: 8002 82 | - host: 192.168.6.201 83 | port: 8003 84 | 85 | cert_name: "{{ test_site.name }}.crt" 86 | key_name: "{{ test_site.name }}.key" 87 | chain_name: "{{ test_site.name }}-ca-bundle.crt" 88 | 89 | f5_provider: 90 | server: "{{ f5_address }}" 91 | server_port: 443 92 | user: "{{ f5_username }}" 93 | password: "{{ f5_password }}" 94 | validate_certs: no 95 | ``` 96 | 97 | ### Step 1b: Creating the playbook 98 | 99 | Start by creating a YAML file named `f5_create_playbook.yaml`, inside, define a name for the playbook, the hosts in which the tasks will be executed, the type of connection to use and specify the variables file created in the previous step : 100 | 101 | ```yaml 102 | - name: Create F5 Application 103 | hosts: localhost 104 | connection: local 105 | 106 | vars_files: 107 | - variables.yaml 108 | ``` 109 | 110 | ### Step 1c: Requesting and retrieving the certificate using Venafi Role 111 | 112 | In the following block of instructions the Venafi Ansible Role is being specified along with the variables it needs to request and retrieve the certificate from the Venafi services, by adding these instructions the Ansible will: 113 | 114 | - Request and retrieve a certificate which common and alternate names are `demo-f5.venafi.example`. 115 | - Create a RSA private key of a size of 2048 bits. 116 | - Generate a chain bundle file where the CA certificate will be place at the end of the file. 117 | - Create a `tmp` directory on the current working directory which will store the retrieved certificate files. 118 | - 3 files will be retrieved and stored using the names on the variables file (*demonstration.{crt,key,-ca-bundle.crt}*). 119 | - Simulate the copy of the retrieved files to the remote host by generating a duplicate of them adding the `.remote` extension (the certificate files retrieved are going to be copied to F5 BIG-IP using the F5 Ansible modules that's the reason why the options `certificate_copy_private_key_to_remote` and `certificate_remote_execution` are set to `false`). 120 | 121 | 122 | ```yaml 123 | --- 124 | 125 | roles: 126 | - role: venafi.ansible_role_venafi 127 | 128 | certificate_common_name: "{{ test_site.name }}.{{ test_site.domain }}" 129 | certificate_alt_name: "DNS:{{ test_site.name }}.{{ test_site.domain }}" 130 | certificate_privatekey_type: "RSA" 131 | certificate_privatekey_size: "2048" 132 | certificate_chain_option: "last" 133 | 134 | certificate_cert_dir: "./tmp" 135 | certificate_cert_path: "./tmp/{{ cert_name }}" 136 | certificate_chain_path: "./tmp/{{ chain_name }}" 137 | certificate_privatekey_path: "./tmp/{{ key_name }}" 138 | certificate_copy_private_key_to_remote: false 139 | 140 | certificate_remote_execution: false 141 | certificate_remote_privatekey_path: "/tmp/{{ key_name }}.remote" 142 | certificate_remote_cert_path: "/tmp/{{ cert_name }}.remote" 143 | certificate_remote_chain_path: "/tmp/{{ chan_name }}.remote" 144 | ``` 145 | 146 | ## Step 2: Copy the retrieved certificate files to F5 BIG-IP 147 | 148 | By adding the instructions below to the playbook, we specify the actions the playbook will execute. Ansible will connect to the F5 BIG-IP (using the credentials specified in the provider dictionary) and then it will create the key, CA bundle and certificate using the local files retrieved in the previous step. 149 | 150 | 151 | ```yaml 152 | --- 153 | 154 | tasks: 155 | - name: Create Private Key on F5 BIG-IP {{ f5_address }} 156 | bigip_ssl_key: 157 | state: present 158 | provider: "{{ f5_provider }}" 159 | name: "{{ key_name }}" 160 | partition: "{{ f5_partition }}" 161 | content: "{{ lookup('file', './tmp/' + key_name) }}" 162 | delegate_to: localhost 163 | 164 | - name: Create Certificate on F5 BIG-IP {{ f5_address }} 165 | bigip_ssl_certificate: 166 | state: present 167 | provider: "{{ f5_provider }}" 168 | name: "{{ cert_name }}" 169 | partition: "{{ f5_partition }}" 170 | content: "{{ lookup('file', './tmp/' + cert_name + '.remote') }}" 171 | delegate_to: localhost 172 | 173 | - name: Create CA Bundle on F5 BIG-IP {{ f5_address }} 174 | bigip_ssl_certificate: 175 | state: present 176 | provider: "{{ f5_provider }}" 177 | name: "{{ chain_name }}" 178 | partition: "{{ f5_partition }}" 179 | content: "{{ lookup('file', './tmp/' + chain_name + '.remote') }}" 180 | delegate_to: localhost 181 | 182 | ``` 183 | 184 | ## Step 3: Create a Client SSL Profile on F5 BIG-IP 185 | 186 | After copying the certificate files to the F5 BIG-IP, we need to specify where those files will be used. You can do this by adding `Client SSL profile`, which enables the F5 BIG-IP system to accept and terminate client requests that are using SSL. And once again, we're specifying the credentials used to execute this task on the F5 instance, as well as specifying the certificate files to use. 187 | 188 | ```yaml 189 | --- 190 | - name: Create Client SSL Profile on F5 BIG-IP {{ f5_address }} 191 | bigip_profile_client_ssl: 192 | state: present 193 | provider: "{{ f5_provider }}" 194 | name: "clientssl_{{ test_site.name }}" 195 | partition: "{{ f5_partition }}" 196 | parent: "clientssl" 197 | cert_key_chain: 198 | - cert: "{{ cert_name }}" 199 | key: "{{ key_name }}" 200 | chain: "{{ chain_name }}" 201 | delegate_to: localhost 202 | ``` 203 | 204 | ## Step 4: Create a Pool on F5 BIG-IP 205 | 206 | The next step is to add a pool, which is a collection of resources to which F5 will distribute the requests. This provides load balancing [functionality](https://www.f5.com/services/resources/glossary/load-balancer) by using the [round-robin](https://en.wikipedia.org/wiki/Round-robin_scheduling) method. In this case the members of the pool are the NGINX servers defined in the variables file. 207 | 208 | ```yaml 209 | --- 210 | 211 | - name: Create Pool on F5 BIG-IP {{ f5_address }} 212 | bigip_pool: 213 | state: present 214 | provider: "{{ f5_provider }}" 215 | name: "pool_{{ test_site.name }}" 216 | partition: "{{ f5_partition }}" 217 | lb_method: round-robin 218 | delegate_to: localhost 219 | ``` 220 | 221 | ## Step 5: Add Pool members on F5 BIG-IP 222 | 223 | Once the pool is created, Ansible needs to create the pool member in the F5 BIG-IP instance. The members actually serve the requests (NGINX servers hosting the application). Ansible will use the host and port variables defined in the variables file for each one of the pool members defined in the `f5_pool_members` dictionary. 224 | 225 | ```yaml 226 | --- 227 | 228 | - name: Add Pool Members on F5 BIG-IP {{ f5_address }} 229 | bigip_pool_member: 230 | state: present 231 | provider: "{{ f5_provider }}" 232 | partition: "{{ f5_partition }}" 233 | host: "{{ item.host }}" 234 | port: "{{ item.port }}" 235 | pool: "pool_{{ test_site.name }}" 236 | with_items: "{{ f5_pool_members }}" 237 | delegate_to: localhost 238 | ``` 239 | 240 | ## Step 6: Create a virtual server on F5 BIG-IP 241 | 242 | Now that the pool and the nodes are members of the pool, Ansible has to create a virtual IP address in order to send the external requests to pool members. The following task creates the virtual server and assigns it the virtual IP defined in the variables files, as well as the port and Client SSL profile created previously. 243 | 244 | ```yaml 245 | --- 246 | 247 | - name: Create Virtual Server on F5 BIG-IP {{ f5_address }} 248 | bigip_virtual_server: 249 | state: present 250 | provider: "{{ f5_provider }}" 251 | name: "vs_{{ test_site.name }}" 252 | partition: "{{ f5_partition }}" 253 | description: "Provisioned by Ansible" 254 | destination: "{{ f5_virtual_ip }}" 255 | port: "{{ f5_virtual_port }}" 256 | snat: Automap 257 | pool: "pool_{{ test_site.name }}" 258 | profiles: 259 | - "clientssl_{{ test_site.name }}" 260 | delegate_to: localhost 261 | ``` 262 | 263 | ## Step 7: Execute the playbook 264 | 265 | After you finish the [playbook](f5_create_playbook.yaml), use the following command to run it: 266 | 267 | ```bash 268 | ansible-playbook f5_create_playbook.yaml --ask-vault-pass 269 | ``` 270 | 271 | If done correctly, you should see output similar to the following: 272 | 273 | [![asciicast](https://asciinema.org/a/ff3Ulbvvr6XdP8XTn4gbCFLyy.svg)](https://asciinema.org/a/ff3Ulbvvr6XdP8XTn4gbCFLyy) 274 | 275 | ## Reversing the changes performed 276 | 277 | In this example, we include a [playbook that lets you revert the changes made by running f5_create_playbook.yaml](f5_delete_playbook.yaml). Use the following command to run it: 278 | 279 | ```bash 280 | ansible-playbook f5_delete_playbook.yaml 281 | ``` 282 | -------------------------------------------------------------------------------- /examples/citrix_adc/README.md: -------------------------------------------------------------------------------- 1 | # Configuring secure application delivery using Citrix ADC and the Venafi Ansible Role 2 | 3 | In this example, we'll show you how to better secure application delivery using the Venafi Ansible Role with your [Citrix ADC](https://www.citrix.com/products/citrix-adc/) instance. 4 | Adding Venafi enables you to manage certificates more securely as part of the [TLS Termination](https://en.wikipedia.org/wiki/TLS_termination_proxy) process on your load balancer. 5 | 6 | ## Who should use this example? 7 | 8 | The steps described in this document are typically performed by _DevOps engineers_ or _system administrators_. Generally, you'll need a basic undestanding of Citrix ADC, Venafi Trust Protection Platform or Venafi Cloud, and the required permissions for completing the tastks described in the example. 9 | 10 | ## About this example 11 | 12 | An _application delivery controller_ (ADC) is used to increase the capacity and reliability of applications. ADC improves the performance of applications by decreasing the load on associated servers while managing and maintaining application and network sessions. But ADC configuration can become a long process. However, you can actually automate the process by using a configuration management tool. 13 | 14 | In this example, we use [RedHat Ansible](https://www.ansible.com/) with the _Venafi Ansible Role_ to automate the process of requesting, retrieving and installing a certificate as part of SSL termination on an ADC (specifically, Citrix ADC) for load balancing web traffic. We'll also utilize three HTTP servers contained in a cluster as the endpoints that are sending and receiving web traffic and being managed by Citrix ADC. 15 | 16 | Later in this example, you'll generate a certificate for the `demo-citrix.venafi.example` domain using the Venafi Ansible Role to request and retrieve it from either _Venafi Trust Protection Platform_ or _Venafi Cloud_ services. Then you'll copy the certificate files (certificate, private key, chain Bundle) to the Citrix ADC. Finally, you'll configure Citrix ADC to distribute the traffic between three NGINX servers using the round-robin load balancing method. Here below you can find a diagram of what we are trying to accomplish. 17 | 18 | > **NOTE** In our example, we suggest that you use the round-robin balancing method. But keep in mind that there are [other methods](https://docs.citrix.com/en-us/citrix-adc/current-release/load-balancing/load-balancing-customizing-algorithms.html) that might be more suitable for your specific use case. 19 | 20 | ![AnsibleVenafi](venafi_ansible_role.png) 21 | 22 | ## Prerequisites 23 | 24 | > **BEST PRACTICES** In general, be careful when using self-signed certificates because of the inherent risks of no identity verification or trust control. The public and private keys are both held by the same entity. Also, self-signed certificates cannot be revoked; they can only be replaced. If an attacker has already gained access to a system, the attacker can spoof the identity of the subject. Of course, CAs can revoke a certificate only when they discover the compromise. 25 | 26 | To perform the tasks described in this example, you'll need: 27 | 28 | - The Venafi Ansible Role installed on your machine; you can install it using `ansible-galaxy` [as described here](https://github.com/Venafi/ansible-role-venafi#using-with-ansible-galaxy) 29 | - Access to either Venafi Trust Protection Platform or Venafi Cloud services (the `credentials.yml` [file](https://github.com/Venafi/ansible-role-venafi#using-with-ansible-galaxy) is used in this example) 30 | - If you are working with Trust Protection Platform, obtain the `access_token` and `refresh_token` using the [VCert CLI](https://github.com/Venafi/vcert/blob/master/README-CLI-PLATFORM.md#obtaining-an-authorization-token). 31 | - Administration access to a Citrix ADC instance 32 | - Nitro Python SDK (available from https://www.citrix.com/downloads/netscaler-adc or from the _Downloads_ tab of the Citrix ADC GUI) 33 | - Citrix ADC modules for Ansible installed from `ansible-galaxy` (for installation instructions, see [this guide](https://github.com/citrix/citrix-adc-ansible-modules#installation)) 34 | - A set of three (3) NGINX servers running your application 35 | 36 | ## Getting started 37 | 38 | Here are the steps we'll complete as we go through this example: 39 | 40 | 1. Retrieve a certificate using the Venafi Ansible Role 41 | 2. Copy the retrieved certificate files to Citrix ADC 42 | 3. Create a certificate-key pair on Citrix ADC 43 | 4. Create HTTP back-end services on Citrix ADC 44 | 5. Create a virtual server on Citrix ADC 45 | 6. Execute the playbook 46 | 47 | ## Step 1: Retrieve a certificate using the Venafi Ansible Role 48 | 49 | ### Step 1a: Creating variables file 50 | 51 | The first step is to create the `variables.yaml` file, in this file are defined the variables used during the execution of the playbook such as: 52 | 53 | - The Citrix ADC management IP address. 54 | - The credentials used to manage the Citrix ADC. 55 | - The CN needed to generate the certificate. 56 | - The Virtual IP and port on which all the HTTPS traffic will be handled. 57 | - The http services (the NGINX servers running the application). 58 | - The name for the certificate files which will be copied to the Citrix ADC. 59 | 60 | ```yaml 61 | 62 | adc_address: "192.168.5.188" 63 | adc_username: "youruser" 64 | adc_password: "yourpassword" 65 | 66 | test_site: 67 | name: "demo-citrix" 68 | domain: "venafi.example" 69 | 70 | adc_virtual_ip: "192.168.3.167" 71 | adc_virtual_port: "443" 72 | 73 | http_service: 192.168.6.201 74 | port1: 8001 75 | port2: 8002 76 | port3: 8003 77 | 78 | cert_name: "{{ test_site.name }}.crt" 79 | key_name: "{{ test_site.name }}.key" 80 | chain_name: "{{ test_site.name }}-ca-bundle.crt" 81 | ``` 82 | 83 | ### Step 1b: Creating the playbook 84 | 85 | Start by creating a YAML file named `citrix_create_playbook.yaml`, inside, define a name for the playbook, the hosts in which the tasks will be executed, the type of connection to use, the Citrix ADC collection and specify the variables file created in the previous step : 86 | 87 | ```yaml 88 | - name: Create Critx ADC Application 89 | hosts: localhost 90 | connection: local 91 | collections: citrix.adc 92 | 93 | vars_files: 94 | - variables.yaml 95 | ``` 96 | 97 | 98 | ### Step 1c: Requesting and retrieving the certificate using Venafi Role 99 | 100 | In the following block of instructions the Venafi Ansible Role is being specified along with the variables it needs to request and retrieve the certificate from the Venafi services, by adding these instructions the Ansible Role will: 101 | 102 | - Request and retrieve a certificate which common and alternate names are `demo-citrix.venafi.example`. 103 | - Create a RSA private key of a size of 2048 bits. 104 | - Generate a chain bundle file where the CA certificate will be place at the end of the file. 105 | - Create a `tmp` directory on the current working directory which will store the retrieved certificate files. 106 | - 3 files will be retrieved and stored using the names on the variables file (*demo-citrix.{crt,key,-ca-bundle.crt}*). 107 | - Simulate the copy of the retrieved files to the remote host by generating a duplicate of them adding the `.remote` extension (the certificate files retrieved are going to be copied to the Citrix ADC using the Citrix ADC Ansible modules that's the reason why the options `certificate_copy_private_key_to_remote` and `certificate_remote_execution` are set to `false`). 108 | 109 | 110 | ```yaml 111 | --- 112 | 113 | roles: 114 | - role: venafi.ansible_role_venafi 115 | 116 | certificate_common_name: "{{ test_site.name }}.{{ test_site.domain }}" 117 | certificate_alt_name: "DNS:{{ test_site.name }}.{{ test_site.domain }}" 118 | certificate_privatekey_type: "RSA" 119 | certificate_privatekey_size: "2048" 120 | certificate_chain_option: "last" 121 | 122 | certificate_cert_dir: "./tmp" 123 | certificate_cert_path: "./tmp/{{ cert_name }}" 124 | certificate_chain_path: "./tmp/{{ chain_name }}" 125 | certificate_privatekey_path: "./tmp/{{ key_name }}" 126 | certificate_copy_private_key_to_remote: false 127 | 128 | certificate_remote_execution: false 129 | certificate_remote_privatekey_path: "./tmp/{{ key_name }}.remote" 130 | certificate_remote_cert_path: "./tmp/{{ cert_name }}.remote" 131 | certificate_remote_chain_path: "./tmp/{{ chain_name }}.remote" 132 | ``` 133 | 134 | ## Step 2: Copy the retrieved certificate files to Citrix ADC 135 | 136 | By adding the following instructions to the playbook, we specify the actions the playbook will execute. Ansible will connect to the Citrix ADC (using the credentials specified in the variables file) and then it will create the key, CA bundle and certificate using the local files retrieved in the previous step. 137 | 138 | ```yaml 139 | --- 140 | 141 | tasks: 142 | - name: Copy Private Key to Citrix ADC {{ adc_address }} 143 | citrix_adc_system_file: 144 | nsip: "{{ adc_address }}" 145 | nitro_user: "{{ adc_username }}" 146 | nitro_pass: "{{ adc_password }}" 147 | nitro_protocol: http 148 | validate_certs: false 149 | state: present 150 | filename: "{{ key_name }}" 151 | filelocation: "/nsconfig/ssl/" 152 | filecontent: "{{ lookup('file', './tmp/' + key_name) }}" 153 | delegate_to: localhost 154 | 155 | - name: Copy Certificate to Citrix ADC {{ adc_address }} 156 | citrix_adc_system_file: 157 | nsip: "{{ adc_address }}" 158 | nitro_user: "{{ adc_username }}" 159 | nitro_pass: "{{ adc_password }}" 160 | nitro_protocol: http 161 | validate_certs: false 162 | state: present 163 | filename: "{{ cert_name }}" 164 | filelocation: "/nsconfig/ssl/" 165 | filecontent: "{{ lookup('file', './tmp/' + cert_name + '.remote') }}" 166 | delegate_to: localhost 167 | 168 | - name: Copy CA Bundle to Citrix ADC {{ adc_address }} 169 | citrix_adc_system_file: 170 | nsip: "{{ adc_address }}" 171 | nitro_user: "{{ adc_username }}" 172 | nitro_pass: "{{ adc_password }}" 173 | nitro_protocol: http 174 | validate_certs: false 175 | state: present 176 | filename: "{{ chain_name }}" 177 | filelocation: "/nsconfig/ssl" 178 | filecontent: "{{ lookup('file', './tmp/' + chain_name + '.remote') }}" 179 | delegate_to: localhost 180 | 181 | ``` 182 | 183 | ## Step 3: Create a certificate-key pair on Citrix ADC 184 | 185 | To be able to handle the HTTPS requests, Ansible needs to create a cert-key pair on the Citrix ADC using the files you copied in the previous step. 186 | ```yaml 187 | --- 188 | 189 | - name: Create Certkey on Citrix ADC {{ adc_address }} 190 | citrix_adc_ssl_certkey: 191 | nsip: "{{ adc_address }}" 192 | nitro_user: "{{ adc_username }}" 193 | nitro_pass: "{{ adc_password }}" 194 | nitro_protocol: http 195 | validate_certs: false 196 | state: present 197 | certkey: "{{ test_site.name }}.{{ test_site.domain }}_certkey" 198 | cert: "/nsconfig/ssl/{{ cert_name }}" 199 | key: "/nsconfig/ssl/{{ key_name }}" 200 | ``` 201 | 202 | ## Step 4: Create HTTP back-end services on Citrix ADC 203 | 204 | After you create the cert-key pair on the Citrix ADC, Ansible needs to create the HTTP services on the Citric ADC instance. These services are the ones that will actually serve the requests (NGINX servers hosting the application). Ansible will use the host and port variables defined in the variables file for each service. 205 | 206 | ```yaml 207 | --- 208 | 209 | - name: Create service-http-1 on Citrix ADC {{ adc_address }} 210 | citrix_adc_service: 211 | nsip: "{{ adc_address }}" 212 | nitro_user: "{{ adc_username }}" 213 | nitro_pass: "{{ adc_password }}" 214 | nitro_protocol: http 215 | validate_certs: false 216 | state: present 217 | name: service-http-1 218 | servicetype: HTTP 219 | ip: "{{ http_service }}" 220 | ipaddress: "{{ http_service }}" 221 | port: "{{ port1 }}" 222 | delegate_to: localhost 223 | 224 | - name: Create service-http-2 on Citrix ADC {{ adc_address }} 225 | citrix_adc_service: 226 | nsip: "{{ adc_address }}" 227 | nitro_user: "{{ adc_username }}" 228 | nitro_pass: "{{ adc_password }}" 229 | nitro_protocol: http 230 | validate_certs: false 231 | state: present 232 | name: service-http-2 233 | servicetype: HTTP 234 | ip: "{{ http_service }}" 235 | ipaddress: "{{ http_service }}" 236 | port: "{{ port2 }}" 237 | delegate_to: localhost 238 | 239 | - name: Create service-http-3 on Citrix ADC {{ adc_address }} 240 | citrix_adc_service: 241 | nsip: "{{ adc_address }}" 242 | nitro_user: "{{ adc_username }}" 243 | nitro_pass: "{{ adc_password }}" 244 | nitro_protocol: http 245 | validate_certs: false 246 | state: present 247 | name: service-http-3 248 | servicetype: HTTP 249 | ip: "{{ http_service }}" 250 | ipaddress: "{{ http_service }}" 251 | port: "{{ port3 }}" 252 | delegate_to: localhost 253 | ``` 254 | 255 | ## Step 5: Create a virtual server on Citrix ADC 256 | 257 | Now that HTTP services have been created, Ansible must create a virtual IP address in order to send the external requests to the HTTP back-end services. The following task creates the virtual server and assigns it the virtual IP defined in the variables file, the port, the certificate-key pair and the HTTP services previously created, as well as the round-robin load balancing [method](https://docs.citrix.com/en-us/citrix-adc/current-release/load-balancing/load-balancing-customizing-algorithms.html) which will allow the virtual server to distribute the load between the NGINX servers hosting the application. 258 | 259 | ```yaml 260 | --- 261 | 262 | - name: Create lb vserver on Citrix ADC {{ adc_address }} 263 | citrix_adc_lb_vserver: 264 | nsip: "{{ adc_address }}" 265 | nitro_user: "{{ adc_username }}" 266 | nitro_pass: "{{ adc_password }}" 267 | nitro_protocol: http 268 | validate_certs: false 269 | state: present 270 | name: "vs-{{ test_site.name }}.{{ test_site.domain }}" 271 | servicetype: SSL 272 | timeout: 2 273 | ipv46: "{{ adc_virtual_ip }}" 274 | port: "{{ adc_virtual_port }}" 275 | lbmethod: ROUNDROBIN 276 | ssl_certkey: "{{ test_site.name }}.{{ test_site.domain }}_certkey" 277 | servicebindings: 278 | - servicename: service-http-1 279 | weight: 80 280 | - servicename: service-http-2 281 | weight: 60 282 | - servicename: service-http-3 283 | weight: 40 284 | disabled: no 285 | delegate_to: localhost 286 | 287 | ``` 288 | 289 | ## Step 6: Executing the playbook 290 | 291 | After you finish the [playbook](citrix_create_playbook.yaml), use the following command to run it: 292 | 293 | ```bash 294 | ansible-playbook citrix_create_playbook.yaml --ask-vault-pass 295 | ``` 296 | 297 | If done correctly, you should see output similar to the following: 298 | 299 | [![asciicast](https://asciinema.org/a/d9BlbbXWGNFcSzQUkIAeJ0Qcr.svg)](https://asciinema.org/a/d9BlbbXWGNFcSzQUkIAeJ0Qcr) 300 | 301 | ## Reversing the changes performed 302 | 303 | In this example, we include a [playbook that lets you revert the changes made by running citrix_create_playbook.yaml](citrix_delete_playbook.yaml). Use the following command to run it: 304 | 305 | ```bash 306 | ansible-playbook citrix_delete_playbook.yaml 307 | ``` 308 | -------------------------------------------------------------------------------- /library/venafi_certificate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Copyright 2019 Venafi, Inc. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | 19 | from __future__ import absolute_import, print_function, unicode_literals 20 | import time 21 | import datetime 22 | import os.path 23 | import random 24 | from ansible.module_utils.basic import AnsibleModule 25 | from ansible.module_utils._text import to_bytes, to_text 26 | 27 | HAS_VCERT = HAS_CRYPTOGRAPHY = True 28 | try: 29 | from vcert import CertificateRequest, Connection, KeyType,\ 30 | venafi_connection 31 | except ImportError: 32 | HAS_VCERT = False 33 | try: 34 | from cryptography import x509 35 | from cryptography.hazmat.backends import default_backend 36 | from cryptography.x509.oid import NameOID, ExtensionOID 37 | from cryptography.hazmat.primitives import serialization, hashes 38 | except ImportError: 39 | HAS_CRYPTOGRAPHY = False 40 | 41 | ANSIBLE_METADATA = { 42 | 'metadata_version': '1.1', 43 | 'status': ['preview'], 44 | 'supported_by': 'community' 45 | } 46 | 47 | DOCUMENTATION = ''' 48 | --- 49 | module: venafi_certificate_module 50 | 51 | short_description: This is Venafi certificate module for working with 52 | Venafi Cloud or Venafi Trusted Platform 53 | 54 | version_added: "2.7" 55 | 56 | description: 57 | - This is Venafi certificate module for working with Venafi Cloud or 58 | Venafi Trust Platform" 59 | 60 | options: 61 | force: 62 | default: False 63 | type: bool 64 | description: 65 | - Generate the certificate, even if it already exists. 66 | 67 | state: 68 | default: "present" 69 | choices: [ present, absent ] 70 | description: 71 | - > Whether the certificate should exist or not, 72 | taking action if the state is different from what is stated. 73 | 74 | renew: 75 | default: True 76 | type: bool 77 | description: 78 | - Try to renew certificate if is existing but not valid. 79 | 80 | cert_path: 81 | required: true 82 | description: 83 | - Remote absolute path where the generated certificate file should 84 | be created or is already located. 85 | 86 | chain_path: 87 | required: false 88 | description: 89 | - > Remote absolute path where the generated certificate chain file 90 | should 91 | be created or is already located. If set certificate and chain will 92 | be in separated files. 93 | 94 | chain_option: 95 | required: false 96 | default: "last" 97 | description: 98 | - > Specify ordering certificates in chain. Root can be "first" or 99 | "last" 100 | 101 | common_name: 102 | required: false 103 | aliases: [ 'CN', 'commonName' ] 104 | description: 105 | - commonName field of the certificate signing request subject 106 | 107 | alt_name: 108 | required: false 109 | aliases: [ 'alt_name' ] 110 | description: 111 | - SAN extension to attach to the certificate signing request 112 | - This can either be a 'comma separated string' or a YAML list. 113 | - Values should be prefixed by their options. (IP:,email:,DNS:) 114 | 115 | privatekey_path: 116 | required: false 117 | description: 118 | - > Path to the private key to use when signing the certificate 119 | signing request. If not set will be placed 120 | near certificate with key suffix. 121 | 122 | privatekey_type: 123 | default: "RSA" 124 | required: false 125 | description: 126 | - Type of private key. RSA or ECDSA 127 | 128 | privatekey_size: 129 | required: false 130 | default: 2048 131 | description: 132 | - Size (in bits) of the TLS/SSL key to generate. Used only for RSA. 133 | 134 | privatekey_curve: 135 | required: false 136 | default: "P521" 137 | description: 138 | - | Curves name for ECDSA algorithm. Choices are "P224", "P256", 139 | "P384", "P521". 140 | 141 | privatekey_passphrase: 142 | required: false 143 | description: 144 | - The passphrase for the privatekey. 145 | 146 | privatekey_reuse: 147 | required: false 148 | type: bool 149 | description: 150 | - If set to false new key won't be generated 151 | 152 | before_expired_hours: 153 | required: false 154 | type: int 155 | default: 72 156 | description: 157 | - | If certificate will expire in less hours than this value 158 | module will try to renew it. 159 | extends_documentation_fragment: 160 | - files 161 | 162 | author: 163 | - Alexander Rykalin (@arykalin) 164 | ''' 165 | 166 | EXAMPLES = ''' 167 | # Enroll fake certificate for testing purposes 168 | - name: venafi_certificate_fake 169 | connection: local 170 | hosts: localhost 171 | tags: 172 | - fake 173 | tasks: 174 | - name: venafi_certificate 175 | venafi_certificate: 176 | test_mode: true 177 | common_name: 'testcert-fake-{{ 99999999 | random }}.example.com' 178 | alt_name: 'DNS:www.venafi.example,DNS:m.venafi.example' 179 | cert_path: '/tmp' 180 | register: certout 181 | - name: dump test output 182 | debug: 183 | msg: '{{ certout }}' 184 | 185 | # Enroll Platform certificate with a lot of alt names 186 | - name: venafi_certificate_tpp 187 | connection: local 188 | hosts: localhost 189 | tags: 190 | - tpp 191 | tasks: 192 | - name: venafi_certificate 193 | venafi_certificate: 194 | url: 'https://venafi.example.com/vedsdk' 195 | user: 'admin' 196 | password: !vault | 197 | $ANSIBLE_VAULT;1.1;AES256 198 | zone: 'example\\\\policy' 199 | cert_path: '/tmp' 200 | common_name: 'testcert-tpp-{{ 99999999 | random }}.example.com' 201 | alt_name: | 202 | IP:192.168.1.1,DNS:www.venafi.example.com, 203 | DNS:m.venafi.example.com,email:test@venafi.com,IP Address:192.168.2.2 204 | register: certout 205 | - name: dump test output 206 | debug: 207 | msg: '{{ certout }}' 208 | 209 | # Enroll Cloud certificate 210 | - name: venafi_certificate_cloud 211 | connection: local 212 | hosts: localhost 213 | tags: 214 | - cloud 215 | tasks: 216 | - name: venafi_certificate 217 | venafi_certificate: 218 | token: !vault | 219 | $ANSIBLE_VAULT;1.1;AES256 220 | zone: 'Default' 221 | cert_path: '/tmp' 222 | common_name: 'testcert-cloud.example.com' 223 | register: certout 224 | - name: dump test output 225 | debug: 226 | msg: '{{ certout }}' 227 | ''' 228 | 229 | RETURN = ''' 230 | privatekey_filename: 231 | description: Path to the TLS/SSL private key the CSR was generated for 232 | returned: changed or success 233 | type: string 234 | sample: /etc/ssl/private/venafi.example.pem 235 | 236 | privatekey_size: 237 | description: Size (in bits) of the TLS/SSL private key 238 | returned: changed or success 239 | type: int 240 | sample: 4096 241 | 242 | privatekey_curve: 243 | description: > ECDSA curve of generated private key. Variants are "P521", 244 | "P384", "P256", "P224". 245 | 246 | returned: changed or success 247 | type: string 248 | sample: "P521" 249 | 250 | privatekey_type: 251 | description: > Algorithm used to generate the TLS/SSL private key. 252 | Variants are RSA or ECDSA 253 | 254 | returned: changed or success 255 | type: string 256 | sample: RSA 257 | 258 | certificate_filename: 259 | description: Path to the signed certificate 260 | returned: changed or success 261 | type: string 262 | sample: /etc/ssl/www.venafi.example.pem 263 | 264 | chain_filename: 265 | description: > Path to the chain of CA certificates that link 266 | the certificate to a trust anchor 267 | 268 | returned: changed or success 269 | type: string 270 | sample: /etc/ssl/www.venafi.example_chain.pem 271 | ''' 272 | # Some strings variables 273 | STRING_FAILED_TO_CHECK_CERT_VALIDITY = "Certificate is not yet valid, " \ 274 | "has expired, or has CN or SANs " \ 275 | "that differ from the request" 276 | STRING_PKEY_NOT_MATCHED = "Private key does not match certificate public key" 277 | STRING_BAD_PKEY = "Private key file does not contain a valid private key" 278 | STRING_CERT_FILE_NOT_EXISTS = "Certificate file does not exist" 279 | STRING_BAD_PERMISSIONS = "Insufficient file permissions" 280 | 281 | 282 | class VCertificate: 283 | def __init__(self, module): 284 | """ 285 | :param AnsibleModule module: 286 | """ 287 | self.common_name = module.params['common_name'] 288 | 289 | self.test_mode = module.params['test_mode'] 290 | self.url = module.params['url'] 291 | self.password = module.params['password'] 292 | self.access_token = module.params['access_token'] 293 | self.token = module.params['token'] 294 | self.user = module.params['user'] 295 | self.zone = module.params['zone'] 296 | self.privatekey_filename = module.params['privatekey_path'] 297 | self.certificate_filename = module.params['cert_path'] 298 | self.privatekey_type = module.params['privatekey_type'] 299 | 300 | if self.user != "": 301 | module.warn("User is deprecated use access token instead") 302 | if self.password != "": 303 | module.warn("Password is deprecated use access token instead") 304 | 305 | if module.params['privatekey_curve']: 306 | if not module.params['privatekey_type']: 307 | module.fail_json( 308 | msg="privatekey_type should be " 309 | "set if privatekey_curve configured") 310 | self.privatekey_curve = module.params['privatekey_curve'] 311 | if module.params['privatekey_size']: 312 | if not module.params['privatekey_type']: 313 | module.fail_json( 314 | msg="privatekey_type should be set if " 315 | "privatekey_size configured") 316 | self.privatekey_size = module.params['privatekey_size'] 317 | self.privatekey_passphrase = module.params['privatekey_passphrase'] 318 | self.privatekey_reuse = module.params['privatekey_reuse'] 319 | self.chain_filename = module.params['chain_path'] 320 | self.csr_path = module.params['csr_path'] 321 | self.args = "" 322 | self.changed = False 323 | self.module = module 324 | self.ip_addresses = [] 325 | self.email_addresses = [] 326 | self.san_dns = [] 327 | self.changed_message = [] 328 | if module.params['alt_name']: 329 | for n in module.params['alt_name']: 330 | if n.startswith(("IP:", "IP Address:")): 331 | ip = n.split(":", 1)[1] 332 | self.ip_addresses.append(ip) 333 | elif n.startswith("DNS:"): 334 | ns = n.split(":", 1)[1] 335 | self.san_dns.append(ns) 336 | elif n.startswith("email:"): 337 | mail = n.split(":", 1)[1] 338 | self.email_addresses.append(mail) 339 | else: 340 | self.module.fail_json( 341 | msg="Failed to determine extension type: %s" % n) 342 | trust_bundle = module.params['trust_bundle'] 343 | if trust_bundle: 344 | if self.access_token and self.access_token != "": 345 | self.conn = venafi_connection( 346 | url=self.url, user=None, password=None, 347 | access_token=self.access_token, 348 | refresh_token=None, 349 | http_request_kwargs={"verify": trust_bundle}, 350 | api_key=None, fake=self.test_mode) 351 | else: 352 | self.conn = Connection( 353 | url=self.url, token=self.token, password=self.password, 354 | user=self.user, fake=self.test_mode, 355 | http_request_kwargs={"verify": trust_bundle}) 356 | else: 357 | if self.access_token and self.access_token != "": 358 | self.conn = venafi_connection( 359 | url=self.url, access_token=self.access_token, 360 | user=None, password=None, api_key=None, fake=self.test_mode) 361 | else: 362 | self.conn = Connection( 363 | url=self.url, token=self.token, fake=self.test_mode, 364 | user=self.user, password=self.password) 365 | self.before_expired_hours = module.params['before_expired_hours'] 366 | 367 | def check_dirs_existed(self): 368 | cert_dir = os.path.dirname(self.certificate_filename or "/a") 369 | key_dir = os.path.dirname(self.privatekey_filename or "/a") 370 | chain_dir = os.path.dirname(self.chain_filename or "/a") 371 | ok = True 372 | for p in set((cert_dir, key_dir, chain_dir)): 373 | if os.path.isdir(p): 374 | continue 375 | elif os.path.exists(p): 376 | self.module.fail_json( 377 | msg="Path %s already exists but this is not directory" % p) 378 | elif not os.path.exists(p): 379 | self.module.fail_json(msg="Directory %s does not exists" % p) 380 | ok = False 381 | return ok 382 | 383 | def _check_private_key_correct(self): 384 | if not self.privatekey_filename: 385 | return None 386 | if not os.path.exists(self.privatekey_filename): 387 | return False 388 | private_key = to_text(open(self.privatekey_filename, "rb").read()) 389 | 390 | r = CertificateRequest(private_key=private_key, 391 | key_password=self.privatekey_passphrase) 392 | key_type = {"RSA": "rsa", "ECDSA": "ec", "EC": "ec"}. \ 393 | get(self.privatekey_type) 394 | if key_type and key_type != r.key_type.key_type: 395 | return False 396 | if key_type == "rsa" and self.privatekey_size: 397 | if self.privatekey_size != r.key_type.option: 398 | return False 399 | if key_type == "ec" and self.privatekey_curve: 400 | if self.privatekey_curve != r.key_type.option: 401 | return False 402 | return True 403 | 404 | def enroll(self): 405 | request = CertificateRequest( 406 | common_name=self.common_name, 407 | key_password=self.privatekey_passphrase, 408 | origin="Red Hat Ansible" 409 | ) 410 | zone_config = self.conn.read_zone_conf(self.zone) 411 | request.update_from_zone_config(zone_config) 412 | 413 | use_existed_key = False 414 | if self._check_private_key_correct() and not self.privatekey_reuse: 415 | private_key = to_text(open(self.privatekey_filename, "rb").read()) 416 | request.private_key = private_key 417 | use_existed_key = True 418 | elif self.privatekey_type: 419 | key_type = {"RSA": "rsa", "ECDSA": "ec", "EC": "ec"}. \ 420 | get(self.privatekey_type) 421 | if not key_type: 422 | self.module.fail_json(msg=( 423 | "Failed to determine key type: %s." 424 | "Must be RSA or ECDSA" % self.privatekey_type)) 425 | if key_type == "rsa": 426 | request.key_type = KeyType(KeyType.RSA, 427 | self.privatekey_size) 428 | elif key_type == "ecdsa" or "ec": 429 | request.key_type = KeyType(KeyType.ECDSA, 430 | self.privatekey_curve) 431 | else: 432 | self.module.fail_json(msg=( 433 | "Failed to determine key type: %s." 434 | "Must be RSA or ECDSA" % self.privatekey_type)) 435 | 436 | request.ip_addresses = self.ip_addresses 437 | request.san_dns = self.san_dns 438 | request.email_addresses = self.email_addresses 439 | 440 | request.chain_option = self.module.params['chain_option'] 441 | try: 442 | csr = open(self.csr_path, "rb").read() 443 | request.csr = csr 444 | except Exception as e: 445 | self.module.log(msg=str(e)) 446 | pass 447 | 448 | self.conn.request_cert(request, self.zone) 449 | print(request.csr) 450 | while True: 451 | cert = self.conn.retrieve_cert(request) # vcert.Certificate 452 | if cert: 453 | break 454 | else: 455 | time.sleep(5) 456 | if self.chain_filename: 457 | self._atomic_write(self.chain_filename, "\n".join(cert.chain)) 458 | self._atomic_write(self.certificate_filename, cert.cert) 459 | else: 460 | self._atomic_write(self.certificate_filename, cert.full_chain) 461 | if not use_existed_key: 462 | self._atomic_write(self.privatekey_filename, 463 | request.private_key_pem) 464 | # todo: server generated private key 465 | 466 | def _atomic_write(self, path, content): 467 | suffix = ".atomic_%s" % random.randint(100, 100000) 468 | try: 469 | with open(path + suffix, "wb") as f: 470 | f.write(to_bytes(content)) 471 | except OSError as e: 472 | self.module.fail_json(msg="Failed to write file %s: %s" % ( 473 | path + suffix, e)) 474 | 475 | self.module.atomic_move(path + suffix, path) 476 | self.changed = True 477 | self._check_and_update_permissions(path) 478 | 479 | def _check_and_update_permissions(self, path): 480 | file_args = self.module.load_file_common_arguments(self.module.params) 481 | file_args['path'] = path 482 | if self.module.set_fs_attributes_if_different(file_args, False): 483 | self.changed = True 484 | 485 | def _check_dns_sans_correct(self, actual, required, optional): 486 | if len(optional) == 0 and len(actual) != len(required): 487 | return False 488 | for i in required: 489 | found = False 490 | for j in actual: 491 | if i == j: 492 | found = True 493 | break 494 | if not found: 495 | return False 496 | combined = required + optional 497 | for i in actual: 498 | found = False 499 | for j in combined: 500 | if i == j: 501 | found = True 502 | break 503 | if not found: 504 | return False 505 | return True 506 | 507 | def _check_certificate_validity(self, cert, validate): 508 | cn = cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value 509 | if cn != self.common_name: 510 | self.changed_message.append( 511 | 'Certificate CN %s not matched to expected %s' 512 | % (cn, self.common_name) 513 | ) 514 | return False 515 | # Check if certificate not already expired 516 | if cert.not_valid_after < datetime.datetime.now(): 517 | self.changed_message.append( 518 | 'Certificate expiration date %s ' 519 | 'is less than current time %s (certificate expired)' 520 | % (cert.not_valid_after, self.before_expired_hours) 521 | ) 522 | return False 523 | # Check if certificate expiring time is greater than 524 | # before_expired_hours (only for creating new certificate) 525 | if not validate: 526 | if cert.not_valid_after - datetime.timedelta( 527 | hours=self.before_expired_hours) < datetime.datetime.now(): 528 | self.changed_message.append( 529 | 'Hours before certificate expiration date %s ' 530 | 'is less than before_expired_hours value %s' 531 | % (cert.not_valid_after, self.before_expired_hours) 532 | ) 533 | return False 534 | if cert.not_valid_before - datetime.timedelta( 535 | hours=24) > datetime.datetime.now(): 536 | self.changed_message.append( 537 | "Certificate expiration date %s " 538 | "is set to future from server time %s." 539 | % (cert.not_valid_before - 540 | datetime.timedelta(hours=24), 541 | (datetime.datetime.now())) 542 | ) 543 | return False 544 | ips = [] 545 | dns = [] 546 | alternative_names = cert.extensions.get_extension_for_oid( 547 | ExtensionOID.SUBJECT_ALTERNATIVE_NAME).value 548 | for e in alternative_names: 549 | if isinstance(e, x509.general_name.DNSName): 550 | dns.append(e.value) 551 | elif isinstance(e, x509.general_name.IPAddress): 552 | ips.append(e.value.exploded) 553 | if self.ip_addresses and sorted(self.ip_addresses) != sorted(ips): 554 | self.changed_message.append("IP address in request: %s and in" 555 | "certificate: %s are different" 556 | % (sorted(self.ip_addresses), ips)) 557 | self.changed_message.append("CN is %s" % cn) 558 | return False 559 | if self.san_dns and not self._check_dns_sans_correct( 560 | dns, self.san_dns, [self.common_name]): 561 | self.changed_message.append("DNS addresses in request: %s and in " 562 | "certificate: %s are different" 563 | % (sorted(self.san_dns), sorted(dns))) 564 | return False 565 | return True 566 | 567 | def _check_public_key_matched_to_private_key(self, cert): 568 | if not self.privatekey_filename: 569 | return True 570 | if not os.path.exists(self.privatekey_filename): 571 | return False 572 | try: 573 | with open(self.privatekey_filename, 'rb') as key_data: 574 | password = self.privatekey_passphrase.encode() if \ 575 | self.privatekey_passphrase else None 576 | pkey = serialization.load_pem_private_key( 577 | key_data.read(), password=password, 578 | backend=default_backend()) 579 | 580 | except OSError as exc: 581 | self.module.fail_json( 582 | msg="Failed to read private key file: %s" % exc) 583 | 584 | cert_public_key_pem = cert.public_key().public_bytes( 585 | encoding=serialization.Encoding.PEM, 586 | format=serialization.PublicFormat.SubjectPublicKeyInfo 587 | ).decode() 588 | 589 | private_key_public_key_pem = pkey.public_key().public_bytes( 590 | encoding=serialization.Encoding.PEM, 591 | format=serialization.PublicFormat.SubjectPublicKeyInfo 592 | ).decode() 593 | 594 | if cert_public_key_pem != private_key_public_key_pem: 595 | return False 596 | return True 597 | 598 | def _check_files_permissions(self): 599 | files = (self.privatekey_filename, self.certificate_filename, 600 | self.chain_filename) 601 | return all([self._check_file_permissions(x) for x in files]) 602 | 603 | def _check_file_permissions(self, path, update=False): 604 | return True # todo: write 605 | 606 | def check(self, validate): 607 | """Return true if running will change anything""" 608 | result = { 609 | 'cert_file_exists': True, 610 | 'changed': False, 611 | } 612 | if not os.path.exists(self.certificate_filename): 613 | result = { 614 | 'cert_file_exists': False, 615 | 'changed': True, 616 | 'changed_msg': 617 | self.changed_message.append(STRING_CERT_FILE_NOT_EXISTS), 618 | } 619 | else: 620 | try: 621 | with open(self.certificate_filename, 'rb') as cert_data: 622 | try: 623 | cert = x509.load_pem_x509_certificate( 624 | cert_data.read(), default_backend()) 625 | except Exception: 626 | self.module.fail_json( 627 | msg="Failed to load certificate from file: %s" 628 | % self.certificate_filename) 629 | except OSError as exc: 630 | self.module.fail_json( 631 | msg="Failed to read certificate file: %s" % exc) 632 | 633 | if not self._check_public_key_matched_to_private_key(cert): 634 | result['changed'] = True 635 | self.changed_message.append(STRING_PKEY_NOT_MATCHED) 636 | 637 | if not self._check_certificate_validity(cert, validate): 638 | result['changed'] = True 639 | self.changed_message.append( 640 | STRING_FAILED_TO_CHECK_CERT_VALIDITY) 641 | 642 | if self._check_private_key_correct() is False: # may be None 643 | result['changed'] = True 644 | self.changed_message.append(STRING_BAD_PKEY) 645 | 646 | if not self._check_files_permissions(): 647 | result['changed'] = True 648 | self.changed_message.append(STRING_BAD_PERMISSIONS) 649 | 650 | result['changed_msg'] = ' | '.join(self.changed_message) 651 | return result 652 | 653 | def validate(self): 654 | """Ensure the resource is in its desired state.""" 655 | result = self.check(validate=True) 656 | if result['changed']: 657 | self.module.fail_json( 658 | msg=result['changed_msg'] 659 | ) 660 | 661 | def dump(self): 662 | 663 | result = { 664 | 'changed': self.changed, 665 | 'privatekey_filename': self.privatekey_filename, 666 | 'privatekey_size': self.privatekey_size, 667 | 'privatekey_curve': self.privatekey_curve, 668 | 'privatekey_type': self.privatekey_type, 669 | 'certificate_filename': self.certificate_filename, 670 | 'chain_filename': self.chain_filename, 671 | } 672 | 673 | return result 674 | 675 | 676 | def main(): 677 | # the AnsibleModule object will be our abstraction working with Ansible 678 | # this includes instantiation, a couple of common attr would be the 679 | # args/params passed to the execution, as well as if the module 680 | # supports check mode 681 | module = AnsibleModule( 682 | # define the available arguments/parameters that a user can pass to 683 | # the module 684 | argument_spec=dict( 685 | state=dict(type='str', choices=['present', 'absent'], 686 | default='present'), 687 | force=dict(type='bool', default=False, ), 688 | 689 | # Endpoint 690 | test_mode=dict(type='bool', required=False, default=False), 691 | url=dict(type='str', required=False, default=''), 692 | password=dict(type='str', required=False, default='', no_log=True), 693 | token=dict(type='str', required=False, default='', no_log=True), 694 | access_token=dict(type='str', required=False, 695 | default='', no_log=True), 696 | user=dict(type='str', required=False, default='', no_log=True), 697 | zone=dict(type='str', required=False, default=''), 698 | log_verbose=dict(type='str', required=False, default=''), 699 | config_file=dict(type='str', required=False, default=''), 700 | config_section=dict(type='str', required=False, default=''), 701 | trust_bundle=dict(type='str', required=False), 702 | 703 | # General properties of a certificate 704 | path=dict(type='path', aliases=['cert_path'], require=True), 705 | chain_path=dict(type='path', require=False), 706 | privatekey_path=dict(type='path', required=False), 707 | privatekey_type=dict(type='str', required=False), 708 | privatekey_size=dict(type='int', required=False), 709 | privatekey_curve=dict(type='str', required=False), 710 | privatekey_passphrase=dict(type='str', no_log=True), 711 | privatekey_reuse=dict(type='bool', required=False, default=True), 712 | alt_name=dict(type='list', aliases=['subjectAltName'], 713 | elements='str'), 714 | common_name=dict(aliases=['CN', 'commonName', 'common_name'], 715 | type='str', required=True), 716 | chain_option=dict(type='str', required=False, default='last'), 717 | csr_path=dict(type='path', require=False), 718 | 719 | # Role config 720 | before_expired_hours=dict(type='int', required=False, default=72), 721 | renew=dict(type='bool', required=False, default=True) 722 | ), 723 | supports_check_mode=True, 724 | add_file_common_args=True, 725 | ) 726 | if not HAS_VCERT: 727 | module.fail_json(msg='"vcert" python library is required') 728 | if not HAS_CRYPTOGRAPHY: 729 | module.fail_json(msg='"cryptography" python library is required') 730 | vcert = VCertificate(module) 731 | change_dump = vcert.check(validate=False) 732 | if module.check_mode: 733 | module.exit_json(**change_dump) 734 | 735 | if not vcert.check_dirs_existed(): 736 | module.fail_json(msg="Dirs not existed") 737 | if change_dump['changed']: 738 | # TODO: Cover it by tests 739 | """ 740 | make a following choice: 741 | 1. If certificate is present and renew is true validate it 742 | 2. If certificate not present renew it 743 | 3. If it present and renew is false just keep it. 744 | """ 745 | if change_dump['cert_file_exists']: 746 | if module.params['renew']: 747 | vcert.enroll() 748 | else: 749 | module.exit_json(**change_dump) 750 | else: 751 | vcert.enroll() 752 | elif module.params['force']: 753 | vcert.enroll() 754 | vcert.validate() 755 | result = vcert.dump() 756 | module.exit_json(**result) 757 | 758 | 759 | if __name__ == '__main__': 760 | main() 761 | --------------------------------------------------------------------------------