diff --git a/ansible/octavia-certificates.yml b/ansible/octavia-certificates.yml new file mode 100644 index 0000000000..aeebb814f1 --- /dev/null +++ b/ansible/octavia-certificates.yml @@ -0,0 +1,5 @@ +--- +- name: Apply role octavia-certificates + hosts: localhost + roles: + - octavia-certificates diff --git a/ansible/roles/octavia-certificates/defaults/main.yml b/ansible/roles/octavia-certificates/defaults/main.yml new file mode 100644 index 0000000000..67fe9085af --- /dev/null +++ b/ansible/roles/octavia-certificates/defaults/main.yml @@ -0,0 +1,45 @@ +--- +##################### +# Certificate options. +##################### + +octavia_certs_work_dir: "{{ node_config }}/octavia-certificates" + +# OpenSSL configuration file path. +octavia_certs_openssl_cnf_path: openssl.cnf + +# For more info see: https://en.wikipedia.org/wiki/Certificate_signing_request +# Country; The two-letter ISO code for the country where your organization is located +octavia_certs_country: US +# Province, Region, County or State +octavia_certs_state: Oregon +# Business name / Organization +octavia_certs_organization: OpenStack +# Department Name / Organizational Unit +octavia_certs_organizational_unit: Octavia + +# Server CA. +octavia_certs_server_ca_expiry: 3650 +octavia_certs_server_ca_country: "{{ octavia_certs_country }}" +octavia_certs_server_ca_state: "{{ octavia_certs_state }}" +octavia_certs_server_ca_organization: "{{ octavia_certs_organization }}" +octavia_certs_server_ca_organizational_unit: "{{ octavia_certs_organizational_unit }}" +octavia_certs_server_ca_common_name: server-ca.example.org + +# Client CA. +octavia_certs_client_ca_expiry: 3650 +octavia_certs_client_ca_country: "{{ octavia_certs_country }}" +octavia_certs_client_ca_state: "{{ octavia_certs_state }}" +octavia_certs_client_ca_organization: "{{ octavia_certs_organization }}" +octavia_certs_client_ca_organizational_unit: "{{ octavia_certs_organizational_unit }}" +octavia_certs_client_ca_common_name: client-ca.example.org + +# Client certificate. +octavia_certs_client_expiry: 365 +octavia_certs_client_req_country: "{{ octavia_certs_country }}" +octavia_certs_client_req_state: "{{ octavia_certs_state }}" +octavia_certs_client_req_organization: "{{ octavia_certs_organization }}" +octavia_certs_client_req_organizational_unit: "{{ octavia_certs_organizational_unit }}" +# NOTE(yoctozepto): This should ideally be per controller, i.e. controller +# generates its key&CSR and this CA signs it. +octavia_certs_client_req_common_name: client.example.org diff --git a/ansible/roles/octavia-certificates/files/openssl.cnf b/ansible/roles/octavia-certificates/files/openssl.cnf new file mode 100644 index 0000000000..34161b89e7 --- /dev/null +++ b/ansible/roles/octavia-certificates/files/openssl.cnf @@ -0,0 +1,49 @@ +[ client_ca ] +new_certs_dir = . +database = index.txt +serial = serial +RANDFILE = .rand + +private_key = client_ca.key.pem +certificate = client_ca.cert.pem + +# SHA-1 is deprecated, so use SHA-2 instead. +default_md = sha256 + +name_opt = ca_default +cert_opt = ca_default +default_days = 3650 + +x509_extensions = client_cert + +policy = policy_any + +[ policy_any ] +countryName = supplied +stateOrProvinceName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[ req ] +distinguished_name = req_distinguished_name +x509_extensions = v3_ca + +# SHA-1 is deprecated, so use SHA-2 instead. +default_md = sha256 + +[ req_distinguished_name ] + +[ v3_ca ] +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always +basicConstraints = critical, CA:TRUE +keyUsage = critical, cRLSign, keyCertSign + +[ client_cert ] +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always +basicConstraints = critical, CA:FALSE +keyUsage = critical, digitalSignature +extendedKeyUsage = clientAuth diff --git a/ansible/roles/octavia-certificates/tasks/client_ca.yml b/ansible/roles/octavia-certificates/tasks/client_ca.yml new file mode 100644 index 0000000000..b9cd711e18 --- /dev/null +++ b/ansible/roles/octavia-certificates/tasks/client_ca.yml @@ -0,0 +1,43 @@ +--- + +- name: Create client_ca index.txt + copy: + content: '' + dest: "{{ octavia_certs_work_dir }}/client_ca/index.txt" + force: no + mode: 0660 + +- name: Create client_ca serial + copy: + content: "1000\n" + dest: "{{ octavia_certs_work_dir }}/client_ca/serial" + force: no + mode: 0660 + +- name: Create client_ca private key + command: > + openssl genrsa -aes256 -out client_ca.key.pem + -passout pass:{{ octavia_client_ca_password }} 4096 + args: + chdir: "{{ octavia_certs_work_dir }}/client_ca" + creates: "{{ octavia_certs_work_dir }}/client_ca/client_ca.key.pem" + +- name: Create client_ca certificate + vars: + client_ca_subject: + C: "{{ octavia_certs_client_ca_country }}" + ST: "{{ octavia_certs_client_ca_state }}" + O: "{{ octavia_certs_client_ca_organization }}" + OU: "{{ octavia_certs_client_ca_organizational_unit }}" + CN: "{{ octavia_certs_client_ca_common_name }}" + command: > + openssl req -new -x509 -config ../openssl.cnf + -key client_ca.key.pem + -days {{ octavia_certs_client_ca_expiry }} + -out client_ca.cert.pem + -subj "/{{ client_ca_subject.items() | map('join', '=') | join('/') }}" + -passin pass:{{ octavia_client_ca_password }} + -batch + args: + chdir: "{{ octavia_certs_work_dir }}/client_ca" + creates: "{{ octavia_certs_work_dir }}/client_ca/client_ca.cert.pem" diff --git a/ansible/roles/octavia-certificates/tasks/client_cert.yml b/ansible/roles/octavia-certificates/tasks/client_cert.yml new file mode 100644 index 0000000000..384c7d81b0 --- /dev/null +++ b/ansible/roles/octavia-certificates/tasks/client_cert.yml @@ -0,0 +1,50 @@ +--- + +# NOTE(yoctozepto): This should ideally be per controller, i.e. controller +# generates its key&CSR and this CA signs it. + +- name: Create a key for the client certificate + command: > + openssl genrsa -out client.key.pem 4096 + args: + chdir: "{{ octavia_certs_work_dir }}/client_ca" + creates: "{{ octavia_certs_work_dir }}/client_ca/client.key.pem" + +- name: Create the certificate request for the client certificate + vars: + client_req_subject: + C: "{{ octavia_certs_client_req_country }}" + ST: "{{ octavia_certs_client_req_state }}" + O: "{{ octavia_certs_client_req_organization }}" + OU: "{{ octavia_certs_client_req_organizational_unit }}" + CN: "{{ octavia_certs_client_req_common_name }}" + command: > + openssl req -new -config ../openssl.cnf + -key client.key.pem + -out client.csr.pem + -subj "/{{ client_req_subject.items() | map('join', '=') | join('/') }}" + -batch + args: + chdir: "{{ octavia_certs_work_dir }}/client_ca" + creates: "{{ octavia_certs_work_dir }}/client_ca/client.csr.pem" + +- name: Sign the client certificate request + command: > + openssl ca -config ../openssl.cnf + -name client_ca + -days {{ octavia_certs_client_expiry }} + -in client.csr.pem + -out client.cert.pem + -key {{ octavia_client_ca_password }} + -notext + -batch + args: + chdir: "{{ octavia_certs_work_dir }}/client_ca" + creates: "{{ octavia_certs_work_dir }}/client_ca/client.cert.pem" + +- name: Create a concatenated client certificate and key file + assemble: + regexp: ^client\.(cert|key)\.pem$ + src: "{{ octavia_certs_work_dir }}/client_ca" + dest: "{{ octavia_certs_work_dir }}/client_ca/client.cert-and-key.pem" + mode: "0660" diff --git a/ansible/roles/octavia-certificates/tasks/main.yml b/ansible/roles/octavia-certificates/tasks/main.yml new file mode 100644 index 0000000000..58ad6c9e55 --- /dev/null +++ b/ansible/roles/octavia-certificates/tasks/main.yml @@ -0,0 +1,44 @@ +--- +# This play adapts https://docs.openstack.org/octavia/victoria/admin/guides/certificates.html + +# Kolla-Ansible prepares the Server CA certificate and key for use by Octavia +# to generate Amphorae certificates. + +# Kolla-Ansible prepares and controls the Client CA certificate and key. +# Client CA is used to generate certificates for Octavia controllers. + +- name: Ensure server_ca and client_ca directories exist + file: + path: "{{ octavia_certs_work_dir }}/{{ item }}" + state: "directory" + mode: 0770 + loop: + - server_ca + - client_ca + +- name: Copy openssl.cnf + copy: + src: "{{ octavia_certs_openssl_cnf_path }}" + dest: "{{ octavia_certs_work_dir }}/openssl.cnf" + +- import_tasks: server_ca.yml + +- import_tasks: client_ca.yml + +- import_tasks: client_cert.yml + +- name: Ensure {{ node_custom_config }}/octavia directory exists + file: + path: "{{ node_custom_config }}/octavia" + state: "directory" + mode: 0770 + +- name: Copy the to-be-deployed keys and certs to {{ node_custom_config }}/octavia + copy: + src: "{{ octavia_certs_work_dir }}/{{ item.src }}" + dest: "{{ node_custom_config }}/octavia/{{ item.dest }}" + with_items: + - { src: "server_ca/server_ca.cert.pem", dest: "server_ca.cert.pem" } + - { src: "server_ca/server_ca.key.pem", dest: "server_ca.key.pem" } + - { src: "client_ca/client_ca.cert.pem", dest: "client_ca.cert.pem" } + - { src: "client_ca/client.cert-and-key.pem", dest: "client.cert-and-key.pem" } diff --git a/ansible/roles/octavia-certificates/tasks/server_ca.yml b/ansible/roles/octavia-certificates/tasks/server_ca.yml new file mode 100644 index 0000000000..15c30f8934 --- /dev/null +++ b/ansible/roles/octavia-certificates/tasks/server_ca.yml @@ -0,0 +1,29 @@ +--- + +- name: Generate server_ca private key + command: > + openssl genrsa -aes256 -out server_ca.key.pem + -passout pass:{{ octavia_ca_password }} 4096 + args: + chdir: "{{ octavia_certs_work_dir }}/server_ca" + creates: "{{ octavia_certs_work_dir }}/server_ca/server_ca.key.pem" + +- name: Create server_ca certificate + vars: + server_ca_subject: + C: "{{ octavia_certs_server_ca_country }}" + ST: "{{ octavia_certs_server_ca_state }}" + O: "{{ octavia_certs_server_ca_organization }}" + OU: "{{ octavia_certs_server_ca_organizational_unit }}" + CN: "{{ octavia_certs_server_ca_common_name }}" + command: > + openssl req -new -x509 -config ../openssl.cnf + -key server_ca.key.pem + -days {{ octavia_certs_server_ca_expiry }} + -out server_ca.cert.pem + -subj "/{{ server_ca_subject.items() | map('join', '=') | join('/') }}" + -passin pass:{{ octavia_ca_password }} + -batch + args: + chdir: "{{ octavia_certs_work_dir }}/server_ca" + creates: "{{ octavia_certs_work_dir }}/server_ca/server_ca.cert.pem" diff --git a/etc/kolla/passwords.yml b/etc/kolla/passwords.yml index 902d8d1d62..80fafec360 100644 --- a/etc/kolla/passwords.yml +++ b/etc/kolla/passwords.yml @@ -170,6 +170,7 @@ manila_keystone_password: octavia_database_password: octavia_keystone_password: octavia_ca_password: +octavia_client_ca_password: searchlight_keystone_password: diff --git a/tests/check-config.sh b/tests/check-config.sh index de6577e08f..ce77711ab2 100755 --- a/tests/check-config.sh +++ b/tests/check-config.sh @@ -16,6 +16,7 @@ function check_config { for f in $(sudo find /etc/kolla \ -not -regex /etc/kolla/config.* \ -not -regex /etc/kolla/certificates.* \ + -not -regex /etc/kolla/octavia-certificates.* \ -not -regex .*pem \ -not -regex .*key \ -not -regex ".*ca-certificates.*" \ diff --git a/tests/run.yml b/tests/run.yml index f9190b0ee1..7caad43edf 100644 --- a/tests/run.yml +++ b/tests/run.yml @@ -194,24 +194,6 @@ dest: ironic-agent.kernel when: scenario == "ironic" - - block: - - name: ensure octavia config directory exists - file: - path: /etc/kolla/config/octavia - state: directory - mode: 0777 - - - name: create dummy TLS certificates for octavia - file: - path: "/etc/kolla/config/octavia/{{ item }}" - state: touch - with_items: - - client.cert-and-key.pem - - client_ca.cert.pem - - server_ca.cert.pem - - server_ca.key.pem - when: scenario == 'magnum' - - name: ensure /etc/ansible exists file: path: /etc/ansible @@ -282,6 +264,13 @@ docker_image_tag: "{{ build_image_tag if need_build_image else (zuul.branch | basename) ~ docker_image_tag_suffix }}" docker_image_prefix: "{{ 'primary:4000/lokolla/' if need_build_image else 'kolla/' }}" + # NOTE(yoctozepto): k-a octavia-certificates should run before k-a bootstrap-servers + # because the latter hijacks /etc/kolla permissions (due to same directory on the + # same host being used by both) + - name: create TLS certificates for octavia + command: kolla-ansible octavia-certificates + when: scenario == 'magnum' + # NOTE(mgoddard): We are using the script module here and later to ensure # we use the local copy of these scripts, rather than the one on the remote # host, which could be checked out to a previous release (in an upgrade diff --git a/tools/kolla-ansible b/tools/kolla-ansible index 9d8ba9e137..87cc7a4404 100755 --- a/tools/kolla-ansible +++ b/tools/kolla-ansible @@ -121,29 +121,30 @@ Environment variables: EXTRA_OPTS Additional arguments to pass to ansible-playbook Commands: - prechecks Do pre-deployment checks for hosts - check Do post-deployment smoke tests - mariadb_recovery Recover a completely stopped mariadb cluster - mariadb_backup Take a backup of MariaDB databases - --full (default) - --incremental - bootstrap-servers Bootstrap servers with kolla deploy dependencies - destroy Destroy Kolla containers, volumes and host configuration - --include-images to also destroy Kolla images - --include-dev to also destroy dev mode repos - deploy Deploy and start all kolla containers - deploy-bifrost Deploy and start bifrost container - deploy-servers Enroll and deploy servers with bifrost - deploy-containers Only deploy and start containers (no config updates or bootstrapping) - post-deploy Do post deploy on deploy node - pull Pull all images for containers (only pulls, no running container changes) - reconfigure Reconfigure OpenStack service - stop Stop Kolla containers - certificates Generate self-signed certificate for TLS *For Development Only* - upgrade Upgrades existing OpenStack Environment - upgrade-bifrost Upgrades an existing bifrost container - genconfig Generate configuration files for enabled OpenStack services - prune-images Prune orphaned Kolla images + prechecks Do pre-deployment checks for hosts + check Do post-deployment smoke tests + mariadb_recovery Recover a completely stopped mariadb cluster + mariadb_backup Take a backup of MariaDB databases + --full (default) + --incremental + bootstrap-servers Bootstrap servers with kolla deploy dependencies + destroy Destroy Kolla containers, volumes and host configuration + --include-images to also destroy Kolla images + --include-dev to also destroy dev mode repos + deploy Deploy and start all kolla containers + deploy-bifrost Deploy and start bifrost container + deploy-servers Enroll and deploy servers with bifrost + deploy-containers Only deploy and start containers (no config updates or bootstrapping) + post-deploy Do post deploy on deploy node + pull Pull all images for containers (only pulls, no running container changes) + reconfigure Reconfigure OpenStack service + stop Stop Kolla containers + certificates Generate self-signed certificate for TLS *For Development Only* + octavia-certificates Generate certificates for octavia deployment + upgrade Upgrades existing OpenStack Environment + upgrade-bifrost Upgrades an existing bifrost container + genconfig Generate configuration files for enabled OpenStack services + prune-images Prune orphaned Kolla images EOF } @@ -179,6 +180,7 @@ pull reconfigure stop certificates +octavia-certificates upgrade upgrade-bifrost genconfig @@ -431,6 +433,10 @@ EOF ACTION="Generate TLS Certificates" PLAYBOOK="${BASEDIR}/ansible/certificates.yml" ;; +(octavia-certificates) + ACTION="Generate octavia Certificates" + PLAYBOOK="${BASEDIR}/ansible/octavia-certificates.yml" + ;; (genconfig) ACTION="Generate configuration files for enabled OpenStack services" EXTRA_OPTS="$EXTRA_OPTS -e kolla_action=config" diff --git a/zuul.d/base.yaml b/zuul.d/base.yaml index 87760c25e2..e7c0739a24 100644 --- a/zuul.d/base.yaml +++ b/zuul.d/base.yaml @@ -131,7 +131,7 @@ parent: kolla-ansible-base voting: false files: - - ^ansible/roles/(designate|magnum|octavia)/ + - ^ansible/roles/(designate|magnum|octavia|octavia-certificates)/ - ^tests/test-dashboard.sh - ^tests/test-magnum.sh vars: