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