From 22a58a8c483f099a06993c5c0db36e1a81adc08d Mon Sep 17 00:00:00 2001 From: Giovanni Baratta Date: Sat, 23 Mar 2024 19:57:38 +0100 Subject: [PATCH] tf(harbor-standalone): use vsphere-vm module to create vm and add support for pre-existing tls artifacts --- terraform/modules/harbor-standalone/README.md | 5 +- .../files/harbor-ansible-vars.yaml.tpl | 11 ++ .../files/harbor-cloud-config.yml.tftpl | 64 ------ .../files/harbor-config.yml.tftpl | 1 - .../files/harbor-install-playbook.yaml | 183 +++++++++++------- .../files/harbor-tls-ca-chain.j2 | 1 + .../files/harbor-tls-certificate.j2 | 1 + .../harbor-standalone/files/harbor-tls-key.j2 | 1 + .../modules/harbor-standalone/outputs.tf | 4 +- .../modules/harbor-standalone/variables.tf | 15 +- terraform/modules/harbor-standalone/version | 2 +- terraform/modules/harbor-standalone/vm.tf | 112 ++++------- 12 files changed, 184 insertions(+), 216 deletions(-) create mode 100644 terraform/modules/harbor-standalone/files/harbor-ansible-vars.yaml.tpl delete mode 100644 terraform/modules/harbor-standalone/files/harbor-cloud-config.yml.tftpl create mode 100644 terraform/modules/harbor-standalone/files/harbor-tls-ca-chain.j2 create mode 100644 terraform/modules/harbor-standalone/files/harbor-tls-certificate.j2 create mode 100644 terraform/modules/harbor-standalone/files/harbor-tls-key.j2 diff --git a/terraform/modules/harbor-standalone/README.md b/terraform/modules/harbor-standalone/README.md index ef7c019..7499d91 100644 --- a/terraform/modules/harbor-standalone/README.md +++ b/terraform/modules/harbor-standalone/README.md @@ -8,14 +8,15 @@ The module deploys Harbor registry in a VM in a vSphere environment. | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| domain | n/a | `string` | n/a | yes | +| fqdn | Fully qualified domain name of the VM | `string` | n/a | yes | | vm\_authorized\_key | Public key authorized to ssh into the VM | `string` | n/a | yes | | vsphere | vSphere related references to deploy the VM |
object({
resource_pool_id = string
datastore_id = string
network_id = string
template_id = string
})
| n/a | yes | +| tls | TLS configuration to use. Private key and certificate must be base64 encoded |
object({
private_key = string
certificate = string
ca_chain = optional(string, null)
})
| `null` | no | ## Outputs | Name | Description | |------|-------------| | harbor\_admin\_password | n/a | -| harbor\_instance\_ip | n/a | +| instance\_ip | n/a | \ No newline at end of file diff --git a/terraform/modules/harbor-standalone/files/harbor-ansible-vars.yaml.tpl b/terraform/modules/harbor-standalone/files/harbor-ansible-vars.yaml.tpl new file mode 100644 index 0000000..7b6722b --- /dev/null +++ b/terraform/modules/harbor-standalone/files/harbor-ansible-vars.yaml.tpl @@ -0,0 +1,11 @@ +harbor_hostname: ${harbor_hostname} + +%{ if harbor_base64_tls_key != null ~} +harbor_base64_tls_key: ${harbor_base64_tls_key} +%{ endif ~} +%{ if harbor_base64_tls_cert != null ~} +harbor_base64_tls_cert: ${harbor_base64_tls_cert} +%{ endif ~} +%{ if harbor_base64_tls_ca_chain != null ~} +harbor_base64_tls_ca_chain: ${harbor_base64_tls_ca_chain} +%{ endif ~} diff --git a/terraform/modules/harbor-standalone/files/harbor-cloud-config.yml.tftpl b/terraform/modules/harbor-standalone/files/harbor-cloud-config.yml.tftpl deleted file mode 100644 index 45a55db..0000000 --- a/terraform/modules/harbor-standalone/files/harbor-cloud-config.yml.tftpl +++ /dev/null @@ -1,64 +0,0 @@ -#cloud-config - -fqdn: ${fqdn} - -ssh_pwauth: false - -chpasswd: - # Passwords don't need to be reset on next login - expire: false - -users: - - name: root - lock_passwd: false - plain_text_passwd: ${password} - - name: ubuntu - ssh_authorized_keys: - - ${authorized_key} - sudo: ALL=(ALL) NOPASSWD:ALL - groups: sudo,docker - lock_passwd: true - shell: /bin/bash - - name: ansible - gecos: Ansible User - shell: /bin/bash - groups: sudo - lock_passwd: true - sudo: ALL=(ALL) NOPASSWD:ALL - -package_update: true -package_upgrade: true -packages: - - python3-pip # Required to install Ansible - -write_files: - - path: /harbor/ansible/harbor-install-playbook.yaml - owner: root:root - permissions: '0644' - encoding: base64 - content: ${harbor_install_playbook} - - path: /harbor/harbor.yml - owner: root:root - permissions: '0644' - encoding: base64 - content: ${harbor_conf} - - path: /etc/systemd/system/harbor.service - owner: root:root - permissions: '0644' - encoding: base64 - content: ${harbor_service} - -ansible: - install_method: pip - package_name: ansible - run_user: ansible - galaxy: - actions: - - ["ansible-galaxy", "role", "install", "geerlingguy.docker"] - - setup_controller: - run_ansible: - - playbook_dir: /harbor/ansible - playbook_name: harbor-install-playbook.yaml - timeout: 120 - forks: 1 diff --git a/terraform/modules/harbor-standalone/files/harbor-config.yml.tftpl b/terraform/modules/harbor-standalone/files/harbor-config.yml.tftpl index 18cbc32..1a17ff0 100644 --- a/terraform/modules/harbor-standalone/files/harbor-config.yml.tftpl +++ b/terraform/modules/harbor-standalone/files/harbor-config.yml.tftpl @@ -43,7 +43,6 @@ jobservice: - FILE logger_sweeper_duration: 1 #days - notification: webhook_job_max_retry: 3 webhook_job_http_client_timeout: 3 #seconds diff --git a/terraform/modules/harbor-standalone/files/harbor-install-playbook.yaml b/terraform/modules/harbor-standalone/files/harbor-install-playbook.yaml index d79bced..d1d513d 100644 --- a/terraform/modules/harbor-standalone/files/harbor-install-playbook.yaml +++ b/terraform/modules/harbor-standalone/files/harbor-install-playbook.yaml @@ -12,8 +12,8 @@ connection: local become: true - vars: - harbor_hostname: "${harbor_fqdn}" + vars_files: + - ./vars.yaml tasks: - name: Create TLS folder @@ -22,60 +22,85 @@ state: directory mode: "0700" - # Create self-signed CA - # https://docs.ansible.com/ansible/latest/collections/community/crypto/docsite/guide_ownca.html#set-up-the-ca - - name: Create CA private key - community.crypto.openssl_privatekey: - path: /harbor/tls/ca.key - - - name: Create certificate signing request for CA certificate - community.crypto.openssl_csr_pipe: - privatekey_path: /harbor/tls/ca.key - common_name: Harbor CA - use_common_name_for_san: false - basic_constraints: - - "CA:TRUE" - basic_constraints_critical: true - key_usage: - - keyCertSign - key_usage_critical: true - register: ca_csr - - - name: Create self-signed CA certificate from CSR - community.crypto.x509_certificate: - path: /harbor/tls/ca.pem - csr_content: "{{ ca_csr.csr }}" - privatekey_path: /harbor/tls/ca.key - provider: selfsigned - # It must be less than 825 days to work on newer MacOs versions - selfsigned_not_after: "+730d" - - # Create Harbor certificate - - name: Create Harbor private key - community.crypto.openssl_privatekey: - path: /harbor/tls/harbor.key - - # Create CSR and sign it with the self-signed cA - - name: Create Harbor certificate signing request (CSR) - community.crypto.openssl_csr_pipe: - privatekey_path: /harbor/tls/harbor.key - common_name: "{{ harbor_hostname }}" - subject_alt_name: - - "DNS:{{ harbor_hostname }}" - register: harbor_csr - - - name: Create Harbor certificate - community.crypto.x509_certificate: - path: /harbor/tls/harbor.pem - csr_content: "{{ harbor_csr.csr }}" - ownca_privatekey_path: /harbor/tls/ca.key - ownca_path: /harbor/tls/ca.pem - # It must be less than 825 days to work on newer MacOs versions - ownca_not_after: "+365d" - provider: ownca - notify: - - Regenerate Harbor configs - - Restart Harbor service + - name: Generate TLS artifacts + when: harbor_base64_tls_key is undefined or harbor_base64_tls_cert is undefined + block: + # Create self-signed CA + # https://docs.ansible.com/ansible/latest/collections/community/crypto/docsite/guide_ownca.html#set-up-the-ca + - name: Create CA private key + community.crypto.openssl_privatekey: + path: /harbor/tls/ca.key + + - name: Create certificate signing request for CA certificate + community.crypto.openssl_csr_pipe: + privatekey_path: /harbor/tls/ca.key + common_name: Harbor CA + use_common_name_for_san: false + basic_constraints: + - "CA:TRUE" + basic_constraints_critical: true + key_usage: + - keyCertSign + key_usage_critical: true + register: ca_csr + + - name: Create self-signed CA certificate from CSR + community.crypto.x509_certificate: + path: /harbor/tls/ca.pem + csr_content: "{{ ca_csr.csr }}" + privatekey_path: /harbor/tls/ca.key + provider: selfsigned + # It must be less than 825 days to work on newer MacOs versions + selfsigned_not_after: "+730d" + return_content: true + register: harbor_ca_cert_task + + # Create Harbor key and certificate + - name: Create Harbor private key + community.crypto.openssl_privatekey: + path: /harbor/tls/harbor.key + format: "pkcs1" + return_content: true + register: harbor_private_key_task + + # Create CSR and sign it with the self-signed CA + - name: Create Harbor certificate signing request (CSR) + community.crypto.openssl_csr_pipe: + privatekey_path: /harbor/tls/harbor.key + common_name: "{{ harbor_hostname }}" + subject_alt_name: + - "DNS:{{ harbor_hostname }}" + register: harbor_csr + + - name: Create Harbor certificate + community.crypto.x509_certificate: + path: /harbor/tls/harbor.pem + csr_content: "{{ harbor_csr.csr }}" + ownca_privatekey_path: /harbor/tls/ca.key + ownca_path: /harbor/tls/ca.pem + # It must be less than 825 days to work on newer MacOs versions + ownca_not_after: "+365d" + provider: ownca + return_content: true + register: harbor_cert_task + notify: + - Regenerate Harbor configs + - Restart Harbor service + + - name: Set TLS facts using variables + ansible.builtin.set_fact: + harbor_tls_key: "{{ harbor_private_key_task.privatekey }}" + harbor_tls_cert: "{{ harbor_cert_task.certificate }}" + harbor_tls_ca_chain: "{{ harbor_ca_cert_task.certificate }}" + + - name: Extract TLS artifacts from variables + when: harbor_base64_tls_key is defined and harbor_base64_tls_cert is defined + block: + - name: Set TLS facts using variables + ansible.builtin.set_fact: + harbor_tls_key: "{{ harbor_base64_tls_key | b64decode }}" + harbor_tls_cert: "{{ harbor_base64_tls_cert | b64decode }}" + harbor_tls_ca_chain: '{{ harbor_base64_tls_ca_chain | default(None) | b64decode or "" }}' # Copy certificate and private key in the all the required folders - name: Create Docker cert folder @@ -103,40 +128,50 @@ mode: "0755" - name: Copy Harbor private key to Docker certs - ansible.builtin.copy: - src: /harbor/tls/harbor.key + ansible.builtin.template: + src: "templates/harbor-tls-key.j2" dest: "/etc/docker/certs.d/{{ harbor_hostname }}/{{ harbor_hostname}}.key" - remote_src: yes - name: Copy Harbor certificate to Docker certs - ansible.builtin.copy: - src: /harbor/tls/harbor.pem + ansible.builtin.template: + src: "templates/harbor-tls-certificate.j2" dest: "/etc/docker/certs.d/{{ harbor_hostname }}/{{ harbor_hostname}}.pem" - remote_src: yes - name: Copy CA certificate - ansible.builtin.copy: - src: /harbor/tls/ca.pem + ansible.builtin.template: + src: "templates/harbor-tls-ca-chain.j2" dest: "/etc/docker/certs.d/{{ harbor_hostname }}/ca.pem" - remote_src: yes - name: Copy Harbor private key to data volume - ansible.builtin.copy: - src: /harbor/tls/harbor.key + ansible.builtin.template: + src: "templates/harbor-tls-key.j2" dest: "/data/cert/{{ harbor_hostname}}.key" - remote_src: yes - name: Copy Harbor certificate to data volume - ansible.builtin.copy: - src: /harbor/tls/harbor.pem + ansible.builtin.template: + src: "templates/harbor-tls-certificate.j2" dest: "/data/cert/{{ harbor_hostname}}.pem" - remote_src: yes - name: Copy CA to data volume - ansible.builtin.copy: - src: /harbor/tls/ca.pem + ansible.builtin.template: + src: "templates/harbor-tls-ca-chain.j2" dest: "/data/ca_download/ca.crt" - remote_src: yes + + - name: Copy Harbor config + ansible.builtin.copy: + src: "/ansible/harbor-config.yml" + dest: "/harbor/harbor.yml" + notify: + - Regenerate Harbor configs + - Restart Harbor service + + - name: Copy Harbor service + ansible.builtin.copy: + src: "templates/harbor-systemd.service" + dest: "/etc/systemd/system/harbor.service" + notify: + - Regenerate Harbor configs + - Restart Harbor service - name: Check if Harbor is already installed ansible.builtin.shell: diff --git a/terraform/modules/harbor-standalone/files/harbor-tls-ca-chain.j2 b/terraform/modules/harbor-standalone/files/harbor-tls-ca-chain.j2 new file mode 100644 index 0000000..5e0ab07 --- /dev/null +++ b/terraform/modules/harbor-standalone/files/harbor-tls-ca-chain.j2 @@ -0,0 +1 @@ +{{ harbor_tls_ca_chain }} \ No newline at end of file diff --git a/terraform/modules/harbor-standalone/files/harbor-tls-certificate.j2 b/terraform/modules/harbor-standalone/files/harbor-tls-certificate.j2 new file mode 100644 index 0000000..6b67b29 --- /dev/null +++ b/terraform/modules/harbor-standalone/files/harbor-tls-certificate.j2 @@ -0,0 +1 @@ +{{ harbor_tls_cert }} \ No newline at end of file diff --git a/terraform/modules/harbor-standalone/files/harbor-tls-key.j2 b/terraform/modules/harbor-standalone/files/harbor-tls-key.j2 new file mode 100644 index 0000000..86fb191 --- /dev/null +++ b/terraform/modules/harbor-standalone/files/harbor-tls-key.j2 @@ -0,0 +1 @@ +{{ harbor_tls_key }} \ No newline at end of file diff --git a/terraform/modules/harbor-standalone/outputs.tf b/terraform/modules/harbor-standalone/outputs.tf index 21dfd6d..3a797a2 100644 --- a/terraform/modules/harbor-standalone/outputs.tf +++ b/terraform/modules/harbor-standalone/outputs.tf @@ -1,5 +1,5 @@ -output "harbor_instance_ip" { - value = vsphere_virtual_machine.harbor.default_ip_address +output "instance_ip" { + value = module.vm.instance_ip } output "harbor_admin_password" { diff --git a/terraform/modules/harbor-standalone/variables.tf b/terraform/modules/harbor-standalone/variables.tf index 92c8cd2..554f27d 100644 --- a/terraform/modules/harbor-standalone/variables.tf +++ b/terraform/modules/harbor-standalone/variables.tf @@ -3,8 +3,9 @@ variable "vm_authorized_key" { type = string } -variable "domain" { +variable "fqdn" { type = string + description = "Fully qualified domain name of the VM" } variable "vsphere" { @@ -17,4 +18,16 @@ variable "vsphere" { network_id = string template_id = string }) +} + +variable "tls" { + type = object({ + private_key = string + certificate = string + ca_chain = optional(string, null) + }) + + nullable = true + default = null + description = "TLS configuration to use. Private key and certificate must be base64 encoded" } \ No newline at end of file diff --git a/terraform/modules/harbor-standalone/version b/terraform/modules/harbor-standalone/version index afaf360..359a5b9 100644 --- a/terraform/modules/harbor-standalone/version +++ b/terraform/modules/harbor-standalone/version @@ -1 +1 @@ -1.0.0 \ No newline at end of file +2.0.0 \ No newline at end of file diff --git a/terraform/modules/harbor-standalone/vm.tf b/terraform/modules/harbor-standalone/vm.tf index f787815..518d92d 100644 --- a/terraform/modules/harbor-standalone/vm.tf +++ b/terraform/modules/harbor-standalone/vm.tf @@ -1,88 +1,58 @@ -resource "random_password" "vm_root_user" { - length = 6 - upper = false - special = false - override_special = "#$%&*()=+[]{}<>:?" -} - -resource "random_password" "admin_password" { - length = 16 - special = true - override_special = "#$%&*()=+[]{}<>:?" -} - -resource "random_password" "db_root_password" { - length = 16 - special = true - override_special = "#$%&*()=+[]{}<>:?" -} - locals { - harbor_fqdn = "harbor.${var.domain}" - - harbor_playbook = templatefile("${path.module}/files/harbor-install-playbook.yaml", { - harbor_fqdn : local.harbor_fqdn - }) - harbor_conf = templatefile("${path.module}/files/harbor-config.yml.tftpl", { - fqdn : local.harbor_fqdn + harbor_config_file = templatefile("${path.module}/files/harbor-config.yml.tftpl", { + fqdn : var.fqdn admin_password : random_password.admin_password.result db_root_password : random_password.db_root_password.result }) +} - # User data rendered using the Terraform provider produce an invalid configuration - # See https://github.com/hashicorp/terraform-provider-cloudinit/issues/165 - harbor_user_data = templatefile("${path.module}/files/harbor-cloud-config.yml.tftpl", { - fqdn : local.harbor_fqdn - password : random_password.vm_root_user.result - authorized_key : var.vm_authorized_key - harbor_install_playbook : base64encode(local.harbor_playbook) - harbor_conf : base64encode(local.harbor_conf) - harbor_service : base64encode(file("${path.module}/files/harbor-systemd.service")) +locals { + ansible_files = [ + "harbor-systemd.service", + "harbor-tls-ca-chain.j2", + "harbor-tls-certificate.j2", + "harbor-tls-key.j2" + ] + + ansible_var_file = templatefile("${path.module}/files/harbor-ansible-vars.yaml.tpl", { + harbor_hostname = var.fqdn + harbor_base64_tls_key = try(var.tls.private_key, null) + harbor_base64_tls_cert = try(var.tls.certificate, null) + harbor_base64_tls_ca_chain = try(var.tls.ca_chain, null) }) } -resource "vsphere_virtual_machine" "harbor" { - name = "harbor" - resource_pool_id = var.vsphere.resource_pool_id - datastore_id = var.vsphere.datastore_id - - num_cpus = 2 - memory = 4096 - - clone { - template_uuid = var.vsphere.template_id +resource "random_password" "admin_password" { + length = 10 + special = true + override_special = "#$%*()=+[]{}:?" +} - # Do not customize the clone when using user-data. - # See https://github.com/vmware/open-vm-tools/issues/684, - # https://github.com/canonical/cloud-init/issues/4188, - # https://github.com/canonical/cloud-init/issues/4404 - } +resource "random_password" "db_root_password" { + length = 20 + special = true + override_special = "#$%*()=+[]{}:?" +} - # This is required to load vApp properties correctly - cdrom { - client_device = true - } +module "vm" { + source = "github.com/giovannibaratta/vmware-tanzu-training//terraform/modules/vsphere-vm?ref=vsphere-vm-v0.0.3&depth=1" - disk { - label = "disk0" - size = 200 - thin_provisioned = true - } + fqdn = var.fqdn + vsphere = var.vsphere + vm_authorized_key = var.vm_authorized_key - network_interface { - network_id = var.vsphere.network_id - } + ansible_playbook = filebase64("${path.module}/files/harbor-install-playbook.yaml") - vapp { - properties = { - "user-data" = base64encode(local.harbor_user_data) - "public-keys" = var.vm_authorized_key + cloud_init_write_files = merge( + { for file in local.ansible_files : "/ansible/templates/${file}" => filebase64("${path.module}/files/${file}") }, + { + "/ansible/vars.yaml" = base64encode(local.ansible_var_file), + "/ansible/harbor-config.yml" = base64encode(local.harbor_config_file) } - } + ) - # Perma diff - lifecycle { - ignore_changes = [ept_rvi_mode, hv_mode, clone] - } + ansible_galaxy_actions = [ + ["ansible-galaxy", "role", "install", "geerlingguy.docker"] + ] }