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.

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