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.
 
 

530 lines
14 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"
export SERVER_ALIAS=$(relation-get server-aliases 2>/dev/null) || true
export PROTOCOLS=$(__vhost_cfg_normalize_protocol) || return 1
export SSL_PLUGIN_FUN=$(ssl_get_plugin_fun) || return 1
if is_protocol_enabled https; then
"$SSL_PLUGIN_FUN"_vars "$(relation-get ssl)" || 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
"$SSL_PLUGIN_FUN"_prepare "$(relation-get ssl)" || return 1
}
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() {
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
else
echo ssl_fallback
fi
}
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_letsencrypt-dns_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_letsencrypt-dns_prepare() {
local service="$1" letsencrypt_charm
shift
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
config-add "\
services:
$MASTER_TARGET_SERVICE_NAME:
volumes:
- $DATASTORE/${letsencrypt_charm}/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
export SSL_PLUGIN_FUN=$(ssl_get_plugin_fun) || return 1
"$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
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
"
}