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.

826 lines
23 KiB

  1. # -*- mode: shell-script -*-
  2. get_domain() {
  3. local cfg="$1" cache_file="$state_tmpdir/$FUNCNAME.cache.$(H "$SERVICE_NAME" "$MASTER_BASE_SERVICE_NAME" "$@")" \
  4. domain
  5. if [ -e "$cache_file" ]; then
  6. cat "$cache_file"
  7. return 0
  8. fi
  9. domain=$(e "$cfg" | cfg-get-value domain 2>/dev/null) || true
  10. if [ "$domain" ]; then
  11. echo "$domain" | tee "$cache_file"
  12. elif [[ "$BASE_SERVICE_NAME" =~ ^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$ ]]; then
  13. echo "$BASE_SERVICE_NAME" | tee "$cache_file"
  14. else
  15. err "You must specify a ${WHITE}domain$NORMAL option. (${FUNCNAME[*]})"
  16. return 1
  17. fi
  18. }
  19. ##
  20. ## Master entrypoints
  21. ##
  22. apache_proxy_dir() {
  23. local cfg="$1" domain
  24. apache_vhost_create web_proxy "$cfg" || return 1
  25. }
  26. export -f apache_proxy_dir
  27. apache_publish_dir() {
  28. local cfg="$1" domain
  29. apache_vhost_create publish_dir "$cfg" || return 1
  30. apache_code_dir "$cfg" || return 1
  31. apache_data_dirs "$cfg"
  32. }
  33. export -f apache_publish_dir
  34. apache_ssh_tunnel() {
  35. local cfg="$1" domain ssh_tunnel
  36. domain=$(e "$cfg" | cfg-get-value domain) || {
  37. err "${WHITE}domain${NORMAL} must be valued in ${WHITE}ssh-tunnel${NORMAL} config."
  38. return 1
  39. }
  40. protocols=$(__vhost_cfg_normalize_protocol "$cfg") || return 1
  41. if ! is_protocol_enabled https "$protocols"; then
  42. err "${WHITE}ssl${NORMAL} must be valued in ${WHITE}ssh-tunnel${NORMAL} config."
  43. return 1
  44. fi
  45. apache_vhost_create ssh_tunnel "$cfg" ",https," "000-$domain" || return 1
  46. }
  47. export -f apache_publish_dir
  48. ##
  49. ## Simple functions
  50. ##
  51. apache_vhost_create() {
  52. local type="$1" cfg="$2" protocols="$3" dest="$4" custom_rules vhost_statement creds \
  53. redirect domain ssl_plugin_fun ssl_cfg_value ssl_cfg_options
  54. export APACHE_CONFIG_LOCATION="$SERVICE_CONFIGSTORE/etc/apache2/sites-enabled"
  55. if [ "$cfg" == "None" ]; then
  56. cfg=
  57. fi
  58. if [ -z "$protocols" ]; then
  59. protocols=$(__vhost_cfg_normalize_protocol "$cfg") || return 1
  60. fi
  61. domain=$(get_domain "$cfg") && {
  62. [ "$RELATION_DATA_FILE" ] && relation-set domain "$domain"
  63. }
  64. if is_protocol_enabled https "$protocols"; then
  65. [ "$RELATION_DATA_FILE" ] && {
  66. relation-set url "https://$domain"
  67. }
  68. if [ -z "$domain" ]; then
  69. err "You must specify a domain for ssl to work."
  70. return 1
  71. fi
  72. read-0 ssl_plugin_fun ssl_cfg_value ssl_cfg_options < <(ssl_get_plugin_fun "$cfg") || return 1
  73. "$ssl_plugin_fun"_vars "$cfg" "$ssl_cfg_options" "$ssl_cfg_value" "$domain" || return 1
  74. redirect=$(e "$cfg" | cfg-get-value 'redirect-to-ssl' 2>/dev/null) || true
  75. if is_protocol_enabled http "$protocols"; then
  76. redirect=${redirect:-true}
  77. else
  78. redirect=false
  79. fi
  80. if [ "$redirect" == "true" ]; then
  81. custom_rules=$(_get_custom_rules "$cfg") || return 1
  82. if [[ "$custom_rules" != *"## Auto-redirection from http to https"* ]]; then
  83. redirect_rule="- |
  84. ## Auto-redirection from http to https
  85. RewriteEngine On
  86. RewriteCond %{HTTPS} off
  87. RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=302,L,QSA]
  88. "
  89. [ "$RELATION_DATA_FILE" ] && \
  90. relation-set apache-custom-rules "$redirect_rule
  91. $(if [ "$custom_rules" ]; then
  92. echo "- |"$'\n'"$(echo "$custom_rules" | prefix " ")"
  93. fi)"
  94. cfg=$(merge_yaml_str "$cfg" "$(yaml_key_val_str "apache-custom-rules" "$redirect_rule
  95. $(if [ "$custom_rules" ]; then
  96. echo "- |"$'\n'"$(echo "$custom_rules" | prefix " ")"
  97. fi)")") || return 1
  98. fi
  99. fi
  100. else
  101. [ "$RELATION_DATA_FILE" ] && {
  102. relation-set url "http://$domain"
  103. }
  104. fi
  105. vhost_statement=$(apache_vhost_statement "$type" "$protocols" "$cfg" "$domain") || {
  106. err "Failed to get vhost statement for type $type on ${protocols:1:-1}"
  107. return 1
  108. }
  109. dest=${dest:-$domain}
  110. if [ -z "$dest" ]; then
  111. err "Please set either a domain or set a destination file."
  112. return 1
  113. fi
  114. echo "$vhost_statement" | file_put "$APACHE_CONFIG_LOCATION/$dest.conf" || return 1
  115. creds=$(e "$cfg" | cfg-get-value creds 2>/dev/null) || true
  116. if [ "$creds" ]; then
  117. apache_passwd_file "$cfg" "$dest"|| return 1
  118. fi
  119. if is_protocol_enabled https "$protocols"; then
  120. "$ssl_plugin_fun"_prepare "$cfg" "$ssl_cfg_options" "$ssl_cfg_value" || return 1
  121. fi
  122. }
  123. is_protocol_enabled() {
  124. local protocol="$1" protocols="$2"
  125. [[ "$protocols" == *",$protocol,"* ]]
  126. }
  127. export -f is_protocol_enabled
  128. _get_ssl_option_value() {
  129. local cfg="$1" cache_file="$state_tmpdir/$FUNCNAME.cache.$(H "$SERVICE_NAME" "$MASTER_BASE_SERVICE_NAME" "$@")" \
  130. target_relation rn ts rc td
  131. if [ -e "$cache_file" ]; then
  132. cat "$cache_file"
  133. return 0
  134. fi
  135. if ssl_cfg=$(e "$cfg" | cfg-get-value ssl 2>/dev/null); then
  136. if [[ "$ssl_cfg" =~ ^False|None$ ]]; then
  137. ssl_cfg=""
  138. fi
  139. echo "$ssl_cfg" | tee "$cache_file"
  140. return 0
  141. fi
  142. target_relation="cert-provider"
  143. while read-0 rn ts rc td; do
  144. [ "$rn" == "${target_relation}" ] || continue
  145. info "A cert-provider '$ts' declared as 'ssl' value"
  146. echo "$ts" | tee "$cache_file"
  147. return 0
  148. done < <(get_service_relations "$SERVICE_NAME")
  149. return 1
  150. }
  151. __vhost_cfg_normalize_protocol() {
  152. local cfg="$1" protocol ssl
  153. ## XXXvlab: can't cache if libcharm is not able to give me some checksums
  154. ## indeed, ``_get_ssl_option_value`` depends on relations calculations...
  155. # local cfg="$1" cache_file="$CACHEDIR/$FUNCNAME.cache.$(p0 "$@" | md5_compat)" \
  156. # protocol
  157. # if [ -e "$cache_file" ]; then
  158. # #debug "$FUNCNAME: STATIC cache hit $1"
  159. # cat "$cache_file" &&
  160. # touch "$cache_file" || return 1
  161. # return 0
  162. # fi
  163. if protocol=$(e "$cfg" | cfg-get-value protocol 2>/dev/null); then
  164. protocol=${protocol:-auto}
  165. else
  166. protocol=auto
  167. fi
  168. case "$protocol" in
  169. auto)
  170. ssl=$(_get_ssl_option_value "$cfg" 2>/dev/null)
  171. if [ "$ssl" ]; then
  172. protocol="http,https"
  173. else
  174. protocol="http"
  175. fi
  176. ;;
  177. both)
  178. protocol="http,https"
  179. ;;
  180. ssl|https)
  181. protocol="https"
  182. ;;
  183. http)
  184. protocol="http"
  185. ;;
  186. *)
  187. err "Invalid value '$protocol' for ${WHITE}protocol$NORMAL option (use one of: http, https, both, auto)."
  188. return 1
  189. esac
  190. echo -n ",$protocol,"
  191. #| tee "$cache_file"
  192. }
  193. ## ssl_plugin_* and ssl_fallback should :
  194. ## - do anything to ensure that
  195. ## - issue config-add to add volumes if necessary
  196. ## - output 3 vars of where to find the 3 files from within the docker apache
  197. ssl_get_plugin_fun() {
  198. # from ssl conf, return the function that should manage SSL code creation
  199. local master_cfg="$1" cfg type keys
  200. cfg=$(_get_ssl_option_value "$master_cfg") || {
  201. err "No ssl options available."
  202. return 1
  203. }
  204. local cache_file="$state_tmpdir/$FUNCNAME.cache.$(H "$SERVICE_NAME" "$cfg")"
  205. if [ -e "$cache_file" ]; then
  206. cat "$cache_file"
  207. return 0
  208. fi
  209. [ "$cfg" ] || {
  210. touch "$cache_file"
  211. return 0
  212. }
  213. type="$(echo "$cfg" | shyaml -y get-type 2>/dev/null)" || return 1
  214. if [[ "$type" == "bool" ]]; then
  215. printf "%s\0" "ssl_fallback" "" "$cfg" | tee "$cache_file"
  216. return 0
  217. fi
  218. if ! [[ "$type" == "str" || "$type" == "struct" ]]; then
  219. err "Invalid ${WHITE}ssl${NORMAL} value type '$type': please provide a string or a struct."
  220. return 1
  221. fi
  222. if [ -z "$NO_CERT_PROVIDER" ]; then
  223. if [[ "$type" == "str" ]]; then
  224. keys=("$cfg")
  225. else
  226. keys=($(echo "$cfg" | shyaml keys 2>/dev/null))
  227. fi
  228. for key in "${keys[@]}"; do
  229. target_relation="cert-provider"
  230. fun="ssl_plugin_${target_relation}"
  231. while read-0 relation_name target_service relation_config tech_dep; do
  232. [ "$relation_name" == "${target_relation}" ] || continue
  233. [ "$target_service" == "$key" ] || continue
  234. verb "Corresponding plugin ${DARKGREEN}found${NORMAL}" \
  235. "in ${DARKBLUE}$relation_name${NORMAL}/${DARKYELLOW}$key${NORMAL}"
  236. ssl_cfg=$(printf "%s" "$cfg" | shyaml get-value "$key" 2>/dev/null) || true
  237. merged_config=$(merge_yaml_str "$relation_config" "$ssl_cfg") || return 1
  238. printf "%s\0" "$fun" "$key" "$merged_config" | tee "$cache_file"
  239. return 0
  240. done < <(get_service_relations "$SERVICE_NAME") || return 1
  241. case "$key" in
  242. cert|ca-cert|key)
  243. :
  244. ;;
  245. *)
  246. err "Invalid key '$key' in ${WHITE}ssl${NORMAL}:" \
  247. "no corresponding services declared in ${DARKBLUE}${target_relation}$NORMAL"
  248. return 1
  249. ;;
  250. esac
  251. done
  252. fi
  253. ## No key of the struct seem to be declared cert-provider, so fallback
  254. printf "%s\0" "ssl_fallback" "" "$cfg" | tee "$cache_file"
  255. }
  256. ssl_fallback_vars() {
  257. local cfg="$1" ssl_cfg="$2" value="$3" domain="$4" cert key ca_cert domain
  258. if __vhost_cfg_ssl_cert=$(echo "$ssl_cfg" | shyaml get-value cert 2>/dev/null); then
  259. __vhost_cfg_SSL_CERT_LOCATION=/etc/ssl/certs/${domain}.pem
  260. fi
  261. if __vhost_cfg_ssl_key=$(echo "$ssl_cfg" | shyaml get-value key 2>/dev/null); then
  262. __vhost_cfg_SSL_KEY_LOCATION=/etc/ssl/private/${domain}.key
  263. fi
  264. if __vhost_cfg_ssl_ca_cert=$(echo "$ssl_cfg" | shyaml get-value ca-cert 2>/dev/null); then
  265. __vhost_cfg_SSL_CA_CERT_LOCATION=/etc/ssl/certs/${domain}-ca.pem
  266. fi
  267. }
  268. ssl_fallback_prepare() {
  269. local cfg="$1" cert key ca_cert
  270. dst="$CONFIGSTORE/$BASE_SERVICE_NAME"
  271. volumes=""
  272. for label in cert key ca_cert; do
  273. content="$(eval echo "\"\$__vhost_cfg_ssl_$label\"")"
  274. if [ "$content" ]; then
  275. location="$(eval echo "\$__vhost_cfg_SSL_${label^^}_LOCATION")"
  276. echo "$content" | file_put "$dst$location"
  277. config_hash=$(printf "%s\0" "$config_hash" "$label" "$content" | md5_compat)
  278. volumes="$volumes
  279. - $dst$location:$location:ro"
  280. fi
  281. done
  282. if [ "$volumes" ]; then
  283. init-config-add "\
  284. $MASTER_TARGET_SERVICE_NAME:
  285. volumes:
  286. $volumes
  287. "
  288. fi
  289. }
  290. ssl_plugin_cert-provider_vars() {
  291. local cfg="$1" ssl_cfg="$2" value="$3" domain="$4"
  292. __vhost_cfg_SSL_CERT_LOCATION=/etc/letsencrypt/live/${domain}/cert.pem
  293. __vhost_cfg_SSL_KEY_LOCATION=/etc/letsencrypt/live/${domain}/privkey.pem
  294. __vhost_cfg_SSL_CHAIN=/etc/letsencrypt/live/${domain}/chain.pem
  295. }
  296. ssl_plugin_cert-provider_prepare() {
  297. local cfg="$1" ssl_cfg="$2" service="$3" options domain server_aliases
  298. domain=$(get_domain "$cfg") || return 1
  299. options=$(yaml_key_val_str "options" "$ssl_cfg") || return 1
  300. service_config=$(yaml_key_val_str "$service" "$options")
  301. server_aliases=$(e "$cfg" | cfg-get-value server-aliases 2>/dev/null) || true
  302. [ "$server_aliases" == None ] && server_aliases=""
  303. if [ "$server_aliases" ]; then
  304. server_aliases=($(echo "$server_aliases" | shyaml get-values)) || return 1
  305. else
  306. server_aliases=()
  307. fi
  308. compose --debug --add-compose-content "$service_config" crt "$service" \
  309. create "$domain" "${server_aliases[@]}" || {
  310. err "Failed to launch letsencrypt for certificate creation."
  311. return 1
  312. }
  313. init-config-add "\
  314. $SERVICE_NAME:
  315. volumes:
  316. - $DATASTORE/$service/etc/letsencrypt:/etc/letsencrypt:ro
  317. " || return 1
  318. }
  319. apache_passwd_file() {
  320. local cfg="$1" dest="$2" creds
  321. include parse || true
  322. ## XXXvlab: called twice... no better way to do this ?
  323. creds=$(e "$cfg" | cfg-get-value creds 2>/dev/null) || true
  324. password_path=$(password-path-get "$dest")
  325. first=
  326. if ! [ -e "$CONFIGSTORE/$MASTER_TARGET_SERVICE_NAME$password_path" ]; then
  327. debug "No file $CONFIGSTORE/$MASTER_TARGET_SERVICE_NAME$password_path, creating password file." || true
  328. first=c
  329. fi
  330. while read-0 login password; do
  331. debug "htpasswd -b$first '${password_path}' '$login' '$password'"
  332. echo "htpasswd -b$first '${password_path}' '$login' '$password'"
  333. if [ "$first" ]; then
  334. first=
  335. fi
  336. done < <(e "$creds" | shyaml key-values-0 2>/dev/null) |
  337. docker run -i --entrypoint "/bin/bash" \
  338. -v "$APACHE_CONFIG_LOCATION:/etc/apache2/sites-enabled" \
  339. "$DOCKER_BASE_IMAGE" || return 1
  340. }
  341. ## Produce the full statements depending on relation-get informations
  342. apache_vhost_statement() {
  343. local type="$1" protocols="$2" cfg="$3" domain="$4" \
  344. vhost_statement
  345. if is_protocol_enabled http "$protocols"; then
  346. __vhost_full_vhost_statement "$type" http "$cfg" "$domain" || return 1
  347. fi
  348. if is_protocol_enabled https "$protocols"; then
  349. read-0 ssl_plugin_fun ssl_cfg_value ssl_cfg_options < <(ssl_get_plugin_fun "$cfg") || return 1
  350. "$ssl_plugin_fun"_vars "$cfg" "$ssl_cfg_options" "$ssl_cfg_value" "$domain" || return 1
  351. vhost_statement=$(__vhost_full_vhost_statement "$type" https "$cfg" "$domain") || return 1
  352. cat <<EOF
  353. <IfModule mod_ssl.c>
  354. $(echo "$vhost_statement" | prefix " ")
  355. </IfModule>
  356. EOF
  357. fi
  358. }
  359. export -f apache_vhost_statement
  360. apache_code_dir() {
  361. local cfg="$1" www_data_gid local_path
  362. www_data_gid=$(cached_cmd_on_base_image "$TARGET_SERVICE_NAME" 'id -g www-data') || {
  363. debug "Failed to query for www-data gid in ${DARKYELLOW}$TARGET_SERVICE_NAME${NORMAL} base image."
  364. return 1
  365. }
  366. domain=$(get_domain "$cfg") || return 1
  367. local_path="/var/www/${domain}"
  368. host_path=$(e "$cfg" | cfg-get-value location 2>/dev/null) ||
  369. host_path="$DATASTORE/$BASE_SERVICE_NAME${local_path}"
  370. mkdir -p "$host_path" || return 1
  371. init-config-add "
  372. $SERVICE_NAME:
  373. volumes:
  374. - \"$HOST_DATASTORE${host_path##$DATASTORE}:$local_path\"
  375. "
  376. }
  377. apache_data_dirs() {
  378. local cfg="$1" data_dirs dst data dirs
  379. data_dirs=$(e "$cfg" | cfg-get-value data-dirs 2>/dev/null | shyaml get-values 2>/dev/null) || true
  380. if [ -z "$data_dirs" ]; then
  381. return 0
  382. fi
  383. domain=$(get_domain "$cfg") || return 1
  384. local_path="/var/www/${domain}"
  385. dst=$DATASTORE/$BASE_SERVICE_NAME$local_path
  386. data=()
  387. while IFS="," read -ra addr; do
  388. for dir in "${addr[@]}"; do
  389. data+=($dir)
  390. done
  391. done <<< "$data_dirs"
  392. www_data_gid=$(cached_cmd_on_base_image "$TARGET_SERVICE_NAME" 'id -g www-data') || {
  393. debug "Failed to query for www-data gid in ${DARKYELLOW}$TARGET_SERVICE_NAME${NORMAL} base image."
  394. return 1
  395. }
  396. info "www-data gid from ${DARKYELLOW}$TARGET_SERVICE_NAME${NORMAL} is '$www_data_gid'"
  397. dirs=()
  398. for d in "${data[@]}"; do
  399. dirs+=("$dst/$d")
  400. done
  401. mkdir -p "${dirs[@]}" || return 1
  402. init-config-add "
  403. $SERVICE_NAME:
  404. volumes:
  405. $(
  406. for d in "${data[@]}"; do
  407. echo " - $HOST_DATASTORE${dst##$DATASTORE}/$d:$local_path/$d"
  408. done
  409. )"
  410. }
  411. deploy_files() {
  412. local src="$1" dst="$2"
  413. if ! [ -d "$dst" ]; then
  414. err "Destination '$dst' does not exist or is not a directory"
  415. return 1
  416. fi
  417. (
  418. cd "$dst" && info "In $dst:" &&
  419. get_file "$src" | tar xv
  420. )
  421. }
  422. export -f deploy_files
  423. apache_core_rules_add() {
  424. local conf="$1" dst="/etc/apache2/conf-enabled/$SERVICE_NAME.conf"
  425. debug "Adding core rule."
  426. echo "$conf" | file_put "$CONFIGSTORE/$SERVICE_NAME$dst"
  427. config_hash=$(printf "%s\0" "$config_hash" "$conf" | md5_compat)
  428. init-config-add "
  429. $SERVICE_NAME:
  430. volumes:
  431. - $CONFIGSTORE/$SERVICE_NAME$dst:$dst:ro
  432. "
  433. }
  434. __vhost_ssl_statement() {
  435. ## defaults
  436. __vhost_cfg_SSL_CERT_LOCATION=${__vhost_cfg_SSL_CERT_LOCATION:-/etc/ssl/certs/ssl-cert-snakeoil.pem}
  437. __vhost_cfg_SSL_KEY_LOCATION=${__vhost_cfg_SSL_KEY_LOCATION:-/etc/ssl/private/ssl-cert-snakeoil.key}
  438. cat <<EOF
  439. ##
  440. ## SSL Configuration
  441. ##
  442. SSLEngine On
  443. SSLCertificateFile $__vhost_cfg_SSL_CERT_LOCATION
  444. SSLCertificateKeyFile $__vhost_cfg_SSL_KEY_LOCATION
  445. $([ -z "$__vhost_cfg_SSL_CA_CERT_LOCATION" ] || echo "SSLCACertificateFile $__vhost_cfg_SSL_CA_CERT_LOCATION")
  446. $([ -z "$__vhost_cfg_SSL_CHAIN" ] || echo "SSLCertificateChainFile $__vhost_cfg_SSL_CHAIN")
  447. SSLVerifyClient None
  448. EOF
  449. }
  450. password-path-get() {
  451. local dest="$1"
  452. echo "/etc/apache2/sites-enabled/${dest}.passwd"
  453. }
  454. __vhost_creds_statement() {
  455. local cfg="$1" dest="$2" password_path
  456. password_path=$(password-path-get "$dest") || return 1
  457. if ! e "$cfg" | cfg-get-value creds >/dev/null 2>&1; then
  458. echo "Allow from all"
  459. return 0
  460. fi
  461. cat <<EOF
  462. AuthType basic
  463. AuthName "private"
  464. AuthUserFile ${password_path}
  465. Require valid-user
  466. EOF
  467. }
  468. __vhost_head_statement() {
  469. local cfg="$1" protocol="$2" domain="$3" server_aliases admin_mail prefix
  470. admin_mail=$(e "$1" | cfg-get-value "admin-mail" 2>/dev/null) || true
  471. if [ -z "$admin_mail" ]; then
  472. if [ -z "$domain" ]; then
  473. admin_mail=webmaster@localhost
  474. else
  475. admin_mail=${admin_mail:-contact@$domain}
  476. fi
  477. fi
  478. server_aliases=$(e "$cfg" | cfg-get-value server-aliases 2>/dev/null) || true
  479. [ "$server_aliases" == None ] && server_aliases=""
  480. if [ "$server_aliases" ]; then
  481. server_aliases=($(e "$server_aliases" | shyaml get-values)) || return 1
  482. if [ -z "$domain" ]; then
  483. err "You can't specify server aliases if you don't have a domain."
  484. return 1
  485. fi
  486. else
  487. server_aliases=()
  488. fi
  489. if [ "$protocol" == "https" ]; then
  490. prefix="s-"
  491. else
  492. prefix=
  493. fi
  494. if [ "$domain" ]; then
  495. log_prefix="${prefix}${domain}_"
  496. else
  497. log_prefix=""
  498. fi
  499. cat <<EOF
  500. $(
  501. echo "ServerAdmin ${admin_mail}"
  502. [ "$domain" ] && echo "ServerName ${domain}"
  503. for alias in "${server_aliases[@]}"; do
  504. [ "$alias" ] || continue
  505. echo "ServerAlias $alias"
  506. done
  507. )
  508. ServerSignature Off
  509. CustomLog /var/log/apache2/${log_prefix}access.log combined
  510. ErrorLog /var/log/apache2/${log_prefix}error.log
  511. ErrorLog syslog:local2
  512. EOF
  513. }
  514. _get_custom_rules() {
  515. local cfg="$1" custom_rules
  516. custom_rules=$(e "$cfg" | shyaml -y -q get-value apache-custom-rules 2>/dev/null) || true
  517. e "$custom_rules" | yaml_get_values
  518. }
  519. __vhost_custom_rules() {
  520. local cfg="$1" custom_rules
  521. custom_rules=$(_get_custom_rules "$cfg") || return 1
  522. if [ "$custom_rules" ]; then
  523. cat <<EOF
  524. ##
  525. ## Custom rules
  526. ##
  527. $custom_rules
  528. EOF
  529. fi
  530. }
  531. __vhost_content_statement() {
  532. local type="$1"
  533. shift
  534. case "$type" in
  535. "web_proxy")
  536. __vhost_proxy_statement "$@" || return 1
  537. ;;
  538. "publish_dir")
  539. __vhost_publish_dir_statement "$@" || return 1
  540. ;;
  541. "ssh_tunnel")
  542. __vhost_tunnel_ssh_statement "$@" || return 1
  543. ;;
  544. esac
  545. }
  546. target-get() {
  547. local cfg="$1" target first_exposed_port base_image
  548. target=$(e "$cfg" | cfg-get-value target 2>/dev/null) || true
  549. if [ -z "$target" ]; then
  550. ## First exposed port:
  551. base_image=$(service_base_docker_image "$BASE_SERVICE_NAME") || return 1
  552. if ! docker_has_image "$base_image"; then
  553. docker pull "$base_image" >&2
  554. fi
  555. first_exposed_port=$(image_exposed_ports_0 "$base_image" | tr '\0' '\n' | head -n 1 | cut -f 1 -d /) || return 1
  556. if [ -z "$first_exposed_port" ]; then
  557. err "Failed to get first exposed port of image '$base_image'."
  558. return 1
  559. fi
  560. target=$MASTER_BASE_SERVICE_NAME:$first_exposed_port
  561. info "No target was specified, introspection found: $target"
  562. fi
  563. echo "$target"
  564. }
  565. __vhost_proxy_statement() {
  566. local protocol="$1" cfg="$2" dest="$3" proxy_pass_options target
  567. target=$(target-get "$cfg") || return 1
  568. proxy_pass_options=($(e "$cfg" | shyaml -y -q get-value apache-proxy-pass-options | yaml_get_values))
  569. if [ "${#proxy_pass_options[@]}" == 0 ]; then
  570. proxy_pass_options=(${proxy_pass_options:-"retry=0"})
  571. fi
  572. cat <<EOF
  573. ##
  574. ## Proxy declaration towards $target
  575. ##
  576. <IfModule mod_proxy.c>
  577. ProxyRequests Off
  578. <Proxy *>
  579. Order deny,allow
  580. Allow from all
  581. </Proxy>
  582. ProxyVia On
  583. ProxyPass / http://$target/ ${proxy_pass_options[*]}
  584. <Location / >
  585. $(__vhost_creds_statement "$cfg" "$dest" | prefix " ")
  586. ProxyPassReverse http://$target/
  587. </Location>
  588. $([ "$protocol" == "https" ] && echo " SSLProxyEngine On")
  589. </IfModule>
  590. RequestHeader set "X-Forwarded-Proto" "$protocol"
  591. ## Fix IE problem (httpapache proxy dav error 408/409)
  592. SetEnv proxy-nokeepalive 1
  593. EOF
  594. }
  595. __vhost_full_vhost_statement() {
  596. local type="$1" protocol="$2" cfg="$3" domain="$4" head_statement custom_rules content_statement
  597. head_statement=$(__vhost_head_statement "$cfg" "$protocol" "$domain") || return 1
  598. custom_rules=$(__vhost_custom_rules "$cfg") || return 1
  599. content_statement=$(__vhost_content_statement "$type" "$protocol" "$cfg" "${domain:-html}") || return 1
  600. case "$protocol" in
  601. https)
  602. PORT=443
  603. ;;
  604. http)
  605. PORT=80
  606. ;;
  607. esac
  608. cat <<EOF
  609. <VirtualHost *:$PORT>
  610. $(
  611. {
  612. echo "$head_statement"
  613. [ "$custom_rules" ] && echo "$custom_rules"
  614. echo "$content_statement"
  615. } | prefix " ")
  616. ## Forbid any cache, this is only usefull on dev server.
  617. #Header set Cache-Control "no-cache"
  618. #Header set Access-Control-Allow-Origin "*"
  619. #Header set Access-Control-Allow-Methods "POST, GET, OPTIONS"
  620. #Header set Access-Control-Allow-Headers "origin, content-type, accept"
  621. $([ "$protocol" == "https" ] && __vhost_ssl_statement | prefix " " && echo )
  622. </VirtualHost>
  623. EOF
  624. }
  625. __vhost_publish_dir_statement() {
  626. local protocol="$1" cfg="$2" dest="$3" location
  627. local_path="/var/www/${dest}"
  628. cat <<EOF
  629. ##
  630. ## Publish directory $local_path
  631. ##
  632. DocumentRoot $local_path
  633. <Directory />
  634. Options FollowSymLinks
  635. AllowOverride None
  636. </Directory>
  637. <Directory $local_path>
  638. Options Indexes FollowSymLinks MultiViews
  639. AllowOverride all
  640. $(__vhost_creds_statement "$cfg" "$dest" | prefix " ")
  641. </Directory>
  642. EOF
  643. }
  644. __vhost_tunnel_ssh_statement() {
  645. local protocol="$1" cfg="$2" dest="$3" custom_rules content_statement
  646. cat <<EOF
  647. ##
  648. ## SSH Tunnel
  649. ##
  650. #HostnameLookups On
  651. ProxyRequests On
  652. AllowConnect 22
  653. #ProxyVia on
  654. ### Deny everything by default
  655. <Proxy *>
  656. Order deny,allow
  657. Deny from all
  658. </proxy>
  659. ### Accept redirect only to same domain
  660. <Proxy $domain>
  661. Order deny,allow
  662. $(__vhost_creds_statement "$cfg" "$dest" | prefix " ")
  663. </Proxy>
  664. EOF
  665. }
  666. apache_config_hash() {
  667. debug "Adding config hash to enable recreating upon config change."
  668. config_hash=$({
  669. printf "%s\0" "$config_hash"
  670. find "$SERVICE_CONFIGSTORE/etc/apache2/sites-enabled" \
  671. -name \*.conf -exec md5sum {} \;
  672. } | md5_compat) || exit 1
  673. init-config-add "
  674. $SERVICE_NAME:
  675. labels:
  676. - compose.config_hash=$config_hash
  677. "
  678. }