Commit bc2b1742 by Łukasz Nowak Committed by Łukasz Nowak

caddy-frontend: Implement KeDiFa SSL information

Use KeDiFa to store keys, and transmit the url to the requester for master
and slave partitions.

Download keys on the slave partitions level.

Use caucase to fetch main caucase CA.

kedifa-caucase-url is published in order to have access to it.

Note: caucase is prepended with kedifa, as this is that one.

Use kedifa-csr tool to generate CSR and use caucase-updater macro.

Switch to KeDiFa with SSL Auth and updated goodies.

KeDiFa endpoint URLs are randomised.

Only one (first) user certificate is going to be automatically accepted. This
one shall be operated by the cluster owner, the requester of frontend master
partition.

Then he will be able to sign certificates for other users and also for
services - so each node in the cluster.

Special trick from https://security.stackexchange.com/questions/74345/provide-subjectaltname-to-openssl-directly-on-command-line
is used for one command generation of extensions in the certificate.
Note: We could upgrade to openssl 1.1.1 in order to have it really
simplified (see https://security.stackexchange.com/a/183973 )

Improve CSR readability by creating cluster-identification, which is master
partition title, and use it as Organization of the CSR.

Reserve slots for data exchange in KeDiFa.
1 parent fb37422b
......@@ -83,13 +83,70 @@ Those slave instances will be redirected to the "master" instance, and you will
Finally, the slave instance will be accessible from: https://someidentifier.moulefrite.org.
About SSL
=========
``default`` and ``custom-personl`` software type can handle specific ssl for one slave instance.
About SSL and SlapOS Master Zero Knowledge
==========================================
**IMPORTANT**: One Caddy can not serve more than one specific SSL site and be compatible with obsolete browser (i.e.: IE8). See http://wiki.apache.org/httpd/NameBasedSSLVHostsWithSNI
SSL keys and certificates are directly send to the frontend cluster in order to follow zero knowledge principle of SlapOS Master.
Master partition
----------------
After requesting master partition it will return ``master-key-generate-auth-url`` and ``master-key-upload-url``.
Doing HTTP GET on ``master-key-generate-auth-url`` will return authentication token, which is used to communicate with ``master-key-upload-url``. This token shall be stored securely.
By doing HTTP PUT to ``master-key-upload-url`` with appended authentication token it is possible to upload PEM bundle of certificate, key and any accompanying CA certificates to the master.
Example sessions is::
request(...)
curl -X GET master-key-generate-auth-url
> authtoken
cat certificate.pem key.pem ca-bundle.pem > master.pem
curl -X PUT --data-binary @master.pem master-key-upload-url+authtoken
This replaces old request parameters:
* ``apache-certificate``
* ``apache-key``
* ``apache-ca-certificate``
(*Note*: They are still supported for backward compatibility, but any value send to the ``master-key-upload-url`` will supersede information from SlapOS Master.)
Slave partition
---------------
After requesting slave partition it will return ``key-generate-auth-url`` and ``key-upload-url``.
Doing HTTP GET on ``key-generate-auth-url`` will return authentication token, which is used to communicate with ``key-upload-url``. This token shall be stored securely.
By doing HTTP PUT to ``key-upload-url`` with appended authentication token it is possible to upload PEM bundle of certificate, key and any accompanying CA certificates to the master.
Example sessions is::
request(...)
curl -X GET key-generate-auth-url
> authtoken
cat certificate.pem key.pem ca-bundle.pem > master.pem
curl -X PUT --data-binary @master.pem key-upload-url+authtoken
This replaces old request parameters:
* ``ssl_crt``
* ``ssl_key``
* ``ssl_ca_crt``
(*Note*: They are still supported for backward compatibility, but any value send to the ``key-upload-url`` will supersede information from SlapOS Master.)
How to have custom configuration in frontend server - XXX - to be written
=========================================================================
......@@ -195,13 +252,6 @@ Necessary to activate cache.
``enable_cache`` is an optional parameter.
ssl_key, ssl_crt, ssl_ca_crt
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
SSL certificates of the slave.
They are optional.
Functionalities for Caddy configuration
---------------------------------------
......@@ -210,7 +260,7 @@ In the slave Caddy configuration you can use parameters that will be replaced du
* ``cache_access`` : url of the cache. Should replace backend url in configuration to use the cache
* ``access_log`` : path of the slave error log in order to log in a file.
* ``error_log`` : path of the slave access log in order to log in a file.
* ``ssl_key``, ``ssl_crt``, ``ssl_ca_crt``, ``ssl_crs`` : paths of the certificates given in slave instance parameters
* ``certificate`` : path to the certificate
Examples
......@@ -293,7 +343,7 @@ Request slave frontend instance so that https://[1:2:3:4:5:6:7:8]:1234 will be::
"caddy_custom_https":'
https://www.example.com:%(https_port)s, https://example.com:%(https_port)s {
bind %(local_ipv4)s
tls %(ssl_crt)s %(ssl_key)s
tls %%(certificate)s %%(certificate)s
log / %(access_log)s {combined}
errors %(error_log)s
......@@ -479,6 +529,13 @@ Note that in some cases promises will fail:
This is known issue and shall be tackled soon.
KeDiFa
======
Additional partition with KeDiFa (Key Distribution Facility) is by default requested on the same computer as master frontend partition.
By adding to the request keys like ``-sla-kedifa-<key>`` it is possible to provide SLA information for kedifa partition. Eg to put it on computer ``couscous`` it shall be ``-sla-kedifa-computer_guid: couscous``.
Notes
=====
......
......@@ -3,13 +3,11 @@ Generally things to be done with ``caddy-frontend``:
* tests: add assertion with results of promises in etc/promise for each partition
* README: cleanup the documentation, explain various specifics
* check the whole frontend slave snippet with ``caddy -validate`` during buildout run, and reject if does not pass validation
* BUG?? check that changing ``apache-certificate`` on master partition results in reloading slave partition
* (new) ``type:websocket`` slave
* ``type:eventsource``:
* **Jérome Perrin**: *For event source, if I understand https://github.com/mholt/caddy/issues/1355 correctly, we could use caddy as a proxy in front of nginx-push-stream . If we have a "central shared" caddy instance, can it handle keeping connections opens for many clients ?*
* ``check-error-on-caddy-log`` like ``check-error-on-apache-log``
* move out ``test/utils.py`` and use it from shared python distribution
* reduce the time of configuration validation (in ``instance-apache-frontend.cfg.in`` sections ``[configtest]``, ``[caddy-configuration]``, ``[nginx-configuration]``), as it is not scalable on frontend with 2000+ slaves (takes few minutes instead of few, < 5, seconds), issue posted `upstream <https://github.com/mholt/caddy/issues/2220>`_
* drop ``6tunnel`` and use ``bind`` in Caddy configuration, as soon as multiple binds will be possible, tracked in upstream `bind: support multiple values <https://github.com/mholt/caddy/pull/2128>`_ and `ipv6: does not bind on ipv4 and ipv6 for sites that resolve to both <https://github.com/mholt/caddy/issues/864>`_
......
......@@ -14,7 +14,7 @@
# not need these here).
[template]
filename = instance.cfg.in
md5sum = 2747f9125c8dffa0c27b79a6902a55cb
md5sum = 733ef269151e9884e44174bb4dc9c6ea
[template-common]
filename = instance-common.cfg.in
......@@ -22,15 +22,15 @@ md5sum = c801b7f9f11f0965677c22e6bbe9281b
[template-apache-frontend]
filename = instance-apache-frontend.cfg.in
md5sum = 6fd023f0d29421d8579f0b3351473bb0
md5sum = cb6406e0b8fe6b6decd587416ddbb882
[template-apache-replicate]
filename = instance-apache-replicate.cfg.in
md5sum = 5c5462ccc327fe109e0c102f0d3e7e53
md5sum = e4d6f2df21a60f5a68b3a78b01a6868c
[template-slave-list]
filename = templates/apache-custom-slave-list.cfg.in
md5sum = 028ca41fdf7a758ba701ccc5e449419a
md5sum = 7ddc510084c73cee910d9e0aa546d99a
[template-slave-configuration]
filename = templates/custom-virtualhost.conf.in
......@@ -38,11 +38,11 @@ md5sum = 54ae95597a126ae552c3a913ddf29e5e
[template-replicate-publish-slave-information]
filename = templates/replicate-publish-slave-information.cfg.in
md5sum = 696ef7690f51a521cc6f6c9d9d5d844e
md5sum = 125181ce0065b2acddb0e3ee38f81864
[template-caddy-frontend-configuration]
filename = templates/Caddyfile.in
md5sum = 0134a1586f15cd5665069d6d81a505be
md5sum = 1814950fe926b4a5f730d81d52cc827f
[caddy-backend-url-validator]
filename = templates/caddy-backend-url-validator.in
......@@ -58,7 +58,7 @@ md5sum = f20d6c3d2d94fb685f8d26dfca1e822b
[template-default-slave-virtualhost]
filename = templates/default-virtualhost.conf.in
md5sum = e57b9ae012f777482295698a23b7e850
md5sum = ce86264b0c4cc1d447edf40db34e01c4
[template-cached-slave-virtualhost]
filename = templates/cached-virtualhost.conf.in
......@@ -66,7 +66,7 @@ md5sum = 907372828d1ceb05c41240078196f439
[template-log-access]
filename = templates/template-log-access.conf.in
md5sum = 122b05829ecc4c0ad4e47e7d1c21166b
md5sum = 59644acda51eb1a51eab448e6fc6aa74
[template-empty]
filename = templates/empty.in
......@@ -86,15 +86,15 @@ md5sum = 117238225b3fc3c5b5be381815f44c67
[template-nginx-configuration]
filename = templates/nginx.cfg.in
md5sum = fadb2fcaf0f2b4fe735617fac222f7ed
md5sum = e5b65b424d01fa539c92f4ba10e847eb
[template-nginx-eventsource-slave-virtualhost]
filename = templates/nginx-eventsource-slave.conf.in
md5sum = 176cbca2070734a185a7ae5a4d1181c5
md5sum = 217a6c801b8330b0b825f7b8b4c77184
[template-nginx-notebook-slave-virtualhost]
filename = templates/nginx-notebook-slave.conf.in
md5sum = ee3b5c23f1c81aa43ce7cd8f8e327f70
md5sum = 3fd1fefaec2d630eee54f75e7a873c27
[template-apache-lazy-script-call]
filename = templates/apache-lazy-script-call.sh.in
......@@ -115,3 +115,7 @@ md5sum = d9b6476bb0b36cf463fddb00d41dfbaa
[caddyprofiledeps-dummy]
filename = caddyprofiledummy.py
md5sum = 38792c2dceae38ab411592ec36fff6a8
[template-kedifa]
filename = instance-kedifa.cfg.in
md5sum = ee58402bbf374e3a3522ce59002880e3
......@@ -30,6 +30,25 @@ parts +=
http-proxy
caddyprofiledeps
kedifa-develop
kedifa
[kedifa-repository]
recipe = slapos.recipe.build:gitclone
repository = https://lab.nexedi.com/nexedi/kedifa.git
git-executable = ${git:location}/bin/git
revision = 67bd60ea1bfb4fc6aafdfe4fa204f725731f20cf
[kedifa-develop]
recipe = zc.recipe.egg:develop
setup = ${kedifa-repository:location}
[kedifa]
recipe = zc.recipe.egg
eggs =
${python-cryptography:egg}
kedifa
[caddyprofiledeps-setup]
recipe = slapos.recipe.build:download
url = ${:_profile_base_location_}/setup.py
......@@ -88,9 +107,13 @@ dash = ${dash:location}
dcron = ${dcron:location}
gzip = ${gzip:location}
logrotate = ${logrotate:location}
openssl = ${openssl:location}
openssl = ${openssl:location}/bin/openssl
openssl_cnf = ${openssl:location}/etc/ssl/openssl.cnf
trafficserver = ${trafficserver:location}
sha256sum = ${coreutils:location}/bin/sha256sum
kedifa = ${:bin_directory}/kedifa
kedifa-getter = ${:bin_directory}/kedifa-getter
kedifa-csr = ${:bin_directory}/kedifa-csr
monitor_template = ${monitor-template:output}
template_cached_slave_virtualhost = ${template-cached-slave-virtualhost:target}
......@@ -125,11 +148,13 @@ context =
key monitor2_template monitor2-template:rendered
key template_caddy_frontend template-caddy-frontend:target
key template_caddy_replicate template-caddy-replicate:target
key template_kedifa template-kedifa:target
key template_replicate_publish_slave_information template-replicate-publish-slave-information:target
key caddy_backend_url_validator caddy-backend-url-validator:output
key caddy_custom_http_validator caddy-custom-http-validator:output
section template_frontend_parameter_dict template-frontend-parameter-section
key caucase_jinja2_library caucase-jinja2-library:target
[template-caddy-frontend]
recipe = slapos.recipe.build:download
......@@ -155,6 +180,11 @@ recipe = slapos.recipe.build:download
url = ${:_profile_base_location_}/instance-apache-replicate.cfg.in
mode = 0644
[template-kedifa]
recipe = slapos.recipe.build:download
url = ${:_profile_base_location_}/instance-kedifa.cfg.in
mode = 0644
[download-template]
recipe = slapos.recipe.build:download
url = ${:_profile_base_location_}/templates/${:filename}
......
......@@ -30,6 +30,10 @@ context =
{% if not sla_computer_caddy_1_key in slapparameter_dict %}
{% do slapparameter_dict.__setitem__(sla_computer_caddy_1_key, '${slap-connection:computer-id}') %}
{% endif %}
{% set sla_computer_kedifa_key = '-sla-kedifa-computer_guid' %}
{% if not sla_computer_kedifa_key in slapparameter_dict %}
{% do slapparameter_dict.__setitem__(sla_computer_kedifa_key, '${slap-connection:computer-id}') %}
{% endif %}
# Here we request individually each frontend.
# The presence of sla parameters is checked and added if found
......@@ -122,18 +126,6 @@ context =
{% do slave_error_list.append('slave https-url %r invalid' % (slave['https-url'],)) %}
{% endif %}
{% endif %}
{% if slave.get('ssl_ca_crt') and not (slave.get('ssl_crt') and slave.get('ssl_key')) %}
{% do slave_error_list.append('ssl_ca_crt is present, so ssl_crt and ssl_key are required') %}
{% endif %}
{% if slave.get('ssl_key') and slave.get('ssl_crt') %}
{% set key_popen = popen([openssl, 'rsa', '-noout', '-modulus']) %}
{% set crt_popen = popen([openssl, 'x509', '-noout', '-modulus']) %}
{% set key_modulus = key_popen.communicate('' ~ slave['ssl_key'])[0] | trim %}
{% set crt_modulus = crt_popen.communicate('' ~ slave['ssl_crt'])[0] | trim %}
{% if not key_modulus or key_modulus != crt_modulus %}
{% do slave_error_list.append('slave ssl_key and ssl_crt does not match') %}
{% endif %}
{% endif %}
{% if slave.get('custom_domain') %}
{% set slave_custom_domain = '' ~ slave['custom_domain'] %}
{% if slave_custom_domain.startswith('*.') %}
......@@ -178,6 +170,10 @@ name = {{ frontend_request.get('name') }}
{% if frontend_request.get('state') %}
state = {{ frontend_request.get('state') }}
{% endif%}
config-slave-kedifa-information = ${request-kedifa:connection-slave-kedifa-information}
config-kedifa-caucase-url = ${request-kedifa:connection-caucase-url}
config-master-key-download-url = ${request-kedifa:connection-master-key-download-url}
config-cluster-identification = {{ cluster_identification }}
{% set slave_configuration_dict = slapparameter_dict %}
{% do slave_configuration_dict.update(frontend_request.get('config')) %}
{% do slave_configuration_dict.__setitem__(slave_list_name, json_module.dumps(authorized_slave_list)) %}
......@@ -201,6 +197,9 @@ slave-amount = {{ slave_instance_list | length }}
accepted-slave-amount = {{ authorized_slave_list | length }}
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}
#----------------------------
#--
......@@ -213,6 +212,37 @@ replicate = ${dynamic-publish-slave-information:rendered}
custom-personal = ${dynamic-publish-slave-information:rendered}
custom-group = ${dynamic-publish-slave-information:rendered}
[request-kedifa]
<= slap-connection
recipe = slapos.cookbook:requestoptional.serialised
config-monitor-cors-domains = {{ slapparameter_dict.get('monitor-cors-domains', 'monitor.app.officejs.com') }}
config-monitor-username = ${monitor-instance-parameter:username}
config-monitor-password = ${monitor-htpasswd:passwd}
{% for key in ['kedifa_port', 'caucase_port'] -%}
{%- if key in slapparameter_dict %}
config-{{ key }} = {{ dumps(slapparameter_dict[key]) }}
{%- endif %}
{%- endfor %}
config-slave-list = {{ dumps(slave_instance_list) }}
config-cluster-identification = {{ cluster_identification }}
{% set frontend_software_url_key = "-frontend-software-release-url" %}
{% if slapparameter_dict.has_key(frontend_software_url_key) %}
software-url = {{ slapparameter_dict.pop(frontend_software_url_key) }}
{% else %}
software-url = ${slap-connection:software-release-url}
{% endif %}
software-type = kedifa
name = kedifa
return = slave-kedifa-information master-key-generate-auth-url master-key-upload-url master-key-download-url caucase-url
{% set sla_kedifa_key = "-sla-kedifa-" %}
{% set sla_kedifa_key_length = sla_kedifa_key | length %}
{% for key in slapparameter_dict.keys() %}
{% if key.startswith(sla_kedifa_key) %}
sla-{{ key[sla_kedifa_key_length:] }} = {{ slapparameter_dict.pop(key) }}
{% endif %}
{% endfor %}
[rejected-slave-information]
{% for slave_id, rejected_list in rejected_slave_dict.iteritems() %}
{{ slave_id }} = {{ dumps(json_module.dumps(rejected_list)) }}
......@@ -241,6 +271,7 @@ extra-context =
section slave_information slave-information
section rejected_slave_information rejected-slave-information
section active_slave_instance_dict active-slave-instance
key slave_kedifa_information request-kedifa:connection-slave-kedifa-information
[monitor-conf-parameters]
monitor-url-list +=
......@@ -256,6 +287,7 @@ parts =
monitor-base
publish-slave-information
publish-information
request-kedifa
{% for part in part_list %}
{{ ' %s' % part }}
{% endfor %}
......
......@@ -11,24 +11,6 @@
"title": "Frontend Replication Quantity",
"type": "integer"
},
"apache-ca-certificate": {
"description": "[NOT IMPLEMENTED] SSL CA Certificate used by the server. You can append it to 'apache-certificate'.",
"textarea": true,
"title": "[NOT IMPLEMENTED] SSL CA Certificate",
"type": "string"
},
"apache-certificate": {
"description": "SSL Certificate used by the server.",
"textarea": true,
"title": "SSL Certificate",
"type": "string"
},
"apache-key": {
"description": "SSL Key used by the server.",
"textarea": true,
"title": "SSL Key",
"type": "string"
},
"domain": {
"description": "Base Domain for create subdomains (ie.: example.com).",
"pattern": "^([a-zA-Z0-9]([a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,6}$",
......
{%- if slap_software_type == software_type -%}
{% import "caucase" as caucase with context %}
# KeDiFa instance profile
[buildout]
extends =
{{ parameter_dict['common_profile'] }}
{{ parameter_dict['monitor_template'] }}
parts =
directory
kedifa
slave-kedifa-information
caucased
caucased-promise
caucase-updater
[caucased]
hash-files = ${buildout:directory}/software_release/buildout.cfg
{% set caucase_host = '[' ~ instance_parameter['ipv6-random'] ~ ']' %}
{% set caucase_netloc = caucase_host ~ ':' ~ instance_parameter['configuration.caucase_port'] -%}
{% set caucase_url = 'http://' ~ caucase_netloc -%}
{{ caucase.caucased(
prefix='caucased',
buildout_bin_directory=parameter_dict['bin_directory'],
caucased_path='${directory:service}/caucased',
backup_dir='${directory:backup-caucased}',
data_dir='${directory:caucased}',
netloc=caucase_netloc,
service_auto_approve_count=0,
user_auto_approve_count=1,
key_len=2048,
promise='${directory:promise}/caucased',
)}}
# Create all needed directories
[directory]
recipe = slapos.cookbook:mkdirectory
bin = ${buildout:directory}/bin/
etc = ${buildout:directory}/etc/
srv = ${buildout:directory}/srv/
var = ${buildout:directory}/var/
backup = ${:srv}/backup
log = ${:var}/log
run = ${:var}/run
service = ${:etc}/service
etc-run = ${:etc}/run
promise = ${:etc}/promise
logrotate-backup = ${:backup}/logrotate
logrotate-entries = ${:etc}/logrotate.d
cron-entries = ${:etc}/cron.d
crontabs = ${:etc}/crontabs
cronstamps = ${:etc}/cronstamps
# KeDiFa directories
kedifa = ${:srv}/kedifa
etc-kedifa = ${:etc}/kedifa
# CAUCASE directories
caucased = ${:srv}/caucased
backup-caucased = ${:backup}/caucased
# reservation
reservation = ${:srv}/reservation
[kedifa-csr]
recipe = plone.recipe.command
organization = {{ slapparameter_dict['cluster-identification'] }}
organizational_unit = Kedifa Partition
command =
if [ ! -f ${:csr} ] && [ ! -f ${:key} ] ; then
/bin/bash -c '{{ parameter_dict['openssl'] }} req -new -sha256 \
-newkey rsa:2048 -nodes -keyout ${:key} \
-subj "/O=${:organization}/OU=${:organizational_unit}" \
-reqexts SAN \
-config <(cat {{ parameter_dict['openssl_cnf'] }} \
<(printf "\n[SAN]\nsubjectAltName=IP:${kedifa-config:ip}")) \
-out ${:csr}'
fi
update-command = ${:command}
csr = ${kedifa-config:csr}
key = ${kedifa-config:key}
stop-on-error = True
{{ caucase.updater(
prefix='caucase-updater',
buildout_bin_directory=parameter_dict['bin_directory'],
updater_path='${directory:service}/caucase-updater',
url=caucase_url,
data_dir='${directory:srv}/caucase-updater',
crt_path='${kedifa-config:certificate}',
ca_path='${kedifa-config:ca-certificate}',
crl_path='${kedifa-config:crl}',
key_path='${kedifa-csr:key}',
on_renew='${kedifa-reloader:wrapper-path}',
template_csr='${kedifa-csr:csr}',
openssl=parameter_dict['openssl'] ~ '/bin/openssl',
)}}
[jinja2-template-base]
recipe = slapos.recipe.template:jinja2
rendered = ${buildout:directory}/${:filename}
extra-context =
slapparameter_dict = {{ dumps(instance_parameter['configuration']) }}
slap_software_type = {{ dumps(instance_parameter['slap-software-type']) }}
context =
import json_module json
raw common_profile {{ parameter_dict['common_profile'] }}
key slap_software_type :slap_software_type
key slapparameter_dict :slapparameter_dict
section directory directory
${:extra-context}
[kedifa-config]
ip = {{ instance_parameter['ipv6-random'] }}
port = {{ instance_parameter['configuration.kedifa_port'] }}
db = ${directory:kedifa}/kedifa.sqlite
certificate = ${directory:etc-kedifa}/certificate.pem
key = ${:certificate}
ca-certificate = ${directory:etc-kedifa}/ca-certificate.pem
cas-ca-certificate = ${directory:etc-kedifa}/cas-ca-certificate.pem
crl = ${directory:etc-kedifa}/crl.pem
csr = ${directory:etc-kedifa}/csr.pem
pidfile = ${directory:run}/kedifa.pid
[kedifa-reloader]
recipe = slapos.cookbook:wrapper
command-line = kill -SIGHUP `cat ${kedifa-config:pidfile}`
wrapper-path = ${directory:etc-run}/kedifa-reloader
[kedifa]
recipe = slapos.cookbook:wrapper
command-line = {{ parameter_dict['kedifa'] }}
--ip ${kedifa-config:ip}
--port ${kedifa-config:port}
--db ${kedifa-config:db}
--certificate ${kedifa-config:certificate}
--ca-certificate ${kedifa-config:ca-certificate}
--crl ${kedifa-config:crl}
--pidfile ${kedifa-config:pidfile}
wrapper-path = ${directory:service}/kedifa
hash-files = ${buildout:directory}/software_release/buildout.cfg
# Publish KeDiFa configuration for upload and download for each slave
{%- set slave_kedifa_information = {} -%}
{%- for slave in slapparameter_dict['slave-list'] -%}
{%- set slave_reference = slave['slave_reference'] -%}
{%- set slave_dict = {} -%}
{%- do slave_dict.__setitem__('key-generate-auth-url', 'https://[${kedifa-config:ip}]:${kedifa-config:port}/${%s-auth-random:passwd}/generateauth' % (slave_reference,)) -%}
{%- do slave_dict.__setitem__('key-upload-url', 'https://[${kedifa-config:ip}]:${kedifa-config:port}/${%s-auth-random:passwd}?auth=' % (slave_reference,)) -%}
{%- do slave_dict.__setitem__('key-download-url', 'https://[${kedifa-config:ip}]:${kedifa-config:port}/${%s-auth-random:passwd}' % (slave_reference,)) -%}
{%- do slave_kedifa_information.__setitem__(slave_reference, slave_dict) %}
[{{ slave_reference }}-auth-random-generate]
recipe = plone.recipe.command
file = ${directory:reservation}/${:_buildout_section_name_}
command =
[ ! -f ${:file} ] && {{ parameter_dict['curl'] }}/bin/curl -s -g -X POST https://[${kedifa-config:ip}]:${kedifa-config:port}/reserve-id --cert ${kedifa-config:certificate} --cacert ${kedifa-config:ca-certificate} > ${:file}.tmp && mv ${:file}.tmp ${:file}
update-command = ${:command}
[{{ slave_reference }}-auth-random]
recipe = collective.recipe.shelloutput
file = {{ '${' + slave_reference }}-auth-random-generate:file}
commands =
passwd = cat ${:file} 2>/dev/null || echo "NotReadyYet"
{% endfor %}
[master-auth-random-generate]
recipe = plone.recipe.command
file = ${directory:reservation}/${:_buildout_section_name_}
command =
[ ! -f ${:file} ] && {{ parameter_dict['curl'] }}/bin/curl -s -g -X POST https://[${kedifa-config:ip}]:${kedifa-config:port}/reserve-id --cert ${kedifa-config:certificate} --cacert ${kedifa-config:ca-certificate} > ${:file}.tmp && mv ${:file}.tmp ${:file}
update-command = ${:command}
[master-auth-random]
recipe = collective.recipe.shelloutput
file = ${master-auth-random-generate:file}
commands =
passwd = cat ${:file} 2>/dev/null || echo "NotReadyYet"
[slave-kedifa-information]
recipe = slapos.cookbook:publish.serialised
slave-kedifa-information = {{ json_module.dumps(slave_kedifa_information) }}
caucase-url = {{ caucase_url }}
master-key-generate-auth-url = https://[${kedifa-config:ip}]:${kedifa-config:port}/${master-auth-random:passwd}/generateauth
master-key-upload-url = https://[${kedifa-config:ip}]:${kedifa-config:port}/${master-auth-random:passwd}?auth=
master-key-download-url = https://[${kedifa-config:ip}]:${kedifa-config:port}/${master-auth-random:passwd}
{%- endif -%} {# if slap_software_type in software_type #}
......@@ -10,6 +10,14 @@
"description": "Base domain used by the instance",
"type": "string"
},
"master-key-generate-auth-url": {
"description": "URL to GET once auth for master-key-upload-url",
"type": "string"
},
"master-key-upload-url": {
"description": "URL to PUT PEM bundle of main certificate and key",
"type": "string"
},
"monitor-base-url": {
"description": "Base url for monitor",
"type": "string"
......
......@@ -146,20 +146,6 @@
"title": "SSL Certificate Authority's Certificate",
"type": "string"
},
"ssl_crt": {
"default": "",
"description": "Content of the SSL Certificate file",
"textarea": true,
"title": "SSL Certificate",
"type": "string"
},
"ssl_key": {
"default": "",
"description": "Content of the SSL Key file",
"textarea": true,
"title": "SSL Key",
"type": "string"
},
"ssl_proxy_ca_crt": {
"default": "",
"description": "Content of the SSL Certificate Authority file of the backend (to be used with ssl-proxy-verify)",
......
......@@ -6,6 +6,14 @@
"description": "Base domain used by the instance",
"type": "string"
},
"key-generate-auth-url": {
"description": "URL to GET once auth for key-upload-url",
"type": "array"
},
"key-upload-url": {
"description": "URL to PUT PEM bundle of certificate and key",
"type": "array"
},
"log-access-url": {
"description": "List of URLs to access logs",
"type": "array"
......
......@@ -28,6 +28,7 @@ custom-personal = ${dynamic-template-caddy-replicate:rendered}
single-default = ${dynamic-template-caddy-frontend:rendered}
single-custom-personal = ${dynamic-template-caddy-frontend:rendered}
replicate = ${dynamic-template-caddy-replicate:rendered}
kedifa = ${dynamic-template-kedifa:rendered}
[dynamic-template-caddy-frontend-parameters]
{% for key,value in template_frontend_parameter_dict.iteritems() %}
......@@ -42,6 +43,9 @@ extensions = jinja2.ext.do
extra-context =
section parameter_dict dynamic-template-caddy-frontend-parameters
raw software_type single-custom-personal
caucase-jinja2-library = {{ caucase_jinja2_library }}
import-list =
file caucase :caucase-jinja2-library
[dynamic-template-caddy-replicate]
< = jinja2-template-base
......@@ -49,12 +53,11 @@ depends = ${caddyprofiledeps:recipe}
template = {{ template_caddy_replicate }}
filename = instance-caddy-replicate.cfg
extensions = jinja2.ext.do
openssl = {{ template_frontend_parameter_dict['openssl'] ~ '/bin/openssl' }}
extra-context =
import subprocess_module subprocess
import functools_module functools
import validators validators
key openssl :openssl
key cluster_identification instance-parameter:root-instance-title
raw caddy_backend_url_validator {{ caddy_backend_url_validator }}
raw caddy_custom_http_validator {{ caddy_custom_http_validator }}
raw template_publish_slave_information {{ template_replicate_publish_slave_information }}
......@@ -63,6 +66,18 @@ extra-context =
raw template_monitor {{ monitor2_template }}
raw common_profile {{ common_profile }}
[dynamic-template-kedifa]
< = jinja2-template-base
template = {{ template_kedifa }}
filename = instance-kedifa.cfg
extensions = jinja2.ext.do
extra-context =
section parameter_dict dynamic-template-caddy-frontend-parameters
raw software_type kedifa
caucase-jinja2-library = {{ caucase_jinja2_library }}
import-list =
file caucase :caucase-jinja2-library
[instance-parameter]
# Fetches parameters defined in SlapOS Master for this instance.
# Always the same.
......@@ -82,14 +97,14 @@ configuration.port = 4443
configuration.plain_http_port = 8080
configuration.plain_nginx_port = 8081
configuration.nginx_port = 9443
configuration.kedifa_port = 7879
# Warning: Caucase takes also cacuase_port+1
configuration.caucase_port = 8890
# BBB: apache_custom_https and apache_custom_http
configuration.apache_custom_https = ""
configuration.apache_custom_http = ""
configuration.caddy_custom_https = ""
configuration.caddy_custom_http = ""
configuration.apache-key =
configuration.apache-certificate =
configuration.apache-ca-certificate =
configuration.open-port = 80 443
configuration.disk-cache-size = 8G
configuration.ram-cache-size = 1G
......
......@@ -2,6 +2,16 @@
extends = common.cfg
[versions]
# Versions pinned for kedifa need urllib3 >= 1.18
urllib3 = 1.24
requests = 2.20.0
certifi = 2018.10.15
idna = 2.7
chardet = 3.0.4
# ipaddress is patching IPAddress so IPv6 match works
ipaddress = 1.0.22
# Versions pinned for kedifa need urllib3 >= 1.18
validators = 0.12.2
PyRSS2Gen = 1.1
cns.recipe.symlink = 0.2.3
......
......@@ -5,8 +5,9 @@ import {{ slave_configuration_directory }}/*.conf
import {{ slave_with_cache_configuration_directory }}/*.conf
# Catch-all and 404 for not configured instances
{% if os_module.path.exists(master_certificate) %}
:{{ https_port }} {
tls {{ login_certificate }} {{ login_key }}
tls {{ master_certificate }} {{ master_certificate }}
bind {{ local_ipv4 }}
# Compress the output
gzip
......@@ -16,6 +17,7 @@ import {{ slave_with_cache_configuration_directory }}/*.conf
* {{ not_found_file }}
}
}
{% endif %}
:{{ http_port }} {
bind {{ local_ipv4 }}
......
......@@ -20,6 +20,7 @@ recipe = slapos.recipe.template:jinja2
extensions = jinja2.ext.do
extra-context =
context =
import os_module os
raw common_profile {{ common_profile }}
${:extra-context}
......@@ -36,13 +37,20 @@ sharedscripts = true
notifempty = true
create = true
[cadirectory]
recipe = slapos.cookbook:mkdirectory
requests = {{ custom_ssl_directory }}/requests/
private = {{ custom_ssl_directory }}/private/
certs = {{ custom_ssl_directory }}/certs/
newcerts = {{ custom_ssl_directory }}/newcerts/
crl = {{ custom_ssl_directory }}/crl/
{% if master_key_download_url %}
{% do part_list.append('master-key-download') %}
[master-key-download]
recipe = plone.recipe.command
destination = {{ master_certificate }}
command = {{ kedifa_getter }} --out ${:destination} --server-ca-certificate {{ kedifa_caucase_ca_certificate }} --identity {{ kedifa_login_certificate }} {{ master_key_download_url }}
update-command = ${:command}
{% endif %}
{% if slave_kedifa_information %}
{% set slave_kedifa_information = json_module.loads(slave_kedifa_information) %}
{% else %}
{% set slave_kedifa_information = {} %}
{% endif %}
[promise-plugin-base]
recipe = slapos.cookbook:promise.plugin
......@@ -55,6 +63,12 @@ output = {{ plugin_directory }}/${:name}
{# Loop thought slave list to set up slaves #}
{% for slave_instance in slave_instance_list %}
{% set slave_reference = slave_instance.get('slave_reference') %}
{% set slave_kedifa = slave_kedifa_information.get(slave_reference) %}
{% if slave_kedifa %}
{% set key_download_url = slave_kedifa.get('key-download-url') %}
{% else %}
{% set key_download_url = '' %}
{% endif %}
{% set slave_type = slave_instance.get('type', '') %}
{% set slave_section_title = 'dynamic-template-slave-instance-%s' % slave_reference %}
{% set slave_parameter_dict = generic_instance_parameter_dict.copy() %}
......@@ -167,66 +181,37 @@ bytes = 8
{# ################################################## #}
{# Set Slave Certificates if needed #}
{% set cert_dirname = slave_reference.replace('-','.') %}
{% set autocert_dir = '/'.join([autocert, cert_dirname]) %}
[{{ slave_reference }}-path]
recipe = slapos.cookbook:mkdirectory
cert = {{ autocert_dir }}
{# Set certificate key for custom configuration #}
{% set certificate = '%s/certificate.pem' % (autocert_dir, ) %}
{% do slave_parameter_dict.__setitem__('certificate', certificate )%}
{# Set ssl certificates for each slave #}
{% for cert_name in ('ssl_csr', 'ssl_proxy_ca_crt')%}
{% if slave_instance.get(cert_name) %}
{% set cert_title = '%s-%s' % (slave_reference, cert_name.replace('ssl_', '')) %}
{% set cert_file = '/'.join([custom_ssl_directory, cert_title.replace('-','.')]) %}
{% do part_list.append(cert_title) %}
{% do slave_parameter_dict.__setitem__(cert_name, cert_file) %}
{% do slave_instance.__setitem__('path_to_' + cert_name, cert_file) %}
{# Store certificates on fs #}
[{{ cert_title }}]
< = jinja2-template-base
template = {{ empty_template }}
rendered = {{ cert_file }}
extra-context =
key content {{ cert_title + '-config:value' }}
# Store certificate in config
[{{ cert_title + '-config' }}]
value = {{ dumps(slave_instance.get(cert_name)) }}
{% endif %}
{% endfor %}
{#- Set Up Certs #}
{% do slave_instance.__setitem__('login_certificate', login_certificate) %}
{% do slave_instance.__setitem__('login_key', login_key) %}
{% do slave_instance.__setitem__('login_ca_crt', login_ca_crt) %}
{% do slave_parameter_dict.__setitem__('ssl_crt', login_certificate) %}
{% do slave_parameter_dict.__setitem__('ssl_key', login_key) %}
{% if slave_instance.get('ssl_key') and slave_instance.get('ssl_crt') %}
{% set cert_title = '%s-crt' % (slave_reference) %}
{% set key_title = '%s-key' % (slave_reference) %}
{% set cert_file = '/'.join([custom_ssl_directory, cert_title.replace('-','.')]) %}
{% set key_file = '/'.join([custom_ssl_directory, key_title.replace('-','.')]) %}
{% do part_list.append(cert_title) %}
{% do part_list.append(key_title) %}
{% do slave_parameter_dict.__setitem__("ssl_crt", cert_file) %}
{% do slave_parameter_dict.__setitem__("ssl_key", key_file) %}
{% do slave_instance.__setitem__('path_to_ssl_crt', cert_file) %}
{% do slave_instance.__setitem__('path_to_ssl_key', key_file) %}
[{{key_title}}]
< = jinja2-template-base
template = {{ empty_template }}
rendered = {{ key_file }}
key-content = {{ dumps(slave_instance.get('ssl_key')) }}
extra-context =
key content :key-content
[{{cert_title}}]
< = jinja2-template-base
template = {{ empty_template }}
rendered = {{ cert_file }}
cert-content = {{ dumps('%s\n%s' % (slave_instance.get('ssl_crt'), slave_instance.get('ssl_ca_crt', '') or '')) }}
extra-context =
key content :cert-content
{% endif %}
[{{ slave_reference }}-key-download]
recipe = plone.recipe.command
destination = {{ '${' + slave_reference + '-path:cert}/downloaded.pem' }}
used = {{ '${' + slave_reference + '-path:cert}/certificate.pem' }}
source-master = ${master-key-download:destination}
command =
{{ kedifa_getter }} --out ${:destination} --server-ca-certificate {{ kedifa_caucase_ca_certificate }} --identity {{ kedifa_login_certificate }} {{ key_download_url }}
if [ -f ${:destination} ] ; then
# if the slave specific certificate is available, use it
ln -sf ${:destination} ${:used}
elif [ -f ${:source-master} ] ; then
# if the master provided certificate is available, use it
ln -sf ${:source-master} ${:used}
else
rm -f ${:used}
fi