Initial commit

This commit is contained in:
Jesper Hess 2020-04-21 08:21:08 +02:00
commit 8b3acb54ab
Signed by: graffen
GPG key ID: 351A89E40D763F0F
35 changed files with 642 additions and 0 deletions

9
ansible.cfg Normal file
View file

@ -0,0 +1,9 @@
[defaults]
inventory = inventory
interpreter_python = auto_silent
[privilege_escalation]
become = True
become_method = sudo
become_user = root
become_ask_pass = False

6
group_vars/all Normal file
View file

@ -0,0 +1,6 @@
---
hessnet_asn: "209616"
v4_prefixes:
- "44.145.128.0/24"
v6_prefixes:
- "2001:678:15c::/48"

View file

@ -0,0 +1 @@
---

View file

@ -0,0 +1,40 @@
---
router_id: "136.244.111.183"
router_v4_ip: "136.244.111.183"
router_v6_ip: "2001:19f0:5001:256b:5400:02ff:feb0:cd41"
bgp_peers:
- name: "vultr_v4"
template: "peer_vultr_v4"
asn: "64515"
neighbor_ip: "169.254.169.254"
password: !vault |
$ANSIBLE_VAULT;1.1;AES256
65313532636233353638373732623333376562393335373233396534616531353164386636616538
6436323265316639373831346634366332666333663265320a323534626166653432343562386639
30626239303536376162643730633536303964616131636139656233316363363338633362376137
6434313931353037610a323261313664356261323963623530636536393162626666376265333532
3934
- name: "vultr_v6"
template: "peer_vultr_v6"
asn: "64515"
neighbor_ip: "2001:19f0:ffff::1"
password: !vault |
$ANSIBLE_VAULT;1.1;AES256
65313532636233353638373732623333376562393335373233396534616531353164386636616538
6436323265316639373831346634366332666333663265320a323534626166653432343562386639
30626239303536376162643730633536303964616131636139656233316363363338633362376137
6434313931353037610a323261313664356261323963623530636536393162626666376265333532
3934
- name: "home_router"
template: "peer_hessnet"
asn: "209616"
neighbor_ip: "172.16.12.19"
filters:
export: "{ peer_export_default_only(); }"
import: "myroutes_import_export();"
announce_from_here: false
configure_static_multihop_routes: true

10
inventory Normal file
View file

@ -0,0 +1,10 @@
[vms]
archvm1.hosts.v6.hessnet.dk
archvm2.hosts.v6.hessnet.dk
[routers]
# frb1.cph.dk.routers.v6.hessnet.dk
vul1.ams.nl.routers.v6.hessnet.dk
[as209616_webservers]
archvm1.hosts.v6.hessnet.dk

View file

@ -0,0 +1,5 @@
---
- name: "Full pacman upgrade"
pacman:
update_cache: "yes"
upgrade: "yes"

View file

@ -0,0 +1,2 @@
---
- include_tasks: "full_upgrade.yml"

3
roles/bird/meta/main.yml Normal file
View file

@ -0,0 +1,3 @@
---
dependencies:
- { role: "handlers" }

17
roles/bird/tasks/bird.yml Normal file
View file

@ -0,0 +1,17 @@
---
- name: "Install bird"
pacman:
name: "bird"
state: "installed"
- name: "Copy bird configuration templates"
template:
src: "bird.conf.j2"
dest: "/etc/bird.conf"
notify: "reconfigure bird"
- name: "Enable bird service"
service:
name: "bird"
enabled: "yes"
state: "started"

View file

@ -0,0 +1,2 @@
---
- include_tasks: "bird.yml"

View file

@ -0,0 +1,267 @@
### This file is generated using Ansible and will be overwritten.
### Do not change this file directly!
log syslog all;
log "/var/log/bird.log" all;
debug protocols all;
timeformat base iso long;
timeformat log iso long;
timeformat protocol iso long;
timeformat route iso long;
router id {{ router_id }};
define my_asn = {{ hessnet_asn }};
define my_prefixes_ipv6 = [
{% for prefix in v6_prefixes %}
{{ prefix }}+{{"," if not loop.last }}
{% endfor %}
];
define my_prefixes_ipv4 = [
{% for prefix in v4_prefixes %}
{{ prefix }}+{{"," if not loop.last }}
{% endfor %}
];
define my_net_aggregated_ipv6 = [
{% for prefix in v6_prefixes %}
{{ prefix }}{{"," if not loop.last }}
{% endfor %}
];
define my_net_aggregated_ipv4 = [
{% for prefix in v4_prefixes %}
{{ prefix }}{{"," if not loop.last }}
{% endfor %}
];
# functions and filters
function is_default_route() {
case net.type {
NET_IP4: if net = 0.0.0.0/0 then return true;
NET_IP6: if net = ::/0 then return true;
}
return false;
}
function is_own_route() {
case net.type {
NET_IP4: if net ~ my_prefixes_ipv4 then return true;
NET_IP6: if net ~ my_prefixes_ipv6 then return true;
}
return false;
}
function is_own_aggregated_net() {
case net.type {
NET_IP4: if net ~ my_net_aggregated_ipv4 then return true;
NET_IP6: if net ~ my_net_aggregated_ipv6 then return true;
}
return false;
}
function honor_graceful_shutdown()
{
# RFC 8326 Graceful BGP Session Shutdown
if (65535, 0) ~ bgp_community then {
bgp_local_pref = 0;
}
}
function peer_export_default_only() {
if !is_default_route() then reject;
accept;
}
function peer_export_dfz() {
if source !~ [ RTS_BGP, RTS_STATIC ] then reject;
if is_default_route() then reject;
accept;
}
function peer_export_dfz_and_default() {
if is_default_route() then {
peer_export_default_only();
}
else {
peer_export_dfz();
}
}
filter kernel_export {
if source !~ [ RTS_BGP, RTS_OSPF, RTS_STATIC ] then reject;
if is_default_route() then accept;
if is_own_route() then accept;
reject;
}
filter ospf_export {
if source = RTS_DEVICE then accept;
reject;
}
filter transit_import {
honor_graceful_shutdown();
# bgp_large_community.add(({{hessnet_asn}},1,1));
accept;
}
filter transit_export {
{% if configure_static_multihop_routes is sameas true %}
if proto = "noAnnounce_v6" then reject;
if proto = "noAnnounce_v4" then reject;
{% endif %}
if is_own_aggregated_net() then accept;
reject;
}
filter myroutes_import_export {
if source !~ [ RTS_BGP, RTS_OSPF, RTS_STATIC ] then reject;
if is_own_route() then accept;
reject;
}
{% if announce_from_here is sameas true %}
protocol static announce_v6 {
ipv6;
{% for prefix in v6_prefixes %}
route {{ prefix }} unreachable;
{% endfor %}
}
protocol static announce_v4 {
ipv4;
{% for prefix in v4_prefixes %}
route {{ prefix }} unreachable;
{% endfor %}
}
{% endif %}
{% if configure_static_multihop_routes is sameas true %}
protocol static noAnnounce_v6 {
ipv6;
{% for peer in bgp_peers %}
{% if peer.neighbor_ip | ipv6 %}
route {{ peer.neighbor_ip }}/128 via {{ router_v6_ip }};
{% endif %}
{% endfor %}
}
protocol static noAnnounce_v4 {
ipv4;
{% for peer in bgp_peers %}
{% if peer.neighbor_ip | ipv4 %}
route {{ peer.neighbor_ip }}/32 via {{ router_v4_ip }};
{% endif %}
{% endfor %}
}
{% endif %}
protocol device {
scan time 5;
}
protocol direct {
ipv6;
interface "dummy*";
}
protocol kernel {
ipv4 {
import filter myroutes_import_export;
export none; #filter kernel_export;
};
}
protocol kernel kernel6 {
scan time 5;
ipv6 {
import none;
export none; #filter kernel_export;
};
}
template bgp transit_v6 {
local as my_asn;
hold time 600;
ipv6 {
import filter transit_import;
export filter transit_export;
};
}
template bgp transit_v4 {
local as my_asn;
hold time 600;
ipv4 {
import filter transit_import;
export filter transit_export;
};
}
template bgp peer_vultr_v6 {
local as my_asn;
source address {{ router_v6_ip }};
graceful restart on;
multihop 2;
ipv6 {
import filter transit_import;
export filter transit_export;
};
}
template bgp peer_vultr_v4 {
local as my_asn;
source address {{router_v4_ip}};
graceful restart on;
multihop 2;
ipv4 {
import filter transit_import;
export filter transit_export;
};
}
template bgp peer_hessnet {
local as my_asn;
ipv6 {
#next hop self;
import none;
export none;
};
}
{% for peer in bgp_peers %}
protocol bgp {{ peer.name }} from {{ peer.template }} {
neighbor {{peer.neighbor_ip}} as {{peer.asn}};
{% if peer.password is defined %}
password "{{ peer.password }}";
{% endif %}
{% if peer.filters is defined %}
ipv6 {
export filter {{ peer.filters.export }};
import filter {{ peer.filters.import }};
};
{% endif %}
}
{% endfor %}
# OSPF
protocol ospf v3 {
area 0 {
interface "dummy0" {
stub;
};
interface "wg*" { };
interface "tun*" { };
};
ipv6 {
import all;
export filter ospf_export;
};
}

View file

@ -0,0 +1,11 @@
---
- name: "Install cockpit"
pacman:
name: "cockpit"
state: "present"
- name: "Enable cockpit service"
service:
name: "cockpit"
enabled: "true"
state: "started"

View file

@ -0,0 +1,2 @@
---
- include_tasks: "cockpit.yml"

View file

@ -0,0 +1,3 @@
---
- name: "reconfigure bird"
command: "birdc configure"

View file

@ -0,0 +1,4 @@
---
- import_tasks: "nginx.yml"
- import_tasks: "systemd.yml"
- import_tasks: "bird.yml"

View file

@ -0,0 +1,10 @@
---
- name: "start nginx"
service:
name: "nginx"
state: "started"
- name: "reload nginx"
service:
name: "nginx"
state: "reloaded"

View file

@ -0,0 +1,5 @@
---
- name: "systemd daemon reload"
systemd:
daemon_reload: "yes"

View file

@ -0,0 +1,6 @@
[Unit]
Description=LetsEncrypt renewal
[Service]
Type=oneshot
ExecStart=/usr/bin/certbot renew --quiet --agree-tos --deploy-hook "systemctl reload nginx.service"

View file

@ -0,0 +1,10 @@
[Unit]
Description=Twice daily renewal of LetsEncrypt certificates
[Timer]
OnCalendar=0/12:00:00
RandomizedDelaySec=1h
Persistent=true
[Install]
WantedBy=timers.target

View file

@ -0,0 +1,3 @@
---
dependencies:
- { role: handlers }

View file

@ -0,0 +1,5 @@
---
- name: "Install certbot"
pacman:
name: "certbot"
state: "present"

View file

@ -0,0 +1,27 @@
---
- name: "Install certbot systemd service file"
copy:
owner: "root"
group: "root"
mode: "755"
src: "certbot-renew.service"
dest: "/etc/systemd/system/certbot-renew.service"
notify: "systemd daemon reload"
- name: "Install certbot systemd timer"
copy:
owner: "root"
group: "root"
mode: "755"
src: "certbot-renew.timer"
dest: "/etc/systemd/system/certbot-renew.timer"
notify: "systemd daemon reload"
- name: "Force systemd handlers run"
meta: "flush_handlers"
- name: "Enable certbot systemd timer"
systemd:
name: "certbot-renew.timer"
state: "started"
enabled: "true"

View file

@ -0,0 +1,3 @@
---
- include_tasks: "certbot.yml"
- include_tasks: "install_timers.yml"

View file

@ -0,0 +1,3 @@
---
domain_name: "as209616.net"
letsencrypt_email: "jesper@graffen.dk"

View file

@ -0,0 +1,4 @@
---
dependencies:
- { role: "handlers" }
- { role: "letsencrypt" }

View file

@ -0,0 +1,2 @@
---
- include_tasks: "nginx.yml"

View file

@ -0,0 +1,57 @@
---
- name: "Install nginx"
pacman:
name: "nginx"
state: "present"
- name: "Enable and start nginx service"
service:
name: "nginx"
enabled: "yes"
state: "started"
- name: "Create nginx config directory"
file:
path: "/etc/nginx/conf.d"
state: "directory"
- name: "Create nginx vhost directory"
file:
path: "/etc/nginx/sites"
state: "directory"
- name: "Create certbot directory"
file:
path: "/usr/share/nginx/letsencrypt"
state: "directory"
- name: "Copy base nginx.conf"
template:
src: "nginx.conf.j2"
dest: "/etc/nginx/nginx.conf"
notify: "reload nginx"
- name: "Install nginx site for letsencrypt requests"
template:
src: "nginx-http.j2"
dest: "/etc/nginx/sites/http"
notify: "reload nginx"
- name: "Force all notified nginx handlers to enable letsencrypt"
meta: "flush_handlers"
- name: "Create letsencrypt certificate"
shell: "certbot certonly -n --webroot -w /usr/share/nginx/letsencrypt -m {{ letsencrypt_email }} --agree-tos -d {{ domain_name }}"
args:
creates: "/etc/letsencrypt/live/{{ domain_name }}"
- name: "Generate dhparams"
shell: "openssl dhparam -out /etc/nginx/dhparams.pem 2048"
args:
creates: "/etc/nginx/dhparams.pem"
- name: "Install nginx site config"
template:
src: templates/nginx-https.j2
dest: /etc/nginx/sites/https
notify: "reload nginx"

View file

@ -0,0 +1,14 @@
# HTTP for LetsEncrypt
server {
listen [::]:80 default_server;
server_name {{ domain_name }};
location /.well-known/acme-challenge {
root /usr/share/nginx/letsencrypt;
try_files $uri $uri/ =404;
}
location / {
rewrite ^ https://{{ domain_name }}$request_uri? permanent;
}
}

View file

@ -0,0 +1,37 @@
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; img-src 'self'; style-src 'self' 'unsafe-inline'; font-src 'self'; frame-src 'none'; object-src 'none'";
add_header Referrer-Policy "same-origin";
add_header Strict-Transport-Security "max-age=31536000";
server_tokens off;
# HTTPS server
#
server {
listen [::]:443 ssl http2 default;
server_name {{ domain_name }};
ssl_certificate /etc/letsencrypt/live/{{ domain_name }}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/{{ domain_name }}/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/{{ domain_name }}/fullchain.pem;
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 5m;
ssl_stapling on;
ssl_stapling_verify on;
ssl_protocols TLSv1.2;
ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
ssl_dhparam /etc/nginx/dhparams.pem;
ssl_prefer_server_ciphers on;
root /usr/share/nginx/{{ domain_name }};
index index.html index.htm;
location / {
try_files $uri $uri/ =404;
}
}

View file

@ -0,0 +1,21 @@
worker_processes 4;
events {
worker_connections 768;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
gzip on;
gzip_disable "msie6";
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites/*;
}

22
roles/playbook.yml Normal file
View file

@ -0,0 +1,22 @@
---
- name: "Basic housekeeping"
hosts: "all"
become: "yes"
roles:
- "archvms_base"
- name: "Configure webservers"
hosts: "as209616_webservers"
become: "yes"
roles:
- "letsencrypt"
- "nginx_server"
- "website"
- "cockpit"
- name: "Configure Bird 2.0 Routers"
hosts: "routers"
become: "yes"
roles:
- { role: "bird", tags: "bird" }

View file

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<title>Welcome to AS209616!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. </p>
</body>
</html>

Binary file not shown.

View file

@ -0,0 +1,2 @@
---
- import_tasks: "website.yml"

View file

@ -0,0 +1,11 @@
---
- name: "Create vhost folder"
file:
path: "/usr/share/nginx/{{ domain_name }}"
state: "directory"
- name: "Unarchive website to vhost root"
unarchive:
src: "files/site.tar.gz"
dest: "/usr/share/nginx/{{ domain_name }}"