From 28a1283dd4c70d3ff1604bec21b718a7e2796ad1 Mon Sep 17 00:00:00 2001
From: Lukasz Nowak <luke@nexedi.com>
Date: Thu, 15 Nov 2018 17:21:38 +0100
Subject: [PATCH] caddy-frontend: Implement AIKC

AIKC - Automatic Internal Kedifa's Caucase CSR signing, which can be triggered
by option automatic-internal-kedifa-caucase-csr.

It signs all CSR which match csr_id and certificate from the nodes which needs them.
---
 software/caddy-frontend/buildout.hash.cfg     |   6 +-
 .../instance-apache-replicate.cfg.in          | 129 +++++++++++++++++-
 .../instance-caddy-input-schema.json          |  10 ++
 .../caddy-frontend/instance-kedifa.cfg.in     |   2 +-
 software/caddy-frontend/instance.cfg.in       |   1 +
 5 files changed, 141 insertions(+), 7 deletions(-)

diff --git a/software/caddy-frontend/buildout.hash.cfg b/software/caddy-frontend/buildout.hash.cfg
index 3fde8cfdb..af61748f1 100644
--- a/software/caddy-frontend/buildout.hash.cfg
+++ b/software/caddy-frontend/buildout.hash.cfg
@@ -14,7 +14,7 @@
 # not need these here).
 [template]
 filename = instance.cfg.in
-md5sum = 733ef269151e9884e44174bb4dc9c6ea
+md5sum = ffaf426c68b2f7a35558bf187b5981b7
 
 [template-common]
 filename = instance-common.cfg.in
@@ -26,7 +26,7 @@ md5sum = b3275d8203b36506ea0f2f9c12f86399
 
 [template-apache-replicate]
 filename = instance-apache-replicate.cfg.in
-md5sum = ece5f1c068a3096eef6bcc8deaf8bbe2
+md5sum = ab77522560589fc315ddb6c8d28c4015
 
 [template-slave-list]
 filename = templates/apache-custom-slave-list.cfg.in
@@ -118,4 +118,4 @@ md5sum = 38792c2dceae38ab411592ec36fff6a8
 
 [template-kedifa]
 filename = instance-kedifa.cfg.in
-md5sum = bffe1624132dbf42a788ce8ae5bd7cab
+md5sum = 66de9edcd66447271424be3d92c2cb90
diff --git a/software/caddy-frontend/instance-apache-replicate.cfg.in b/software/caddy-frontend/instance-apache-replicate.cfg.in
index 0f190f5b5..470782a33 100644
--- a/software/caddy-frontend/instance-apache-replicate.cfg.in
+++ b/software/caddy-frontend/instance-apache-replicate.cfg.in
@@ -1,5 +1,6 @@
 {% if slap_software_type in software_type %}
-
+{%- set TRUE_VALUES = ['y', 'yes', '1', 'true'] -%}
+{% set aikc_enabled = slapparameter_dict.get('automatic-internal-kedifa-caucase-csr', '').lower() in TRUE_VALUES %}
 [jinja2-template-base]
 recipe = slapos.recipe.template:jinja2
 rendered = ${buildout:directory}/${:filename}
@@ -199,14 +200,16 @@ rejected-slave-amount = {{ rejected_slave_dict | length }}
 rejected-slave-dict = {{ dumps(json_module.dumps(rejected_slave_dict)) }}
 master-key-upload-url = ${request-kedifa:connection-master-key-upload-url}
 master-key-generate-auth-url = ${request-kedifa:connection-master-key-generate-auth-url}
+kedifa-caucase-url = ${request-kedifa:connection-caucase-url}
+{% if not aikc_enabled %}
 kedifa-csr_id-url = ${request-kedifa:connection-csr_id-url}
 kedifa-csr_id-certificate = ${request-kedifa:connection-csr_id-certificate}
-kedifa-caucase-url = ${request-kedifa:connection-caucase-url}
 {% for frontend in frontend_list %}
   {% set section_part = '${request-' + frontend %}
 {{ frontend }}-csr_id-url = {{ section_part }}:connection-csr_id-url}
-{{ frontend }}-csr_ud-certificate = {{ section_part }}:connection-csr_id-certificate}
+{{ frontend }}-csr_id-certificate = {{ section_part }}:connection-csr_id-certificate}
 {% endfor %}
+{% endif %}
 
 #----------------------------
 #--
@@ -286,6 +289,126 @@ monitor-url-list +=
 {{ '  ${' + frontend + ':connection-monitor-base-url}' }}
 {% endfor %}
 
+{% if aikc_enabled %}
+[directory]
+recipe = slapos.cookbook:mkdirectory
+
+bin = ${buildout:directory}/bin/
+srv = ${buildout:directory}/srv/
+aikc = ${:srv}/aikc
+
+[aikc-config]
+caucase-url = ${request-kedifa:connection-caucase-url}
+
+csr = ${directory:aikc}/csr.pem
+key = ${directory:aikc}/key.pem
+ca-certificate = ${directory:aikc}/cas-ca-certificate.pem
+crl = ${directory:aikc}/crl.pem
+user-ca-certificate = ${directory:aikc}/user-ca-certificate.pem
+user-crl = ${directory:aikc}/user-crl.pem
+user-created = ${directory:aikc}/user-created
+csr_id = ${directory:aikc}/csr_id
+
+[aikc-user-csr]
+recipe = plone.recipe.command
+organization = {{ cluster_identification }}
+organizational_unit = Automatic Internal Kedifa Caucase CSR
+command =
+  if [ ! -f ${:csr} ] && [ ! -f ${:key} ]  ; then
+    {{ parameter_dict['openssl'] }} req -new -sha256 \
+      -newkey rsa:2048 -nodes -keyout ${:key} \
+      -subj "/O=${:organization}/OU=${:organizational_unit}" \
+      -out ${:csr}
+  fi
+update-command = ${:command}
+csr = ${aikc-config:csr}
+key = ${aikc-config:key}
+stop-on-error = True
+
+
+[aikc-caucase-wrapper]
+{# jinja2 instead of wrapper is used with context to remove py'u' #}
+recipe = slapos.recipe.template:jinja2
+context =
+  key caucase_url aikc-config:caucase-url
+template = inline:#!{{ parameter_dict['dash'] }}/bin/dash
+  exec {{ parameter_dict['bin_directory'] }}/caucase \
+{# raw block to use context #}
+{% raw %}
+  --ca-url {{ caucase_url }} \
+{% endraw %}
+  --ca-crt ${aikc-config:ca-certificate} \
+  --user-ca-crt ${aikc-config:user-ca-certificate} \
+  --user-crl ${aikc-config:user-crl} \
+  --crl ${aikc-config:crl} \
+  "$@"
+
+rendered = ${directory:bin}/aikc-caucase-wrapper
+mode = 0700
+
+{% do part_list.append('aikc-create-user') %}
+[aikc-create-user]
+recipe = plone.recipe.command
+stop-on-error = True
+update-command = ${:command}
+command =
+  if ! [ -f ${aikc-config:user-created} ] ; then
+    ${aikc-caucase-wrapper:rendered} --mode user --send-csr ${aikc-user-csr:csr} > ${aikc-config:csr_id} || exit 1
+    cut -d ' ' -f 1 ${aikc-config:csr_id} || exit 1
+    csr_id=`cut -d ' ' -f 1 ${aikc-config:csr_id}`
+    sleep 1
+    ${aikc-caucase-wrapper:rendered} --mode user --get-crt $csr_id ${aikc-config:key} || exit 1
+    touch ${aikc-config:user-created}
+  fi
+
+[aikc-check-certificate]
+recipe = slapos.recipe.template:jinja2
+rendered = ${directory:bin}/aikc-check-certificate
+template = inline:
+  import sys
+  import ssl
+  import urlparse
+  certificate = sys.argv[2]
+  parsed = urlparse.urlparse(sys.argv[1])
+  got_certificate = ssl.get_server_certificate((parsed.hostname, parsed.port))
+  sys.exit(0) if certificate.strip() == got_certificate.strip() else sys.exit(1)
+
+{% for csr in frontend_list + ['kedifa'] %}
+[aikc-{{ csr }}-wrapper]
+{# jinja2 instead of wrapper is used with context to remove py'u' #}
+recipe = slapos.recipe.template:jinja2
+context =
+  key csr_id_url request-{{ csr }}:connection-csr_id-url
+  key csr_id_certificate request-{{ csr }}:connection-csr_id-certificate
+template = inline:#!{{ parameter_dict['dash'] }}/bin/dash
+  test -f ${directory:aikc}/{{ csr }}-done && exit 0
+  ${buildout:executable} ${aikc-check-certificate:rendered} \
+{# raw block to use context #}
+{% raw %}
+  {{ csr_id_url }} \
+  """{{ csr_id_certificate }}"""
+{% endraw %}
+  if [ $? = 0 ]; then
+    csr_id=`{{ parameter_dict['curl'] }}/bin/curl -s -k -g \
+{% raw %}
+  {{ csr_id_url }} \
+{% endraw %}
+  ` || exit 1
+    ${aikc-caucase-wrapper:rendered} --user-key ${aikc-config:key} --sign-csr $csr_id && touch ${directory:aikc}/{{ csr }}-done
+  fi
+rendered = ${directory:bin}/aikc-{{ csr }}-wrapper
+mode = 0700
+
+{% do part_list.append('aikc-%s' % (csr,)) %}
+[aikc-{{ csr }}]
+recipe = plone.recipe.command
+stop-on-error = True
+command =
+  ${aikc-{{ csr }}-wrapper:rendered}
+update-command = ${:command}
+{% endfor %}
+{% endif %}
+
 [buildout]
 extends =
   {{ common_profile }}
diff --git a/software/caddy-frontend/instance-caddy-input-schema.json b/software/caddy-frontend/instance-caddy-input-schema.json
index 8e976dd0b..d8f795129 100644
--- a/software/caddy-frontend/instance-caddy-input-schema.json
+++ b/software/caddy-frontend/instance-caddy-input-schema.json
@@ -80,6 +80,16 @@
       "description": "How often Caddy will try to establish connection with a backend during proxy-try-duration. More info in https://caddyserver.com/docs/proxy try_interval",
       "title": "Interval in milliseconds of tries during proxy-try-duration",
       "type": "integer"
+    },
+    "automatic-internal-kedifa-caucase-csr": {
+      "default": "false",
+      "description": "Automatically signs CSRs sent to KeDiFa's caucase, based on csr_id and matching certificate.",
+      "enum": [
+        "true",
+        "false"
+      ],
+      "title": "Automatic Internal KeDiFa's Caucase CSR",
+      "type": "string"
     }
   },
   "title": "Input Parameters",
diff --git a/software/caddy-frontend/instance-kedifa.cfg.in b/software/caddy-frontend/instance-kedifa.cfg.in
index 1826fdff4..07d61a837 100644
--- a/software/caddy-frontend/instance-kedifa.cfg.in
+++ b/software/caddy-frontend/instance-kedifa.cfg.in
@@ -197,7 +197,7 @@ port = {{ instance_parameter['configuration.kedifa_port'] }}
 db = ${directory:kedifa}/kedifa.sqlite
 certificate = ${directory:etc-kedifa}/certificate.pem
 key = ${:certificate}
-ca-certificate = ${directory:etc-kedifa}/cas-ca-certificate.pem
+ca-certificate = ${directory:etc-kedifa}/ca-certificate.pem
 crl = ${directory:etc-kedifa}/crl.pem
 template-csr = ${directory:etc-kedifa}/template-csr.pem
 pidfile = ${directory:run}/kedifa.pid
diff --git a/software/caddy-frontend/instance.cfg.in b/software/caddy-frontend/instance.cfg.in
index 4e839e151..4784fafe1 100644
--- a/software/caddy-frontend/instance.cfg.in
+++ b/software/caddy-frontend/instance.cfg.in
@@ -65,6 +65,7 @@ extra-context =
     raw software_type RootSoftwareInstance-default-custom-personal-replicate
     raw template_monitor {{ monitor2_template }}
     raw common_profile {{ common_profile }}
+    section parameter_dict dynamic-template-caddy-frontend-parameters
 
 [dynamic-template-kedifa]
 < = jinja2-template-base
-- 
2.30.9