forked from 0k/0k-charms
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.
978 lines
29 KiB
978 lines
29 KiB
# -*- mode: shell-script -*-
|
|
|
|
replace_by_rematch_pattern() {
|
|
local input="$1" output="" char next_char i
|
|
# Loop through each character in the string
|
|
for (( i=0; i<${#input}; i++ )); do
|
|
char="${input:$i:1}"
|
|
|
|
# If a dollar sign is found
|
|
if [[ "$char" == '$' ]]; then
|
|
next_char="${input:$((i+1)):1}"
|
|
# Check if next character is a digit
|
|
if [[ "$next_char" =~ [0-9] ]]; then
|
|
# Replace $N with ${rematch[N]}
|
|
output+='${rematch['"$next_char"']}'
|
|
((i++)) # Skip next character as it's already processed
|
|
continue
|
|
fi
|
|
fi
|
|
|
|
output+="$char"
|
|
done
|
|
|
|
echo "$output"
|
|
}
|
|
export -f replace_by_rematch_pattern
|
|
|
|
|
|
|
|
get_domains() {
|
|
local cfg="$1" service_cfg="$2" cache_file="$state_tmpdir/$FUNCNAME.cache.$(H "$SERVICE_NAME" "$MASTER_BASE_SERVICE_NAME" "$@")" \
|
|
domains new_domains maps m regex rematch type value key domain
|
|
if [ -e "$cache_file" ]; then
|
|
cat "$cache_file"
|
|
return 0
|
|
fi
|
|
domains=()
|
|
for key in domain server-aliases; do
|
|
type=$(e "$cfg" | shyaml -q get-type "$key" 2>/dev/null) || true
|
|
value=$(e "$cfg" | shyaml -q get-value -y "$key" 2>/dev/null) || true
|
|
while true; do
|
|
case "$type" in
|
|
sequence)
|
|
while read-0 domain; do
|
|
if [[ "$domain" != "None" ]]; then
|
|
if [ "${#domains[@]}" == 0 ] && [ "$key" == "server-aliases" ]; then
|
|
err "You can't specify server aliases if you don't have a domain."
|
|
return 1
|
|
fi
|
|
|
|
domains+=("$domain")
|
|
fi
|
|
done < <(e "$value" | shyaml get-values-0)
|
|
;;
|
|
str)
|
|
if ! domain=$(e "$value" | shyaml get-value 2>/dev/null); then
|
|
err "Failed to get domain value from config."
|
|
return 1
|
|
fi
|
|
[[ "$domain" == "" ]] && break
|
|
if [ "${#domains[@]}" == 0 ] && [ "$key" == "server-aliases" ]; then
|
|
err "You can't specify server aliases if you don't have a domain."
|
|
return 1
|
|
fi
|
|
|
|
domains+=("$domain")
|
|
;;
|
|
NoneType|"")
|
|
:
|
|
;;
|
|
\!*)
|
|
value=$(e "$value" | cfg-get-value "$key" 2>/dev/null) || true
|
|
type=$(e "$value" | shyaml -q get-type) || true
|
|
continue
|
|
;;
|
|
esac
|
|
break
|
|
done
|
|
## check and expand domain
|
|
new_domains=()
|
|
for domain in "${domains[@]}"; do
|
|
if ! [[ "$domain" =~ ^[a-z0-9\{\}*\ \,.-]+$ ]]; then
|
|
err "Invalid domain value '$domain' expression in ${WHITE}$key$NORMAL option."
|
|
return 1
|
|
fi
|
|
new_domains+=($(eval echo "${domain//\*/\\*}"))
|
|
done
|
|
domains=("${new_domains[@]}")
|
|
done
|
|
## Fill with service-domain-map
|
|
if [ "${#domains[@]}" == 0 ] &&
|
|
service_domain_map=$(e "$service_cfg" | cfg-get-value 'options.service-domain-map' 2>/dev/null) &&
|
|
[ -n "$service_domain_map" ]; then
|
|
while read-0 regex map; do
|
|
if [[ "$BASE_SERVICE_NAME" =~ ^$regex$ ]]; then
|
|
rematch=("${BASH_REMATCH[@]}")
|
|
maps=()
|
|
type=$(e "$map" | shyaml -q get-type 2>/dev/null) || true
|
|
value=$(e "$map" | shyaml -q get-value -y 2>/dev/null) || true
|
|
case "$type" in
|
|
sequence)
|
|
while read-0 m; do
|
|
if [[ "$m" != "None" ]] && [ -n "$m" ]; then
|
|
maps+=("$m")
|
|
fi
|
|
done < <(e "$value" | shyaml get-values-0)
|
|
;;
|
|
str)
|
|
if ! m=$(e "$value" | shyaml get-value 2>/dev/null); then
|
|
err "Failed to get mapping value from config."
|
|
return 1
|
|
fi
|
|
[ -z "$m" ] && continue
|
|
maps+=("$m")
|
|
;;
|
|
NoneType|"")
|
|
:
|
|
;;
|
|
esac
|
|
for map in "${maps[@]}"; do
|
|
if ! [[ "$map" =~ ^([a-z0-9*\{\}\ \,.-]|\$[0-9])+$ ]]; then
|
|
err "Invalid mapping value '$map' in ${WHITE}service-domain-map$NORMAL option."
|
|
return 1
|
|
fi
|
|
|
|
map="${map//\*/\\*}" ## protect star from eval
|
|
map=$(replace_by_rematch_pattern "$map")
|
|
|
|
domains+=($(set -- "${BASH_REMATCH[@]}"; eval echo "${map//\*/\\*}"))
|
|
done
|
|
break
|
|
fi
|
|
done < <(e "$service_domain_map" | yq e -0 'to_entries | .[] | [.key, .value] |.[]')
|
|
fi
|
|
if [ "${#domains[@]}" == 0 ] &&
|
|
[[ "$BASE_SERVICE_NAME" =~ ^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$ ]]; then
|
|
domains+=("$BASE_SERVICE_NAME")
|
|
fi
|
|
new_domains=()
|
|
## remove duplicates
|
|
for domain in "${domains[@]}"; do
|
|
if ! [[ " ${new_domains[*]} " == *" $domain "* ]]; then
|
|
new_domains+=("$domain")
|
|
fi
|
|
done
|
|
domains=("${new_domains[@]}")
|
|
if [ "${#domains[@]}" == 0 ]; then
|
|
err "No domain name set for your service ${YELLOW}$BASE_SERVICE_NAME${NORMAL}."
|
|
echo " You can specify a ${WHITE}domain$NORMAL option in the" \
|
|
"relation with ${YELLOW}$TARGET_SERVICE_NAME${NORMAL}." >&2
|
|
echo " Or you can specify a service to domain mapping in" \
|
|
"${WHITE}service-domain-map${NORMAL} option of ${YELLOW}$TARGET_SERVICE_NAME${NORMAL}." >&2
|
|
return 1
|
|
fi
|
|
## check that first domain should not have a wildcard
|
|
if [[ "${domains[0]}" == *"*"* ]]; then
|
|
err "First domain name '${domains[0]}' can't contain a wildcard."
|
|
return 1
|
|
fi
|
|
echo "${domains[@]}" | tee "$cache_file"
|
|
}
|
|
|
|
|
|
##
|
|
## Master entrypoints
|
|
##
|
|
|
|
apache_proxy_dir() {
|
|
local cfg="$1" service_cfg="$2"
|
|
apache_vhost_create web_proxy "$cfg" "$service_cfg" || return 1
|
|
}
|
|
export -f apache_proxy_dir
|
|
|
|
|
|
apache_publish_dir() {
|
|
local cfg="$1" service_cfg="$2"
|
|
apache_vhost_create publish_dir "$cfg" "$service_cfg" || return 1
|
|
apache_code_dir "$cfg" "$service_cfg" || return 1
|
|
apache_data_dirs "$cfg" "$service_cfg"
|
|
}
|
|
export -f apache_publish_dir
|
|
|
|
|
|
apache_ssh_tunnel() {
|
|
local cfg="$1" domain ssh_tunnel
|
|
domain=$(e "$cfg" | cfg-get-value domain) || {
|
|
err "${WHITE}domain${NORMAL} must be valued in ${WHITE}ssh-tunnel${NORMAL} config."
|
|
return 1
|
|
}
|
|
protocols=$(__vhost_cfg_normalize_protocol "$cfg") || return 1
|
|
if ! is_protocol_enabled https "$protocols"; then
|
|
err "${WHITE}ssl${NORMAL} must be valued in ${WHITE}ssh-tunnel${NORMAL} config."
|
|
return 1
|
|
fi
|
|
apache_vhost_create ssh_tunnel "$cfg" "" ",https," "000-$domain" || return 1
|
|
}
|
|
export -f apache_publish_dir
|
|
|
|
|
|
##
|
|
## Simple functions
|
|
##
|
|
|
|
|
|
apache_vhost_create() {
|
|
local type="$1" cfg="$2" service_cfg="$3" protocols="$4" dest="$5" custom_rules vhost_statement creds \
|
|
redirect domain ssl_plugin_fun ssl_cfg_value ssl_cfg_options
|
|
|
|
export APACHE_CONFIG_LOCATION="$SERVICE_CONFIGSTORE/etc/apache2/sites-enabled"
|
|
if [ "$cfg" == "None" ]; then
|
|
cfg=
|
|
fi
|
|
if [ -z "$protocols" ]; then
|
|
protocols=$(__vhost_cfg_normalize_protocol "$cfg") || return 1
|
|
fi
|
|
|
|
domains=($(get_domains "$cfg" "$service_cfg")) && {
|
|
[ "$RELATION_DATA_FILE" ] && relation-set domain "${domains[0]}"
|
|
}
|
|
echo "Domains: ${domains[*]}" >&2
|
|
if is_protocol_enabled https "$protocols"; then
|
|
[ "$RELATION_DATA_FILE" ] && {
|
|
relation-set url "https://${domains[0]}"
|
|
}
|
|
|
|
if [ "${#domains[@]}" == 0 ]; then
|
|
err "You must specify a domain for ssl to work."
|
|
return 1
|
|
fi
|
|
read-0 ssl_plugin_fun ssl_cfg_value ssl_cfg_options < <(ssl_get_plugin_fun "$cfg") || return 1
|
|
"$ssl_plugin_fun"_vars "$cfg" "$ssl_cfg_options" "$ssl_cfg_value" "${domains[*]}" || return 1
|
|
redirect=$(e "$cfg" | cfg-get-value 'redirect-to-ssl' 2>/dev/null) || true
|
|
if is_protocol_enabled http "$protocols"; then
|
|
redirect=${redirect:-true}
|
|
else
|
|
redirect=false
|
|
fi
|
|
if [ "$redirect" == "true" ]; then
|
|
custom_rules=$(_get_custom_rules "$cfg") || return 1
|
|
if [[ "$custom_rules" != *"## Auto-redirection from http to https"* ]]; then
|
|
redirect_rule="- |
|
|
## Auto-redirection from http to https
|
|
RewriteEngine On
|
|
RewriteCond %{HTTPS} off
|
|
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=302,L,QSA]
|
|
"
|
|
[ "$RELATION_DATA_FILE" ] && \
|
|
relation-set apache-custom-rules "$redirect_rule
|
|
$(if [ "$custom_rules" ]; then
|
|
echo "- |"$'\n'"$(echo "$custom_rules" | prefix " ")"
|
|
fi)"
|
|
cfg=$(merge_yaml_str "$cfg" "$(yaml_key_val_str "apache-custom-rules" "$redirect_rule
|
|
$(if [ "$custom_rules" ]; then
|
|
echo "- |"$'\n'"$(echo "$custom_rules" | prefix " ")"
|
|
fi)")") || return 1
|
|
fi
|
|
fi
|
|
else
|
|
[ "$RELATION_DATA_FILE" ] && {
|
|
relation-set url "http://${domains[0]}"
|
|
}
|
|
fi
|
|
vhost_statement=$(apache_vhost_statement "$type" "$protocols" "$cfg" "${domains[*]}") || {
|
|
err "Failed to get vhost statement for type $type on ${protocols:1:-1}"
|
|
return 1
|
|
}
|
|
|
|
dest=${dest:-${domains[0]}}
|
|
if [ -z "$dest" ]; then
|
|
err "Please set either a domain or set a destination file."
|
|
return 1
|
|
fi
|
|
echo "$vhost_statement" | file_put "$APACHE_CONFIG_LOCATION/$dest.conf" || return 1
|
|
|
|
creds=$(e "$cfg" | cfg-get-value creds 2>/dev/null) || true
|
|
if [ "$creds" ]; then
|
|
apache_passwd_file "$cfg" "$dest"|| return 1
|
|
fi
|
|
|
|
if is_protocol_enabled https "$protocols"; then
|
|
"$ssl_plugin_fun"_prepare "$cfg" "$ssl_cfg_options" "$ssl_cfg_value" "${domains[*]}" || return 1
|
|
fi
|
|
}
|
|
|
|
|
|
is_protocol_enabled() {
|
|
local protocol="$1" protocols="$2"
|
|
[[ "$protocols" == *",$protocol,"* ]]
|
|
}
|
|
export -f is_protocol_enabled
|
|
|
|
|
|
_get_ssl_option_value() {
|
|
local cfg="$1" cache_file="$state_tmpdir/$FUNCNAME.cache.$(H "$SERVICE_NAME" "$MASTER_BASE_SERVICE_NAME" "$@")" \
|
|
target_relation rn ts rc td
|
|
|
|
if [ -e "$cache_file" ]; then
|
|
cat "$cache_file"
|
|
return 0
|
|
fi
|
|
|
|
if ssl_cfg=$(e "$cfg" | cfg-get-value ssl 2>/dev/null); then
|
|
if [[ "$ssl_cfg" =~ ^False|None|false|null$ ]]; then
|
|
ssl_cfg=""
|
|
fi
|
|
echo "$ssl_cfg" | tee "$cache_file"
|
|
return 0
|
|
fi
|
|
|
|
target_relation="cert-provider"
|
|
while read-0 rn ts rc td; do
|
|
[ "$rn" == "${target_relation}" ] || continue
|
|
info "A cert-provider '$ts' declared as 'ssl' value"
|
|
echo "$ts" | tee "$cache_file"
|
|
return 0
|
|
done < <(get_service_relations "$SERVICE_NAME")
|
|
|
|
return 1
|
|
}
|
|
|
|
|
|
__vhost_cfg_normalize_protocol() {
|
|
local cfg="$1" protocol ssl
|
|
## XXXvlab: can't cache if libcharm is not able to give me some checksums
|
|
## indeed, ``_get_ssl_option_value`` depends on relations calculations...
|
|
|
|
# local cfg="$1" cache_file="$CACHEDIR/$FUNCNAME.cache.$(p0 "$@" | md5_compat)" \
|
|
# protocol
|
|
# if [ -e "$cache_file" ]; then
|
|
# #debug "$FUNCNAME: STATIC cache hit $1"
|
|
# cat "$cache_file" &&
|
|
# touch "$cache_file" || return 1
|
|
# return 0
|
|
# fi
|
|
|
|
if protocol=$(e "$cfg" | cfg-get-value protocol 2>/dev/null); then
|
|
protocol=${protocol:-auto}
|
|
else
|
|
protocol=auto
|
|
fi
|
|
|
|
case "$protocol" in
|
|
auto)
|
|
ssl=$(_get_ssl_option_value "$cfg" 2>/dev/null)
|
|
if [ "$ssl" ]; then
|
|
protocol="http,https"
|
|
else
|
|
protocol="http"
|
|
fi
|
|
;;
|
|
both)
|
|
protocol="http,https"
|
|
;;
|
|
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 -n ",$protocol,"
|
|
#| tee "$cache_file"
|
|
}
|
|
|
|
|
|
## 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 master_cfg="$1" cfg type keys
|
|
cfg=$(_get_ssl_option_value "$master_cfg") || {
|
|
err "No ssl options available."
|
|
return 1
|
|
}
|
|
|
|
local cache_file="$state_tmpdir/$FUNCNAME.cache.$(H "$SERVICE_NAME" "$cfg")"
|
|
if [ -e "$cache_file" ]; then
|
|
cat "$cache_file"
|
|
return 0
|
|
fi
|
|
[ "$cfg" ] || {
|
|
touch "$cache_file"
|
|
return 0
|
|
}
|
|
|
|
type="$(echo "$cfg" | shyaml -y get-type 2>/dev/null)" || return 1
|
|
if [[ "$type" == "bool" ]] || [[ "$type" == "str" && "$cfg" =~ ^false|true$ ]]; then
|
|
printf "%s\0" "ssl_fallback" "" "$cfg" | tee "$cache_file"
|
|
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" | tee "$cache_file"
|
|
return 0
|
|
done < <(get_service_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" | tee "$cache_file"
|
|
}
|
|
|
|
|
|
ssl_fallback_vars() {
|
|
local cfg="$1" ssl_cfg="$2" value="$3" domains="$4" cert key ca_cert
|
|
domains=($domains)
|
|
if __vhost_cfg_ssl_cert=$(echo "$ssl_cfg" | shyaml get-value cert 2>/dev/null); then
|
|
__vhost_cfg_SSL_CERT_LOCATION=/etc/ssl/certs/${domains[0]}.pem
|
|
fi
|
|
|
|
if __vhost_cfg_ssl_key=$(echo "$ssl_cfg" | shyaml get-value key 2>/dev/null); then
|
|
__vhost_cfg_SSL_KEY_LOCATION=/etc/ssl/private/${domains[0]}.key
|
|
fi
|
|
|
|
if __vhost_cfg_ssl_ca_cert=$(echo "$ssl_cfg" | shyaml get-value ca-cert 2>/dev/null); then
|
|
__vhost_cfg_SSL_CA_CERT_LOCATION=/etc/ssl/certs/${domains[0]}-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
|
|
init-config-add "\
|
|
$MASTER_TARGET_SERVICE_NAME:
|
|
volumes:
|
|
$volumes
|
|
"
|
|
fi
|
|
|
|
}
|
|
|
|
ssl_plugin_cert-provider_vars() {
|
|
local cfg="$1" ssl_cfg="$2" value="$3" domains="$4"
|
|
domains=($domains)
|
|
__vhost_cfg_SSL_CERT_LOCATION=/etc/letsencrypt/live/${domains[0]}/cert.pem
|
|
__vhost_cfg_SSL_KEY_LOCATION=/etc/letsencrypt/live/${domains[0]}/privkey.pem
|
|
__vhost_cfg_SSL_CHAIN=/etc/letsencrypt/live/${domains[0]}/chain.pem
|
|
}
|
|
|
|
ssl_plugin_cert-provider_prepare() {
|
|
local cfg="$1" ssl_cfg="$2" service="$3" domains="$4" options server_aliases
|
|
domains=($domains)
|
|
options=$(yaml_key_val_str "options" "$ssl_cfg") || return 1
|
|
service_config=$(yaml_key_val_str "$service" "$options")
|
|
compose --debug --add-compose-content "$service_config" crt "$service" \
|
|
create "${domains[@]}" || {
|
|
err "Failed to launch letsencrypt for certificate creation."
|
|
return 1
|
|
}
|
|
init-config-add "\
|
|
$SERVICE_NAME:
|
|
volumes:
|
|
- $DATASTORE/$service/etc/letsencrypt:/etc/letsencrypt:ro
|
|
" || return 1
|
|
|
|
}
|
|
|
|
|
|
apache_passwd_file() {
|
|
local cfg="$1" dest="$2" creds
|
|
include parse || true
|
|
|
|
## XXXvlab: called twice... no better way to do this ?
|
|
creds=$(e "$cfg" | cfg-get-value creds 2>/dev/null) || true
|
|
password_path=$(password-path-get "$dest")
|
|
first=
|
|
if ! [ -e "$CONFIGSTORE/$MASTER_TARGET_SERVICE_NAME$password_path" ]; then
|
|
debug "No file $CONFIGSTORE/$MASTER_TARGET_SERVICE_NAME$password_path, creating password file." || true
|
|
first=c
|
|
fi
|
|
while read-0 login password; do
|
|
debug "htpasswd -b$first '${password_path}' '$login' '$password'"
|
|
echo "htpasswd -b$first '${password_path}' '$login' '$password'"
|
|
if [ "$first" ]; then
|
|
first=
|
|
fi
|
|
done < <(e "$creds" | 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 type="$1" protocols="$2" cfg="$3" domains="$4" \
|
|
vhost_statement
|
|
|
|
if is_protocol_enabled http "$protocols"; then
|
|
__vhost_full_vhost_statement "$type" http "$cfg" "$domains" || return 1
|
|
fi
|
|
if is_protocol_enabled https "$protocols"; then
|
|
read-0 ssl_plugin_fun ssl_cfg_value ssl_cfg_options < <(ssl_get_plugin_fun "$cfg") || return 1
|
|
"$ssl_plugin_fun"_vars "$cfg" "$ssl_cfg_options" "$ssl_cfg_value" "$domains" || return 1
|
|
vhost_statement=$(__vhost_full_vhost_statement "$type" https "$cfg" "$domains") || return 1
|
|
cat <<EOF
|
|
|
|
<IfModule mod_ssl.c>
|
|
$(echo "$vhost_statement" | prefix " ")
|
|
</IfModule>
|
|
EOF
|
|
fi
|
|
}
|
|
export -f apache_vhost_statement
|
|
|
|
|
|
apache_code_dir() {
|
|
local cfg="$1" service_cfg="$2" www_data_gid local_path
|
|
www_data_gid=$(cached_cmd_on_base_image "$TARGET_SERVICE_NAME" 'id -g www-data') || {
|
|
debug "Failed to query for www-data gid in ${DARKYELLOW}$TARGET_SERVICE_NAME${NORMAL} base image."
|
|
return 1
|
|
}
|
|
domains=($(get_domains "$cfg" "$service_cfg")) || return 1
|
|
local_path="/var/www/${domains[0]}"
|
|
host_path=$(e "$cfg" | cfg-get-value location 2>/dev/null) ||
|
|
host_path="$DATASTORE/$BASE_SERVICE_NAME${local_path}"
|
|
|
|
|
|
## convert to docker host path
|
|
case "$host_path" in
|
|
"$DATASTORE"*)
|
|
docker_host_path="$HOST_DATASTORE${host_path##$DATASTORE}"
|
|
;;
|
|
"$CONFIGSTORE"*)
|
|
docker_host_path="$HOST_CONFIGSTORE${host_path##$CONFIGSTORE}"
|
|
;;
|
|
*)
|
|
docker_host_path="$host_path"
|
|
;;
|
|
esac
|
|
|
|
mkdir -p "$host_path" || return 1
|
|
|
|
init-config-add "
|
|
$SERVICE_NAME:
|
|
volumes:
|
|
- \"${docker_host_path}:$local_path\"
|
|
"
|
|
}
|
|
|
|
apache_data_dirs() {
|
|
local cfg="$1" service_cfg="$2" data_dirs dst data fdir to_create
|
|
data_dirs=$(e "$cfg" | cfg-get-value data-dirs 2>/dev/null | shyaml get-values 2>/dev/null) || true
|
|
if [ -z "$data_dirs" ]; then
|
|
return 0
|
|
fi
|
|
domains=($(get_domains "$cfg" "$service_cfg")) || return 1
|
|
local_path="/var/www/${domains[0]}"
|
|
dst=$DATASTORE/$BASE_SERVICE_NAME$local_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 "$TARGET_SERVICE_NAME" 'id -g www-data') || {
|
|
debug "Failed to query for www-data gid in ${DARKYELLOW}$TARGET_SERVICE_NAME${NORMAL} base image."
|
|
return 1
|
|
}
|
|
info "www-data gid from ${DARKYELLOW}$TARGET_SERVICE_NAME${NORMAL} is '$www_data_gid'"
|
|
|
|
to_create=()
|
|
volumes=""
|
|
for d in "${data[@]}"; do
|
|
fdir="$dst/$d"
|
|
volumes+=" - $HOST_DATASTORE${dst##$DATASTORE}/$d:$local_path/$d"$'\n'
|
|
if ! [ -d "$fdir" ]; then
|
|
to_create+=("$fdir")
|
|
fi
|
|
done
|
|
|
|
if [ "${#to_create[@]}" -gt 0 ]; then
|
|
mkdir -p "${to_create[@]}" || return 1
|
|
chgrp -v "${www_data_gid}" "${to_create[@]}" || return 1
|
|
chmod g+rwx "${to_create[@]}" || return 1
|
|
fi
|
|
|
|
init-config-add "
|
|
$SERVICE_NAME:
|
|
volumes:
|
|
$volumes"
|
|
|
|
}
|
|
|
|
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/$SERVICE_NAME.conf"
|
|
debug "Adding core rule."
|
|
echo "$conf" | file_put "$CONFIGSTORE/$SERVICE_NAME$dst"
|
|
config_hash=$(printf "%s\0" "$config_hash" "$conf" | md5_compat)
|
|
init-config-add "
|
|
$SERVICE_NAME:
|
|
volumes:
|
|
- $CONFIGSTORE/$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
|
|
|
|
}
|
|
|
|
|
|
password-path-get() {
|
|
local dest="$1"
|
|
echo "/etc/apache2/sites-enabled/${dest}.passwd"
|
|
}
|
|
|
|
__vhost_creds_statement() {
|
|
local cfg="$1" dest="$2" password_path
|
|
password_path=$(password-path-get "$dest") || return 1
|
|
if ! e "$cfg" | cfg-get-value creds >/dev/null 2>&1; then
|
|
echo "Allow from all"
|
|
return 0
|
|
fi
|
|
|
|
cat <<EOF
|
|
AuthType basic
|
|
AuthName "private"
|
|
AuthUserFile ${password_path}
|
|
Require valid-user
|
|
EOF
|
|
|
|
}
|
|
|
|
|
|
__vhost_head_statement() {
|
|
local cfg="$1" protocol="$2" domains="$3" server_aliases admin_mail prefix
|
|
domains=($domains)
|
|
admin_mail=$(e "$1" | cfg-get-value "admin-mail" 2>/dev/null) || true
|
|
if [ -z "$admin_mail" ]; then
|
|
if [ "${#domains[@]}" == 0 ]; then
|
|
admin_mail=webmaster@localhost
|
|
else
|
|
admin_mail=${admin_mail:-contact@${domains[0]}}
|
|
fi
|
|
fi
|
|
|
|
if [ "$protocol" == "https" ]; then
|
|
prefix="s-"
|
|
else
|
|
prefix=
|
|
fi
|
|
|
|
if [ "${#domains[@]}" != 0 ]; then
|
|
log_prefix="${prefix}${domains[0]}_"
|
|
else
|
|
log_prefix=""
|
|
fi
|
|
|
|
cat <<EOF
|
|
$(
|
|
echo "ServerAdmin ${admin_mail}"
|
|
[ "${#domains[@]}" != 0 ] && echo "ServerName ${domains[0]}"
|
|
for alias in "${domains[@]:1}"; do
|
|
[ "$alias" ] || continue
|
|
echo "ServerAlias $alias"
|
|
done
|
|
)
|
|
|
|
ServerSignature Off
|
|
CustomLog /var/log/apache2/${log_prefix}access.log combined
|
|
ErrorLog /var/log/apache2/${log_prefix}error.log
|
|
ErrorLog syslog:local2
|
|
EOF
|
|
|
|
}
|
|
|
|
|
|
_get_custom_rules() {
|
|
local cfg="$1" custom_rules
|
|
custom_rules=$(e "$cfg" | shyaml -y -q get-value apache-custom-rules 2>/dev/null) || true
|
|
e "$custom_rules" | yaml_get_values
|
|
}
|
|
|
|
|
|
__vhost_custom_rules() {
|
|
local cfg="$1" custom_rules
|
|
custom_rules=$(_get_custom_rules "$cfg") || return 1
|
|
if [ "$custom_rules" ]; then
|
|
cat <<EOF
|
|
|
|
|
|
##
|
|
## Custom rules
|
|
##
|
|
|
|
$custom_rules
|
|
|
|
EOF
|
|
fi
|
|
}
|
|
|
|
|
|
__vhost_content_statement() {
|
|
local type="$1"
|
|
shift
|
|
case "$type" in
|
|
"web_proxy")
|
|
__vhost_proxy_statement "$@" || return 1
|
|
;;
|
|
"publish_dir")
|
|
__vhost_publish_dir_statement "$@" || return 1
|
|
;;
|
|
"ssh_tunnel")
|
|
__vhost_tunnel_ssh_statement "$@" || return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
|
|
target-get() {
|
|
local cfg="$1" target first_exposed_port base_image
|
|
target=$(e "$cfg" | cfg-get-value 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" >&2
|
|
fi
|
|
first_exposed_port=$(image_exposed_ports_0 "$base_image" | tr '\0' '\n' | head -n 1 | cut -f 1 -d /) || return 1
|
|
if [ -z "$first_exposed_port" ]; then
|
|
err "Failed to get first exposed port of image '$base_image'."
|
|
return 1
|
|
fi
|
|
target=$MASTER_BASE_SERVICE_NAME:$first_exposed_port
|
|
info "No target was specified, introspection found: $target"
|
|
fi
|
|
echo "$target"
|
|
}
|
|
|
|
__vhost_proxy_statement() {
|
|
local protocol="$1" cfg="$2" domains="$3" proxy_pass_options target
|
|
domains=($domains)
|
|
target=$(target-get "$cfg") || return 1
|
|
proxy_pass_options=($(e "$cfg" | shyaml -y -q get-value apache-proxy-pass-options | yaml_get_values))
|
|
if [ "${#proxy_pass_options[@]}" == 0 ]; then
|
|
proxy_pass_options=(${proxy_pass_options:-"retry=0"})
|
|
fi
|
|
dest=${domains[0]}
|
|
dest=${dest:-html}
|
|
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/ ${proxy_pass_options[*]}
|
|
<Location / >
|
|
$(__vhost_creds_statement "$cfg" "$dest" | prefix " ")
|
|
ProxyPassReverse http://$target/
|
|
</Location>
|
|
$([ "$protocol" == "https" ] && echo " SSLProxyEngine On")
|
|
</IfModule>
|
|
|
|
SetEnvIf X-Forwarded-Proto "^$" forwarded_proto_not_set=true
|
|
RequestHeader set "X-Forwarded-Proto" "$protocol" env=forwarded_proto_not_set
|
|
|
|
## Fix IE problem (httpapache proxy dav error 408/409)
|
|
SetEnv proxy-nokeepalive 1
|
|
EOF
|
|
|
|
}
|
|
|
|
__vhost_full_vhost_statement() {
|
|
local type="$1" protocol="$2" cfg="$3" domains="$4" head_statement custom_rules content_statement
|
|
|
|
domains=($domains)
|
|
head_statement=$(__vhost_head_statement "$cfg" "$protocol" "${domains[*]}") || return 1
|
|
custom_rules=$(__vhost_custom_rules "$cfg") || return 1
|
|
content_statement=$(__vhost_content_statement "$type" "$protocol" "$cfg" "${domains[*]}") || return 1
|
|
|
|
case "$protocol" in
|
|
https)
|
|
PORT=443
|
|
;;
|
|
http)
|
|
PORT=80
|
|
;;
|
|
esac
|
|
|
|
cat <<EOF
|
|
<VirtualHost *:$PORT>
|
|
|
|
$(
|
|
{
|
|
echo "$head_statement"
|
|
[ "$custom_rules" ] && echo "$custom_rules"
|
|
echo "$content_statement"
|
|
} | 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() {
|
|
local protocol="$1" cfg="$2" domains="$3" location
|
|
domains=($domains)
|
|
dest=${domains[0]}
|
|
dest=${dest:-html}
|
|
local_path="/var/www/${dest}"
|
|
|
|
cat <<EOF
|
|
|
|
|
|
##
|
|
## Publish directory $local_path
|
|
##
|
|
|
|
DocumentRoot $local_path
|
|
|
|
<Directory />
|
|
Options FollowSymLinks
|
|
AllowOverride None
|
|
</Directory>
|
|
|
|
<Directory $local_path>
|
|
Options Indexes FollowSymLinks MultiViews
|
|
AllowOverride all
|
|
$(__vhost_creds_statement "$cfg" "$dest" | prefix " ")
|
|
</Directory>
|
|
|
|
EOF
|
|
}
|
|
|
|
|
|
__vhost_tunnel_ssh_statement() {
|
|
local protocol="$1" cfg="$2" domains="$3" custom_rules content_statement
|
|
domains=($domains)
|
|
dest=${domains[0]}
|
|
if [ "${#domains[@]}" == 0 ]; then
|
|
err "You must specify a domain for ssh tunnel to work."
|
|
return 1
|
|
fi
|
|
cat <<EOF
|
|
|
|
|
|
##
|
|
## SSH Tunnel
|
|
##
|
|
|
|
#HostnameLookups On
|
|
ProxyRequests On
|
|
AllowConnect 22
|
|
#ProxyVia on
|
|
|
|
### Deny everything by default
|
|
|
|
<Proxy *>
|
|
Order deny,allow
|
|
Deny from all
|
|
</proxy>
|
|
|
|
### Accept redirect only to same domain
|
|
|
|
<Proxy ${domains[0]}>
|
|
Order deny,allow
|
|
$(__vhost_creds_statement "$cfg" "$dest" | prefix " ")
|
|
</Proxy>
|
|
|
|
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 "
|
|
$SERVICE_NAME:
|
|
labels:
|
|
- compose.config_hash=$config_hash
|
|
"
|
|
}
|
|
|
|
|