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.

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