Browse Source

new: [letsencrypt] not anymore a daemon.

framadate
Valentin Lab 6 years ago
parent
commit
f5a2d60791
  1. 99
      apache/lib/common
  2. 8
      apache/test/vhost
  3. 146
      apache/test/vhost_cert_provider
  4. 46
      letsencrypt/actions/add
  5. 58
      letsencrypt/hooks/dc-pre-run
  6. 71
      letsencrypt/hooks/init
  7. 25
      letsencrypt/lib/common
  8. 10
      letsencrypt/metadata.yml

99
apache/lib/common

@ -33,13 +33,15 @@ export -f apache_publish_dir
apache_vhost_create () { apache_vhost_create () {
export APACHE_CONFIG_LOCATION="$SERVICE_CONFIGSTORE/etc/apache2/sites-enabled" export APACHE_CONFIG_LOCATION="$SERVICE_CONFIGSTORE/etc/apache2/sites-enabled"
export SERVER_ALIAS=$(relation-get server-aliases 2>/dev/null) || true
export PROTOCOLS=$(__vhost_cfg_normalize_protocol) || return 1
SERVER_ALIAS=$(relation-get server-aliases 2>/dev/null) || true
PROTOCOLS=$(__vhost_cfg_normalize_protocol) || return 1
export SERVER_ALIAS PROTOCOLS SSL_PLUGIN_FUN SSL_CFG_{VALUE,OPTION}
export SSL_PLUGIN_FUN=$(ssl_get_plugin_fun) || return 1
if is_protocol_enabled https; then if is_protocol_enabled https; then
"$SSL_PLUGIN_FUN"_vars "$(relation-get ssl)" || return 1
read-0 SSL_PLUGIN_FUN SSL_CFG_VALUE SSL_CFG_OPTIONS < <(ssl_get_plugin_fun) || return 1
"$SSL_PLUGIN_FUN"_vars "$SSL_CFG_OPTIONS" "$SSL_CFG_VALUE" || return 1
fi fi
apache_vhost_statement "$PROTOCOLS" | apache_vhost_statement "$PROTOCOLS" |
file_put "$APACHE_CONFIG_LOCATION/$prefix$DOMAIN.conf" || return 1 file_put "$APACHE_CONFIG_LOCATION/$prefix$DOMAIN.conf" || return 1
@ -49,7 +51,9 @@ apache_vhost_create () {
apache_passwd_file || return 1 apache_passwd_file || return 1
fi fi
"$SSL_PLUGIN_FUN"_prepare "$(relation-get ssl)" || return 1
if is_protocol_enabled https; then
"$SSL_PLUGIN_FUN"_prepare "$SSL_CFG_OPTIONS" "$SSL_CFG_VALUE" || return 1
fi
} }
@ -101,27 +105,59 @@ __vhost_cfg_normalize_protocol() {
## - output 3 vars of where to find the 3 files from within the docker apache ## - output 3 vars of where to find the 3 files from within the docker apache
ssl_get_plugin_fun() { ssl_get_plugin_fun() {
local cfg="$(relation-get ssl 2>/dev/null)"
if [[ "$(echo "$cfg" | shyaml get-type 2>/dev/null)" == "str" ]]; then
target_relation=
while read-0 relation_name target_service relation_config tech_dep; do
[ "$target_service" == "$cfg" ] || continue
verb "service ${DARKYELLOW}$target_service${NORMAL} matches" \
"${WHITE}ssl${NORMAL} value: candidate relation is ${DARKBLUE}$relation_name${NORMAL}"
fun="ssl_plugin_${relation_name}"
if declare -F "${fun}_vars" >/dev/null 2>&1 && declare -F "${fun}_prepare" >/dev/null 2>&1; then
verb "Corresponding plugin ${DARKGREEN}found${NORMAL} for relation ${DARKBLUE}$relation_name${NORMAL}"
echo "$fun"
return 0
else
verb "Corresponding plugin ${DARKRED}not found${NORMAL} for relation ${DARKBLUE}$relation_name${NORMAL}"
fi
done < <(get_compose_relations "$SERVICE_NAME") || return 1
err "Invalid ${WHITE}ssl${NORMAL} value: '$cfg' is not a valid linked service through a support relation."
return 1
# from ssl conf, return the function that should manage SSL code creation
local cfg="$(relation-get ssl 2>/dev/null)" type keys
if [ -z "$cfg" ]; then
return 0
else else
type="$(echo "$cfg" | shyaml -y get-type 2>/dev/null)" || return 1
fi
if [[ "$type" == "bool" ]]; then
printf "%s\0" "ssl_fallback" "" "$cfg"
echo ssl_fallback echo ssl_fallback
return 0
fi
if ! [[ "$type" == "str" || "$type" == "struct" ]]; then
err "Invalid ${WHITE}ssl${NORMAL} value type '$type': please provide a string or a struct."
return 1
fi
if [ -z "$NO_CERT_PROVIDER" ]; then
if [[ "$type" == "str" ]]; then
keys=("$cfg")
else
keys=($(echo "$cfg" | shyaml keys 2>/dev/null))
fi
for key in "${keys[@]}"; do
target_relation="cert-provider"
fun="ssl_plugin_${target_relation}"
while read-0 relation_name target_service relation_config tech_dep; do
[ "$relation_name" == "${target_relation}" ] || continue
[ "$target_service" == "$key" ] || continue
verb "Corresponding plugin ${DARKGREEN}found${NORMAL}" \
"in ${DARKBLUE}$relation_name${NORMAL}/${DARKYELLOW}$key${NORMAL}"
ssl_cfg=$(printf "%s" "$cfg" | shyaml get-value "$key" 2>/dev/null) || true
merged_config=$(merge_yaml_str "$relation_config" "$ssl_cfg") || return 1
printf "%s\0" "$fun" "$key" "$merged_config"
return 0
done < <(get_compose_relations "$SERVICE_NAME") || return 1
case "$key" in
cert|ca-cert|key)
:
;;
*)
err "Invalid key '$key' in ${WHITE}ssl${NORMAL}:" \
"no corresponding services declared in ${DARKBLUE}${target_relation}$NORMAL"
return 1
;;
esac
done
fi fi
## No key of the struct seem to be declared cert-provider, so fallback
printf "%s\0" "ssl_fallback" "" "$cfg"
echo ssl_fallback
} }
@ -168,26 +204,26 @@ $volumes
} }
ssl_plugin_letsencrypt-dns_vars() {
ssl_plugin_cert-provider_vars() {
__vhost_cfg_SSL_CERT_LOCATION=/etc/letsencrypt/live/${DOMAIN}/cert.pem __vhost_cfg_SSL_CERT_LOCATION=/etc/letsencrypt/live/${DOMAIN}/cert.pem
__vhost_cfg_SSL_KEY_LOCATION=/etc/letsencrypt/live/${DOMAIN}/privkey.pem __vhost_cfg_SSL_KEY_LOCATION=/etc/letsencrypt/live/${DOMAIN}/privkey.pem
__vhost_cfg_SSL_CHAIN=/etc/letsencrypt/live/${DOMAIN}/chain.pem __vhost_cfg_SSL_CHAIN=/etc/letsencrypt/live/${DOMAIN}/chain.pem
} }
ssl_plugin_letsencrypt-dns_prepare() {
local service="$1" letsencrypt_charm
shift
ssl_plugin_cert-provider_prepare() {
local cfg="$1" service="$2" options
export DEFAULT_COMPOSE_FILE="$COMPOSE_YML_FILE"
run_service_action "$service" add "$DOMAIN" $(echo "$SERVER_ALIAS" | shyaml get-values 2>/dev/null) || return 1
letsencrypt_charm=$(get_service_charm "$service") || return 1
options=$(yaml_key_val_str "options" "$cfg") || return 1
service_config=$(yaml_key_val_str "$service" "$options")
compose --debug --add-compose-content "$service_config" run "$service" \
crt create "$DOMAIN" $(echo "$SERVER_ALIAS" | shyaml -y get-values 2>/dev/null) || return 1
config-add "\ config-add "\
services: services:
$MASTER_TARGET_SERVICE_NAME: $MASTER_TARGET_SERVICE_NAME:
volumes: volumes:
- $DATASTORE/${letsencrypt_charm}/etc/letsencrypt:/etc/letsencrypt:ro
- $DATASTORE/$service/etc/letsencrypt:/etc/letsencrypt:ro
" || return 1 " || return 1
} }
@ -225,7 +261,6 @@ apache_vhost_statement() {
__vhost_full_vhost_statement http __vhost_full_vhost_statement http
fi fi
if is_protocol_enabled https; then if is_protocol_enabled https; then
export SSL_PLUGIN_FUN=$(ssl_get_plugin_fun) || return 1
"$SSL_PLUGIN_FUN"_vars "$(relation-get ssl 2>/dev/null)" "$SSL_PLUGIN_FUN"_vars "$(relation-get ssl 2>/dev/null)"
cat <<EOF cat <<EOF

8
apache/test/vhost

@ -158,7 +158,7 @@ CFG='
ssl: true ssl: true
target: popo:3333 target: popo:3333
' '
proxy=yes apache_vhost_statement ,https,"
SSL_PLUGIN_FUN=ssl_fallback proxy=yes apache_vhost_statement ,https," "ssl default generation (ssl-cert-snakeoil)"
noerror noerror
is out reg 'VirtualHost \*:443' is out reg 'VirtualHost \*:443'
is out reg '<IfModule mod_ssl.c>' is out reg '<IfModule mod_ssl.c>'
@ -178,7 +178,7 @@ ssl:
cert: c cert: c
target: popo:3333 target: popo:3333
' '
proxy=yes apache_vhost_statement ,https,"
SSL_PLUGIN_FUN=ssl_fallback proxy=yes apache_vhost_statement ,https," "ssl providing keys inline"
noerror noerror
is out reg 'SSLCertificateFile /etc/ssl/certs/www.example.com.pem' is out reg 'SSLCertificateFile /etc/ssl/certs/www.example.com.pem'
is out reg 'SSLCertificateKeyFile /etc/ssl/private/www.example.com.key' is out reg 'SSLCertificateKeyFile /etc/ssl/private/www.example.com.key'
@ -203,7 +203,7 @@ apache-custom-rules: |
RewriteRule ^(/web/webclient/home.*)$ $1?skin=formanoo [L,QSA,R=302] RewriteRule ^(/web/webclient/home.*)$ $1?skin=formanoo [L,QSA,R=302]
target: popo:3333 target: popo:3333
' '
proxy=yes apache_vhost_statement ,https,"
SSL_PLUGIN_FUN=ssl_fallback proxy=yes apache_vhost_statement ,https," "custom rules"
noerror noerror
is out reg 'RewriteEngine On' is out reg 'RewriteEngine On'
@ -226,7 +226,7 @@ apache-custom-rules: |
RewriteRule ^(/web/webclient/home.*)$ $1?skin=formanoo [L,QSA,R=302] RewriteRule ^(/web/webclient/home.*)$ $1?skin=formanoo [L,QSA,R=302]
target: popo:3333 target: popo:3333
' '
proxy=yes apache_vhost_statement ,https,http,"
SSL_PLUGIN_FUN=ssl_fallback proxy=yes apache_vhost_statement ,https,http," "both http and https"
noerror noerror
is out '<VirtualHost *:80> is out '<VirtualHost *:80>

146
apache/test/vhost_cert_provider

@ -0,0 +1,146 @@
#!/bin/bash
exname=$(basename $0)
prefix_cmd="
. /etc/shlib
include common
include parse
. ../lib/common
depends compose
"
##
## Mocks
##
relation-get() {
local key="$1"
echo "$CFG" | shyaml get-value "$key" 2>/dev/null
}
export -f relation-get
get_compose_relations() {
local service="$1"
printf "%s\0" "${RELATIONS[@]}"
}
export -f get_compose_relations
merge_yaml_str() {
printf "<merge_yaml_str("
printf "'%s', " "$@"
printf ")>"
}
export -f merge_yaml_str
compose() {
printf "Calling: compose "
printf "%s " "$*"
echo
}
export -f compose
yaml_key_val_str() {
printf "%s: %s" "$1" "$2"
}
export -f yaml_key_val_str
file_put() {
echo "file_put $1"
cat - | prefix " | "
}
export -f file_put
docker() {
echo "docker" "$@"
echo stdin:
cat - | prefix " | "
}
export -f docker
config-add() {
echo "config-add"
echo "$1" | prefix " | "
}
export -f config-add
mkdir() {
echo "called: $FUNCNAME $@" >&2
}
export -f mkdir
setfacl() {
echo "called: $FUNCNAME $@" >&2
}
export -f setfacl
chgrp() {
echo "called: $FUNCNAME $@" >&2
}
export -f chgrp
chmod() {
echo "called: $FUNCNAME $@" >&2
}
export -f chmod
cached_cmd_on_base_image() {
echo "called: $FUNCNAME $@" >&2
echo "stdout:" >&2
echo "<GID>" | prefix " | " >&2
echo "<GID>"
}
export -f cached_cmd_on_base_image
##
## cert-provider
##
try "
export SERVICE_CONFIGSTORE='\$SERVICE_CONFIGSTORE'
export CONFIGSTORE='\$CONFIGSTORE'
export BASE_SERVICE_NAME='\$BASE_SERVICE_NAME'
export MASTER_TARGET_SERVICE_NAME='\$MASTER_TARGET_SERVICE_NAME'
DOMAIN=www.example.com
DOCKER_SITE_PATH=/var/www/\$DOMAIN
CFG='
ssl:
foo: |
a
b
'
RELATIONS=()
apache_vhost_create" "unknown cert key"
is errlvl 1
is err reg 'Error: .*cert-provider.*'
try "
export SERVICE_CONFIGSTORE='\$SERVICE_CONFIGSTORE'
export CONFIGSTORE='\$CONFIGSTORE'
export DATASTORE='\$DATASTORE'
export BASE_SERVICE_NAME='\$BASE_SERVICE_NAME'
export MASTER_TARGET_SERVICE_NAME='\$MASTER_TARGET_SERVICE_NAME'
DOMAIN=www.example.com
DOCKER_SITE_PATH=/var/www/\$DOMAIN
CFG='
ssl:
foo: 12
'
RELATIONS=(cert-provider foo a True)
apache_vhost_create" "known cert key"
noerror
is out reg 'Calling: compose .*foo: options: <merge_yaml_str\(.a., .12., )>.*run foo.*'
is out part 'config-add
| services:
| $MASTER_TARGET_SERVICE_NAME:
| volumes:
| - $DATASTORE/foo/etc/letsencrypt:/etc/letsencrypt:ro'

46
letsencrypt/actions/add

@ -1,46 +0,0 @@
#!/bin/bash
## Load action gets a first argument a DIRECTORY holding the necessary files.
##
##
if [ -z "$SERVICE_DATASTORE" ]; then
echo "This script is meant to be run through 'compose' to work properly." >&2
exit 1
fi
usage="$exname [-h|--help] DOMAIN [DOMAIN...]"
domains=()
while [ "$1" ]; do
case "$1" in
"--help"|"-h")
print_usage
exit 0
;;
--*|-*)
err "Unexpected optional argument '$1'"
print_usage
exit 1
;;
*)
domains+=("$1")
;;
esac
shift
done
if [ -z "${domains[*]}" ]; then
err "You must provide at least one domain as positional argument."
print_usage
exit 1
fi
set -e
## XXXvlab: should check that domain can be declared (with whois, check that the
## registrar is a provider that have config values declared in compose.conf)
mkdir -p "$SERVICE_DATASTORE/etc/letsencrypt"
echo "${domains[@]}" >> "$SERVICE_DATASTORE/etc/letsencrypt/domains.conf"
info "Added '${domains[*]}' domains to letsencrypt domain lists."

58
letsencrypt/hooks/dc-pre-run

@ -0,0 +1,58 @@
#!/bin/bash
## Init is run on host
## For now it is run every time the script is launched, but
## it should be launched only once after build.
## Accessible variables are:
## - SERVICE_NAME Name of current service
## - DOCKER_BASE_IMAGE Base image from which this service might be built if any
## - SERVICE_DATASTORE Location on host of the DATASTORE of this service
## - SERVICE_CONFIGSTORE Location on host of the CONFIGSTORE of this service
. lib/common || exit 1
set -e
service_def=$(get_compose_service_def "$SERVICE_NAME")
USER_EMAIL=$(echo "$service_def" | shyaml get-value options.email 2>/dev/null) || {
err "No ${WHITE}email${NORMAL} value in ${DARKYELLOW}$SERVICE_NAME${NORMAL} compose's ${WHITE}options${NORMAL}."
exit 1
}
config="
$SERVICE_NAME:
environment:
LETSENCRYPT_USER_MAIL: $USER_EMAIL"
if environment_def="$(printf "%s" "$service_def" | shyaml -y get-value options.env 2>/dev/null)"; then
while read-0 key value; do
config+="$(printf "\n %s: %s" "$key" "$value")"
done < <(printf "%s" "$environment_def" | yaml_opt_bash_env_ignore_first_level LEXICON)
if ! provider=$(printf "%s" "$environment_def" | shyaml -y get-value provider 2>/dev/null); then
provider=
## If no provider is given, we fallback on the first found
while read-0 key value; do
[[ "$(echo "$value" | shyaml get-type)" == "struct" ]] && {
provider="$key"
break
}
done < <(echo "$environment_def" | shyaml key-values-0)
warn "No ${WHITE}provider${NORMAL} key given, had to infer it, chose '$key'."
fi
config+=$(echo -en "\n LEXICON_PROVIDER: $provider")
fi
if ! challenge_type=$(printf "%s" "$service_def" | shyaml get-value "options.challenge-type" 2>/dev/null); then
warn "No ${WHITE}challenge-type${NORMAL} provided, defaulting to 'http'."
challenge_type=http
fi
config+=$(echo -en "\n CHALLENGE_TYPE: $challenge_type")
init-config-add "$config"
mkdir -p "$SERVICE_DATASTORE/etc/letsencrypt"

71
letsencrypt/hooks/init

@ -1,71 +0,0 @@
#!/bin/bash
## Init is run on host
## For now it is run every time the script is launched, but
## it should be launched only once after build.
## Accessible variables are:
## - SERVICE_NAME Name of current service
## - DOCKER_BASE_IMAGE Base image from which this service might be built if any
## - SERVICE_DATASTORE Location on host of the DATASTORE of this service
## - SERVICE_CONFIGSTORE Location on host of the CONFIGSTORE of this service
set -e
service_def=$(get_compose_service_def "$SERVICE_NAME")
USER_EMAIL=$(echo "$service_def" | shyaml get-value options.email 2>/dev/null) || {
err "No ${WHITE}email${NORMAL} value in ${DARKYELLOW}$SERVICE_NAME${NORMAL} compose's ${WHITE}options${NORMAL}."
exit 1
}
yaml_opt_bash_env() {
local prefix="$1" key value
while read-0 key value; do
new_prefix="${prefix}_${key^^}"
if [[ "$(echo "$value" | shyaml get-type)" == "struct" ]]; then
echo "$value" | yaml_opt_bash_env "${new_prefix}"
else
printf "%s\0%s\0" "${new_prefix}" "$value"
fi
done < <(shyaml key-values-0)
}
yaml_opt_bash_env_ignore_first_level() {
local prefix="$1" key value
while read-0 key value; do
new_prefix="${prefix}_${key^^}"
if [[ "$(echo "$value" | shyaml get-type)" == "struct" ]]; then
echo "$value" | yaml_opt_bash_env "${new_prefix}"
fi
done < <(shyaml key-values-0)
}
config="
$SERVICE_NAME:
environment:
LETSENCRYPT_USER_MAIL: $USER_EMAIL"
while read-0 key value; do
config+="$(printf "\n %s: %s" "$key" "$value")"
done < <(yaml_opt_bash_env_ignore_first_level LEXICON < <(echo "$service_def" | shyaml -y get-value options))
## XXXvlab: this is very temporary, we should change image to support more
## than one provider (cf: https://github.com/adferrand/docker-letsencrypt-dns/issues/24)
first_key=
while read-0 key value; do
[[ "$(echo "$value" | shyaml get-type)" == "struct" ]] && {
first_key="$key"
break
}
done < <(echo "$service_def" | shyaml key-values-0 options)
config+=$(echo -en "\n LEXICON_PROVIDER: $first_key")
init-config-add "$config"
mkdir -p "$SERVICE_DATASTORE/etc/letsencrypt"
touch "$SERVICE_DATASTORE/etc/letsencrypt/domains.conf"

25
letsencrypt/lib/common

@ -0,0 +1,25 @@
yaml_opt_bash_env() {
local prefix="$1" key value
while read-0 key value; do
new_prefix="${prefix}_${key^^}"
if [[ "$(echo "$value" | shyaml get-type)" == "struct" ]]; then
echo "$value" | yaml_opt_bash_env "${new_prefix}"
else
printf "%s\0%s\0" "${new_prefix/-/_}" "$value"
fi
done < <(shyaml key-values-0)
}
yaml_opt_bash_env_ignore_first_level() {
local prefix="$1" key value
while read-0 key value; do
new_prefix="${prefix}_${key^^}"
if [[ "$(echo "$value" | shyaml get-type)" == "struct" ]]; then
echo "$value" | yaml_opt_bash_env "${new_prefix}"
fi
done < <(shyaml key-values-0)
}

10
letsencrypt/metadata.yml

@ -1,7 +1,9 @@
description: "Let's Encrypt"
description: "Let's Encrypt server"
type: run-once
maintainer: "Valentin Lab <valentin.lab@kalysto.org>" maintainer: "Valentin Lab <valentin.lab@kalysto.org>"
## XXXvlab: docker uses the 'build' directory or the 'image:' option here. ## XXXvlab: docker uses the 'build' directory or the 'image:' option here.
docker-image: adferrand/letsencrypt-dns
docker-image: docker.0k.io/letsencrypt
data-resources: data-resources:
- /etc/letsencrypt
- /var/log/letsencrypt
- /etc/letsencrypt ## yes certificates are stored here, this is data
- /var/log/letsencrypt ## logs
- /var/lib/tldextract ## latest data about TLDs, this is used by lexicon...
Loading…
Cancel
Save