Nginx

Objectifs de certification

LPI 202

  • Sujet 208 : Services Web
    • 208.4 Mise en place de Nginx en tant que serveur Web et proxy inverse (valeur : 2)

RHCE EX300 pour mémoire

  1. HTTP/HTTPS
    • 3.1. Configure a virtual host.
    • 3.2. Configure private directories.
    • 3.3. Deploy a basic CGI application.
    • 3.4. Configure group-managed content.
    • 3.5. Configure TLS security.

Présentation

Node.js

Sources : https://fr.wikipedia.org/wiki/Node.js et https://nodejs.org/

Source de l’image

Node.js est une plateforme logicielle libre et événementielle en JavaScript orientée vers les applications réseau qui doivent pouvoir monter en charge.

Elle utilise la machine virtuelle V8 et implémente sous licence MIT les spécifications CommonJS.

Node.js contient une bibliothèque de serveur HTTP intégrée, ce qui rend possible de faire tourner un serveur web sans avoir besoin d’un logiciel externe comme Apache ou lighttpd, et permettant de mieux contrôler la façon dont le serveur web fonctionne.

Concrètement, node.js est un environnement d’assez bas niveau permettant d’exécuter du JavaScript non plus dans le navigateur web mais sur le serveur.

Ghost.io

Source de l’image

Source : https://fr.wikipedia.org/wiki/Ghost_(moteur_de_blog)

Ghost est un moteur de blog libre et open source écrit en Node.js, un moteur d’exécution JavaScript côté serveur, basé sur V8 de Google. Il est distribué sous licence MIT. Ghost est conçu pour simplifier le processus de publication en ligne par des blogueurs.

Source : https://ghost.org/fr/features/

Source de l’image

Ngnix

Source : https://fr.wikipedia.org/wiki/Nginx

Source de l’image

Nginx (prononcé [ɛndʒən iks]) est un logiciel libre de serveur Web (ou HTTP) ainsi qu’un proxy inverse écrit par Igor Sysoev, dont le développement a débuté en 2002 pour les besoins d’un site russe à très fort trafic (Rambler). Ses sources sont disponibles sous une licence de type BSD.

Nginx est un serveur asynchrone par opposition aux serveurs synchrones où chaque requête est traitée par un processus dédié. Au lieu d’exploiter une architecture parallèle et un multiplexage temporel des tâches par le système d’exploitation, Nginx utilise les changements d’état pour gérer plusieurs connexions en même temps ; le traitement de chaque requête est découpé en de nombreuses mini-tâches et permet ainsi de réaliser un multiplexage efficace entre les connexions. Afin de tirer parti des ordinateurs multiprocesseurs, plusieurs processus peuvent être démarrés. Ce choix d’architecture se traduit par des performances très élevées, mais également par une charge et une consommation de mémoire particulièrement faibles comparativement aux serveurs HTTP classiques, tels qu’Apache.

Nginx est très modulaire : un noyau minimal et des modules, nombreux, venant compléter les fonctions de base. Chaque module peut agir comme un filtre sur le contenu en entrée, en sortie ou intermédiaire (proxy) par le biais de nombreuses callbacks. Ainsi, à titre d’exemple, un contenu dynamique peut être compressé à la volée par le module « gzip » avant envoi.

Outre le fait d’être un serveur HTTP, Nginx peut être configuré pour être un proxy inverse (en anglais : reverse proxy) Web et un serveur proxy de messagerie électronique (IMAP / POP3). L’utilisation la plus fréquente de Nginx est de le configurer comme un serveur Web classique pour servir des fichiers statiques et comme un proxy pour les requêtes dynamiques typiquement acheminées en utilisant une interface FastCGI vers un ou des serveurs applicatifs avec un mécanisme de répartition de charge.

Nginx est également capable de diffuser, selon le même principe que lighttpd avec mod_flv_streaming, du contenu vidéo par streaming vers un lecteur Flash sans avoir à recourir à Flash Media Server. Pour cela, il comporte un module optionnel http_glv_module de streaming de fichier vidéo flv et plusieurs modules de streaming qui peuvent diffuser une vidéo encodée en H.264. Il permet également de diffuser du mp4 grâce à son module optionnel http_mp4_module.

Il est aussi très utilisé en production pour servir des applications en Ruby on Rails grâce au module Phusion Passenger.

Letsencrypt

Source : https://fr.wikipedia.org/wiki/Let%27s_Encrypt

Source de l’image

Let’s Encrypt (abrégé LE) est une autorité de certification lancée le 3 décembre 2015 (Bêta Version Publique). Cette autorité fournit des certificats gratuits X.509 pour le protocole cryptographique TLS au moyen d’un processus automatisé destiné à se passer du processus complexe actuel impliquant la création manuelle, la validation, la signature, l’installation et le renouvellement des certificats pour la sécurisation des sites internet1. En septembre 2016, plus de 10 millions de certificats ont été délivrés.

Le projet vise à généraliser l’usage de connexions sécurisées sur l’internet. En supprimant la nécessité de paiement, de la configuration du serveur web, des courriels de validation et de gestion de l’expiration des certificats, le projet est fait pour réduire de manière significative la complexité de la mise en place et de la maintenance du chiffrement TLS. Sur un serveur GNU/Linux, l’exécution de seulement deux commandes4 est censée être suffisante pour paramétrer le chiffrement HTTPS, l’acquisition et l’installation de certificats, et ceci en quelques dizaines de secondes.

À cette fin, l’inclusion d’un paquet logiciel dans les dépôts logiciel Debian est en cours; toutefois le paquet est disponible sur GitHub : https://letsencrypt.org/getting-started/, https://certbot.eff.org/.

Cloudflare

Source : https://fr.wikipedia.org/wiki/Cloudflare

Source de l’image

Cloudflare est un service de proxy inverse, permettant principalement de lutter contre les attaques de déni de service et, dans une certaine mesure, de cacher l’adresse IP d’origine d’un serveur. Il propose également des fonctionnalités d’optimisation des pages, de détection d’intrusion ou encore de CDN.

Tout le trafic d’un site utilisant le service passe par le réseau Cloudflare, réparti à travers une centaine de points de présence dans le monde.

On peut dénombrer ses fonctionnalités :

  • DDoS protection
  • Web application firewall
  • Domain name server
  • Reverse proxy
  • Content delivery network

On l’utilisera ici dans le cadre d’une automatisation d’entrées DNS via son API.

https://api.cloudflare.com/

Blog Ghost en Node.js avec Nginx

  • FQDN : blog1.example.com
  • TLS seulement
  • Node.js
  • Ghost.io
  • Ngnix
  • Let’s-encrypt
  • Firewalld
  • Cloudflare

Testé : Ubuntu 16.04 Xenial

Installation de Node.js

Debian / Ubuntu

Mise à jour du système :

apt-get update && apt-get -y upgrade
apt-get -y dist-upgrade

Enfin l’installation, la version >=4.2 <5.* (Node v4 argon LTS) est recommandée :

curl -sL https://deb.nodesource.com/setup_4.x | sudo bash -
apt-get install -y nodejs

Vérification sommaire de l’installation

La commande nodejs -v devrait fournir un résultat similaire :

nodejs -v
v4.8.2

Installation de Ghost

Variables

SITE="blog1"
ZONE="example.com"
MAIL="root@example.com"

Téléchargement de la dernière version de Ghost :

wget https://ghost.org/zip/ghost-latest.zip

Décompression dans un dossier d’hébergement /var/www/$SITE

mkdir /var/www
apt-get install unzip
unzip -d /var/www/$SITE ghost-latest.zip

Installation du logiciel :

cd /var/www/$SITE
npm install --production

Configuration de Ghost

Le fichier de configuration config.js peut se construire à partir d’un fichier d’exemple présent à la racine.

cp config.example.js config.js

On peut afficher le contenu de ce fichier :

cat config.js

On y trouvera trois profils :

  • “production” (url publique et sqlite3)
  • “development” (url locale et exemple de configuration de courrier électronique)
  • “testing” ()
// # Ghost Configuration
// Setup your Ghost install for various [environments](http://support.ghost.org/config/#about-environments).

// Ghost runs in `development` mode by default. Full documentation can be found at http://support.ghost.org/config/

var path = require('path'),
    config;

config = {
    // ### Production
    // When running Ghost in the wild, use the production environment.
    // Configure your URL and mail settings here
    production: {
        url: 'http://my-ghost-blog.com',
        mail: {},
        database: {
            client: 'sqlite3',
            connection: {
                filename: path.join(__dirname, '/content/data/ghost.db')
            },
            debug: false
        },

        server: {
            host: '127.0.0.1',
            port: '2368'
        }
    },

    // ### Development **(default)**
    development: {
        // The url to use when providing links to the site, E.g. in RSS and email.
        // Change this to your Ghost blog's published URL.
        url: 'http://localhost:2368',

        // Example refferer policy
        // Visit https://www.w3.org/TR/referrer-policy/ for instructions
        // default 'origin-when-cross-origin',
        // referrerPolicy: 'origin-when-cross-origin',

        // Example mail config
        // Visit http://support.ghost.org/mail for instructions
        //
        //  mail: {
        //      transport: 'SMTP',
        //      options: {
        //          service: 'Mailgun',
        //          auth: {
        //              user: '', // mailgun username
        //              pass: ''  // mailgun password
        //          }
        //      }
        //  },
        //

        // #### Database
        // Ghost supports sqlite3 (default), MySQL & PostgreSQL
        database: {
            client: 'sqlite3',
            connection: {
                filename: path.join(__dirname, '/content/data/ghost-dev.db')
            },
            debug: false
        },
        // #### Server
        // Can be host & port (default), or socket
        server: {
            // Host to be passed to node's `net.Server#listen()`
            host: '127.0.0.1',
            // Port to be passed to node's `net.Server#listen()`, for iisnode set this to `process.env.PORT`
            port: '2368'
        },
        // #### Paths
        // Specify where your content directory lives
        paths: {
            contentPath: path.join(__dirname, '/content/')
        }
    },

    // **Developers only need to edit below here**

    // ### Testing
    // Used when developing Ghost to run tests and check the health of Ghost
    // Uses a different port number
    testing: {
        url: 'http://127.0.0.1:2369',
        database: {
            client: 'sqlite3',
            connection: {
                filename: path.join(__dirname, '/content/data/ghost-test.db')
            },
            pool: {
                afterCreate: function (conn, done) {
                    conn.run('PRAGMA synchronous=OFF;' +
                    'PRAGMA journal_mode=MEMORY;' +
                    'PRAGMA locking_mode=EXCLUSIVE;' +
                    'BEGIN EXCLUSIVE; COMMIT;', done);
                }
            },
            useNullAsDefault: true
        },
        server: {
            host: '127.0.0.1',
            port: '2369'
        },
        logging: false
    },

    // ### Testing MySQL
    // Used by Travis - Automated testing run through GitHub
    'testing-mysql': {
        url: 'http://127.0.0.1:2369',
        database: {
            client: 'mysql',
            connection: {
                host     : '127.0.0.1',
                user     : 'root',
                password : '',
                database : 'ghost_testing',
                charset  : 'utf8'
            }
        },
        server: {
            host: '127.0.0.1',
            port: '2369'
        },
        logging: false
    },

    // ### Testing pg
    // Used by Travis - Automated testing run through GitHub
    'testing-pg': {
        url: 'http://127.0.0.1:2369',
        database: {
            client: 'pg',
            connection: {
                host     : '127.0.0.1',
                user     : 'postgres',
                password : '',
                database : 'ghost_testing',
                charset  : 'utf8'
            }
        },
        server: {
            host: '127.0.0.1',
            port: '2369'
        },
        logging: false
    }
};

module.exports = config;

Au choix, il est nécessaire de remplacer l’url du mode “production” :

sed -i s/my-ghost-blog.com/${SITE}.${ZONE}/ config.js

Test de démarrage du logiciel :

npm start --production
npm start --production

> ghost@0.11.7 start /var/www/blog1
> node index

WARNING: Ghost is attempting to use a direct method to send email.
It is recommended that you explicitly configure an email service.
Help and documentation can be found at http://support.ghost.org/mail.

Migrations: Creating tables...
Migrations: Creating table: posts
Migrations: Creating table: users
Migrations: Creating table: roles
Migrations: Creating table: roles_users
Migrations: Creating table: permissions
Migrations: Creating table: permissions_users
Migrations: Creating table: permissions_roles
Migrations: Creating table: permissions_apps
Migrations: Creating table: settings
Migrations: Creating table: tags
Migrations: Creating table: posts_tags
Migrations: Creating table: apps
Migrations: Creating table: app_settings
Migrations: Creating table: app_fields
Migrations: Creating table: clients
Migrations: Creating table: client_trusted_domains
Migrations: Creating table: accesstokens
Migrations: Creating table: refreshtokens
Migrations: Creating table: subscribers
Migrations: Running fixture populations
Migrations: Creating owner
Ghost is running in production...
Your blog is now available on http://blog1.example.com
Ctrl+C to shut down

Configuration de l’utilisateur ghost

adduser --shell /bin/bash --gecos 'Ghost application' ghost --disabled-password
chown -R ghost:ghost /var/www/$SITE
su - ghost
cd /var/www/$SITE
npm start --production
exit

Unité Systemd

Création d’un fichier /etc/systemd/system/$SITE.service

cat << EOF > /etc/systemd/system/$SITE.service
[Unit]
Description="Ghost $SITE"
After=network.target

[Service]
Type=simple

WorkingDirectory=/var/www/$SITE
User=ghost
Group=ghost

ExecStart=/usr/bin/npm start --production
ExecStop=/usr/bin/npm stop --production
Restart=always
SyslogIdentifier=Ghost

[Install]
WantedBy=multi-user.target
EOF

Installation et démarrage du service :

systemctl enable $SITE.service
systemctl start $SITE.service

Configuration du service de courrier électronique

Configuration Nginx comme proxy

apt-get install -y nginx

Effacer le site par défaut

rm /etc/nginx/sites-enabled/default
cat << EOF > /etc/nginx/sites-available/$SITE
server {
    listen 80;
    server_name ${SITE}.${ZONE};
    location / {
    proxy_set_header HOST \$host;
    proxy_set_header X-Forwarded-Proto \$scheme;
    proxy_set_header X-Real-IP \$remote_addr;
    proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
        proxy_pass         http://127.0.0.1:2368;
    }
}
EOF
ln -s /etc/nginx/sites-available/$SITE /etc/nginx/sites-enabled/$SITE
nginx -t

Devrait donner ce résultat :

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
systemctl enable nginx
systemctl status nginx
systemctl restart nginx

TLS avec Let’s Encrypt

Il est nécessaire de maîtriser la résolution de nom entre le nom du domaine HTTPS et l’adresse IP à l’écoute sur le serveur Web.

Configuration TLS pour Nginx.

openssl dhparam -dsaparam -out /etc/ssl/certs/dhparam.pem 2048
cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak
cat << EOF > /etc/nginx/nginx.conf
user www-data;
worker_processes auto;
pid /run/nginx.pid;

events {
	worker_connections 768;
	# multi_accept on;
}

http {

	##
	# Basic Settings
	##

	sendfile on;
	tcp_nopush on;
	tcp_nodelay on;
	keepalive_timeout 65;
	types_hash_max_size 2048;
	server_tokens off;

	server_names_hash_bucket_size 64;
	# server_name_in_redirect off;

	include /etc/nginx/mime.types;
	default_type application/octet-stream;

	##
	# SSL Settings
	##

  # from https://cipherli.st/
  # and https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html

  # Only the TLS protocol family
  ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
  ssl_prefer_server_ciphers on;
  # This will block IE6, Android 2.3 and older Java version from accessing your site, but these are the safest settings.
  ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
  # ECDH key exchange prevents all known feasible cryptanalytic attacks
  ssl_ecdh_curve secp384r1;
  # 20MB of cache will host about 80000 sessions
  ssl_session_cache shared:SSL:20m;
  # Session expires every 3 hours
  ssl_session_timeout 180m;
  ssl_session_tickets off;
  ssl_stapling on;
  ssl_stapling_verify on;
  # OCSP stapling using Google public DNS servers
  resolver 8.8.8.8 8.8.4.4 valid=300s;
  resolver_timeout 5s;
  add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";
  add_header X-Frame-Options DENY;
  add_header X-Content-Type-Options nosniff;

  ssl_dhparam /etc/ssl/certs/dhparam.pem;

	##
	# Logging Settings
	##

	access_log /var/log/nginx/access.log;
	error_log /var/log/nginx/error.log;

	##
	# Gzip Settings
	##

	gzip on;
	gzip_disable "msie6";

	# gzip_vary on;
	# gzip_proxied any;
	# gzip_comp_level 6;
	# gzip_buffers 16 8k;
	# gzip_http_version 1.1;
	# gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

	##
	# Virtual Host Configs
	##

	include /etc/nginx/conf.d/*.conf;
	include /etc/nginx/sites-enabled/*;
}


#mail {
#	# See sample authentication script at:
#	# http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript
#
#	# auth_http localhost/auth.php;
#	# pop3_capabilities "TOP" "USER";
#	# imap_capabilities "IMAP4rev1" "UIDPLUS";
#
#	server {
#		listen     localhost:110;
#		protocol   pop3;
#		proxy      on;
#	}
#
#	server {
#		listen     localhost:143;
#		protocol   imap;
#		proxy      on;
#	}
#}
EOF

Configuration du Site en HTTP

cat << EOF > /etc/nginx/sites-available/$SITE
server {
    listen 80;
    server_name ${SITE}.${ZONE};

    location ~ ^/.well-known {
        root /var/www/$SITE;
    }

    location / {
        return 301 https://\$server_name\$request_uri;
    }
}
EOF
ln -s /etc/nginx/sites-available/$SITE /etc/nginx/sites-enabled/$SITE
nginx -t
systemctl restart nginx

Installation du logiciel letsencrypt

apt-get -y install letsencrypt

Génération des certificats : voir https://certbot.eff.org/docs/using.html#getting-certificates-and-choosing-plugins

letsencrypt certonly -a webroot --webroot-path=/var/www/$SITE/ -d ${SITE}.${ZONE}

Nginx HTTPS seulement

cat << EOF > /etc/nginx/sites-available/$SITE
server {
        listen 80;

        server_name ${SITE}.${ZONE};

        location ~ ^/.well-known {
            root /var/www/$SITE;
        }

        location / {
            return 301 https://\$server_name\$request_uri;
        }
}

server {
        listen 443 ssl;

        server_name ${SITE}.${ZONE};

        location / {
                proxy_pass http://localhost:${tcp_port};
                proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
                proxy_set_header Host \$http_host;
                proxy_set_header X-Forwarded-Proto \$scheme;
                proxy_buffering off;
                proxy_redirect off;
        }

        ssl on;
        ssl_certificate /etc/letsencrypt/live/${SITE}.${ZONE}/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/${SITE}.${ZONE}/privkey.pem;

        ssl_prefer_server_ciphers On;
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS;

}
EOF

Redémarrage du service Proxy

systemctl stop nginx ; systemctl start nginx ; systemctl status nginx

Configuration du blog en HTTPS.

sed -i s/http/https/ config.js
chown -R ghost:ghost /var/www/$SITE
systemctl stop $SITE.service ; systemctl start $SITE.service ; systemctl status $SITE.service

Exécution de la tâche de renouvellement toutes les semaines.

crontab -e
30 2 * * 1 /usr/bin/letsencrypt renew >> /var/log/le-renew.log
35 2 * * 1 /bin/systemctl reload nginx

CertBot : https://certbot.eff.org/, https://certbot.eff.org/docs/

Firewall

apt-get install -y firewalld
systemctl enable firewalld
firewall-cmd --permanent --zone=public --add-port=443/tcp
firewall-cmd --permanent --zone=public --add-port=80/tcp
firewall-cmd --permanent --zone=public --add-interface=eth0
firewall-cmd --reload
firewall-cmd --permanent --zone=public --list-all

Fail2Ban

apt-get install -y fail2ban
systemctl enable fail2ban

Mise à jour des entrées DNS via l’API de Cloudflare

A condition de confier sa zone à Cloudflare.

Source : https://github.com/goffinet/cf-ddns.sh/blob/master/cf-ddns.sh

Par exemple :

MAIL="root@example.com"
TOKEN="your_token"
ZONE="example.com"
RECORD="blog1.example.com"
cf-ddns.sh --test -e=$MAIL -a=$TOKEN -z=$ZONE -r=$RECORD

Autre exemple : https://github.com/bAndie91/cloudflare-cli

Autre exemple : https://github.com/goffinet/cloudflare_api

Mais avec un peu de lecture, on arrive facilement à manipuler l’API :

#!/bin/bash
## 1. Set Variables
CF_EMAIL="root@example.com"
CF_TOKEN="your_api"
CF_ZONE="example.com"
CF_NAME="blog1"
CF_API_URL="https://api.cloudflare.com/client/v4"
curl_command='curl'
ip_wan=$(curl -s ipinfo.io/ip)
## 2. Get Zone ID
zones=`${curl_command} -s -X GET "${CF_API_URL}/zones?name=${CF_ZONE}" -H "X-Auth-Email: ${CF_EMAIL}" -H "X-Auth-Key: ${CF_TOKEN}" -H "Content-Type: application/json"`
zone=$(echo "${zones}" | grep -Po '(?<="id":")[^"]*' | head -1)
## 3. Get Record ID et IP Address of hostanme
records=`${curl_command} -s -X GET "${CF_API_URL}/zones/${zone}/dns_records?type=A&name=${CF_NAME}.${CF_ZONE}&page=1&per_page=20&order=type&direction=desc&match=all" -H "X-Auth-Email: ${CF_EMAIL}" -H "X-Auth-Key: ${CF_TOKEN}" -H "Content-Type: application/json"`
records_id=`echo "${records}" | grep -Po '(?<="id":")[^"]*'`
ip=`echo "${records}" | grep -Po '(?<="content":")[^"]*'`
## Check if Record exists
if [ "${ip}" == "${ip_wan}" ]; then
 echo "Noting to do"
fi
if [ ! "${ip}" == "${ip_wan}" ]; then
 echo "do update"
 ${curl_command} -s -X PUT "${CF_API_URL}/zones/${zone}/dns_records/${records_id}" -H "X-Auth-Email: ${CF_EMAIL}" -H "X-Auth-Key: ${CF_TOKEN}" -H "Content-Type: application/json" --data "{\"id\":\"${zone}\",\"type\":\"A\",\"name\":\"${CF_NAME}.${CF_ZONE}\",\"content\":\"${ip_wan}\"}"
fi
if [ -z "$records_id" ]; then
 echo "Please create the record ${CF_NAME}.${CF_ZONE}"
 ${curl_command} -s -X POST "${CF_API_URL}/zones/${zone}/dns_records" -H "X-Auth-Email: ${CF_EMAIL}" -H "X-Auth-Key: ${CF_TOKEN}" -H "Content-Type: application/json" --data "{\"id\":\"${zone}\",\"type\":\"A\",\"name\":\"${CF_NAME}.${CF_ZONE}\",\"content\":\"${ip_wan}\"}"
fi

Script d’installation Ghost - Nginx - Letsencrypt

Source : https://gist.github.com/goffinet/f998fd20b0b79e06deb398ede19943cb

Ce script vise à automatiser l’installation d’un blog Ghost lancé sur un port TCP aléatoire (tcp_port=$(shuf -i 8184-65000 -n 1)) avec Nginx en frontal en HTTPS à partir de n’importe quelle instance Ubuntu 16.04 Xenial connectée à l’Internet (ip_wan=$(curl -s ipinfo.io/ip)). Le proxy Web est configuré pour rediriger les requêtes HTTP en HTTPS. Le certificat TLS est automatiquement généré avec Let’s Encrypt. Une adresse DNS type A est créée ou mise à jour chez Cloudflare via leur API (CF_API_URL="https://api.cloudflare.com/client/v4"). On envisage une sécurité minimale avec le pare-feu Netfilter et le logiciel Fail2ban.

Le script respecte les différentes étapes manuelles décrites plus haut :

  1. Vérification du contexte d’exécution du script
    • Mise à jour du système
    • Création ou mise à jour d’une entrée DNS (via l’API Cloudflare)
    • Installation de la version pré-requise du framework Node.js
    • Installation de Nginx
    • Configuration de Nginx comme Reverse Proxy
    • Installation et configuration de Let’s Encrypt, obtention des certificats et configuration du Proxy
    • Installation et configuration du pare-feu et de Fail2ban
    • Installation de quelques thèmes du blog
#!/bin/bash

## 1. Set variables
SITE="blog1"
ZONE="example.com"
MAIL="root@example.com"
CF_TOKEN="your_api"
## Do not touch any others
CF_EMAIL=$MAIL
CF_ZONE=$ZONE
CF_NAME=$SITE
CF_API_URL="https://api.cloudflare.com/client/v4"
curl_command='curl'
ip_wan=$(curl -s ipinfo.io/ip)
tcp_port=$(shuf -i 8184-65000 -n 1)

## 2. Check root and distro
check_env () {
if [[ $EUID -ne 0 ]]; then
   echo "This script must be run as root" 1>&2
   exit 1
fi
if [ ! $(lsb_release -rs) == "16.04" ]; then
 echo "This script must be run on Ubuntu 16.04 Xenial" 1>&2
 exit 1
fi
}

## 3. Update and upgrade the system
system_update () {
apt-get update && apt-get -y upgrade && apt-get -y dist-upgrade
}

## 4. Create an DNS entry to Cloudflare

set_dns () {
apt-get -y install curl
## 2. Get Zone ID
zones=`${curl_command} -s -X GET "${CF_API_URL}/zones?name=${CF_ZONE}" -H "X-Auth-Email: ${CF_EMAIL}" -H "X-Auth-Key: ${CF_TOKEN}" -H "Content-Type: application/json"`
zone=$(echo "${zones}" | grep -Po '(?<="id":")[^"]*' | head -1)
## 3. Get Record ID et IP Address of hostanme
records=`${curl_command} -s -X GET "${CF_API_URL}/zones/${zone}/dns_records?type=A&name=${CF_NAME}.${CF_ZONE}&page=1&per_page=20&order=type&direction=desc&match=all" -H "X-Auth-Email: ${CF_EMAIL}" -H "X-Auth-Key: ${CF_TOKEN}" -H "Content-Type: application/json"`
records_id=`echo "${records}" | grep -Po '(?<="id":")[^"]*'`
ip=`echo "${records}" | grep -Po '(?<="content":")[^"]*'`
## Check if Record exists
if [ "${ip}" == "${ip_wan}" ]; then
 echo "Noting to do"
fi
if [ ! "${ip}" == "${ip_wan}" ]; then
 echo "do update"
 ${curl_command} -s -X PUT "${CF_API_URL}/zones/${zone}/dns_records/${records_id}" -H "X-Auth-Email: ${CF_EMAIL}" -H "X-Auth-Key: ${CF_TOKEN}" -H "Content-Type: application/json" --data "{\"id\":\"${zone}\",\"type\":\"A\",\"name\":\"${CF_NAME}.${CF_ZONE}\",\"content\":\"${ip_wan}\"}"
fi
if [ -z "$records_id" ]; then
 echo "Please create the record ${CF_NAME}.${CF_ZONE}"
 ${curl_command} -s -X POST "${CF_API_URL}/zones/${zone}/dns_records" -H "X-Auth-Email: ${CF_EMAIL}" -H "X-Auth-Key: ${CF_TOKEN}" -H "Content-Type: application/json" --data "{\"id\":\"${zone}\",\"type\":\"A\",\"name\":\"${CF_NAME}.${CF_ZONE}\",\"content\":\"${ip_wan}\"}"
fi
}

## 5. Get and install Node.js
set_nodejs () {
curl -sL https://deb.nodesource.com/setup_4.x | sudo bash -
apt-get install -y nodejs
}

## 6. Get and Install Ghost Software
set_ghost () {
cd ~
wget https://ghost.org/zip/ghost-latest.zip
mkdir /var/www
apt-get install unzip
unzip -d /var/www/$SITE ghost-latest.zip
cd /var/www/$SITE
npm install --production
cp config.example.js config.js
sed -i s/my-ghost-blog.com/${SITE}.${ZONE}/ config.js
sed -i s/2368/${tcp_port}/ config.js
adduser --shell /bin/bash --gecos 'Ghost application' ghost --disabled-password
chown -R ghost:ghost /var/www/$SITE
cat << EOF > /etc/systemd/system/$SITE.service
[Unit]
Description="Ghost $SITE"
After=network.target

[Service]
Type=simple

WorkingDirectory=/var/www/$SITE
User=ghost
Group=ghost

ExecStart=/usr/bin/npm start --production
ExecStop=/usr/bin/npm stop --production
Restart=always
SyslogIdentifier=Ghost

[Install]
WantedBy=multi-user.target
EOF
systemctl enable $SITE.service
systemctl start $SITE.service
rm ~/ghost-latest.zip
}

## 7. Get and install Nginx
set_nginx () {
apt-get install -y nginx
systemctl enable nginx
rm /etc/nginx/sites-enabled/default
if [ ! -f /etc/ssl/certs/dhparam.pem ]; then
openssl dhparam  -dsaparam -out /etc/ssl/certs/dhparam.pem 2048
fi
cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak
cat << EOF > /etc/nginx/nginx.conf
user www-data;
worker_processes auto;
pid /run/nginx.pid;

events {
	worker_connections 768;
	# multi_accept on;
}

http {

	##
	# Basic Settings
	##

	sendfile on;
	tcp_nopush on;
	tcp_nodelay on;
	keepalive_timeout 65;
	types_hash_max_size 2048;
	server_tokens off;

	server_names_hash_bucket_size 64;
	# server_name_in_redirect off;

	include /etc/nginx/mime.types;
	default_type application/octet-stream;

	##
	# SSL Settings
	##

  # from https://cipherli.st/
  # and https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html

  # Only the TLS protocol family
  ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
  ssl_prefer_server_ciphers on;
  # This will block IE6, Android 2.3 and older Java version from accessing your site, but these are the safest settings.
  ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
  # ECDH key exchange prevents all known feasible cryptanalytic attacks
  ssl_ecdh_curve secp384r1;
  # 20MB of cache will host about 80000 sessions
  ssl_session_cache shared:SSL:20m;
  # Session expires every 3 hours
  ssl_session_timeout 180m;
  ssl_session_tickets off;
  ssl_stapling on;
  ssl_stapling_verify on;
  # OCSP stapling using Google public DNS servers
  resolver 8.8.8.8 8.8.4.4 valid=300s;
  resolver_timeout 5s;
  add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";
  add_header X-Frame-Options DENY;
  add_header X-Content-Type-Options nosniff;

  ssl_dhparam /etc/ssl/certs/dhparam.pem;

	##
	# Logging Settings
	##

	access_log /var/log/nginx/access.log;
	error_log /var/log/nginx/error.log;

	##
	# Gzip Settings
	##

	gzip on;
	gzip_disable "msie6";

	# gzip_vary on;
	# gzip_proxied any;
	# gzip_comp_level 6;
	# gzip_buffers 16 8k;
	# gzip_http_version 1.1;
	# gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

	##
	# Virtual Host Configs
	##

	include /etc/nginx/conf.d/*.conf;
	include /etc/nginx/sites-enabled/*;
}


#mail {
#	# See sample authentication script at:
#	# http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript
#
#	# auth_http localhost/auth.php;
#	# pop3_capabilities "TOP" "USER";
#	# imap_capabilities "IMAP4rev1" "UIDPLUS";
#
#	server {
#		listen     localhost:110;
#		protocol   pop3;
#		proxy      on;
#	}
#
#	server {
#		listen     localhost:143;
#		protocol   imap;
#		proxy      on;
#	}
#}
EOF
cat << EOF > /etc/nginx/sites-available/$SITE
server {
    listen 80;
    server_name ${SITE}.${ZONE};

    location ~ ^/.well-known {
        root /var/www/$SITE;
    }

    location / {
        return 301 https://\$server_name\$request_uri;
    }
}
EOF
ln -s /etc/nginx/sites-available/$SITE /etc/nginx/sites-enabled/$SITE
systemctl stop nginx ; systemctl start nginx
}

## 8. Get and install Letsencrypt
set_letsencrypt () {
apt-get -y install letsencrypt
letsencrypt certonly -a webroot --webroot-path=/var/www/$SITE/ -d ${SITE}.${ZONE} -m $MAIL --agree-tos
cat << EOF > /etc/nginx/sites-available/$SITE
server {
        listen 80;

        server_name ${SITE}.${ZONE};

        location ~ ^/.well-known {
            root /var/www/$SITE;
        }

        location / {
            return 301 https://\$server_name\$request_uri;
        }
}

server {
        listen 443 ssl;

        server_name ${SITE}.${ZONE};

        location / {
                proxy_pass http://localhost:${tcp_port};
                proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
                proxy_set_header Host \$http_host;
                proxy_set_header X-Forwarded-Proto \$scheme;
                proxy_buffering off;
                proxy_redirect off;
        }

        ssl on;
        ssl_certificate /etc/letsencrypt/live/${SITE}.${ZONE}/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/${SITE}.${ZONE}/privkey.pem;

        ssl_prefer_server_ciphers On;
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS;

}
EOF
cat << EOF > /etc/cron.d/le-renew
30 2 * * 1 /usr/bin/letsencrypt renew >> /var/log/le-renew.log
35 2 * * 1 /bin/systemctl reload nginx
EOF
systemctl stop nginx ; systemctl start nginx
cd /var/www/$SITE
sed -i s/http/https/ config.js
chown -R ghost:ghost /var/www/$SITE
systemctl stop $SITE.service ; systemctl start $SITE.service
}

## 9. Set Firewalld and Fail2ban
set_firewall () {
apt-get install -y firewalld
systemctl enable firewalld
firewall-cmd --permanent --zone=public --add-service=https
firewall-cmd --permanent --zone=public --add-service=http
firewall-cmd --permanent --zone=public --add-interface=eth0
firewall-cmd --reload
firewall-cmd --permanent --zone=public --list-all
apt-get install -y fail2ban
systemctl enable fail2ban
}

## 10. Upload some themes

upload_themes () {
apt-get install -y git
cd content/themes
git clone https://github.com/boh717/beautiful-ghost.git beautifulghost
chown -R ghost:ghost beautifulghost
git clone https://github.com/Dennis-Mayk/Practice.git Practice
chown -R ghost:ghost Practice
git clone https://github.com/andreborud/penguin-theme-dark.git penguin-theme-dark
chown -R ghost:ghost penguin-theme-dark
git clone https://github.com/daanbeverdam/buster.git buster
chown -R ghost:ghost buster
git clone https://github.com/godofredoninja/Mapache.git Mapache
chown -R ghost:ghost Mapache
git clone https://github.com/haydenbleasel/ghost-themes.git Phantom
chown -R ghost:ghost Phantom
git clone https://github.com/kagaim/Chopstick.git Chopstick
chown -R ghost:ghost Chopstick
git clone https://github.com/GavickPro/Perfetta-Free-Ghost-Theme.git Perfetta
chown -R ghost:ghost Perfetta
systemctl stop $SITE.service ; systemctl start $SITE.service
}

check_env
system_update
set_dns
set_nodejs
set_ghost
set_nginx
set_letsencrypt
set_firewall
upload_themes

Maintenance de Ghost

  • Sauvegarde des données et meta-données du blog content/*
  • Mise à jour du logiciel (vérification des pré-requis)

Comprendre le fonctionnement du Proxy Nginx

  • Proxying
  • Load Balancing
  • Buffering
  • Caching