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.

861 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. [ "$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 type elt value first
  520. custom_rules=$(e "$cfg" | cfg-get-value apache-custom-rules 2>/dev/null) || true
  521. if [ -z "$custom_rules" -o "$custom_rules" == "None" ]; then
  522. return 0
  523. fi
  524. type=$(echo "$custom_rules" | shyaml get-type)
  525. value=
  526. case "$type" in
  527. "sequence")
  528. first=1
  529. while read-0 elt; do
  530. elt="$(echo "$elt" | yaml_get_interpret)" || return 1
  531. [ "$elt" ] || continue
  532. if [ "$first" ]; then
  533. first=
  534. else
  535. value+=$'\n'
  536. fi
  537. first=
  538. value+="$elt"
  539. done < <(echo "$custom_rules" | shyaml -y get-values-0)
  540. ;;
  541. "struct")
  542. while read-0 _key val; do
  543. value+=$'\n'"$(echo "$val" | yaml_get_interpret)" || return 1
  544. done < <(echo "$custom_rules" | shyaml -y key-values-0)
  545. ;;
  546. "str")
  547. value+=$(echo "$custom_rules")
  548. ;;
  549. "NoneType")
  550. value=""
  551. ;;
  552. *)
  553. value+=$(echo "$custom_rules")
  554. ;;
  555. esac
  556. printf "%s" "$value"
  557. }
  558. __vhost_custom_rules() {
  559. local cfg="$1" custom_rules
  560. custom_rules=$(_get_custom_rules "$cfg") || return 1
  561. if [ "$custom_rules" ]; then
  562. cat <<EOF
  563. ##
  564. ## Custom rules
  565. ##
  566. $custom_rules
  567. EOF
  568. fi
  569. }
  570. __vhost_content_statement() {
  571. local type="$1"
  572. shift
  573. case "$type" in
  574. "web_proxy")
  575. __vhost_proxy_statement "$@" || return 1
  576. ;;
  577. "publish_dir")
  578. __vhost_publish_dir_statement "$@" || return 1
  579. ;;
  580. "ssh_tunnel")
  581. __vhost_tunnel_ssh_statement "$@" || return 1
  582. ;;
  583. esac
  584. }
  585. target-get() {
  586. local cfg="$1" target first_exposed_port base_image
  587. target=$(e "$cfg" | cfg-get-value target 2>/dev/null) || true
  588. if [ -z "$target" ]; then
  589. ## First exposed port:
  590. base_image=$(service_base_docker_image "$BASE_SERVICE_NAME") || return 1
  591. if ! docker_has_image "$base_image"; then
  592. docker pull "$base_image" >&2
  593. fi
  594. first_exposed_port=$(image_exposed_ports_0 "$base_image" | tr '\0' '\n' | head -n 1 | cut -f 1 -d /) || return 1
  595. if [ -z "$first_exposed_port" ]; then
  596. err "Failed to get first exposed port of image '$base_image'."
  597. return 1
  598. fi
  599. target=$MASTER_BASE_SERVICE_NAME:$first_exposed_port
  600. info "No target was specified, introspection found: $target"
  601. fi
  602. echo "$target"
  603. }
  604. __vhost_proxy_statement() {
  605. local protocol="$1" cfg="$2" dest="$3"
  606. target=$(target-get "$cfg") || return 1
  607. cat <<EOF
  608. ##
  609. ## Proxy declaration towards $target
  610. ##
  611. <IfModule mod_proxy.c>
  612. ProxyRequests Off
  613. <Proxy *>
  614. Order deny,allow
  615. Allow from all
  616. </Proxy>
  617. ProxyVia On
  618. ProxyPass / http://$target/ retry=0
  619. <Location / >
  620. $(__vhost_creds_statement "$cfg" "$dest" | prefix " ")
  621. ProxyPassReverse /
  622. </Location>
  623. $([ "$protocol" == "https" ] && echo " SSLProxyEngine On")
  624. </IfModule>
  625. RequestHeader set "X-Forwarded-Proto" "$protocol"
  626. ## Fix IE problem (httpapache proxy dav error 408/409)
  627. SetEnv proxy-nokeepalive 1
  628. EOF
  629. }
  630. __vhost_full_vhost_statement() {
  631. local type="$1" protocol="$2" cfg="$3" domain="$4" head_statement custom_rules content_statement
  632. head_statement=$(__vhost_head_statement "$cfg" "$protocol" "$domain") || return 1
  633. custom_rules=$(__vhost_custom_rules "$cfg") || return 1
  634. content_statement=$(__vhost_content_statement "$type" "$protocol" "$cfg" "${domain:-html}") || return 1
  635. case "$protocol" in
  636. https)
  637. PORT=443
  638. ;;
  639. http)
  640. PORT=80
  641. ;;
  642. esac
  643. cat <<EOF
  644. <VirtualHost *:$PORT>
  645. $(
  646. {
  647. echo "$head_statement"
  648. [ "$custom_rules" ] && echo "$custom_rules"
  649. echo "$content_statement"
  650. } | prefix " ")
  651. ## Forbid any cache, this is only usefull on dev server.
  652. #Header set Cache-Control "no-cache"
  653. #Header set Access-Control-Allow-Origin "*"
  654. #Header set Access-Control-Allow-Methods "POST, GET, OPTIONS"
  655. #Header set Access-Control-Allow-Headers "origin, content-type, accept"
  656. $([ "$protocol" == "https" ] && __vhost_ssl_statement | prefix " " && echo )
  657. </VirtualHost>
  658. EOF
  659. }
  660. __vhost_publish_dir_statement() {
  661. local protocol="$1" cfg="$2" dest="$3" dest
  662. local_path="/var/www/${dest}"
  663. cat <<EOF
  664. ##
  665. ## Publish directory $local_path
  666. ##
  667. DocumentRoot $local_path
  668. <Directory />
  669. Options FollowSymLinks
  670. AllowOverride None
  671. </Directory>
  672. <Directory $local_path>
  673. Options Indexes FollowSymLinks MultiViews
  674. AllowOverride all
  675. $(__vhost_creds_statement "$cfg" "$dest" | prefix " ")
  676. </Directory>
  677. EOF
  678. }
  679. __vhost_tunnel_ssh_statement() {
  680. local protocol="$1" cfg="$2" dest="$3" custom_rules content_statement
  681. cat <<EOF
  682. ##
  683. ## SSH Tunnel
  684. ##
  685. #HostnameLookups On
  686. ProxyRequests On
  687. AllowConnect 22
  688. #ProxyVia on
  689. ### Deny everything by default
  690. <Proxy *>
  691. Order deny,allow
  692. Deny from all
  693. </proxy>
  694. ### Accept redirect only to same domain
  695. <Proxy $domain>
  696. Order deny,allow
  697. $(__vhost_creds_statement "$cfg" "$dest" | prefix " ")
  698. </Proxy>
  699. EOF
  700. }
  701. apache_config_hash() {
  702. debug "Adding config hash to enable recreating upon config change."
  703. config_hash=$({
  704. printf "%s\0" "$config_hash"
  705. find "$SERVICE_CONFIGSTORE/etc/apache2/sites-enabled" \
  706. -name \*.conf -exec md5sum {} \;
  707. } | md5_compat) || exit 1
  708. init-config-add "
  709. $SERVICE_NAME:
  710. labels:
  711. - compose.config_hash=$config_hash
  712. "
  713. }