diff --git a/inventory/service/group_vars/zookeeper.yaml b/inventory/service/group_vars/zookeeper.yaml index f74b283b0f..5816d2ff18 100644 --- a/inventory/service/group_vars/zookeeper.yaml +++ b/inventory/service/group_vars/zookeeper.yaml @@ -3,8 +3,12 @@ zookeeper_group: zookeeper zookeeper_uid: 10001 zookeeper_gid: 10001 iptables_extra_allowed_groups: + # Insecure - {'protocol': 'tcp', 'port': '2181', 'group': 'nodepool'} - {'protocol': 'tcp', 'port': '2181', 'group': 'zuul'} + # Secure + - {'protocol': 'tcp', 'port': '2281', 'group': 'nodepool'} + - {'protocol': 'tcp', 'port': '2281', 'group': 'zuul'} # Zookeeper election - {'protocol': 'tcp', 'port': '2888', 'group': 'zookeeper'} # Zookeeper leader diff --git a/playbooks/roles/nodepool-base/tasks/main.yaml b/playbooks/roles/nodepool-base/tasks/main.yaml index c0a5460d7d..d2c85f2bc5 100644 --- a/playbooks/roles/nodepool-base/tasks/main.yaml +++ b/playbooks/roles/nodepool-base/tasks/main.yaml @@ -26,6 +26,14 @@ group: '{{ nodepool_group }}' mode: 0755 +- name: Generate ZooKeeper TLS cert + include_role: + name: zk-ca + vars: + zk_ca_cert_dir: /etc/nodepool + zk_ca_cert_dir_owner: '{{ nodepool_user }}' + zk_ca_cert_dir_group: '{{ nodepool_group }}' + - name: Create nodepool log dir file: name: /var/log/nodepool diff --git a/playbooks/roles/zk-ca/README.rst b/playbooks/roles/zk-ca/README.rst new file mode 100644 index 0000000000..e314c2ab20 --- /dev/null +++ b/playbooks/roles/zk-ca/README.rst @@ -0,0 +1,4 @@ +Generate TLS certs for ZooKeeper + +This will copy the certs to the remote node into the /etc/zuul +directory by default. diff --git a/playbooks/roles/zk-ca/defaults/main.yaml b/playbooks/roles/zk-ca/defaults/main.yaml new file mode 100644 index 0000000000..db0eb65f72 --- /dev/null +++ b/playbooks/roles/zk-ca/defaults/main.yaml @@ -0,0 +1,5 @@ +zk_ca_root: /var/zk-ca +zk_ca_server: "{{ inventory_hostname }}" +zk_ca_cert_dir: /etc/zuul +zk_ca_cert_dir_owner: 10001 +zk_ca_cert_dir_group: 10001 diff --git a/playbooks/roles/zk-ca/tasks/main.yaml b/playbooks/roles/zk-ca/tasks/main.yaml new file mode 100644 index 0000000000..f76f82937b --- /dev/null +++ b/playbooks/roles/zk-ca/tasks/main.yaml @@ -0,0 +1,49 @@ +- name: Ensure zk-ca directory exists + delegate_to: localhost + file: + path: "{{ zk_ca_root }}" + state: directory + +# Run this in flock so that we can run it in plays for multiple target +# hosts in parallel while serializing access to the CA files. +- name: Run zk-ca.sh + delegate_to: localhost + script: "zk-ca.sh {{ zk_ca_root }} {{ zk_ca_server }}" + args: + executable: "flock {{ zk_ca_root }}/lock" + +- name: Ensure cert dir exists + file: + path: "{{ zk_ca_cert_dir }}/certs" + state: directory + owner: "{{ zk_ca_cert_dir_owner }}" + group: "{{ zk_ca_cert_dir_group }}" + mode: '0755' + +- name: Ensure keys dir exists + file: + path: "{{ zk_ca_cert_dir }}/keys" + state: directory + owner: "{{ zk_ca_cert_dir_owner }}" + group: "{{ zk_ca_cert_dir_group }}" + mode: '0700' + +- name: Copy TLS cacert into place + copy: + src: "/var/zk-ca/certs/cacert.pem" + dest: "{{ zk_ca_cert_dir }}/certs/cacert.pem" + +- name: Copy TLS cert into place + copy: + src: "/var/zk-ca/certs/{{ inventory_hostname }}.pem" + dest: "{{ zk_ca_cert_dir }}/certs/cert.pem" + +- name: Copy TLS key into place + copy: + src: "/var/zk-ca/keys/{{ inventory_hostname }}key.pem" + dest: "{{ zk_ca_cert_dir }}/keys/key.pem" + +- name: Copy TLS keystore into place + copy: + src: "/var/zk-ca/keystores/{{ inventory_hostname }}.pem" + dest: "{{ zk_ca_cert_dir }}/keys/keystore.pem" diff --git a/playbooks/roles/zk-ca/zk-ca.sh b/playbooks/roles/zk-ca/zk-ca.sh new file mode 100755 index 0000000000..69c393bb02 --- /dev/null +++ b/playbooks/roles/zk-ca/zk-ca.sh @@ -0,0 +1,104 @@ +#!/bin/sh -e + +# Copyright 2020 Red Hat, Inc +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +# Manage a CA for Zookeeper + +CAROOT=$1 +SERVER=$2 + +SUBJECT='/C=US/ST=California/L=Oakland/O=Company Name/OU=Org' +TOOLSDIR=$(dirname $0) +# Unlike the zk-ca.sh in zuul, we use the system openssl.cnf +CONFIG="" + +make_ca() { + mkdir $CAROOT/demoCA + mkdir $CAROOT/demoCA/reqs + mkdir $CAROOT/demoCA/newcerts + mkdir $CAROOT/demoCA/crl + mkdir $CAROOT/demoCA/private + chmod 700 $CAROOT/demoCA/private + touch $CAROOT/demoCA/index.txt + touch $CAROOT/demoCA/index.txt.attr + mkdir $CAROOT/certs + mkdir $CAROOT/keys + mkdir $CAROOT/keystores + chmod 700 $CAROOT/keys + chmod 700 $CAROOT/keystores + + openssl req $CONFIG -new -nodes -subj "$SUBJECT/CN=caroot" \ + -keyout $CAROOT/demoCA/private/cakey.pem \ + -out $CAROOT/demoCA/reqs/careq.pem + openssl ca $CONFIG -create_serial -days 3560 -batch -selfsign -extensions v3_ca \ + -out $CAROOT/demoCA/cacert.pem \ + -keyfile $CAROOT/demoCA/private/cakey.pem \ + -infiles $CAROOT/demoCA/reqs/careq.pem + cp $CAROOT/demoCA/cacert.pem $CAROOT/certs +} + +make_client() { + openssl req $CONFIG -new -nodes -subj "$SUBJECT/CN=client" \ + -keyout $CAROOT/keys/clientkey.pem \ + -out $CAROOT/demoCA/reqs/clientreq.pem + openssl ca $CONFIG -batch -policy policy_anything -days 3560 \ + -out $CAROOT/certs/client.pem \ + -infiles $CAROOT/demoCA/reqs/clientreq.pem +} + +make_server() { + openssl req $CONFIG -new -nodes -subj "$SUBJECT/CN=$SERVER" \ + -keyout $CAROOT/keys/${SERVER}key.pem \ + -out $CAROOT/demoCA/reqs/${SERVER}req.pem + openssl ca $CONFIG -batch -policy policy_anything -days 3560 \ + -out $CAROOT/certs/$SERVER.pem \ + -infiles $CAROOT/demoCA/reqs/${SERVER}req.pem + cat $CAROOT/certs/$SERVER.pem $CAROOT/keys/${SERVER}key.pem \ + > $CAROOT/keystores/$SERVER.pem +} + +help() { + echo "$0 CAROOT [SERVER]" + echo + echo " CAROOT is the path to a directory in which to store the CA" + echo " and certificates." + echo " SERVER is the FQDN of a server for which a certificate should" + echo " be generated" +} + +if [ ! -d "$CAROOT" ]; then + echo "CAROOT must be a directory" + help + exit 1 +fi + +cd $CAROOT +CAROOT=`pwd` + +if [ ! -d "$CAROOT/demoCA" ]; then + echo 'Generate CA' + make_ca + echo 'Generate client certificate' + make_client +fi + +if [ -f "$CAROOT/certs/$SERVER.pem" ]; then + echo "Certificate for $SERVER already exists" + exit 0 +fi + +if [ "$SERVER" != "" ]; then + make_server +fi diff --git a/playbooks/roles/zookeeper/files/zookeeper-compose/docker-compose.yaml b/playbooks/roles/zookeeper/files/zookeeper-compose/docker-compose.yaml index ae6aa3a9c4..96e0ecbe00 100644 --- a/playbooks/roles/zookeeper/files/zookeeper-compose/docker-compose.yaml +++ b/playbooks/roles/zookeeper/files/zookeeper-compose/docker-compose.yaml @@ -12,3 +12,4 @@ services: - "/var/zookeeper/data:/data" - "/var/zookeeper/datalog:/datalog" - "/var/zookeeper/logs:/logs" + - "/var/zookeeper/tls:/tls" diff --git a/playbooks/roles/zookeeper/tasks/main.yaml b/playbooks/roles/zookeeper/tasks/main.yaml index 10ceaa2dba..1acc60d29e 100644 --- a/playbooks/roles/zookeeper/tasks/main.yaml +++ b/playbooks/roles/zookeeper/tasks/main.yaml @@ -27,6 +27,14 @@ - data - datalog - logs + - tls +- name: Generate ZooKeeper TLS cert + include_role: + name: zk-ca + vars: + zk_ca_cert_dir: /var/zookeeper/tls + zk_ca_cert_dir_owner: 10001 + zk_ca_cert_dir_group: 10001 - name: Write config template: src: zoo.cfg.j2 diff --git a/playbooks/roles/zookeeper/templates/zoo.cfg.j2 b/playbooks/roles/zookeeper/templates/zoo.cfg.j2 index 63dff1b82a..d943db37b8 100644 --- a/playbooks/roles/zookeeper/templates/zoo.cfg.j2 +++ b/playbooks/roles/zookeeper/templates/zoo.cfg.j2 @@ -23,6 +23,13 @@ maxClientCnxns=60 standaloneEnabled=true admin.enableServer=true clientPort=2181 +secureClientPort=2281 +ssl.keyStore.location=/tls/keys/keystore.pem +ssl.trustStore.location=/tls/certs/cacert.pem {% for host in groups['zookeeper'] %} -server.{{ loop.index }}={{ (hostvars[host].ansible_default_ipv4.address) }}:2888:3888 +server.{{ loop.index }}={{ (hostvars[host].public_v4) }}:2888:3888 {% endfor %} +sslQuorum=true +serverCnxnFactory=org.apache.zookeeper.server.NettyServerCnxnFactory +ssl.quorum.keyStore.location=/tls/keys/keystore.pem +ssl.quorum.trustStore.location=/tls/certs/cacert.pem diff --git a/playbooks/roles/zuul/tasks/main.yaml b/playbooks/roles/zuul/tasks/main.yaml index 4c1738a18c..1451caff02 100644 --- a/playbooks/roles/zuul/tasks/main.yaml +++ b/playbooks/roles/zuul/tasks/main.yaml @@ -21,6 +21,13 @@ owner: "{{ zuul_user }}" group: "{{ zuul_group }}" +- name: Generate ZooKeeper TLS cert + include_role: + name: zk-ca + vars: + zk_ca_cert_dir_owner: "{{ zuul_user_id }}" + zk_ca_cert_dir_group: "{{ zuul_group_id }}" + - name: Create Zuul SSL dir file: state: directory diff --git a/playbooks/roles/zuul/templates/zuul.conf.j2 b/playbooks/roles/zuul/templates/zuul.conf.j2 index eb8d512cb6..93f27de39d 100644 --- a/playbooks/roles/zuul/templates/zuul.conf.j2 +++ b/playbooks/roles/zuul/templates/zuul.conf.j2 @@ -28,7 +28,7 @@ relative_priority=true user=zuul [zookeeper] -hosts={% for host in groups['zookeeper'] %}{{ (hostvars[host].ansible_default_ipv4.address) }}{% if not loop.last %},{% endif %}{% endfor %} +hosts={% for host in groups['zookeeper'] %}{{ (hostvars[host].public_v4) }}:2181{% if not loop.last %},{% endif %}{% endfor %} session_timeout=40 diff --git a/testinfra/test_zookeeper.py b/testinfra/test_zookeeper.py index 0890bfaa46..6327a71c8f 100644 --- a/testinfra/test_zookeeper.py +++ b/testinfra/test_zookeeper.py @@ -24,3 +24,7 @@ def test_id_file(host): def test_zk_listening(host): zk = host.socket("tcp://0.0.0.0:2181") assert zk.is_listening + +def test_zk_listening_ssl(host): + zk = host.socket("tcp://0.0.0.0:2281") + assert zk.is_listening