diff --git a/type/__recycledcloud_nginx/man.rst b/type/__recycledcloud_nginx/man.rst new file mode 100644 index 0000000..b1de718 --- /dev/null +++ b/type/__recycledcloud_nginx/man.rst @@ -0,0 +1,40 @@ +cdist-type__recycledcloud_nginx(7) +=================================== + +NAME +---- +cdist-type__recycledcloud_nginx - Serve web content with NGINX + + +DESCRIPTION +----------- +Leverages `__recycledcloud_nginx_vhost` to serve web content. + +REQUIRED PARAMETERS +------------------- +domain + Domain name to be served. + +OPTIONAL PARAMETERS +------------------- +config + Custom NGINX logic, templated within a standard `server` section with + `server_name` and TLS parameters set. Defaults to simple static hosting. + +altdomains + Alternative domain names for this vhost and related TLS certificate. + +uacme-hookscript + Custom hook passed to the __uacme_obtain type: useful to integrate the + dns-01 challenge with third-party DNS providers. + +AUTHORS +------- +Timothée Floure + +COPYING +------- +Copyright \(C) 2020 Joachim Desroches. You can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. diff --git a/type/__recycledcloud_nginx/manifest b/type/__recycledcloud_nginx/manifest new file mode 100644 index 0000000..75db7cd --- /dev/null +++ b/type/__recycledcloud_nginx/manifest @@ -0,0 +1,76 @@ +#!/bin/sh + +os="$(cat "${__global:?}"/explorer/os)" +case "$os" in + alpine) + nginx_user=nginx + nginx_certdir=/etc/nginx/ssl + ;; + debian|ubuntu) + nginx_user=www-data + nginx_certdir=/etc/nginx/ssl + ;; + *) + echo "This type does not support $os yet. Aborting." >&2; + exit 1; + ;; +esac + +if [ -f "${__object:?}/parameter/domain" ]; +then + domain="$(cat "${__object:?}/parameter/domain")" +else + domain="${__object_id:?}" +fi + +altdomains= +if [ -f "${__object:?}/parameter/altdomains" ]; +then + altdomains="$(cat "${__object:?}/parameter/altdomains")" +fi + +set_custom_uacme_hookscript= +if [ -f "${__object:?}/parameter/uacme-hookscript" ]; +then + uacme_hookscript="$(cat "${__object:?}/parameter/uacme-hookscript")" + set_custom_uacme_hookscript="--hookscript $uacme_hookscript" +fi + +# Deploy simple HTTP vhost, allowing to serve ACME challenges. +__recycledcloud_nginx_vhost "301-to-https-$domain" \ + --domain "$domain" --altdomains "$altdomains" --to-https + +# Obtaining TLS cert. +cert_ownership=$nginx_user +if [ -f "${__object:?}/parameter/force-cert-ownership-to" ]; then + cert_ownership=$(cat "${__object:?}/parameter/force-cert-ownership-to") +fi + +__uacme_account +# shellcheck disable=SC2086 +require="__recycledcloud_nginx_vhost/301-to-https-$domain __uacme_account" \ + __uacme_obtain "$domain" \ + --altdomains "$altdomains" \ + $set_custom_uacme_hookscript \ + --owner "$cert_ownership" \ + --install-key-to "$nginx_certdir/$domain/privkey.pem" \ + --install-cert-to "/$nginx_certdir/$domain/fullchain.pem" \ + --renew-hook "service nginx reload" + +# Deploy HTTPS nginx vhost. +if [ -f "${__object:?}/parameter/config" ]; then + if [ "$(cat "${__object:?}/parameter/config")" = "-" ]; then + nginx_logic="${__object:?}/stdin" + else + nginx_logic="${__object:?}/parameter/config" + fi + + mkdir -p "${__object:?}/files" + cat "$nginx_logic" > "${__object:?}/files/config" + + require="__uacme_obtain/$domain" __recycledcloud_nginx_vhost "$domain" \ + --altdomains "$altdomains" --config "${__object:?}/files/config" +else + require="__uacme_obtain/$domain" __recycledcloud_nginx_vhost "$domain" \ + --altdomains "$altdomains" +fi diff --git a/type/__recycledcloud_nginx/parameter/default/http-port b/type/__recycledcloud_nginx/parameter/default/http-port new file mode 100644 index 0000000..d15a2cc --- /dev/null +++ b/type/__recycledcloud_nginx/parameter/default/http-port @@ -0,0 +1 @@ +80 diff --git a/type/__recycledcloud_nginx/parameter/default/https-port b/type/__recycledcloud_nginx/parameter/default/https-port new file mode 100644 index 0000000..6a13cf6 --- /dev/null +++ b/type/__recycledcloud_nginx/parameter/default/https-port @@ -0,0 +1 @@ +443 diff --git a/type/__recycledcloud_nginx/parameter/optional b/type/__recycledcloud_nginx/parameter/optional new file mode 100644 index 0000000..1a5fb95 --- /dev/null +++ b/type/__recycledcloud_nginx/parameter/optional @@ -0,0 +1,5 @@ +config +domain +altdomains +uacme-hookscript +force-cert-ownership-to diff --git a/type/__recycledcloud_nginx_vhost/files/301-to-https b/type/__recycledcloud_nginx_vhost/files/301-to-https new file mode 100644 index 0000000..2675732 --- /dev/null +++ b/type/__recycledcloud_nginx_vhost/files/301-to-https @@ -0,0 +1,4 @@ +# Redirect request to this page in HTTPS. +location / { + return 301 https://$host$request_uri; +} diff --git a/type/__recycledcloud_nginx_vhost/files/generic.conf.sh b/type/__recycledcloud_nginx_vhost/files/generic.conf.sh new file mode 100755 index 0000000..13e36aa --- /dev/null +++ b/type/__recycledcloud_nginx_vhost/files/generic.conf.sh @@ -0,0 +1,37 @@ +#!/bin/sh +# Template for static NGINX hosting. + +echo 'server {' + +# Listen +cat <<- EOF + listen ${LPORT:?} $TLS; + listen [::]:${LPORT:?} $TLS; +EOF + +# Name +echo "server_name ${DOMAIN:?} $ALTDOMAINS;" + +# ACME challenges. +cat << EOF +location /.well-known/acme-challenge/ { + alias ${ACME_CHALLENGE_DIR:?}; +} +EOF + +if [ -n "$TLS" ]; +then + if [ -n "$HSTS" ]; + then + echo 'include snippets/hsts;' + fi + + cat <<- EOF + ssl_certificate ${NGINX_CERTDIR:?}/${DOMAIN:?}/fullchain.pem; + ssl_certificate_key ${NGINX_CERTDIR:?}/${DOMAIN:?}/privkey.pem; + EOF +fi + +echo "${NGINX_LOGIC:?}" + +echo '}' diff --git a/type/__recycledcloud_nginx_vhost/files/hsts b/type/__recycledcloud_nginx_vhost/files/hsts new file mode 100644 index 0000000..7e4a854 --- /dev/null +++ b/type/__recycledcloud_nginx_vhost/files/hsts @@ -0,0 +1 @@ +add_header Strict-Transport-Security "max-age=31536000" always; diff --git a/type/__recycledcloud_nginx_vhost/files/index.html b/type/__recycledcloud_nginx_vhost/files/index.html new file mode 100644 index 0000000..bcadf4d --- /dev/null +++ b/type/__recycledcloud_nginx_vhost/files/index.html @@ -0,0 +1,12 @@ + + + + + + cdist configured! + + + You have successfully configured a vhost with + cdist. You can now upload content! + + diff --git a/type/__recycledcloud_nginx_vhost/files/static.conf.sh b/type/__recycledcloud_nginx_vhost/files/static.conf.sh new file mode 100755 index 0000000..363f228 --- /dev/null +++ b/type/__recycledcloud_nginx_vhost/files/static.conf.sh @@ -0,0 +1,13 @@ +#!/bin/sh +# Template for static NGINX hosting. + +NGINX_LOGIC="$(cat << EOF + location / { + root ${NGINX_WEBROOT:?}/${DOMAIN:?}; + index index.html; + } +EOF +)" +export NGINX_LOGIC + +"${__type:?}/files/generic.conf.sh" diff --git a/type/__recycledcloud_nginx_vhost/files/to-https.conf.sh b/type/__recycledcloud_nginx_vhost/files/to-https.conf.sh new file mode 100755 index 0000000..77dd45b --- /dev/null +++ b/type/__recycledcloud_nginx_vhost/files/to-https.conf.sh @@ -0,0 +1,25 @@ +#!/bin/sh +# Template for HTTPS redirection. + +echo 'server {' + +# Listen +cat <<- EOF + listen ${LPORT:?}; + listen [::]:${LPORT:?}; +EOF + +# Name +echo "server_name ${DOMAIN:?} $ALTDOMAINS;" + +# ACME challenges. +cat << EOF +location /.well-known/acme-challenge/ { + alias ${ACME_CHALLENGE_DIR:?}; +} +EOF + +# HTTPS redirection. +echo 'include snippets/301-to-https;' + +echo '}' diff --git a/type/__recycledcloud_nginx_vhost/gencode-remote b/type/__recycledcloud_nginx_vhost/gencode-remote new file mode 100644 index 0000000..d634d83 --- /dev/null +++ b/type/__recycledcloud_nginx_vhost/gencode-remote @@ -0,0 +1,26 @@ +#!/bin/sh + +os="$(cat "${__global:?}"/explorer/os)" + +case "$os" in + alpine) + reload_hook="service nginx --ifstopped start;\ + service nginx --ifstarted reload" + ;; + debian|ubuntu|*) + reload_hook="systemctl reload-or-restart nginx" + ;; +esac + +# Check configuration and reload if valid. +# TODO: only check if configuration was changed (= listen for __file's +# messages). +cat << EOF +if nginx -t; then + $reload_hook +else + echo "NGINX configuration is invalid. Exiting." >2& + nginx -t >2& + exit 1 +fi +EOF diff --git a/type/__recycledcloud_nginx_vhost/man.rst b/type/__recycledcloud_nginx_vhost/man.rst new file mode 100644 index 0000000..28767d7 --- /dev/null +++ b/type/__recycledcloud_nginx_vhost/man.rst @@ -0,0 +1,83 @@ +cdist-type__nginx_vhost(7) +=================================== + +NAME +---- +cdist-type__nginx_vhost - Have nginx serve content for a virtual host + + +DESCRIPTION +----------- +This type setups up nginx with reasonable defaults and creates a vhost to be +served, optionally with TLS certificates obtained from the Let's Encrypt CA +through the ACME HTTP-01 challenge-response mechanism. + +By default, if no rules are specified, then the vhost will serve as-is the +contents of the `WEBROOT/foo.com` directory, where WEBROOT is +determined depending on the OS, adhering as close to `hier(7)` as possible. + +NGINX expects files in the vhost to be served to be at least readable by the +`USER` group, that it creates if it does not exist. It is recommended to have +the user owning the files to be someone else, and the files beeing +group-readable but not writeable. + +Finally, if TLS is not disabled, then this type makes nginx expect the +fullchain certificate and the private key in +`CERTDIR/domain/{fullchain,privkey}.pem`. + ++------------------+---------+-------------------+-----------------------------+ +| Operating System | USER | WEBROOT | CERTDIR | ++==================+=========+===================+=============================+ +| Alpine Linux | `nginx` | `/srv/www/` | `/etc/nginx/ssl/` | ++------------------+---------+-------------------+-----------------------------+ +| Arch Linux | `www` | `/srv/www/` | `/etc/nginx/ssl/` | ++------------------+---------+-------------------+-----------------------------+ +| FreeBSD | `www` | `/usr/local/www/` | `/usr/local/etc/nginx/ssl/` | ++------------------+---------+-------------------+-----------------------------+ + +OPTIONAL PARAMETERS +------------------- + +config + A custom configuration file for the vhost, inserted in a server section + populated with `server_name` and TLS parameters unless `--standalone-config` + is specified. Can be specified either as a file path, or if the value of this + flag is '-', then the configuration is read from stdin. + +domain + The domain this server will respond to. If this is omitted, then the + `__object_id` is used. + +lport + The port to which we listen. If this is omitted, the defaults of `80` for + HTTP and `443` for HTTPS are used. + +altdomains + Alternative domain names for this vhost. + +BOOLEAN PARAMETERS +------------------ + +no-hsts + Do not use HSTS pinning. + +no-tls + Do not serve over HTTPS. + +to-https + Ignore --config flag and redirect to HTTPS. Implies --no-tls. + +standalone-config + Insert the content of + +AUTHORS +------- +Joachim Desroches +Timothée Floure + +COPYING +------- +Copyright \(C) 2020 Joachim Desroches. You can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. diff --git a/type/__recycledcloud_nginx_vhost/manifest b/type/__recycledcloud_nginx_vhost/manifest new file mode 100644 index 0000000..3e7b913 --- /dev/null +++ b/type/__recycledcloud_nginx_vhost/manifest @@ -0,0 +1,162 @@ +#!/bin/sh +# +# 2020 Joachim Desroches +# +# This file is part of cdist. +# +# cdist is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# cdist is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with cdist. If not, see . +# +# Create NGINX vhosts + +os="$(cat "${__global:?}"/explorer/os)" +mkdir -p "${__object:?}/files" + +case "$os" in + alpine) + __package nginx + + nginx_confdir="/etc/nginx" + install_reqs="__package/nginx" + + require="$install_reqs" __start_on_boot nginx + + export NGINX_SITEDIR="$nginx_confdir/conf.d" + export NGINX_CERTDIR="$nginx_confdir/ssl" + export NGINX_SNIPPETSDIR="$nginx_confdir/snippets" + export NGINX_WEBROOT="/var/www" + export ACME_CHALLENGE_DIR="$NGINX_WEBROOT/.well-known/acme-challenge/" + ;; + debian|ubuntu) + __package nginx + + nginx_confdir="/etc/nginx" + install_reqs="__package/nginx" + + export NGINX_SITEDIR="$nginx_confdir/sites-enabled" + export NGINX_CERTDIR="$nginx_confdir/ssl" + export NGINX_SNIPPETSDIR="$nginx_confdir/snippets" + export NGINX_WEBROOT="/var/www" + export ACME_CHALLENGE_DIR="$NGINX_WEBROOT/.well-known/acme-challenge/" + ;; + *) + echo "This type does not support $os yet. Aborting." >&2; + exit 1; +esac + +# Domain +if [ -f "${__object:?}/parameter/domain" ]; +then + DOMAIN="$(cat "${__object:?}/parameter/domain")" +else + DOMAIN="${__object_id:?}" +fi +export DOMAIN + +ALTDOMAINS= +if [ -f "${__object:?}/parameter/altdomains" ]; +then + ALTDOMAINS="$(cat "${__object:?}/parameter/altdomains")" +fi +export ALTDOMAINS + +# Use TLS ? +if [ -f "${__object:?}/parameter/no-tls" ]; +then + TLS= + echo "WARNING: you have disabled TLS for vhost $DOMAIN" >&2 +else + TLS=ssl +fi +export TLS + +# Use HSTS ? +if [ -f "${__object:?}/parameter/no-hsts" ]; +then + HSTS= +else + HSTS=true +fi +export HSTS + +# Redirect to HTTPS ? +if [ -f "${__object:?}/parameter/to-https" ]; +then + TO_HTTPS=true +else + TO_HTTPS= +fi +export HSTS + +# Port to listen on +if [ -f "${__object:?}/parameter/lport" ]; +then + LPORT="$(cat "${__object:?}/parameter/lport")" +else + if [ -n "$TLS" ] && [ -z "$TO_HTTPS" ]; + then + LPORT=443 + else + LPORT=80 + fi +fi +export LPORT + +# Server definition +if [ -n "$TO_HTTPS" ]; +then + # Ignore configuration, simply serve ACME challenge and redirect to HTTPS. + "${__type:?}/files/to-https.conf.sh" > "${__object:?}/files/vhost.conf" + vhost_conf="${__object:?}/files/vhost.conf" +elif [ -f "${__object:?}/parameter/config" ]; +then + # Extract nginx config from type parameter. + if [ "$(cat "${__object:?}/parameter/config")" = "-" ]; + then + vhost_partial="${__object:?}/stdin" + else + vhost_partial=$(cat "${__object:?}/parameter/config") + fi + + # Either use config as-in or template it in generic vhost structure. + if [ -f "${__object:?}/parameter/standalone-config" ]; then + vhost_conf=$vhost_partial + else + NGINX_LOGIC=$(cat "$vhost_partial") "${__type:?}/files/generic.conf.sh" \ + > "${__object:?}/files/vhost.conf" + + vhost_conf="${__object:?}/files/vhost.conf" + fi +else + # Default to simple static configuration. + "${__type:?}/files/static.conf.sh" > "${__object:?}/files/vhost.conf" + vhost_conf="${__object:?}/files/vhost.conf" + + require="$install_reqs" __directory "$NGINX_WEBROOT/$DOMAIN" + require="__directory$NGINX_WEBROOT/$DOMAIN" \ + __file "$NGINX_WEBROOT/$DOMAIN/index.html" --state exists \ + --source "${__type:?}/files/index.html" \ + --mode 0644 +fi + +# Install snippets. +require="$install_reqs" __directory "$NGINX_SNIPPETSDIR" +for snippet in hsts 301-to-https; do + require="__directory/$NGINX_SNIPPETSDIR" __file \ + "$NGINX_SNIPPETSDIR/$snippet" --source "${__type:?}/files/$snippet" +done + +# Install vhost. +require="$install_reqs" __file "$NGINX_SITEDIR/$__object_id.conf" \ + --source "$vhost_conf" \ + --mode 0644 diff --git a/type/__recycledcloud_nginx_vhost/parameter/boolean b/type/__recycledcloud_nginx_vhost/parameter/boolean new file mode 100644 index 0000000..aa06036 --- /dev/null +++ b/type/__recycledcloud_nginx_vhost/parameter/boolean @@ -0,0 +1,4 @@ +no-tls +no-hsts +to-https +standalone-config diff --git a/type/__recycledcloud_nginx_vhost/parameter/default/index b/type/__recycledcloud_nginx_vhost/parameter/default/index new file mode 100644 index 0000000..d5b7a40 --- /dev/null +++ b/type/__recycledcloud_nginx_vhost/parameter/default/index @@ -0,0 +1 @@ +index.html index.htm diff --git a/type/__recycledcloud_nginx_vhost/parameter/optional b/type/__recycledcloud_nginx_vhost/parameter/optional new file mode 100644 index 0000000..9c47616 --- /dev/null +++ b/type/__recycledcloud_nginx_vhost/parameter/optional @@ -0,0 +1,4 @@ +domain +config +altdomains +lport