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.

830 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 apache 'id -g www-data') || {
  363. debug "Failed to query for www-data gid in ${DARKYELLOW}apache${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. setfacl -R -m g:"$www_data_gid":rx "$host_path"
  372. info "Set permission for read and traversal on '$host_path'."
  373. init-config-add "
  374. $SERVICE_NAME:
  375. volumes:
  376. - $host_path:$local_path
  377. "
  378. }
  379. apache_data_dirs() {
  380. local cfg="$1" data_dirs dst data dirs
  381. data_dirs=$(e "$cfg" | cfg-get-value data-dirs 2>/dev/null | shyaml get-values 2>/dev/null) || true
  382. if [ -z "$data_dirs" ]; then
  383. return 0
  384. fi
  385. domain=$(get_domain "$cfg") || return 1
  386. local_path="/var/www/${domain}"
  387. dst=$DATASTORE/$BASE_SERVICE_NAME$local_path
  388. data=()
  389. while IFS="," read -ra addr; do
  390. for dir in "${addr[@]}"; do
  391. data+=($dir)
  392. done
  393. done <<< "$data_dirs"
  394. www_data_gid=$(cached_cmd_on_base_image apache 'id -g www-data') || {
  395. debug "Failed to query for www-data gid in ${DARKYELLOW}apache${NORMAL} base image."
  396. return 1
  397. }
  398. info "www-data gid from ${DARKYELLOW}apache${NORMAL} is '$www_data_gid'"
  399. dirs=()
  400. for d in "${data[@]}"; do
  401. dirs+=("$dst/$d")
  402. done
  403. mkdir -p "${dirs[@]}"
  404. setfacl -R -m g:"$www_data_gid":rwx "${dirs[@]}"
  405. setfacl -R -d -m g:"$www_data_gid":rwx "${dirs[@]}"
  406. init-config-add "
  407. $SERVICE_NAME:
  408. volumes:
  409. $(
  410. for d in "${data[@]}"; do
  411. echo " - $dst/$d:$local_path/$d"
  412. done
  413. )"
  414. }
  415. deploy_files() {
  416. local src="$1" dst="$2"
  417. if ! [ -d "$dst" ]; then
  418. err "Destination '$dst' does not exist or is not a directory"
  419. return 1
  420. fi
  421. (
  422. cd "$dst" && info "In $dst:" &&
  423. get_file "$src" | tar xv
  424. )
  425. }
  426. export -f deploy_files
  427. apache_core_rules_add() {
  428. local conf="$1" dst="/etc/apache2/conf-enabled/$SERVICE_NAME.conf"
  429. debug "Adding core rule."
  430. echo "$conf" | file_put "$CONFIGSTORE/$SERVICE_NAME$dst"
  431. config_hash=$(printf "%s\0" "$config_hash" "$conf" | md5_compat)
  432. init-config-add "
  433. $SERVICE_NAME:
  434. volumes:
  435. - $CONFIGSTORE/$SERVICE_NAME$dst:$dst:ro
  436. "
  437. }
  438. __vhost_ssl_statement() {
  439. ## defaults
  440. __vhost_cfg_SSL_CERT_LOCATION=${__vhost_cfg_SSL_CERT_LOCATION:-/etc/ssl/certs/ssl-cert-snakeoil.pem}
  441. __vhost_cfg_SSL_KEY_LOCATION=${__vhost_cfg_SSL_KEY_LOCATION:-/etc/ssl/private/ssl-cert-snakeoil.key}
  442. cat <<EOF
  443. ##
  444. ## SSL Configuration
  445. ##
  446. SSLEngine On
  447. SSLCertificateFile $__vhost_cfg_SSL_CERT_LOCATION
  448. SSLCertificateKeyFile $__vhost_cfg_SSL_KEY_LOCATION
  449. $([ -z "$__vhost_cfg_SSL_CA_CERT_LOCATION" ] || echo "SSLCACertificateFile $__vhost_cfg_SSL_CA_CERT_LOCATION")
  450. $([ -z "$__vhost_cfg_SSL_CHAIN" ] || echo "SSLCertificateChainFile $__vhost_cfg_SSL_CHAIN")
  451. SSLVerifyClient None
  452. EOF
  453. }
  454. password-path-get() {
  455. local dest="$1"
  456. echo "/etc/apache2/sites-enabled/${dest}.passwd"
  457. }
  458. __vhost_creds_statement() {
  459. local cfg="$1" dest="$2" password_path
  460. password_path=$(password-path-get "$dest") || return 1
  461. if ! e "$cfg" | cfg-get-value creds >/dev/null 2>&1; then
  462. echo "Allow from all"
  463. return 0
  464. fi
  465. cat <<EOF
  466. AuthType basic
  467. AuthName "private"
  468. AuthUserFile ${password_path}
  469. Require valid-user
  470. EOF
  471. }
  472. __vhost_head_statement() {
  473. local cfg="$1" protocol="$2" domain="$3" server_aliases admin_mail prefix
  474. admin_mail=$(e "$1" | cfg-get-value "admin-mail" 2>/dev/null) || true
  475. if [ -z "$admin_mail" ]; then
  476. if [ -z "$domain" ]; then
  477. admin_mail=webmaster@localhost
  478. else
  479. admin_mail=${admin_mail:-contact@$domain}
  480. fi
  481. fi
  482. server_aliases=$(e "$cfg" | cfg-get-value server-aliases 2>/dev/null) || true
  483. [ "$server_aliases" == None ] && server_aliases=""
  484. if [ "$server_aliases" ]; then
  485. server_aliases=($(e "$server_aliases" | shyaml get-values)) || return 1
  486. if [ -z "$domain" ]; then
  487. err "You can't specify server aliases if you don't have a domain."
  488. return 1
  489. fi
  490. else
  491. server_aliases=()
  492. fi
  493. if [ "$protocol" == "https" ]; then
  494. prefix="s-"
  495. else
  496. prefix=
  497. fi
  498. if [ "$domain" ]; then
  499. log_prefix="${prefix}${domain}_"
  500. else
  501. log_prefix=""
  502. fi
  503. cat <<EOF
  504. $(
  505. echo "ServerAdmin ${admin_mail}"
  506. [ "$domain" ] && echo "ServerName ${domain}"
  507. for alias in "${server_aliases[@]}"; do
  508. [ "$alias" ] || continue
  509. echo "ServerAlias $alias"
  510. done
  511. )
  512. ServerSignature Off
  513. CustomLog /var/log/apache2/${log_prefix}access.log combined
  514. ErrorLog /var/log/apache2/${log_prefix}error.log
  515. ErrorLog syslog:local2
  516. EOF
  517. }
  518. _get_custom_rules() {
  519. local cfg="$1" custom_rules
  520. custom_rules=$(e "$cfg" | shyaml -y -q get-value apache-custom-rules 2>/dev/null) || true
  521. e "$custom_rules" | yaml_get_values
  522. }
  523. __vhost_custom_rules() {
  524. local cfg="$1" custom_rules
  525. custom_rules=$(_get_custom_rules "$cfg") || return 1
  526. if [ "$custom_rules" ]; then
  527. cat <<EOF
  528. ##
  529. ## Custom rules
  530. ##
  531. $custom_rules
  532. EOF
  533. fi
  534. }
  535. __vhost_content_statement() {
  536. local type="$1"
  537. shift
  538. case "$type" in
  539. "web_proxy")
  540. __vhost_proxy_statement "$@" || return 1
  541. ;;
  542. "publish_dir")
  543. __vhost_publish_dir_statement "$@" || return 1
  544. ;;
  545. "ssh_tunnel")
  546. __vhost_tunnel_ssh_statement "$@" || return 1
  547. ;;
  548. esac
  549. }
  550. target-get() {
  551. local cfg="$1" target first_exposed_port base_image
  552. target=$(e "$cfg" | cfg-get-value target 2>/dev/null) || true
  553. if [ -z "$target" ]; then
  554. ## First exposed port:
  555. base_image=$(service_base_docker_image "$BASE_SERVICE_NAME") || return 1
  556. if ! docker_has_image "$base_image"; then
  557. docker pull "$base_image" >&2
  558. fi
  559. first_exposed_port=$(image_exposed_ports_0 "$base_image" | tr '\0' '\n' | head -n 1 | cut -f 1 -d /) || return 1
  560. if [ -z "$first_exposed_port" ]; then
  561. err "Failed to get first exposed port of image '$base_image'."
  562. return 1
  563. fi
  564. target=$MASTER_BASE_SERVICE_NAME:$first_exposed_port
  565. info "No target was specified, introspection found: $target"
  566. fi
  567. echo "$target"
  568. }
  569. __vhost_proxy_statement() {
  570. local protocol="$1" cfg="$2" dest="$3" proxy_pass_options target
  571. target=$(target-get "$cfg") || return 1
  572. proxy_pass_options=($(e "$cfg" | shyaml -y -q get-value apache-proxy-pass-options | yaml_get_values))
  573. if [ "${#proxy_pass_options[@]}" == 0 ]; then
  574. proxy_pass_options=(${proxy_pass_options:-"retry=0"})
  575. fi
  576. cat <<EOF
  577. ##
  578. ## Proxy declaration towards $target
  579. ##
  580. <IfModule mod_proxy.c>
  581. ProxyRequests Off
  582. <Proxy *>
  583. Order deny,allow
  584. Allow from all
  585. </Proxy>
  586. ProxyVia On
  587. ProxyPass / http://$target/ ${proxy_pass_options[*]}
  588. <Location / >
  589. $(__vhost_creds_statement "$cfg" "$dest" | prefix " ")
  590. ProxyPassReverse http://$target/
  591. </Location>
  592. $([ "$protocol" == "https" ] && echo " SSLProxyEngine On")
  593. </IfModule>
  594. RequestHeader set "X-Forwarded-Proto" "$protocol"
  595. ## Fix IE problem (httpapache proxy dav error 408/409)
  596. SetEnv proxy-nokeepalive 1
  597. EOF
  598. }
  599. __vhost_full_vhost_statement() {
  600. local type="$1" protocol="$2" cfg="$3" domain="$4" head_statement custom_rules content_statement
  601. head_statement=$(__vhost_head_statement "$cfg" "$protocol" "$domain") || return 1
  602. custom_rules=$(__vhost_custom_rules "$cfg") || return 1
  603. content_statement=$(__vhost_content_statement "$type" "$protocol" "$cfg" "${domain:-html}") || return 1
  604. case "$protocol" in
  605. https)
  606. PORT=443
  607. ;;
  608. http)
  609. PORT=80
  610. ;;
  611. esac
  612. cat <<EOF
  613. <VirtualHost *:$PORT>
  614. $(
  615. {
  616. echo "$head_statement"
  617. [ "$custom_rules" ] && echo "$custom_rules"
  618. echo "$content_statement"
  619. } | prefix " ")
  620. ## Forbid any cache, this is only usefull on dev server.
  621. #Header set Cache-Control "no-cache"
  622. #Header set Access-Control-Allow-Origin "*"
  623. #Header set Access-Control-Allow-Methods "POST, GET, OPTIONS"
  624. #Header set Access-Control-Allow-Headers "origin, content-type, accept"
  625. $([ "$protocol" == "https" ] && __vhost_ssl_statement | prefix " " && echo )
  626. </VirtualHost>
  627. EOF
  628. }
  629. __vhost_publish_dir_statement() {
  630. local protocol="$1" cfg="$2" dest="$3" dest
  631. local_path="/var/www/${dest}"
  632. cat <<EOF
  633. ##
  634. ## Publish directory $local_path
  635. ##
  636. DocumentRoot $local_path
  637. <Directory />
  638. Options FollowSymLinks
  639. AllowOverride None
  640. </Directory>
  641. <Directory $local_path>
  642. Options Indexes FollowSymLinks MultiViews
  643. AllowOverride all
  644. $(__vhost_creds_statement "$cfg" "$dest" | prefix " ")
  645. </Directory>
  646. EOF
  647. }
  648. __vhost_tunnel_ssh_statement() {
  649. local protocol="$1" cfg="$2" dest="$3" custom_rules content_statement
  650. cat <<EOF
  651. ##
  652. ## SSH Tunnel
  653. ##
  654. #HostnameLookups On
  655. ProxyRequests On
  656. AllowConnect 22
  657. #ProxyVia on
  658. ### Deny everything by default
  659. <Proxy *>
  660. Order deny,allow
  661. Deny from all
  662. </proxy>
  663. ### Accept redirect only to same domain
  664. <Proxy $domain>
  665. Order deny,allow
  666. $(__vhost_creds_statement "$cfg" "$dest" | prefix " ")
  667. </Proxy>
  668. EOF
  669. }
  670. apache_config_hash() {
  671. debug "Adding config hash to enable recreating upon config change."
  672. config_hash=$({
  673. printf "%s\0" "$config_hash"
  674. find "$SERVICE_CONFIGSTORE/etc/apache2/sites-enabled" \
  675. -name \*.conf -exec md5sum {} \;
  676. } | md5_compat) || exit 1
  677. init-config-add "
  678. $SERVICE_NAME:
  679. labels:
  680. - compose.config_hash=$config_hash
  681. "
  682. }