From 524868c632b23385145d140e5e6abab82cda50cf Mon Sep 17 00:00:00 2001
From: Shaun Smekel <shaun.smekel@theorem.net.au>
Date: Fri, 5 Aug 2016 07:44:42 +1000
Subject: [PATCH] Add dockerfiles for keystone fernet

This adds the docker aspects of fernet key bootstrapping as well as
distributed key rotation.

- Bootstrapping is handled in the same way as keystone bootstrap.
- A new keystone-fernet and keystone-ssh container is created to allow
  the nodes to communicate with each other (taken from nova-ssh).
- The keystone-fernet is a keystone container with crontab installed.
  This will handle key rotations through keystone-manage and trigger
  an rsync to push new tokens to other nodes.

The Ansible component is implemented in:
  https://review.openstack.org/#/c/349366

Change-Id: Id610e00e8c63c7f1bc0974c0aa1b3f44c18e1019
Partially-Implements: blueprint keystone-fernet-token
Partially-Implements: blueprint third-party-plugin-support
---
 .../{ => keystone-base}/Dockerfile.j2         | 36 ++++----
 docker/keystone/keystone-fernet/Dockerfile.j2 | 25 ++++++
 .../keystone/keystone-fernet/extend_start.sh  | 12 +++
 .../keystone-fernet/fetch_fernet_tokens.py    | 84 +++++++++++++++++++
 .../keystone-fernet/keystone_bootstrap.sh     | 43 ++++++++++
 docker/keystone/keystone-ssh/Dockerfile.j2    | 21 +++++
 docker/keystone/keystone-ssh/extend_start.sh  | 20 +++++
 docker/keystone/keystone/Dockerfile.j2        | 10 +++
 .../keystone/{ => keystone}/extend_start.sh   |  0
 .../{ => keystone}/keystone_bootstrap.sh      |  0
 kolla/common/config.py                        |  2 +-
 11 files changed, 230 insertions(+), 23 deletions(-)
 rename docker/keystone/{ => keystone-base}/Dockerfile.j2 (73%)
 create mode 100644 docker/keystone/keystone-fernet/Dockerfile.j2
 create mode 100644 docker/keystone/keystone-fernet/extend_start.sh
 create mode 100644 docker/keystone/keystone-fernet/fetch_fernet_tokens.py
 create mode 100644 docker/keystone/keystone-fernet/keystone_bootstrap.sh
 create mode 100644 docker/keystone/keystone-ssh/Dockerfile.j2
 create mode 100644 docker/keystone/keystone-ssh/extend_start.sh
 create mode 100644 docker/keystone/keystone/Dockerfile.j2
 rename docker/keystone/{ => keystone}/extend_start.sh (100%)
 rename docker/keystone/{ => keystone}/keystone_bootstrap.sh (100%)

diff --git a/docker/keystone/Dockerfile.j2 b/docker/keystone/keystone-base/Dockerfile.j2
similarity index 73%
rename from docker/keystone/Dockerfile.j2
rename to docker/keystone/keystone-base/Dockerfile.j2
index e4a37baab6..3364377320 100644
--- a/docker/keystone/Dockerfile.j2
+++ b/docker/keystone/keystone-base/Dockerfile.j2
@@ -1,34 +1,32 @@
 FROM {{ namespace }}/{{ image_prefix }}openstack-base:{{ tag }}
 MAINTAINER {{ maintainer }}
-
 {% import "macros.j2" as macros with context %}
 
 {% if install_type == 'binary' %}
     {% if base_distro in ['fedora', 'centos', 'oraclelinux', 'rhel'] %}
-       {% set keystone_packages = [
-            'openstack-keystone',  
+        {% set keystone_base_packages = [
+            'openstack-keystone',
             'python-keystoneclient',
             'httpd',
             'mod_wsgi',
             'python-ldappool'
         ] %}
 
-{{ macros.install_packages(keystone_packages | customizable("packages")) }}
+{{ macros.install_packages(keystone_base_packages | customizable("packages")) }}
 RUN mkdir -p /var/www/cgi-bin/keystone \
     && cp -a /usr/share/keystone/keystone.wsgi /var/www/cgi-bin/keystone/main \
     && cp -a /usr/share/keystone/keystone.wsgi /var/www/cgi-bin/keystone/admin \
     && sed -i -r 's,^(Listen 80),#\1,' /etc/httpd/conf/httpd.conf
 
     {% elif base_distro in ['ubuntu'] %}
-
-       {% set keystone_packages = [
+        {% set keystone_base_packages = [
             'keystone',
             'apache2',
             'libapache2-mod-wsgi',
             'python-ldappool'
         ] %}
 
-{{ macros.install_packages(keystone_packages | customizable("packages")) }}
+{{ macros.install_packages(keystone_base_packages | customizable("packages")) }}
 RUN mkdir -p /var/www/cgi-bin/keystone \
     && cp -a /usr/share/keystone/wsgi.py /var/www/cgi-bin/keystone/main \
     && cp -a /usr/share/keystone/wsgi.py /var/www/cgi-bin/keystone/admin \
@@ -38,28 +36,27 @@ RUN mkdir -p /var/www/cgi-bin/keystone \
     {% endif %}
 {% elif install_type == 'source' %}
     {% if base_distro in ['fedora', 'centos', 'oraclelinux', 'rhel'] %}
-
-       {% set keystone_packages = [
+        {% set keystone_base_packages = [
             'httpd',
             'mod_wsgi',
             'python-ldappool'
         ] %}
-{{ macros.install_packages(keystone_packages | customizable("packages")) }}
+{{ macros.install_packages(keystone_base_packages | customizable("packages")) }}
 RUN sed -i -r 's,^(Listen 80),#\1,' /etc/httpd/conf/httpd.conf
 
     {% elif base_distro in ['ubuntu', 'debian'] %}
-
-       {% set keystone_packages = [
+        {% set keystone_base_packages = [
             'apache2',
             'libapache2-mod-wsgi',
             'python-ldappool'
         ] %}
-{{ macros.install_packages(keystone_packages | customizable("packages")) }}
+{{ macros.install_packages(keystone_base_packages | customizable("packages")) }}
 RUN echo > /etc/apache2/ports.conf
 
     {% endif %}
-ADD keystone-archive /keystone-source
-RUN ln -s keystone-source/* keystone \
+
+ADD keystone-base-archive /keystone-base-source
+RUN ln -s keystone-base-source/* keystone \
     && useradd --user-group keystone \
     && /var/lib/kolla/venv/bin/pip --no-cache-dir install --upgrade -c requirements/upper-constraints.txt /keystone \
     && mkdir -p /etc/keystone /var/www/cgi-bin/keystone /var/log/apache2 /home/keystone \
@@ -74,11 +71,6 @@ RUN usermod -a -G kolla keystone \
     && chown -R keystone: /var/www/cgi-bin/keystone \
     && chmod 755 /var/www/cgi-bin/keystone/*
 
-COPY keystone_bootstrap.sh /usr/local/bin/kolla_keystone_bootstrap
-COPY extend_start.sh /usr/local/bin/kolla_extend_start
-RUN chmod 755 /usr/local/bin/kolla_extend_start /usr/local/bin/kolla_keystone_bootstrap
-
-{% block keystone_footer %}{% endblock %}
+{% block keystone_base_footer %}{% endblock %}
 {% block footer %}{% endblock %}
-
-{{ include_footer }}
+{{ include_footer }}
\ No newline at end of file
diff --git a/docker/keystone/keystone-fernet/Dockerfile.j2 b/docker/keystone/keystone-fernet/Dockerfile.j2
new file mode 100644
index 0000000000..0488ceb91b
--- /dev/null
+++ b/docker/keystone/keystone-fernet/Dockerfile.j2
@@ -0,0 +1,25 @@
+FROM {{ namespace }}/{{ image_prefix }}keystone-base:{{ tag }}
+MAINTAINER {{ maintainer }}
+{% import "macros.j2" as macros with context %}
+
+{% if base_distro in ['fedora', 'centos', 'oraclelinux', 'rhel'] %}
+    {% set keystone_fernet_packages = [
+        'cronie',
+        'rsync'
+    ] %}
+{% elif base_distro in ['ubuntu', 'debian'] %}
+    {% set keystone_fernet_packages = [
+        'cron',
+        'rsync'
+    ] %}
+{% endif %}
+{{ macros.install_packages(keystone_fernet_packages | customizable("packages")) }}
+
+COPY fetch_fernet_tokens.py /usr/bin/
+COPY keystone_bootstrap.sh /usr/local/bin/kolla_keystone_bootstrap
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+RUN chmod 755 /usr/local/bin/kolla_extend_start /usr/local/bin/kolla_keystone_bootstrap /usr/bin/fetch_fernet_tokens.py
+
+{% block keystone_fernet_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+{{ include_footer }}
diff --git a/docker/keystone/keystone-fernet/extend_start.sh b/docker/keystone/keystone-fernet/extend_start.sh
new file mode 100644
index 0000000000..0d6a3a539a
--- /dev/null
+++ b/docker/keystone/keystone-fernet/extend_start.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+FERNET_SYNC=/usr/bin/fernet-node-sync.sh
+FERNET_TOKEN_DIR="/etc/keystone/fernet-keys"
+
+if [[ -f "${FERNET_SYNC}" ]]; then
+    ${FERNET_SYNC}
+fi
+
+if [[ $(stat -c %U:%G ${FERNET_TOKEN_DIR}) != "keystone:keystone" ]]; then
+    chown keystone:keystone ${FERNET_TOKEN_DIR}
+fi
\ No newline at end of file
diff --git a/docker/keystone/keystone-fernet/fetch_fernet_tokens.py b/docker/keystone/keystone-fernet/fetch_fernet_tokens.py
new file mode 100644
index 0000000000..7ef7be0858
--- /dev/null
+++ b/docker/keystone/keystone-fernet/fetch_fernet_tokens.py
@@ -0,0 +1,84 @@
+#!/usr/bin/python
+
+# 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.
+
+# Basically this module will fetch the fernet tokens and compare them to the
+# required time constrains to determine whether the host needs to resync with
+# other nodes in the cluster.
+
+from __future__ import print_function
+import argparse
+from datetime import datetime
+from datetime import timedelta
+import json
+import os
+import sys
+
+TOKEN_PATH = '/etc/keystone/fernet-keys'
+
+
+def json_exit(msg=None, failed=False, changed=False):
+    if type(msg) is not dict:
+        msg = {'msg': str(msg)}
+    msg.update({'failed': failed, 'changed': changed})
+    print(json.dumps(msg))
+    sys.exit()
+
+
+def has_file(filename_path):
+    if not os.path.exists(filename_path):
+        return False
+    return True
+
+
+def num_tokens():
+    _, _, files = os.walk(TOKEN_PATH).next()
+    return len(files)
+
+
+def tokens_populated(expected):
+    return num_tokens() == int(expected)
+
+
+def token_stale(seconds, filename='0'):
+    max_token_age = datetime.now() - timedelta(seconds=int(seconds))
+    filename_path = os.path.join(TOKEN_PATH, filename)
+
+    if not has_file(filename_path):
+        return True
+    modified_date = datetime.fromtimestamp(os.path.getmtime(filename_path))
+    return modified_date < max_token_age
+
+
+def main():
+    parser = argparse.ArgumentParser(description='''Checks to see if a fernet
+        token no older than a desired time.''')
+    parser.add_argument('-t', '--time',
+                        help='Time in seconds for a token rotation',
+                        required=True)
+    parser.add_argument('-f', '--filename',
+                        help='Filename of token to check',
+                        default='0')
+    parser.add_argument('-n', '--number',
+                        help='Number of tokens that should exist',
+                        required=True)
+    args = parser.parse_args()
+
+    json_exit({
+        'populated': tokens_populated(args.number),
+        'update_required': token_stale(args.time, args.filename),
+    })
+
+
+if __name__ == '__main__':
+    main()
diff --git a/docker/keystone/keystone-fernet/keystone_bootstrap.sh b/docker/keystone/keystone-fernet/keystone_bootstrap.sh
new file mode 100644
index 0000000000..d361767bbc
--- /dev/null
+++ b/docker/keystone/keystone-fernet/keystone_bootstrap.sh
@@ -0,0 +1,43 @@
+#!/bin/bash
+
+# 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.
+
+set -x
+
+USERNAME=$1
+GROUP=$2
+
+function fail_json {
+    echo '{"failed": true, "msg": "'$1'", "changed": true}'
+    exit 1
+}
+
+function exit_json {
+    echo '{"failed": false, "changed": '"${changed}"'}'
+}
+
+changed="false"
+keystone_bootstrap=$(keystone-manage --config-file /etc/keystone/keystone.conf fernet_setup --keystone-user ${USERNAME} --keystone-group ${GROUP} 2>&1)
+if [[ $? != 0 ]]; then
+    fail_json "${keystone_bootstrap}"
+fi
+
+changed=$(echo "${keystone_bootstrap}" | awk '
+    /Key repository is already initialized/ {count++}
+    END {
+        if (count == 1) changed="true"; else changed="false"
+        print changed
+    }'
+)
+
+exit_json
diff --git a/docker/keystone/keystone-ssh/Dockerfile.j2 b/docker/keystone/keystone-ssh/Dockerfile.j2
new file mode 100644
index 0000000000..2f53897420
--- /dev/null
+++ b/docker/keystone/keystone-ssh/Dockerfile.j2
@@ -0,0 +1,21 @@
+FROM {{ namespace }}/{{ image_prefix }}keystone-base:{{ tag }}
+MAINTAINER {{ maintainer }}
+{% import "macros.j2" as macros with context %}
+
+{% if base_distro in ['centos', 'fedora', 'oraclelinux', 'rhel'] %}
+    {% set keystone_ssh_packages = ['openssh-server'] %}
+{% elif base_distro in ['ubuntu', 'debian'] %}
+    {% set keystone_ssh_packages = ['openssh-server'] %}
+
+RUN mkdir -p /var/run/sshd \
+    && chmod 0755 /var/run/sshd
+
+{% endif %}
+{{ macros.install_packages(keystone_ssh_packages | customizable("packages")) }}
+
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+RUN chmod 755 /usr/local/bin/kolla_extend_start
+
+{% block keystone_ssh_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+{{ include_footer }}
diff --git a/docker/keystone/keystone-ssh/extend_start.sh b/docker/keystone/keystone-ssh/extend_start.sh
new file mode 100644
index 0000000000..23744cead2
--- /dev/null
+++ b/docker/keystone/keystone-ssh/extend_start.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+if [[ ! -L /dev/log ]]; then
+    ln -sf /var/lib/kolla/heka/log /dev/log
+fi
+
+SSH_HOST_KEY_TYPES=( "rsa" "dsa" "ecdsa" "ed25519" )
+
+for key_type in ${SSH_HOST_KEY_TYPES[@]}; do
+    KEY_PATH=/etc/ssh/ssh_host_${key_type}_key
+    if [[ ! -f "${KEY_PATH}" ]]; then
+        ssh-keygen -q -t ${key_type} -f ${KEY_PATH} -N ""
+    fi
+done
+
+mkdir -p /var/lib/keystone/.ssh
+
+if [[ $(stat -c %U:%G /var/lib/keystone/.ssh) != "keystone:keystone" ]]; then
+    sudo chown keystone: /var/lib/keystone/.ssh
+fi
diff --git a/docker/keystone/keystone/Dockerfile.j2 b/docker/keystone/keystone/Dockerfile.j2
new file mode 100644
index 0000000000..e6baa93492
--- /dev/null
+++ b/docker/keystone/keystone/Dockerfile.j2
@@ -0,0 +1,10 @@
+FROM {{ namespace }}/{{ image_prefix }}keystone-base:{{ tag }}
+MAINTAINER {{ maintainer }}
+
+COPY keystone_bootstrap.sh /usr/local/bin/kolla_keystone_bootstrap
+COPY extend_start.sh /usr/local/bin/kolla_extend_start
+RUN chmod 755 /usr/local/bin/kolla_extend_start /usr/local/bin/kolla_keystone_bootstrap
+
+{% block keystone_footer %}{% endblock %}
+{% block footer %}{% endblock %}
+{{ include_footer }}
\ No newline at end of file
diff --git a/docker/keystone/extend_start.sh b/docker/keystone/keystone/extend_start.sh
similarity index 100%
rename from docker/keystone/extend_start.sh
rename to docker/keystone/keystone/extend_start.sh
diff --git a/docker/keystone/keystone_bootstrap.sh b/docker/keystone/keystone/keystone_bootstrap.sh
similarity index 100%
rename from docker/keystone/keystone_bootstrap.sh
rename to docker/keystone/keystone/keystone_bootstrap.sh
diff --git a/kolla/common/config.py b/kolla/common/config.py
index 4be0bebcea..687b425cb4 100644
--- a/kolla/common/config.py
+++ b/kolla/common/config.py
@@ -207,7 +207,7 @@ SOURCES = {
         'type': 'url',
         'location': ('http://tarballs.openstack.org/ironic/'
                      'ironic-master.tar.gz')},
-    'keystone': {
+    'keystone-base': {
         'type': 'url',
         'location': ('http://tarballs.openstack.org/keystone/'
                      'keystone-master.tar.gz')},