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.

524 lines
14 KiB

  1. # -*- mode: shell-script -*-
  2. apache_proxy_dir () {
  3. DOMAIN=$(relation-get domain) || {
  4. err "You must specify a ${WHITE}domain$NORMAL option in relation."
  5. return 1
  6. }
  7. proxy=yes apache_vhost_create || return 1
  8. info "Added $DOMAIN as a proxy to $TARGET."
  9. }
  10. export -f apache_proxy_dir
  11. apache_publish_dir () {
  12. DOMAIN=$(relation-get domain) || {
  13. err "You must specify a ${WHITE}domain$NORMAL option in relation."
  14. return 1
  15. }
  16. DOCKER_SITE_PATH="/var/www/${DOMAIN}"
  17. LOCATION=$(relation-get location 2>/dev/null) ||
  18. LOCATION="$DATASTORE/$BASE_SERVICE_NAME$DOCKER_SITE_PATH"
  19. apache_vhost_create || return 1
  20. info "Added $DOMAIN apache config."
  21. apache_code_dir || return 1
  22. apache_data_dirs
  23. }
  24. export -f apache_publish_dir
  25. apache_vhost_create () {
  26. export APACHE_CONFIG_LOCATION="$SERVICE_CONFIGSTORE/etc/apache2/sites-enabled"
  27. export SERVER_ALIAS=$(relation-get server-aliases 2>/dev/null) || true
  28. export PROTOCOLS=$(__vhost_cfg_normalize_protocol) || return 1
  29. export SSL_PLUGIN_FUN=$(ssl_get_plugin_fun) || return 1
  30. if is_protocol_enabled https; then
  31. "$SSL_PLUGIN_FUN"_vars "$(relation-get ssl)" || return 1
  32. fi
  33. apache_vhost_statement "$PROTOCOLS" |
  34. file_put "$APACHE_CONFIG_LOCATION/$prefix$DOMAIN.conf" || return 1
  35. __vhost_cfg_creds_enabled=$(relation-get creds 2>/dev/null) || true
  36. if [ "$__vhost_cfg_creds_enabled" ]; then
  37. apache_passwd_file || return 1
  38. fi
  39. "$SSL_PLUGIN_FUN"_prepare "$(relation-get ssl)" || return 1
  40. }
  41. is_protocol_enabled() {
  42. local protocol=$1
  43. [[ "$PROTOCOLS" == *",$protocol,"* ]]
  44. }
  45. export -f is_protocol_enabled
  46. __vhost_cfg_normalize_protocol() {
  47. local protocol
  48. if ! protocol=$(relation-get protocol 2>/dev/null); then
  49. protocol=auto
  50. else
  51. protocol=${protocol:-auto}
  52. fi
  53. case "$protocol" in
  54. auto)
  55. if __vhost_cfg_ssl="$(relation-get ssl 2>/dev/null)"; then
  56. protocol="https"
  57. export __vhost_cfg_ssl
  58. else
  59. protocol="http"
  60. fi
  61. ;;
  62. both)
  63. protocol="https,http"
  64. ;;
  65. ssl|https)
  66. protocol="https"
  67. ;;
  68. http)
  69. protocol="http"
  70. ;;
  71. *)
  72. err "Invalid value '$protocol' for ${WHITE}protocol$NORMAL option (use one of: http, https, both, auto)."
  73. return 1
  74. esac
  75. echo ",$protocol,"
  76. }
  77. ## ssl_plugin_* and ssl_fallback should :
  78. ## - do anything to ensure that
  79. ## - issue config-add to add volumes if necessary
  80. ## - output 3 vars of where to find the 3 files from within the docker apache
  81. ssl_get_plugin_fun() {
  82. local cfg="$(relation-get ssl 2>/dev/null)"
  83. if [[ "$(echo "$cfg" | shyaml get-type 2>/dev/null)" == "str" ]]; then
  84. target_relation=
  85. while read-0 relation_name target_service relation_config tech_dep; do
  86. [ "$target_service" == "$cfg" ] || continue
  87. verb "service ${DARKYELLOW}$target_service${NORMAL} matches" \
  88. "${WHITE}ssl${NORMAL} value: candidate relation is ${DARKBLUE}$relation_name${NORMAL}"
  89. fun="ssl_plugin_${relation_name}"
  90. if declare -F "${fun}_vars" >/dev/null 2>&1 && declare -F "${fun}_prepare" >/dev/null 2>&1; then
  91. verb "Corresponding plugin ${DARKGREEN}found${NORMAL} for relation ${DARKBLUE}$relation_name${NORMAL}"
  92. echo "$fun"
  93. return 0
  94. else
  95. verb "Corresponding plugin ${DARKRED}not found${NORMAL} for relation ${DARKBLUE}$relation_name${NORMAL}"
  96. fi
  97. done < <(get_compose_relations "$SERVICE_NAME") || return 1
  98. err "Invalid ${WHITE}ssl${NORMAL} value: '$cfg' is not a valid linked service through a support relation."
  99. return 1
  100. else
  101. echo ssl_fallback
  102. fi
  103. }
  104. ssl_fallback_vars() {
  105. local cfg="$1" cert key ca_cert
  106. if __vhost_cfg_ssl_cert=$(echo "$cfg" | shyaml get-value cert 2>/dev/null); then
  107. __vhost_cfg_SSL_CERT_LOCATION=/etc/ssl/certs/${DOMAIN}.pem
  108. fi
  109. if __vhost_cfg_ssl_key=$(echo "$cfg" | shyaml get-value key 2>/dev/null); then
  110. __vhost_cfg_SSL_KEY_LOCATION=/etc/ssl/private/${DOMAIN}.key
  111. fi
  112. if __vhost_cfg_ssl_ca_cert=$(echo "$cfg" | shyaml get-value ca-cert 2>/dev/null); then
  113. __vhost_cfg_SSL_CA_CERT_LOCATION=/etc/ssl/certs/${DOMAIN}-ca.pem
  114. fi
  115. }
  116. ssl_fallback_prepare() {
  117. local cfg="$1" cert key ca_cert
  118. dst="$CONFIGSTORE/$BASE_SERVICE_NAME"
  119. volumes=""
  120. for label in cert key ca_cert; do
  121. content="$(eval echo "\"\$__vhost_cfg_ssl_$label\"")"
  122. if [ "$content" ]; then
  123. location="$(eval echo "\$__vhost_cfg_SSL_${label^^}_LOCATION")"
  124. echo "$content" | file_put "$dst$location"
  125. volumes="$volumes
  126. - $dst$location:$location:ro"
  127. fi
  128. done
  129. if [ "$volumes" ]; then
  130. config-add "\
  131. services:
  132. $MASTER_TARGET_SERVICE_NAME:
  133. volumes:
  134. $volumes
  135. "
  136. fi
  137. }
  138. ssl_plugin_letsencrypt-dns_vars() {
  139. __vhost_cfg_SSL_CERT_LOCATION=/etc/letsencrypt/live/${DOMAIN}/cert.pem
  140. __vhost_cfg_SSL_KEY_LOCATION=/etc/letsencrypt/live/${DOMAIN}/privkey.pem
  141. __vhost_cfg_SSL_CHAIN=/etc/letsencrypt/live/${DOMAIN}/chain.pem
  142. }
  143. ssl_plugin_letsencrypt-dns_prepare() {
  144. local service="$1" letsencrypt_charm
  145. shift
  146. export DEFAULT_COMPOSE_FILE="$COMPOSE_YML_FILE"
  147. run_service_action "$service" add "$DOMAIN" $(echo "$SERVER_ALIAS" | shyaml get-values 2>/dev/null) || return 1
  148. letsencrypt_charm=$(get_service_charm "$service") || return 1
  149. config-add "\
  150. services:
  151. $MASTER_TARGET_SERVICE_NAME:
  152. volumes:
  153. - $DATASTORE/${letsencrypt_charm}/etc/letsencrypt:/etc/letsencrypt:ro
  154. " || return 1
  155. }
  156. apache_passwd_file() {
  157. include parse || true
  158. ## XXXvlab: called twice... no better way to do this ?
  159. __vhost_creds_statement >/dev/null
  160. first=
  161. if ! [ -e "$CONFIGSTORE/$MASTER_TARGET_SERVICE_NAME$password_file" ]; then
  162. debug "No file $CONFIGSTORE/$MASTER_TARGET_SERVICE_NAME$password_file, creating password file." || true
  163. first=c
  164. fi
  165. while read-0 login password; do
  166. debug "htpasswd -b$first '${password_file}' '$login' '$password'"
  167. echo "htpasswd -b$first '${password_file}' '$login' '$password'"
  168. if [ "$first" ]; then
  169. first=
  170. fi
  171. done < <(echo "$__vhost_cfg_creds_enabled" | shyaml key-values-0 2>/dev/null) |
  172. docker run -i --entrypoint "/bin/bash" \
  173. -v "$APACHE_CONFIG_LOCATION:/etc/apache2/sites-enabled" \
  174. "$DOCKER_BASE_IMAGE" || return 1
  175. }
  176. ## Produce the full statements depending on relation-get informations
  177. apache_vhost_statement() {
  178. local vhost_statement
  179. export SERVER_ALIAS=$(relation-get server-aliases 2>/dev/null) || true
  180. export PROTOCOLS="$1"
  181. if is_protocol_enabled http; then
  182. __vhost_full_vhost_statement http
  183. fi
  184. if is_protocol_enabled https; then
  185. export SSL_PLUGIN_FUN=$(ssl_get_plugin_fun) || return 1
  186. "$SSL_PLUGIN_FUN"_vars "$(relation-get ssl 2>/dev/null)"
  187. cat <<EOF
  188. <IfModule mod_ssl.c>
  189. $(__vhost_full_vhost_statement https | prefix " ")
  190. </IfModule>
  191. EOF
  192. fi
  193. }
  194. export -f apache_vhost_statement
  195. apache_code_dir() {
  196. local www_data_gid
  197. www_data_gid=$(cached_cmd_on_base_image apache 'id -g www-data') || {
  198. debug "Failed to query for www-data gid in ${DARKYELLOW}apache${NORMAL} base image."
  199. return 1
  200. }
  201. mkdir -p "$LOCATION" || return 1
  202. setfacl -R -m g:"$www_data_gid":rx "$LOCATION"
  203. info "Set permission for read and traversal on '$LOCATION'."
  204. config-add "
  205. $MASTER_BASE_SERVICE_NAME:
  206. volumes:
  207. - $LOCATION:$DOCKER_SITE_PATH
  208. "
  209. }
  210. apache_data_dirs() {
  211. DATA_DIRS=$(relation-get data-dirs 2>/dev/null | shyaml get-values 2>/dev/null) || true
  212. if [ -z "$DATA_DIRS" ]; then
  213. return 0
  214. fi
  215. DST=$DATASTORE/$BASE_SERVICE_NAME$DOCKER_SITE_PATH
  216. DATA=()
  217. while IFS="," read -ra ADDR; do
  218. for dir in "${ADDR[@]}"; do
  219. DATA+=($dir)
  220. done
  221. done <<< "$DATA_DIRS"
  222. www_data_gid=$(cached_cmd_on_base_image apache 'id -g www-data') || {
  223. debug "Failed to query for www-data gid in ${DARKYELLOW}apache${NORMAL} base image."
  224. return 1
  225. }
  226. info "www-data gid from ${DARKYELLOW}apache${NORMAL} is '$www_data_gid'"
  227. dirs=()
  228. for d in "${DATA[@]}"; do
  229. dirs+=("$DST/$d")
  230. done
  231. mkdir -p "${dirs[@]}"
  232. setfacl -R -m g:"$www_data_gid":rwx "${dirs[@]}"
  233. setfacl -R -d -m g:"$www_data_gid":rwx "${dirs[@]}"
  234. config-add "
  235. $MASTER_BASE_SERVICE_NAME:
  236. volumes:
  237. $(
  238. for d in "${DATA[@]}"; do
  239. echo " - $DST/$d:$DOCKER_SITE_PATH/$d"
  240. done
  241. )"
  242. }
  243. deploy_files() {
  244. local src="$1" dst="$2"
  245. if ! [ -d "$dst" ]; then
  246. err "Destination '$dst' does not exist or is not a directory"
  247. return 1
  248. fi
  249. (
  250. cd "$dst" && info "In $dst:" &&
  251. get_file "$src" | tar xv
  252. )
  253. }
  254. export -f deploy_files
  255. apache_core_rules_add() {
  256. local conf="$1" dst="/etc/apache2/conf-enabled/$BASE_SERVICE_NAME.conf"
  257. debug "Adding core rule."
  258. echo "$conf" | file_put "$CONFIGSTORE/$BASE_SERVICE_NAME$dst"
  259. config-add "
  260. $MASTER_BASE_SERVICE_NAME:
  261. volumes:
  262. - $CONFIGSTORE/$BASE_SERVICE_NAME$dst:$dst:ro
  263. "
  264. }
  265. __vhost_ssl_statement() {
  266. ## defaults
  267. __vhost_cfg_SSL_CERT_LOCATION=${__vhost_cfg_SSL_CERT_LOCATION:-/etc/ssl/certs/ssl-cert-snakeoil.pem}
  268. __vhost_cfg_SSL_KEY_LOCATION=${__vhost_cfg_SSL_KEY_LOCATION:-/etc/ssl/private/ssl-cert-snakeoil.key}
  269. cat <<EOF
  270. ##
  271. ## SSL Configuration
  272. ##
  273. SSLEngine On
  274. SSLCertificateFile $__vhost_cfg_SSL_CERT_LOCATION
  275. SSLCertificateKeyFile $__vhost_cfg_SSL_KEY_LOCATION
  276. $([ -z "$__vhost_cfg_SSL_CA_CERT_LOCATION" ] || echo "SSLCACertificateFile $__vhost_cfg_SSL_CA_CERT_LOCATION")
  277. $([ -z "$__vhost_cfg_SSL_CHAIN" ] || echo "SSLCertificateChainFile $__vhost_cfg_SSL_CHAIN")
  278. SSLVerifyClient None
  279. EOF
  280. }
  281. __vhost_creds_statement() {
  282. if ! __vhost_cfg_creds_enabled=$(relation-get creds 2>/dev/null); then
  283. echo "Allow from all"
  284. return 0
  285. fi
  286. password_file=/etc/apache2/sites-enabled/${DOMAIN}.passwd
  287. cat <<EOF
  288. AuthType basic
  289. AuthName "private"
  290. AuthUserFile ${password_file}
  291. Require valid-user
  292. EOF
  293. }
  294. __vhost_head_statement() {
  295. local protocol="$1"
  296. if [ "$protocol" == "https" ]; then
  297. prefix="s-"
  298. else
  299. prefix=
  300. fi
  301. cat <<EOF
  302. ServerAdmin ${ADMIN_MAIL:-contact@$DOMAIN}
  303. ServerName ${DOMAIN}
  304. $(
  305. while read-0 alias; do
  306. echo "ServerAlias $alias"
  307. done < <(echo "$SERVER_ALIAS" | shyaml get-values-0 2>/dev/null)
  308. )
  309. ServerSignature Off
  310. CustomLog /var/log/apache2/${prefix}${DOMAIN}_access.log combined
  311. ErrorLog /var/log/apache2/${prefix}${DOMAIN}_error.log
  312. ErrorLog syslog:local2
  313. EOF
  314. }
  315. __vhost_custom_rules() {
  316. local custom_rules
  317. if custom_rules=$(relation-get apache-custom-rules 2>/dev/null); then
  318. cat <<EOF
  319. ##
  320. ## Custom rules
  321. ##
  322. $custom_rules
  323. EOF
  324. fi
  325. }
  326. __vhost_content_statement() {
  327. if [ "$proxy" ]; then
  328. __vhost_proxy_statement "$@"
  329. else
  330. __vhost_publish_dir_statement "$@"
  331. fi
  332. }
  333. __vhost_proxy_statement() {
  334. local protocol="$1"
  335. TARGET=$(relation-get target 2>/dev/null) || true
  336. if [ -z "$TARGET" ]; then
  337. ## First exposed port:
  338. base_image=$(service_base_docker_image "$BASE_SERVICE_NAME") || return 1
  339. first_exposed_port=$(image_exposed_ports_0 "$base_image" | tr '\0' '\n' | head -n 1 | cut -f 1 -d /) || return 1
  340. TARGET=$MASTER_BASE_SERVICE_NAME:$first_exposed_port
  341. info "No target was specified, introspection found: $TARGET"
  342. fi
  343. cat <<EOF
  344. ##
  345. ## Proxy declaration towards $TARGET
  346. ##
  347. <IfModule mod_proxy.c>
  348. ProxyRequests Off
  349. <Proxy *>
  350. Order deny,allow
  351. Allow from all
  352. </Proxy>
  353. ProxyVia On
  354. ProxyPass / http://$TARGET/ retry=0
  355. <Location / >
  356. $(__vhost_creds_statement | prefix " ")
  357. ProxyPassReverse /
  358. </Location>
  359. $([ "$protocol" == "https" ] && echo " SSLProxyEngine On")
  360. </IfModule>
  361. RequestHeader set "X-Forwarded-Proto" "$protocol"
  362. ## Fix IE problem (httpapache proxy dav error 408/409)
  363. SetEnv proxy-nokeepalive 1
  364. EOF
  365. }
  366. __vhost_full_vhost_statement() {
  367. local protocol="$1"
  368. case "$protocol" in
  369. https)
  370. PORT=443
  371. ;;
  372. http)
  373. PORT=80
  374. ;;
  375. esac
  376. cat <<EOF
  377. <VirtualHost *:$PORT>
  378. $(__vhost_head_statement "$protocol" | prefix " " && echo)
  379. $(__vhost_custom_rules | prefix " " && echo)
  380. $(__vhost_content_statement "$protocol" | prefix " ")
  381. ## Forbid any cache, this is only usefull on dev server.
  382. #Header set Cache-Control "no-cache"
  383. #Header set Access-Control-Allow-Origin "*"
  384. #Header set Access-Control-Allow-Methods "POST, GET, OPTIONS"
  385. #Header set Access-Control-Allow-Headers "origin, content-type, accept"
  386. $([ "$protocol" == "https" ] && __vhost_ssl_statement | prefix " " && echo )
  387. </VirtualHost>
  388. EOF
  389. }
  390. __vhost_publish_dir_statement() {
  391. cat <<EOF
  392. ##
  393. ## Publish directory $DOCKER_SITE_PATH
  394. ##
  395. DocumentRoot $DOCKER_SITE_PATH
  396. <Directory />
  397. Options FollowSymLinks
  398. AllowOverride None
  399. </Directory>
  400. <Directory $DOCKER_SITE_PATH>
  401. Options Indexes FollowSymLinks MultiViews
  402. AllowOverride all
  403. $(__vhost_creds_statement | prefix " ")
  404. </Directory>
  405. EOF
  406. }
  407. apache_config_hash() {
  408. debug "Adding config hash to enable recreating upon config change."
  409. config_hash=$(find "$SERVICE_CONFIGSTORE/etc/apache2/sites-enabled" \
  410. -name \*.conf -exec md5sum {} \; | md5_compat) || exit 1
  411. init-config-add "
  412. $MASTER_BASE_SERVICE_NAME:
  413. labels:
  414. - compose.config_hash=$config_hash
  415. "
  416. }