You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
570 lines
15 KiB
570 lines
15 KiB
# -*- mode: shell-script -*-
|
|
|
|
config_hash=
|
|
|
|
apache_proxy_dir () {
|
|
DOMAIN=$(relation-get domain) || {
|
|
err "You must specify a ${WHITE}domain$NORMAL option in relation."
|
|
return 1
|
|
}
|
|
proxy=yes apache_vhost_create || return 1
|
|
info "Added $DOMAIN as a proxy to $TARGET."
|
|
}
|
|
export -f apache_proxy_dir
|
|
|
|
|
|
apache_publish_dir () {
|
|
DOMAIN=$(relation-get domain) || {
|
|
err "You must specify a ${WHITE}domain$NORMAL option in relation."
|
|
return 1
|
|
}
|
|
DOCKER_SITE_PATH="/var/www/${DOMAIN}"
|
|
LOCATION=$(relation-get location 2>/dev/null) ||
|
|
LOCATION="$DATASTORE/$BASE_SERVICE_NAME$DOCKER_SITE_PATH"
|
|
|
|
apache_vhost_create || return 1
|
|
info "Added $DOMAIN apache config."
|
|
apache_code_dir || return 1
|
|
apache_data_dirs
|
|
|
|
}
|
|
export -f apache_publish_dir
|
|
|
|
|
|
apache_vhost_create () {
|
|
export APACHE_CONFIG_LOCATION="$SERVICE_CONFIGSTORE/etc/apache2/sites-enabled"
|
|
|
|
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}
|
|
|
|
if is_protocol_enabled https; then
|
|
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
|
|
apache_vhost_statement "$PROTOCOLS" |
|
|
file_put "$APACHE_CONFIG_LOCATION/$prefix$DOMAIN.conf" || return 1
|
|
|
|
__vhost_cfg_creds_enabled=$(relation-get creds 2>/dev/null) || true
|
|
if [ "$__vhost_cfg_creds_enabled" ]; then
|
|
apache_passwd_file || return 1
|
|
fi
|
|
|
|
if is_protocol_enabled https; then
|
|
"$SSL_PLUGIN_FUN"_prepare "$SSL_CFG_OPTIONS" "$SSL_CFG_VALUE" || return 1
|
|
fi
|
|
}
|
|
|
|
|
|
is_protocol_enabled() {
|
|
local protocol=$1
|
|
[[ "$PROTOCOLS" == *",$protocol,"* ]]
|
|
}
|
|
export -f is_protocol_enabled
|
|
|
|
|
|
__vhost_cfg_normalize_protocol() {
|
|
local protocol
|
|
|
|
if ! protocol=$(relation-get protocol 2>/dev/null); then
|
|
protocol=auto
|
|
else
|
|
protocol=${protocol:-auto}
|
|
fi
|
|
|
|
case "$protocol" in
|
|
auto)
|
|
if __vhost_cfg_ssl="$(relation-get ssl 2>/dev/null)"; then
|
|
protocol="https"
|
|
export __vhost_cfg_ssl
|
|
else
|
|
protocol="http"
|
|
fi
|
|
;;
|
|
both)
|
|
protocol="https,http"
|
|
;;
|
|
ssl|https)
|
|
protocol="https"
|
|
;;
|
|
http)
|
|
protocol="http"
|
|
;;
|
|
*)
|
|
err "Invalid value '$protocol' for ${WHITE}protocol$NORMAL option (use one of: http, https, both, auto)."
|
|
return 1
|
|
esac
|
|
echo ",$protocol,"
|
|
}
|
|
|
|
|
|
## ssl_plugin_* and ssl_fallback should :
|
|
## - do anything to ensure that
|
|
## - issue config-add to add volumes if necessary
|
|
## - output 3 vars of where to find the 3 files from within the docker apache
|
|
|
|
ssl_get_plugin_fun() {
|
|
# 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
|
|
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
|
|
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
|
|
## No key of the struct seem to be declared cert-provider, so fallback
|
|
printf "%s\0" "ssl_fallback" "" "$cfg"
|
|
echo ssl_fallback
|
|
}
|
|
|
|
|
|
ssl_fallback_vars() {
|
|
local cfg="$1" cert key ca_cert
|
|
|
|
if __vhost_cfg_ssl_cert=$(echo "$cfg" | shyaml get-value cert 2>/dev/null); then
|
|
__vhost_cfg_SSL_CERT_LOCATION=/etc/ssl/certs/${DOMAIN}.pem
|
|
fi
|
|
|
|
if __vhost_cfg_ssl_key=$(echo "$cfg" | shyaml get-value key 2>/dev/null); then
|
|
__vhost_cfg_SSL_KEY_LOCATION=/etc/ssl/private/${DOMAIN}.key
|
|
fi
|
|
|
|
if __vhost_cfg_ssl_ca_cert=$(echo "$cfg" | shyaml get-value ca-cert 2>/dev/null); then
|
|
__vhost_cfg_SSL_CA_CERT_LOCATION=/etc/ssl/certs/${DOMAIN}-ca.pem
|
|
fi
|
|
}
|
|
|
|
ssl_fallback_prepare() {
|
|
local cfg="$1" cert key ca_cert
|
|
|
|
dst="$CONFIGSTORE/$BASE_SERVICE_NAME"
|
|
volumes=""
|
|
for label in cert key ca_cert; do
|
|
content="$(eval echo "\"\$__vhost_cfg_ssl_$label\"")"
|
|
if [ "$content" ]; then
|
|
location="$(eval echo "\$__vhost_cfg_SSL_${label^^}_LOCATION")"
|
|
echo "$content" | file_put "$dst$location"
|
|
config_hash=$(printf "%s\0" "$config_hash" "$label" "$content" | md5_compat)
|
|
volumes="$volumes
|
|
- $dst$location:$location:ro"
|
|
fi
|
|
done
|
|
|
|
if [ "$volumes" ]; then
|
|
config-add "\
|
|
services:
|
|
$MASTER_TARGET_SERVICE_NAME:
|
|
volumes:
|
|
$volumes
|
|
"
|
|
fi
|
|
|
|
}
|
|
|
|
ssl_plugin_cert-provider_vars() {
|
|
|
|
__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_CHAIN=/etc/letsencrypt/live/${DOMAIN}/chain.pem
|
|
}
|
|
|
|
ssl_plugin_cert-provider_prepare() {
|
|
local cfg="$1" service="$2" options
|
|
|
|
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 --rm --service-ports "$service" \
|
|
crt create "$DOMAIN" $(echo "$SERVER_ALIAS" | shyaml get-values 2>/dev/null) || {
|
|
err "Failed to launch letsencrypt for certificate creation."
|
|
return 1
|
|
}
|
|
config-add "\
|
|
services:
|
|
$MASTER_TARGET_SERVICE_NAME:
|
|
volumes:
|
|
- $DATASTORE/$service/etc/letsencrypt:/etc/letsencrypt:ro
|
|
" || return 1
|
|
|
|
}
|
|
|
|
|
|
apache_passwd_file() {
|
|
include parse || true
|
|
|
|
## XXXvlab: called twice... no better way to do this ?
|
|
__vhost_creds_statement >/dev/null
|
|
first=
|
|
if ! [ -e "$CONFIGSTORE/$MASTER_TARGET_SERVICE_NAME$password_file" ]; then
|
|
debug "No file $CONFIGSTORE/$MASTER_TARGET_SERVICE_NAME$password_file, creating password file." || true
|
|
first=c
|
|
fi
|
|
while read-0 login password; do
|
|
debug "htpasswd -b$first '${password_file}' '$login' '$password'"
|
|
echo "htpasswd -b$first '${password_file}' '$login' '$password'"
|
|
if [ "$first" ]; then
|
|
first=
|
|
fi
|
|
done < <(echo "$__vhost_cfg_creds_enabled" | shyaml key-values-0 2>/dev/null) |
|
|
docker run -i --entrypoint "/bin/bash" \
|
|
-v "$APACHE_CONFIG_LOCATION:/etc/apache2/sites-enabled" \
|
|
"$DOCKER_BASE_IMAGE" || return 1
|
|
}
|
|
|
|
## Produce the full statements depending on relation-get informations
|
|
apache_vhost_statement() {
|
|
local vhost_statement
|
|
export SERVER_ALIAS=$(relation-get server-aliases 2>/dev/null) || true
|
|
export PROTOCOLS="$1"
|
|
|
|
if is_protocol_enabled http; then
|
|
__vhost_full_vhost_statement http
|
|
fi
|
|
if is_protocol_enabled https; then
|
|
"$SSL_PLUGIN_FUN"_vars "$(relation-get ssl 2>/dev/null)"
|
|
cat <<EOF
|
|
|
|
<IfModule mod_ssl.c>
|
|
$(__vhost_full_vhost_statement https | prefix " ")
|
|
</IfModule>
|
|
EOF
|
|
fi
|
|
}
|
|
export -f apache_vhost_statement
|
|
|
|
|
|
apache_code_dir() {
|
|
local www_data_gid
|
|
www_data_gid=$(cached_cmd_on_base_image apache 'id -g www-data') || {
|
|
debug "Failed to query for www-data gid in ${DARKYELLOW}apache${NORMAL} base image."
|
|
return 1
|
|
}
|
|
|
|
mkdir -p "$LOCATION" || return 1
|
|
setfacl -R -m g:"$www_data_gid":rx "$LOCATION"
|
|
info "Set permission for read and traversal on '$LOCATION'."
|
|
|
|
config-add "
|
|
$MASTER_BASE_SERVICE_NAME:
|
|
volumes:
|
|
- $LOCATION:$DOCKER_SITE_PATH
|
|
"
|
|
}
|
|
|
|
apache_data_dirs() {
|
|
|
|
DATA_DIRS=$(relation-get data-dirs 2>/dev/null | shyaml get-values 2>/dev/null) || true
|
|
if [ -z "$DATA_DIRS" ]; then
|
|
return 0
|
|
fi
|
|
|
|
DST=$DATASTORE/$BASE_SERVICE_NAME$DOCKER_SITE_PATH
|
|
DATA=()
|
|
while IFS="," read -ra ADDR; do
|
|
for dir in "${ADDR[@]}"; do
|
|
DATA+=($dir)
|
|
done
|
|
done <<< "$DATA_DIRS"
|
|
|
|
www_data_gid=$(cached_cmd_on_base_image apache 'id -g www-data') || {
|
|
debug "Failed to query for www-data gid in ${DARKYELLOW}apache${NORMAL} base image."
|
|
return 1
|
|
}
|
|
info "www-data gid from ${DARKYELLOW}apache${NORMAL} is '$www_data_gid'"
|
|
|
|
dirs=()
|
|
for d in "${DATA[@]}"; do
|
|
dirs+=("$DST/$d")
|
|
done
|
|
|
|
mkdir -p "${dirs[@]}"
|
|
setfacl -R -m g:"$www_data_gid":rwx "${dirs[@]}"
|
|
setfacl -R -d -m g:"$www_data_gid":rwx "${dirs[@]}"
|
|
|
|
config-add "
|
|
$MASTER_BASE_SERVICE_NAME:
|
|
volumes:
|
|
$(
|
|
for d in "${DATA[@]}"; do
|
|
echo " - $DST/$d:$DOCKER_SITE_PATH/$d"
|
|
done
|
|
)"
|
|
|
|
}
|
|
|
|
deploy_files() {
|
|
local src="$1" dst="$2"
|
|
|
|
if ! [ -d "$dst" ]; then
|
|
err "Destination '$dst' does not exist or is not a directory"
|
|
return 1
|
|
fi
|
|
(
|
|
cd "$dst" && info "In $dst:" &&
|
|
get_file "$src" | tar xv
|
|
)
|
|
}
|
|
export -f deploy_files
|
|
|
|
|
|
apache_core_rules_add() {
|
|
local conf="$1" dst="/etc/apache2/conf-enabled/$BASE_SERVICE_NAME.conf"
|
|
debug "Adding core rule."
|
|
echo "$conf" | file_put "$CONFIGSTORE/$BASE_SERVICE_NAME$dst"
|
|
config_hash=$(printf "%s\0" "$config_hash" "$conf" | md5_compat)
|
|
config-add "
|
|
$MASTER_BASE_SERVICE_NAME:
|
|
volumes:
|
|
- $CONFIGSTORE/$BASE_SERVICE_NAME$dst:$dst:ro
|
|
"
|
|
}
|
|
|
|
|
|
__vhost_ssl_statement() {
|
|
|
|
## defaults
|
|
|
|
__vhost_cfg_SSL_CERT_LOCATION=${__vhost_cfg_SSL_CERT_LOCATION:-/etc/ssl/certs/ssl-cert-snakeoil.pem}
|
|
__vhost_cfg_SSL_KEY_LOCATION=${__vhost_cfg_SSL_KEY_LOCATION:-/etc/ssl/private/ssl-cert-snakeoil.key}
|
|
|
|
cat <<EOF
|
|
|
|
##
|
|
## SSL Configuration
|
|
##
|
|
|
|
SSLEngine On
|
|
|
|
SSLCertificateFile $__vhost_cfg_SSL_CERT_LOCATION
|
|
SSLCertificateKeyFile $__vhost_cfg_SSL_KEY_LOCATION
|
|
$([ -z "$__vhost_cfg_SSL_CA_CERT_LOCATION" ] || echo "SSLCACertificateFile $__vhost_cfg_SSL_CA_CERT_LOCATION")
|
|
$([ -z "$__vhost_cfg_SSL_CHAIN" ] || echo "SSLCertificateChainFile $__vhost_cfg_SSL_CHAIN")
|
|
SSLVerifyClient None
|
|
|
|
EOF
|
|
|
|
}
|
|
|
|
|
|
__vhost_creds_statement() {
|
|
if ! __vhost_cfg_creds_enabled=$(relation-get creds 2>/dev/null); then
|
|
echo "Allow from all"
|
|
return 0
|
|
fi
|
|
|
|
password_file=/etc/apache2/sites-enabled/${DOMAIN}.passwd
|
|
|
|
cat <<EOF
|
|
AuthType basic
|
|
AuthName "private"
|
|
AuthUserFile ${password_file}
|
|
Require valid-user
|
|
EOF
|
|
|
|
}
|
|
|
|
|
|
__vhost_head_statement() {
|
|
local protocol="$1"
|
|
|
|
if [ "$protocol" == "https" ]; then
|
|
prefix="s-"
|
|
else
|
|
prefix=
|
|
fi
|
|
|
|
cat <<EOF
|
|
ServerAdmin ${ADMIN_MAIL:-contact@$DOMAIN}
|
|
ServerName ${DOMAIN}
|
|
$(
|
|
while read-0 alias; do
|
|
echo "ServerAlias $alias"
|
|
done < <(echo "$SERVER_ALIAS" | shyaml get-values-0 2>/dev/null)
|
|
)
|
|
ServerSignature Off
|
|
CustomLog /var/log/apache2/${prefix}${DOMAIN}_access.log combined
|
|
ErrorLog /var/log/apache2/${prefix}${DOMAIN}_error.log
|
|
ErrorLog syslog:local2
|
|
EOF
|
|
|
|
}
|
|
|
|
|
|
__vhost_custom_rules() {
|
|
local custom_rules
|
|
if custom_rules=$(relation-get apache-custom-rules 2>/dev/null); then
|
|
cat <<EOF
|
|
|
|
|
|
##
|
|
## Custom rules
|
|
##
|
|
|
|
$custom_rules
|
|
|
|
EOF
|
|
fi
|
|
}
|
|
|
|
|
|
__vhost_content_statement() {
|
|
if [ "$proxy" ]; then
|
|
__vhost_proxy_statement "$@"
|
|
else
|
|
__vhost_publish_dir_statement "$@"
|
|
fi
|
|
}
|
|
|
|
|
|
__vhost_proxy_statement() {
|
|
local protocol="$1"
|
|
|
|
TARGET=$(relation-get target 2>/dev/null) || true
|
|
if [ -z "$TARGET" ]; then
|
|
## First exposed port:
|
|
base_image=$(service_base_docker_image "$BASE_SERVICE_NAME") || return 1
|
|
if ! docker_has_image "$base_image"; then
|
|
docker pull "$base_image"
|
|
fi
|
|
first_exposed_port=$(image_exposed_ports_0 "$base_image" | tr '\0' '\n' | head -n 1 | cut -f 1 -d /) || return 1
|
|
TARGET=$MASTER_BASE_SERVICE_NAME:$first_exposed_port
|
|
info "No target was specified, introspection found: $TARGET"
|
|
fi
|
|
|
|
cat <<EOF
|
|
|
|
##
|
|
## Proxy declaration towards $TARGET
|
|
##
|
|
|
|
<IfModule mod_proxy.c>
|
|
ProxyRequests Off
|
|
<Proxy *>
|
|
Order deny,allow
|
|
Allow from all
|
|
</Proxy>
|
|
ProxyVia On
|
|
ProxyPass / http://$TARGET/ retry=0
|
|
<Location / >
|
|
$(__vhost_creds_statement | prefix " ")
|
|
ProxyPassReverse /
|
|
</Location>
|
|
$([ "$protocol" == "https" ] && echo " SSLProxyEngine On")
|
|
</IfModule>
|
|
|
|
RequestHeader set "X-Forwarded-Proto" "$protocol"
|
|
|
|
## Fix IE problem (httpapache proxy dav error 408/409)
|
|
SetEnv proxy-nokeepalive 1
|
|
EOF
|
|
|
|
}
|
|
|
|
__vhost_full_vhost_statement() {
|
|
local protocol="$1"
|
|
|
|
case "$protocol" in
|
|
https)
|
|
PORT=443
|
|
;;
|
|
http)
|
|
PORT=80
|
|
;;
|
|
esac
|
|
|
|
cat <<EOF
|
|
<VirtualHost *:$PORT>
|
|
|
|
$(__vhost_head_statement "$protocol" | prefix " " && echo)
|
|
$(__vhost_custom_rules | prefix " " && echo)
|
|
$(__vhost_content_statement "$protocol" | prefix " ")
|
|
|
|
## Forbid any cache, this is only usefull on dev server.
|
|
#Header set Cache-Control "no-cache"
|
|
#Header set Access-Control-Allow-Origin "*"
|
|
#Header set Access-Control-Allow-Methods "POST, GET, OPTIONS"
|
|
#Header set Access-Control-Allow-Headers "origin, content-type, accept"
|
|
$([ "$protocol" == "https" ] && __vhost_ssl_statement | prefix " " && echo )
|
|
</VirtualHost>
|
|
EOF
|
|
|
|
}
|
|
|
|
__vhost_publish_dir_statement() {
|
|
cat <<EOF
|
|
##
|
|
## Publish directory $DOCKER_SITE_PATH
|
|
##
|
|
|
|
DocumentRoot $DOCKER_SITE_PATH
|
|
|
|
<Directory />
|
|
Options FollowSymLinks
|
|
AllowOverride None
|
|
</Directory>
|
|
|
|
<Directory $DOCKER_SITE_PATH>
|
|
Options Indexes FollowSymLinks MultiViews
|
|
AllowOverride all
|
|
$(__vhost_creds_statement | prefix " ")
|
|
</Directory>
|
|
|
|
EOF
|
|
}
|
|
|
|
|
|
apache_config_hash() {
|
|
debug "Adding config hash to enable recreating upon config change."
|
|
config_hash=$({
|
|
printf "%s\0" "$config_hash"
|
|
find "$SERVICE_CONFIGSTORE/etc/apache2/sites-enabled" \
|
|
-name \*.conf -exec md5sum {} \;
|
|
} | md5_compat) || exit 1
|
|
init-config-add "
|
|
$MASTER_BASE_SERVICE_NAME:
|
|
labels:
|
|
- compose.config_hash=$config_hash
|
|
"
|
|
}
|