evoformations/reveal/httpd.html

890 lines
23 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<title>Formation Evolix : Services HTTP</title>
<meta name="description" content="A framework for easily creating beautiful presentations using HTML">
<meta name="author" content="Hakim El Hattab">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="stylesheet" href="reveal.js/css/reveal.css">
<link rel="stylesheet" href="reveal.js/css/theme/beige.css" id="theme">
<!-- Theme used for syntax highlighting of code -->
<link rel="stylesheet" href="reveal.js/lib/css/zenburn.css">
<!-- Printing and PDF exports -->
<script>
var link = document.createElement( 'link' );
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = window.location.search.match( /print-pdf/gi ) ? 'reveal.js/css/print/pdf.css' : 'reveal.js/css/print/paper.css';
document.getElementsByTagName( 'head' )[0].appendChild( link );
</script>
<!--[if lt IE 9]>
<script src="reveal.js/lib/js/html5shiv.js"></script>
<![endif]-->
</head>
<body>
<div class="reveal">
<!-- Any section element inside of this container is displayed as a slide -->
<div class="slides">
<section>
<h1>Formation Evolix</h1>
<h3>Services HTTP</h3>
</section>
<section>
<section>
<h2>Apache</h2>
Serveur HTTP le plus utilisé, depuis 1996
</section>
<section>
<h3>Installation</h3>
<pre><code data-trim class="hljs nohighlight">
# apt install apache2-mpm-itk \
libapache2-mod-evasive apachetop libwww-perl
# apachectl configtest
</code></pre>
</section>
<section>
<h3>Fichiers de configuration</h3>
<pre>
/etc/apache2/
├── apache2.conf
├── conf-available
│ └── *.conf
├── conf-enabled
│ └── *.conf -> ../conf-available/*.conf
├── envvars
├── magic
├── mods-available
│ ├── *.conf
│ └── *.load
├── mods-enabled
│ ├── *.conf -> ../mods-available/*.conf
│ └── *.load -> ../mods-available/*.load
├── ports.conf
├── sites-available
│ └── *.conf
└── sites-enabled
└── *.conf -> ../sites-available/*.conf
</pre>
</section>
<section>
<h3>Des modules à la carte</h3>
<pre><code data-trim class="hljs nohighlight">
# a2enmod rewrite expires headers rewrite cgi
</code></pre>
</section>
<section>
<h3>Optimisations "Evolix"</h3>
<pre><code data-trim class="apache" style="max-height: 500px">
ServerTokens Prod
Timeout 10
KeepAliveTimeout 2
MaxKeepAliveRequests 10
ServerLimit 250
#MaxClients 250
MaxRequestWorkers 250
StartServers 50
MinSpareServers 20
MaxSpareServers 30
MaxRequestsPerChild 100
&lt;Directory /home/>
AllowOverride None
Require all granted
&lt;/Directory>
&lt;IfModule mod_ssl.c>
SSLProtocol all -SSLv2 -SSLv3
SSLCipherSuite HIGH:MEDIUM:!aNULL:!MD5:!RC4
&lt;/IfModule>
</code></pre>
</section>
<section>
<h4>Forcer le umask</h4>
<pre><code data-trim class="hljs nohighlight"># grep umask /etc/apache2/envvars</code>
umask 007
</pre>
</section>
<section>
<h3>VirtualHost</h3>
<ul>
<li>Permet de séparer de plusieurs sites sur un même serveur HTTP</li>
<li>Séparation par nom de domaine ou par adresses IP</li>
</ul>
</section>
<section>
<h4>VirtualHost basé sur un nom de domaine</h4>
<!-- Je n'ai pas trouvé le moyen d'avoir du code à balise qui soit proprement affiché tout en conservant le texte brut intacte. -->
<pre><code data-trim class="apache" style="max-height: 600px">
&lt;VirtualHost *:80>
ServerName www.example.com
ServerAlias example.com
DocumentRoot /home/example/www/
&lt;Directory /home/example/www/>
Options SymLinksIfOwnerMatch
AllowOverride AuthConfig Limit FileInfo Indexes
&lt;/Directory>
AssignUserID www-example example
MaxClientsVHost 150
CustomLog /var/log/apache2/access.log vhost_combined
CustomLog /home/example/log/access.log combined
ErrorLog /home/example/log/error.log
RewriteEngine On
UseCanonicalName On
RewriteCond %{HTTP_HOST} !^www.example.com$
RewriteRule ^/(.*) http://%{SERVER_NAME}/$1 [L,R]
&lt;/VirtualHost>
</code></pre>
</section>
<section>
<h4>Activation du site</h4>
<pre><code data-trim class="hljs nohighlight">
# adduser example
# adduser --ingroup example www-example
# mkdir /home/example/{www,log,awstats}
# chown example: /home/example/{www,log,awstats}
# a2ensite example
</code></pre>
</section>
<section>
<h3>Gestion des droits avec Apache-ITK</h3>
<ul>
<li>user/group distincts pour chaque VirtualHost</li>
<li>séparation stricte des droits</li>
<li>… y compris la lecture</li>
<li>… y compris avec PHP</li>
</ul>
<pre><code data-trim class="hljs nohighlight">
# chmod 750 /home/example
</code></pre>
</section>
<section>
<h3>Restriction en écriture pour Apache</h3>
<ul>
<li>Lecture seule pour Apache, sauf certains répertoires (uploads/, cache/)</li>
<li>utilisateur dédié pour l'écriture par Apache : www-example</li>
<li>… et pour FTP/SSH/SFTP/SCP/RSYNC : example</li>
<li>groupe commun pour tous et droits accordés au groupe</li>
<li>pas besoin de chmod, Apache a son umask à 007</li>
</ul>
<pre><code data-trim class="hljs nohighlight">
# echo "umask 027" >> /etc/profile
# find /home/example -type f -user www-example -exec chmod 660 {} \;
# find /home/example -type d -user www-example -exec chmod 770 {} \;
</code></pre>
</section>
<section>
<h4>Attention !</h4>
<p><strong>Ne jamais forcer les droits récursivement<br>sur toute larborescence.</strong> <mark>#NEVER777</mark></p>
<p>Si la restriction en écriture pour Apache est impossible :</p>
<ul>
<li>plus dutilisateur distinct</li>
<li>"AssignUserID example example" élimine 100% des soucis de droits</li>
<li>… mais réduction de la sécurité</li>
</ul>
</section>
<section>
<h2>HTTPS TLS/SSL</h2>
</section>
<section>
<h3>Activation du module SSL pour Apache</h3>
<pre><code data-trim class="hljs nohighlight">
# a2enmod ssl
</code></pre>
</section>
<section>
<h3>Création d'un certificat</h3>
<pre><code data-trim class="hljs nohighlight">
# openssl req -newkey rsa:2048 -sha256 -nodes \
-keyout private.key -out demande.csr
# openssl x509 -req -days 3650 -sha256 -in demande.csr \
-signkey private.key -out certificate.crt
# mv private.key /etc/ssl/private/
# chown root:ssl-cert /etc/ssl/private/private.key
# chmod 640 /etc/ssl/private/private.key
# mv certificate.crt /etc/ssl/certs
# chown root:root /etc/ssl/certs/certificate.crt
# chmod 644 /etc/ssl/certs/certificate.crt
</code></pre>
</section>
<section>
<h3>Modification du VirtualHost</h3>
<pre><code data-trim class="apache">
&lt;VirtualHost *:80 *:443>
ServerName secure.example.com
ServerAlias www.example.com example.com
SSLEngine on
SSLProtocol all -SSLv2 -SSLv3
SSLCertificateKeyFile /etc/ssl/private/private.key
SSLCertificateFile /etc/ssl/certs/certificate.crt
# SSLCertificateChainFile /etc/ssl/certs/certificates_chain.pem
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^/(.*) https://%{SERVER_NAME}/$1 [L,R=permanent]
&lt;/VirtualHost>
</code></pre>
</section>
<section>
<h3>logs</h3>
Apache propose plusieurs formats de logs
<pre><code data-trim class="apache">
CustomLog log/global_access.log vhost_combined
CustomLog log/access.log combined
SetEnvIf User-Agent "Foo" dontlog
CustomLog log/access.log combined env=!dontlog
ErrorLog log/error.log
</code></pre>
</section>
<section>
<h3>Authentification "HTTP Basic"</h3>
Le module mod_auth_basic est disponible par défaut.
<pre><code data-trim class="apache">
AuthType Basic
AuthName "Restricted"
AuthUserFile /foo/.htpasswd
AuthGroupFile /dev/null
require valid-user
</code></pre>
</section>
<section>
<h3>Règles de ré-écriture</h3>
<pre><code data-trim class="apache" style="max-height: 500px">
RedirectPermanent / http://new.example.com
RedirectMatch ^/(.*)$ http://new.example.com/$1
# GET / --> /sub/
RedirectMatch ^/$ /sub/
RewriteRule ^/(.*) http://new.example.com/$1 [L,R=permanent]
RewriteCond %{REQUEST_URI} !^/foo.txt
RewriteRule ^/(.*) https://%{SERVER_NAME}/$1 [L,R]
# le drapeau NC pour ne pas tenir compte de la casse
RewriteRule ^/FoO.tXt /sub/ [L,R,NC]
# empêcher des requêtes POST sur une URL particulière
RewriteCond %{REQUEST_METHOD} POST
RewriteRule ^/foo.txt [L,F]
</code></pre>
</section>
<section>
<h3>mod_evasive</h3>
<p>Limite les accès, notamment les dénis de service </p>
<pre><code data-trim class="apache">
&lt;IfModule mod_evasive20.c>
DOSHashTableSize 3097
DOSPageCount 5
DOSSiteCount 30
DOSPageInterval 3
DOSSiteInterval 1
DOSBlockingPeriod 60
DOSEmailNotify security@example.com
&lt;/IfModule>
</code></pre>
</section>
<section>
<h3>Fail2ban</h3>
<ul>
<li>recherche de comportements anormaux dans les logs</li>
<li>blocage temporaire ou permanent, via firewall</li>
</ul>
</section>
<section>
<h3>Apachetop</h3>
Affiche en direct des stats sur le trafic, d'après les logs.
<pre><code data-trim class="hljs nohighlight">
$ apachetop -f access.log -T 3600 -q
</code></pre>
</section>
<section>
<h3>mod_status</h3>
Génère une page web résumant l'état d'Apache.
<pre><code data-trim class="apache">
&lt;IfModule mod_status.c>
ExtendedStatus On
&lt;Location /server-status-XXXX>
SetHandler server-status
Deny from all
Include ipaddr_whitelist.conf
Allow from 192.0.2.43
Allow from 127.0.0.1
&lt;/Location>
&lt;/IfModule>
</code></pre>
</section>
</section>
<section>
<section>
<h2>Nginx</h2>
<p>Serveur web, alternative à Apache</p>
</section>
<section>
<h3>Installation</h3>
<pre><code data-trim class="hljs nohighlight">
# apt install nginx
# nginx -t -c /etc/nginx/nginx.conf
</code>
</pre>
</section>
<section>
<h3>Fichiers de configuration</h3>
<pre>
/etc/nginx/
├── conf.d
├── fastcgi.conf
├── fastcgi_params
├── koi-utf
├── koi-win
├── mime.types
├── nginx.conf
├── proxy_params
├── scgi_params
├── sites-available
│ └── default
├── sites-enabled
│ └── default -> /etc/nginx/sites-available/default
├── snippets
│ ├── fastcgi-php.conf
│ └── snakeoil.conf
├── uwsgi_params
└── win-utf
</pre>
</section>
<section>
<h3>Optimisations "Evolix"</h3>
<pre><code data-trim style="max-height: 500px" class="nginx">
user www-data;
worker_processes 8;
pid /var/run/nginx.pid;
events {
use epoll;
worker_connections 10240;
}
http {
keepalive_timeout 15;
[…]
# disable Nginx version
server_tokens off;
</code></pre>
</section>
<section>
<h3>VirtualHost</h3>
<pre><code data-trim style="max-height: 600px" class="nginx">
server {
listen 80;
server_name static.example.com assets.example.com;
access_log /var/log/nginx/access.log;
root /home/static/www;
location /crossdomain.xml {
alias /home/static/www/crossdomain.xml;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /var/www/nginx-default;
}
location ~\.(jpeg|jpg|gif|png)$ {
add_header Cache-Control "public";
expires 2w;
}
location ~\.(js|pdf|css|swf)$ {
add_header Cache-Control "public";
expires 3w;
}
}
</code></pre>
</section>
<section>
<pre><code data-trim class="nginx">
server {
listen 443;
server_name static.example.com assets.example.com;
ssl on;
ssl_certificate /etc/ssl/certs/static.example.com.crt;
ssl_certificate_key /etc/ssl/private/static.example.com.key;
# add_header Strict-Transport-Security "max-age=31536000;";
}
</code></pre>
</section>
</section>
<section>
<section>
<h2>HAProxy</h2>
<p>Proxy et load-balancer TCP/HTTP/HTTPS</p>
</section>
<section>
<h3>Installation</h3>
<pre><code data-trim class="hljs nohighlight">
# apt install haproxy
# haproxy -c -f /etc/haproxy/haproxy.cfg
</code>
</pre>
</section>
<section>
<h3>Configuration minimale</h3>
<pre><code data-trim class="hljs haproxy">
global
log 127.0.0.1 local5 debug
defaults
mode http
listen www
bind *:80
balance roundrobin
option httpchk OPTIONS * HTTP/1.1\r\nHost:\ www.example.com
stats uri /haproxy-stats
stats auth foo:bar
server www00 192.0.2.1:80 maxconn 50 check inter 10s
server www01 192.0.2.2:80 maxconn 50 check inter 10s
</code></pre>
</section>
<section>
<h3>Configuration avancée</h3>
<ul>
<li>gestion fine des performances</li>
<li>multiples IP/domaines</li>
<li>différents algorithmes de load-balancing</li>
</ul>
</section>
<section>
<h4>TLS/SSL</h4>
<ul>
<li>terminaison SSL ou transmission transparente</li>
<li>multi certificats et SNI</li>
<li>agrafage OCSP facilitée</li>
</ul>
</section>
<section>
<h4>mode TCP</h4>
<pre><code data-trim class="hljs haproxy">
listen memcached 127.0.0.1:11211
option tcp-check
server nosql00 192.0.2.3:11211 check
server nosql01 192.0.2.4:11211 check backup
</code></pre>
</section>
<section>
<h4>Load-balancing MySQL (simple)</h4>
<pre><code data-trim class="hljs haproxy">
listen mysql 127.0.0.1:3306
mode tcp
option mysql-check user haproxy_check
server sql00 192.0.2.1:3306 check
</code></pre>
</section>
<section>
<h4>Load-balancing MySQL (avancé)</h4>
Si le test de connexion à MySQL ne suffit pas,<br>on indique un programme pour un test personnalisé<br>qui indiquera à HAProxy si le backend va bien.
</section>
<section>
<h4>Dashboard</h4>
<img style="margin:0 0 0 1%;width:100%;max-width: 600px" src="./imagesEvoFormation2017/haproxy.png" border="0">
</section>
</section>
<section>
<section>
<h2>Varnish</h2>
<p>Accélérateur web : cache et reverse-proxy</p>
</section>
<section>
<h3>Installation</h3>
<pre><code data-trim class="hljs nohighlight">
# apt install varnish
</code>
</pre>
</section>
<section>
<h3>Configuration minimale</h3>
<p>Varnish relaie les requêtes vers le port 8080 local.</p>
<pre><code data-trim class="hljs varnish">
backend default {
.host = "127.0.0.1";
.port = "8080";
}
</code></pre>
</section>
<section>
<h3>Logs</h3>
Par défaut les logs sont gardés en mémoire (taille fixe) pour les performances.
<pre><code data-trim class="hljs nohighlight">
# varnishstat
# varnishtop -i ReqURL
# varnishlog
# varnishnsca
</code></pre>
Filtres possibles
<pre><code data-trim class="hljs nohighlight">
# varnishlog -q 'TxHeader eq MISS' -q "ReqHeader \
~ '^Host: example\.com$'" | grep RxURL
# varnishncsa -q "ReqHeader eq 'X-Cache: MISS'"
</code></pre>
</section>
<section>
<h3>Syntaxe VCL</h3>
<pre><code data-trim class="hljs varnish" style="max-height: 500px">
sub vcl_recv {
if (req.http.host == "boutique.example.com") {
if (req.url ~ "^/" || req.url ~ "/newsletter" ) {
unset req.http.cookie;
}
}
}
sub vcl_backend_response {
if (bereq.http.host == "boutique.example.com") {
if (bereq.url ~ "^/" || bereq.url ~ "/newsletter" ) {
unset beresp.http.Set-Cookie;
set beresp.ttl = 1h;
return (deliver);
}
}
}
</code></pre>
</section>
</section>
<section>
<section>
<h2>PHP</h2>
<p>Langage de programmation très adapté au web.</p>
</section>
<section>
<h2>Versions de PHP</h2>
<ul>
<li>PHP 3 (1997)</li>
<li>PHP 4 (2000)</li>
<li>PHP 5 (2004)</li>
<li>PHP 5.4 (Debian 7)</li>
<li>PHP 5.6 (Debian 8)</li>
<li>PHP 7 (Debian 9)</li>
</ul>
</section>
<section>
<h2>Installation de PHP5</h2>
<pre><code data-trim class="hljs nohighlight">
# apt install php5 libapache2-mod-php5 php5-gd php5-imap php5-ldap \
php5-mcrypt php5-mhash php5-mysql php5-pgsql \
php-gettext librsvg2-bin
$ php -v
</code>
PHP 5.6.30-0+deb8u1 (cli) (built: Feb 8 2017 08:50:21)
Copyright (c) 1997-2016 The PHP Group
Zend Engine v2.6.0, Copyright (c) 1998-2016 Zend Technologies
with Zend OPcache v7.0.6-dev, Copyright (c) 1999-2016, by Zend Technologies
</pre>
<pre><code data-trim class="hljs nohighlight">
# apt install php5-fpm
</code></pre>
</section>
<section>
<h3>php.ini</h3>
<pre><code data-trim class="ini">
short_open_tags = Off
disable_functions = exec, shell-exec, system, passthru, putenv, popen
expose_php = Off
display_errors = Off
log_errors = On
allow_url_fopen = Off
memory_limit = 128M
max_execution_time = 10
open_basedir = /home
</code></pre>
</section>
<section>
<h3>Délégation dans VirtualHost Apache</h3>
<pre><code data-trim class="apache">
#php_admin_flag engine off
#AddType text/html .html
#php_admin_flag safe_mode off
#php_admin_value safe_mode_exec_dir /usr/bin
#php_admin_flag display_errors on
#php_flag short_open_tag on
#php_flag register_globals on
#php_admin_value upload_tmp_dir "/home/bloginfo/tmp/
php_admin_value sendmail_path "/usr/sbin/sendmail -t -i -f www-bloginfo"
php_admin_value error_log "/home/bloginfo/log/php.log"
php_admin_value memory_limit "64M"
</code></pre>
</section>
<section>
<h3>fpm/php-fpm.conf</h3>
<pre><code data-trim class="ini">
[global]
pid = /run/php5-fpm.pid
error_log = /var/log/php5-fpm.log
</code></pre>
</section>
<section>
<h3>fpm/pool.d/www.conf</h3>
<pre><code data-trim class="ini">
[www]
listen = /var/run/php5-fpm.sock
;listen = 127.0.0.1:9000
user = www-data
group = www-data
pm = dynamic
php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -i -f return-path@example.com
php_flag[display_errors] = off
php_admin_value[error_log] = /var/log/fpm-php.www.log
php_admin_flag[log_errors] = on
php_admin_value[memory_limit] = 32M
</code></pre>
</section>
<section>
<h3>fpm/pool.d/www.conf</h3>
<pre><code data-trim class="ini">
pm = dynamic
pm.max_children = 100
pm.start_servers = 50
pm.min_spare_servers = 20
pm.max_spare_servers = 30
pm.max_requests = 100
</code></pre>
OU
<pre><code data-trim class="ini">
pm = ondemand
pm.max_children = 100
pm.process_idle_timeout = 10s
</code></pre>
</section>
<section>
<h3>fpm/pool.d/www.conf</h3>
<pre><code data-trim class="ini">
slowlog = log/$pool.log.slow
request_slowlog_timeout = 5s
pm.status_path = /fpm-status
request_terminate_timeout = 60s
chroot = /home/foo
access.log = log/$pool.access.log
</code></pre>
</section>
<section>
<h3>Avec Apache :</h3>
<pre><code data-trim class="hljs nohighlight">
# a2enmod proxy_fcgi
</code></pre>
<pre><code data-trim class="apache">
DocumentRoot /home/foo/www/
# ProxyPassMatch "^/(.*\.php(/.*)?)$" "fcgi://127.0.0.1:9000//home/foo/www/$1"
ProxyPassMatch "^/(.*\.php(/.*)?)$" "unix:/var/run/php5-fpm.sock|fcgi://localhost/home/foo/www/"
</code></pre>
</section>
<section>
<h3>Avec Nginx</h3>
<pre><code data-trim class="nginx">
server {
listen 80;
server_name www.example.com example.com;
root /home/foo/www;
index index.php;
location ~ \.php$ {
try_files $uri =404;
#fastcgi_pass 127.0.0.1:9000;
fastcgi_pass unix:/var/run/php5-fpm.sock;
fastcgi_param SCRIPT_FILENAME /home/foo/www$fastcgi_script_name;
include fastcgi_params;
}
}
</code></pre>
</section>
<section>
<pre><code data-trim class="hljs nohighlight">
$ echo "<?php phpinfo() ; ?>" > /var/www/info.php
</code></pre>
</section>
</section>
<section>
<section>
<h2>Let's Encrypt</h2>
<ul>
<li>Autorité de certification</li>
<li>propose des certificats X-509</li>
<li>DV Domain Validation</li>
</ul>
</section>
<section>
<h3>Principes de base</h3>
<ul>
<li>gratuit</li>
<li>automatique</li>
<li>sécurisé</li>
<li>transparent</li>
<li>ouvert</li>
<li>cooperatif</li>
</ul>
</section>
<section>
<h3>Composants</h3>
<ul>
<li>Protocole ACME (standard IETF en janvier 2018)</li>
<li>Boulder : serveur ACME</li>
<li>Certbot : client ACME pour gérer des certificats</li>
<li>des dizaines d'autres clients tiers</li>
</ul>
</section>
<section>
<h3>Installation de certbot</h3>
<pre><code data-trim class="hljs nohighlight">
# apt install certbot
</code></pre>
<ul>
<li>création du compte Let's Encrypt</li>
<li>création de certificats</li>
<li>auto-configuration du serveur web</li>
<li>renouvellement/révocation de certificats</li>
</ul>
</section>
<section>
<h3>3 Challenges pour la validation</h3>
<ul>
<li>HTTP ressource signée</li>
<li>DNS enregistrement DNS signé</li>
<li>TLS-SNI certificat TLS contenant une signature</li>
</ul>
</section>
<section>
<h3>Particularités</h3>
<ul>
<li>Durée de vie de 90 jours</li>
<li>Entièrement automatisé</li>
<li>… donc seulement DV et pas OV ou EV</li>
</ul>
</section>
</section>
</div>
</div>
<script src="reveal.js/lib/js/head.min.js"></script>
<script src="reveal.js/js/reveal.js"></script>
<script>
// More info https://github.com/hakimel/reveal.js#configuration
Reveal.initialize({
controls: true,
progress: true,
history: true,
center: true,
transition: 'slide', // none/fade/slide/convex/concave/zoom
// More info https://github.com/hakimel/reveal.js#dependencies
dependencies: [
{ src: 'reveal.js/lib/js/classList.js', condition: function() { return !document.body.classList; } },
{ src: 'reveal.js/plugin/markdown/marked.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
{ src: 'reveal.js/plugin/markdown/markdown.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
{ src: 'reveal.js/plugin/highlight/highlight.js', async: true, callback: function() { hljs.initHighlightingOnLoad(); } },
{ src: 'reveal.js/plugin/zoom-js/zoom.js', async: true },
{ src: 'reveal.js/plugin/notes/notes.js', async: true }
]
});
</script>
</body>
</html>