Commit 7c5c99b1 authored by Łukasz Nowak's avatar Łukasz Nowak Committed by Łukasz Nowak

caddy-frontend: Expose csr_id over HTTPS

csr_id is exposed over HTTPS with short living self signed certificate,
which is transmitted via SlapOS Master. Thanks to this, it is possible to
match csr_id with certificate of given partition and take decision if it shall
be signed or not.

This is "quite secure" apporach, a bit better than blidny trusting what CSR
to sign in KeDiFa. The bootstrap information, which is short living
(certificates are valid for 5 days), resides in SlapOS Master. The csr_id
is not directly known to SlapOS Master, and shall be consumed as fast as
possible by frontend cluster operator in order to sign CSR appearing in
KeDiFa caucase. The known possible attack vector requires that attacker knows
caucased HTTP listening port and can hijack HTTPS traffic to the csr_id-url
to get the human approve his own csr_id. The second is hoped to be overcomed
by publishing certificate of this endpoint via SlapOS Master.

Unfortunately caucase-updater prefix is directly used to find real CSR, as the
one generated is just a template for rerequest, thus csr_id would be different
from really used by caucase-updater.
parent bc2b1742
......@@ -22,15 +22,15 @@ md5sum = c801b7f9f11f0965677c22e6bbe9281b
[template-apache-frontend]
filename = instance-apache-frontend.cfg.in
md5sum = cb6406e0b8fe6b6decd587416ddbb882
md5sum = b3275d8203b36506ea0f2f9c12f86399
[template-apache-replicate]
filename = instance-apache-replicate.cfg.in
md5sum = e4d6f2df21a60f5a68b3a78b01a6868c
md5sum = ece5f1c068a3096eef6bcc8deaf8bbe2
[template-slave-list]
filename = templates/apache-custom-slave-list.cfg.in
md5sum = 7ddc510084c73cee910d9e0aa546d99a
md5sum = ed9743c1a5c1564c7083113ab54b78e3
[template-slave-configuration]
filename = templates/custom-virtualhost.conf.in
......@@ -118,4 +118,4 @@ md5sum = 38792c2dceae38ab411592ec36fff6a8
[template-kedifa]
filename = instance-kedifa.cfg.in
md5sum = ee58402bbf374e3a3522ce59002880e3
md5sum = bffe1624132dbf42a788ce8ae5bd7cab
......@@ -84,6 +84,7 @@ eggs +=
websockify
erp5.util
${caucase-eggs:eggs}
collective.recipe.shelloutput
[template-common]
recipe = slapos.recipe.template:jinja2
......
......@@ -65,6 +65,7 @@ bin = ${buildout:directory}/bin/
etc = ${buildout:directory}/etc/
srv = ${buildout:directory}/srv/
var = ${buildout:directory}/var/
tmp = ${:var}/tmp
template = ${buildout:directory}/template/
backup = ${:srv}/backup
......@@ -88,6 +89,11 @@ varnginx = ${:var}/nginx
frontend_cluster = ${:var}/frontend_cluster
nginx_cluster = ${:var}/nginx_cluster
# csr_id publication
csr_id = ${:srv}/csr_id
caddy-csr_id = ${:etc}/caddy-csr_id
caddy-csr_id-log = ${:log}/httpd-csr_id
[switch-caddy-softwaretype]
recipe = slapos.cookbook:softwaretype
single-default = ${dynamic-custom-personal-template-slave-list:rendered}
......@@ -152,7 +158,7 @@ template-nginx-notebook-slave-virtualhost = {{ parameter_dict['template_nginx_no
[kedifa-login-config]
d = ${directory:ca-dir}
csr = ${:d}/kedifa-login-csr.pem
template-csr = ${:d}/kedifa-login-template-csr.pem
key = ${:d}/kedifa-login-certificate.pem
certificate = ${:key}
ca-certificate = ${:d}/kedifa-caucase-ca.pem
......@@ -165,16 +171,16 @@ organization = {{ slapparameter_dict['cluster-identification'] }}
organizational_unit = {{ instance_parameter['configuration.frontend-name'] }}
command =
{% if slapparameter_dict['kedifa-caucase-url'] %}
if [ ! -f ${:csr} ] && [ ! -f ${:key} ] ; then
if [ ! -f ${:template-csr} ] && [ ! -f ${:key} ] ; then
{{ parameter_dict['openssl'] }} req -new -sha256 \
-newkey rsa:2048 -nodes -keyout ${:key} \
-subj "/O=${:organization}/OU=${:organizational_unit}" \
-out ${:csr}
-out ${:template-csr}
fi
{% endif %}
test -f ${:key} && test -f ${:csr}
test -f ${:key} && test -f ${:template-csr}
update-command = ${:command}
csr = ${kedifa-login-config:csr}
template-csr = ${kedifa-login-config:template-csr}
key = ${kedifa-login-config:key}
stop-on-error = True
......@@ -188,7 +194,7 @@ stop-on-error = True
ca_path='${kedifa-login-config:ca-certificate}',
crl_path='${kedifa-login-config:crl}',
key_path='${kedifa-login-csr:key}',
template_csr='${kedifa-login-csr:csr}',
template_csr='${kedifa-login-csr:template-csr}',
openssl=parameter_dict['openssl'] ~ '/bin/openssl',
)}}
......@@ -205,6 +211,8 @@ local_ipv4 = {{ dumps(instance_parameter['ipv4-random']) }}
local_ipv6 = {{ dumps(instance_parameter['ipv6-random']) }}
software_type = single-custom-personal
bin_directory = {{ parameter_dict['bin_directory'] }}
caddy_executable = {{ parameter_dict['caddy'] }}
caucase_url = {{ slapparameter_dict['kedifa-caucase-url'] }}
sixtunnel_executable = {{ parameter_dict['sixtunnel'] }}/bin/6tunnel
kedifa-getter = {{ parameter_dict['kedifa-getter'] }}
kedifa-csr = {{ parameter_dict['kedifa-csr'] }}
......@@ -218,6 +226,17 @@ extra-context =
key slave_with_cache_configuration_directory caddy-directory:slave-with-cache-configuration
key kedifa_getter :kedifa-getter
key kedifa_csr :kedifa-csr
key caddy_executable :caddy_executable
key caucase_url :caucase_url
key directory_csr_id directory:csr_id
key directory_caddy_csr_id directory:caddy-csr_id
key directory_tmp directory:tmp
key directory_caddy_csr_id_log directory:caddy-csr_id-log
key certificate_organization kedifa-login-csr:organization
key certificate_organizational_unit kedifa-login-csr:organizational_unit
key csr_id_csr caucase-updater-csr:csr
key csr_crl kedifa-login-config:crl
key csr_cas_ca_certificate kedifa-login-config:cas-ca-certificate
key http_port configuration:plain_http_port
key https_port configuration:port
key nginx_http_port configuration:plain_nginx_port
......
......@@ -161,7 +161,7 @@ software-url = {{ slapparameter_dict.pop(frontend_software_url_key) }}
software-url = ${slap-connection:software-release-url}
{% endif %}
software-type = {{frontend_type}}
return = private-ipv4 public-ipv4 slave-instance-information-list monitor-base-url
return = private-ipv4 public-ipv4 slave-instance-information-list monitor-base-url csr_id-url csr_id-certificate
{% for section, frontend_request in request_dict.iteritems() %}
[{{section}}]
......@@ -199,7 +199,14 @@ 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-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}
{% endfor %}
#----------------------------
#--
......@@ -234,7 +241,7 @@ 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
return = slave-kedifa-information master-key-generate-auth-url master-key-upload-url master-key-download-url caucase-url csr_id-url csr_id-certificate
{% set sla_kedifa_key = "-sla-kedifa-" %}
{% set sla_kedifa_key_length = sla_kedifa_key | length %}
{% for key in slapparameter_dict.keys() %}
......
......@@ -13,6 +13,7 @@ parts =
caucased
caucased-promise
caucase-updater
expose-csr_id
[caucased]
hash-files = ${buildout:directory}/software_release/buildout.cfg
......@@ -41,6 +42,7 @@ bin = ${buildout:directory}/bin/
etc = ${buildout:directory}/etc/
srv = ${buildout:directory}/srv/
var = ${buildout:directory}/var/
tmp = ${buildout:directory}/tmp/
backup = ${:srv}/backup
log = ${:var}/log
......@@ -67,22 +69,26 @@ backup-caucased = ${:backup}/caucased
# reservation
reservation = ${:srv}/reservation
# csr_id publication
csr_id = ${:srv}/csr_id
caddy-csr_id = ${:etc}/caddy-csr_id
[kedifa-csr]
recipe = plone.recipe.command
organization = {{ slapparameter_dict['cluster-identification'] }}
organizational_unit = Kedifa Partition
command =
if [ ! -f ${:csr} ] && [ ! -f ${:key} ] ; then
if [ ! -f ${:template-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}'
-out ${:template-csr}'
fi
update-command = ${:command}
csr = ${kedifa-config:csr}
template-csr = ${kedifa-config:template-csr}
key = ${kedifa-config:key}
stop-on-error = True
......@@ -97,10 +103,80 @@ stop-on-error = True
crl_path='${kedifa-config:crl}',
key_path='${kedifa-csr:key}',
on_renew='${kedifa-reloader:wrapper-path}',
template_csr='${kedifa-csr:csr}',
template_csr='${kedifa-csr:template-csr}',
openssl=parameter_dict['openssl'] ~ '/bin/openssl',
)}}
[store-csr_id]
recipe = plone.recipe.command
csr_id_path = ${directory:csr_id}/csr_id.txt
csr_work_path = ${directory:tmp}/${:_buildout_section_name_}
stop-on-error = False
update-command = ${:command}
command =
{{ parameter_dict['bin_directory'] }}/caucase \
--ca-url {{ caucase_url }} \
--ca-crt ${kedifa-config:ca-certificate} \
--crl ${kedifa-config:crl} \
--mode service \
{#- XXX: Need to use caucase-updater-csr:csr, as there is no way to obatin csr_id from caucase-updater -#}
{#- XXX: nor directly path to the generated CSR #}
--send-csr ${caucase-updater-csr:csr} > ${:csr_work_path} && \
cut -d ' ' -f 1 ${:csr_work_path} > ${:csr_id_path}
[certificate-csr_id]
recipe = plone.recipe.command
certificate = ${directory:caddy-csr_id}/certificate.pem
key = ${directory:caddy-csr_id}/key.pem
stop-on-error = True
update-command = ${:command}
command =
if ! [ -f ${:key} ] && ! [ -f ${:certificate} ] ; then
openssl req -new -newkey rsa:2048 -sha256 -subj \
"/O=${kedifa-csr:organization}/OU=${kedifa-csr:organizational_unit}/CN={{ instance_parameter['ipv6-random'] }}" \
-days 5 -nodes -x509 -keyout ${:key} -out ${:certificate}
fi
[expose-csr_id-configuration]
ip = {{ instance_parameter['ipv6-random'] }}
port = 17000
key = ${certificate-csr_id:key}
certificate = ${certificate-csr_id:certificate}
error-log = ${directory:log}/expose-csr_id.log
[expose-csr_id-template]
recipe = slapos.recipe.template:jinja2
template = inline:
https://:${expose-csr_id-configuration:port}/ {
bind ${expose-csr_id-configuration:ip}
tls ${expose-csr_id-configuration:certificate} ${expose-csr_id-configuration:key}
log ${expose-csr_id-configuration:error-log}
}
rendered = ${directory:caddy-csr_id}/Caddyfile
[expose-csr_id]
depends = ${store-csr_id:command}
recipe = slapos.cookbook:wrapper
command-line = {{ parameter_dict['caddy'] }}
-conf ${expose-csr_id-template:rendered}
-log ${expose-csr_id-configuration:error-log}
-http2=true
-disable-http-challenge
-disable-tls-sni-challenge
-root ${directory:csr_id}
wrapper-path = ${directory:service}/expose-csr_id
hash-files = ${buildout:directory}/software_release/buildout.cfg
[get-csr_id-certificate]
recipe = collective.recipe.shelloutput
commands =
certificate = cat ${certificate-csr_id:certificate}
[jinja2-template-base]
recipe = slapos.recipe.template:jinja2
rendered = ${buildout:directory}/${:filename}
......@@ -121,10 +197,9 @@ 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
ca-certificate = ${directory:etc-kedifa}/cas-ca-certificate.pem
crl = ${directory:etc-kedifa}/crl.pem
csr = ${directory:etc-kedifa}/csr.pem
template-csr = ${directory:etc-kedifa}/template-csr.pem
pidfile = ${directory:run}/kedifa.pid
[kedifa-reloader]
......@@ -190,4 +265,6 @@ 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}
csr_id-url = https://[${expose-csr_id-configuration:ip}]:${expose-csr_id-configuration:port}/csr_id.txt
csr_id-certificate = ${get-csr_id-certificate:certificate}
{%- endif -%} {# if slap_software_type in software_type #}
......@@ -45,6 +45,22 @@
"slave-amount": {
"description": "Total amount of Slaves allocated to the Instance (include blocked ones)",
"type": "integer"
},
"kedifa-csr_id-url": {
"description": "URL on which KeDiFa publishes its csr_id sent to caucase.",
"type": "string"
},
"kedifa-csr_id-certificate": {
"description": "Certificate used to serve data on kedifa-csr_id-url.",
"type": "string"
},
"caddy-frontend-N-csr_id-url": {
"description": "URL on which frontend node number N publishes its csr_id sent to caucase.",
"type": "string"
},
"caddy-frontend-N-csr_id-certificate": {
"description": "Certificate used to serve data on caddy-frontend-N-csr_id-url.",
"type": "string"
}
},
"type": "object"
......
......@@ -25,3 +25,4 @@ smmap = 0.9.0
numpy = 1.11.2
websockify = 0.8.0
collective.recipe.shelloutput = 0.1
......@@ -442,6 +442,8 @@ private-ipv4 = {{ local_ipv4 }}
slave-instance-information-list = {{ json_module.dumps(slave_instance_information_list) }}
{% endif %}
monitor-base-url = {{ monitor_base_url }}
csr_id-url = https://[${expose-csr_id-configuration:ip}]:${expose-csr_id-configuration:port}/csr_id.txt
csr_id-certificate = ${get-csr_id-certificate:certificate}
[buildout]
extends = {{ common_profile }}
......@@ -456,7 +458,76 @@ parts +=
tunnel-6to4-base-ssl_cached_port
tunnel-6to4-base-nginx_http_port
tunnel-6to4-base-nginx_https_port
expose-csr_id
cache-access = {{ cache_access }}
[store-csr_id]
recipe = plone.recipe.command
csr_id_path = {{ directory_csr_id }}/csr_id.txt
csr_work_path = {{ directory_tmp }}/${:_buildout_section_name_}
stop-on-error = False
update-command = ${:command}
command =
{{ bin_directory }}/caucase \
--ca-url {{ caucase_url }} \
--ca-crt {{ csr_cas_ca_certificate }} \
--crl {{ csr_crl }} \
--mode service \
--send-csr {{ csr_id_csr }} > ${:csr_work_path} && \
cut -d ' ' -f 1 ${:csr_work_path} > ${:csr_id_path}
[certificate-csr_id]
recipe = plone.recipe.command
certificate = {{ directory_caddy_csr_id }}/certificate.pem
key = {{ directory_caddy_csr_id }}/key.pem
stop-on-error = True
update-command = ${:command}
command =
if ! [ -f ${:key} ] && ! [ -f ${:certificate} ] ; then
openssl req -new -newkey rsa:2048 -sha256 -subj \
"/O={{ certificate_organization }}/OU={{ certificate_organizational_unit }}/CN=${slap-network-information:global-ipv6}" \
-days 5 -nodes -x509 -keyout ${:key} -out ${:certificate}
fi
[expose-csr_id-configuration]
ip = ${slap-network-information:global-ipv6}
port = 17001
key = ${certificate-csr_id:key}
certificate = ${certificate-csr_id:certificate}
error-log = {{ directory_caddy_csr_id_log }}/expose-csr_id.log
[expose-csr_id-template]
recipe = slapos.recipe.template:jinja2
template = inline:
https://:${expose-csr_id-configuration:port}/ {
bind ${expose-csr_id-configuration:ip}
tls ${expose-csr_id-configuration:certificate} ${expose-csr_id-configuration:key}
log ${expose-csr_id-configuration:error-log}
}
rendered = {{ directory_caddy_csr_id }}/Caddyfile
[expose-csr_id]
depends = ${store-csr_id:command}
recipe = slapos.cookbook:wrapper
command-line = {{ caddy_executable }}
-conf ${expose-csr_id-template:rendered}
-log ${expose-csr_id-configuration:error-log}
-http2=true
-disable-http-challenge
-disable-tls-sni-challenge
-root {{ directory_csr_id }}
wrapper-path = {{ service_directory }}/expose-csr_id
hash-files = ${buildout:directory}/software_release/buildout.cfg
[get-csr_id-certificate]
recipe = collective.recipe.shelloutput
commands =
certificate = cat ${certificate-csr_id:certificate}
{% endif %}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment