Commit bfef6172 authored by Steve Weber's avatar Steve Weber
Browse files

rt1183126

parent b2f00bf8
......@@ -12,16 +12,17 @@ cat /var/log/letsencrypt/letsencrypt.log
Example Pillar:
{{sls}}.certbot:
# WARN: changes to aliases and testcert require old cert be deleted "certbot delete -d $DOMAIN"
testcert: True
domain: dbhub.math.uwaterloo.ca
admin_email: s8weber@uwaterloo.ca
pre_hook: systemctl stop apache2
post_hook: systemctl start apache2
aliases: []
- dbhub-p01.math.uwaterloo.ca
```
# WARN: changes to aliases require old cert be deleted "certbot delete -d $DOMAIN"
{{sls}}.certbot:
testcert: True
domain: scholarships.math.uwaterloo.ca
admin_email: s8weber@uwaterloo.ca
pre_hook: systemctl stop nginx
post_hook: systemctl start nginx
aliases:
- {{grains.fqdn}}
```
Cert files are created at:
......
#!/usr/bin/env bash
set -e
set -x
# runas root!
#test "x$USER" = "xroot" || exit 400
set -ex
# if missing venv: create venv dir
test -e '{{vars.dir_venv}}' || (mkdir '{{vars.dir_venv}}' ; chown -R '{{vars.user}}' '{{vars.dir_venv}}')
bash ./requirements.sh
#bash ./requirements.sh
#sudo apt install -y python3 python3-dev python3-venv
sudo -Hu {{vars.user}} bash << "EOF_user_tasks"
set -e
set -x
set -ex
#cd '{{vars.dir_src}}'
#D="$(dirname "$(realpath "$0")")"
#export LC_LANG=en_US.UTF-8
#export LC_ALL=en_US.UTF-8
#export LANG=en_US.UTF-8
# you can use any of python3 python or python2
python_bin={{vars.python_bin}}
test -e '{{vars.dir_venv}}/bin/activate' || $python_bin -m venv '{{vars.dir_venv}}'
export DJANGO_SETTINGS_MODULE={{vars.module_settings}}
test -e '{{vars.dir_venv}}/bin/activate' \
|| {{vars.python_bin}} -m venv '{{vars.dir_venv}}'
source '{{vars.dir_venv}}/bin/activate'
python --version
python -m pip install --upgrade pip
python -m pip install --upgrade -r ./requirements-uw.txt
# python -m pip install --no-binary psycopg2 psycopg2
XXX pip requirements gunicorn uvicorn
python -m pip install --upgrade -r ./requirements.txt
python ./manage.py collectstatic --noinput
python ./manage.py migrate
python -m pip install --upgrade safety
......
......@@ -23,6 +23,11 @@ DATABASES = {
DATABASES = {{vars.settings.databases|json}}
{% endif %}
MEDIA_ROOT = Path('{{vars.dir_vol}}', 'media')
STATIC_ROOT = Path('{{vars.dir_vol}}', 'static')
PRIVATE_STORAGE_ROOT = Path('{{vars.dir_vol}}', 'private')
# optional other settings
{{vars.settings.get('raw', '')|safe}}
......@@ -5,7 +5,8 @@ Description={{vars.service_name}}
Type=simple
SyslogIdentifier={{vars.service_name}}-asgi
WorkingDirectory={{vars.dir_src}}
ExecStart=DJANGO_SETTINGS_MODULE={{vars.module_settings}} {{vars.dir_venv}}/bin/gunicorn {{vars.module_asgi}}:application -k uvicorn.workers.UvicornWorker -w 6 -b 127.0.0.1 -u {{vars.user}} -g {{vars.group}}
Environment="DJANGO_SETTINGS_MODULE={{vars.module_settings}}"
ExecStart={{vars.dir_venv}}/bin/gunicorn {{vars.module_asgi}}:application -k uvicorn.workers.UvicornWorker -w 6 -b 127.0.0.1 -u {{vars.user}} -g {{vars.group}}
ExecStartPost=/bin/sleep 2
Restart=on-failure
RestartSec=15s
......
if too many failed attempts your log will have errors about rate limit. So Ensure the firewall is open befor requesting cert or you might get banned!
```
cat /var/log/letsencrypt/letsencrypt.log
...
"type": "urn:ietf:params:acme:error:rateLimited",
"detail": "Error creating new order :: too many failed authorizations recently: see https://letsencrypt.org/docs/rate-limits/",
"status": 429
```
Example Pillar:
{{sls}}.certbot:
# WARN: changes to aliases and testcert require old cert be deleted "certbot delete -d $DOMAIN"
testcert: True
domain: dbhub.math.uwaterloo.ca
admin_email: s8weber@uwaterloo.ca
pre_hook: systemctl stop apache2
post_hook: systemctl start apache2
aliases: []
- dbhub-p01.math.uwaterloo.ca
Cert files are created at:
ssl_certificate_key: /etc/letsencrypt/live/{{domain}}/privkey.pem
ssl_certificate_pem: /etc/letsencrypt/live/{{domain}}/fullchain.pem
SSLCertificateFile: /etc/letsencrypt/live/{{domain}}/fullchain.pem
SSLCertificateKeyFile: /etc/letsencrypt/live/{{domain}}/privkey.pem
SSLCACertificateFile: /etc/letsencrypt/live/{{domain}}/fullchain.pem
Deleting old cert
# simple
certbot delete -d "$DOMAIN"
# more advanced
cp -r /etc/letsencrypt/ /etc/letsencrypt.backup
rm -rf /etc/letsencrypt/live/DOMAIN
rm -rf /etc/letsencrypt/renewal/DOMAIN.conf
rm -rf /etc/letsencrypt/archive/DOMAIN
What is run under the hood when getting a cert:
# simple
certbot certonly -d "$DOMAIN"
# more advanced settings can be set
certbot certonly \
--max-log-backups=0 --noninteractive --agree-tos \
--preferred-challenges=http-01 \
--email="$ADMIN_EMAIL" -d "$DOMAIN"
example: `/etc/letsencrypt/cli.ini`
max-log-backups = 0
deploy-hook = {{vars.deploy_hook}}
email = {{vars.admin_email}}
preferred-challenges = http-01
http-01-port = 80
renew-by-default = True
standalone
agree-tos = True
test-cert
# ------ VARS ------
{% from 'uwl/certbot/init.sls' import vars as _certbot %}
{% load_yaml as vars %}
default:
pre_hook: systemctl stop nginx
post_hook: systemctl start nginx
#domain: xxxxxxx.uwaterloo.ca
#admin_email: xxxxxxx@uwaterloo.ca
# WARN: changes to aliases and testcert require old cert be deleted "certbot delete -d $DOMAIN"
aliases: []
testcert: False
{% endload %}
{% set vars = salt.uwl.solve_vars(vars, tplfile) %}
# ------------------
include:
- {{_certbot.sls}}
{{sls}} config:
file.managed:
- name: {{_certbot.config_file}}
- contents: |
max-log-backups = 0
# deploy-hook = {vars.deploy_hook}
pre-hook = {{vars.pre_hook}}
post-hook = {{vars.post_hook}}
agree-tos = True
- require:
- pkg: {{_certbot.sls}}
{{sls}} - {{vars.domain}}:
acme.cert:
- name: {{vars.domain}}
- email: {{vars.admin_email}}
# WARN: changes to aliases require old cert be deleted "certbot delete -d $DOMAIN"
- aliases: {{vars.aliases}}
# WARN: changes to test_cert require old cert be deleted "certbot delete -d $DOMAIN"
{%- if vars.testcert %}
- test_cert: True
{%- else %}
- test_cert: False
{%- endif %}
- require:
- pkg: {{_certbot.sls}}
# ------ VARS ------
{% load_yaml as vars %}
default:
{% endload %}
{% set vars = salt.uwl.solve_vars(vars, tplfile) %}
# ------------------
include:
- .postgresql
- .certbot
- .nginx
#- .apache
- .app
<!DOCTYPE html>
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<meta content="utf-8" http-equiv="encoding">
<title>Issue</title>
</head>
<body>
<h1>Oops</h1>
<p>Sorry, because of maintainance or an issue this service is temporarily offline.</p>
<p>We are likely aware of the issue and actively working on the solution.</p>
<p>Please reload this page or try again in 15 minutes.<p>
</body>
<pre>
,
,-. _,---._ __ / \
/ ) .-' `./ / \
( ( ,' `/ /|
\ `-" \'\ / |
`. , \ \ / |
/`. ,'-`----Y |
( ; | '
| ,-. ,-' | /
| | ( | | /
) | \ `.___________|/
`--' `--'
</pre>
</html>
# salt managed
{{ vars.htpasswd_content }}
user {{vars.user}};
worker_processes auto;
pid /run/nginx.pid;
# load_module "/usr/share/nginx/modules/ngx_http_geoip_module.so";
events {
worker_connections {{vars.worker_connections}};
}
http {
include mime.types;
default_type application/octet-stream;
geo $is_local_uw_ip {
{{vars.map_local_uw_ip}}
}
error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log;
error_log stderr;
access_log syslog:server=unix:/dev/log;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
gzip on;
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name {{vars.server_name}};
return 307 https://{{vars.server_name}}$request_uri;
}
server {
listen 443 ssl;
server_name {{vars.server_name}};
ssl_certificate {{vars.ssl_certificate_pem}};
ssl_certificate_key {{vars.ssl_certificate_key}};
ssl_protocols TLSv1.2;
ssl_session_cache shared:SSL:10m;
ssl_ciphers "AES128+EECDH:AES128+EDH:AES256+EECDH:AES256+EDH";
ssl_prefer_server_ciphers on;
{%- if vars.htpasswd_enabled %}
auth_basic "{{vars.htpasswd_msg}}";
auth_basic_user_file {{vars.htpasswd_path}};
{%- endif %}
error_page 504 502 /custom_error.html;
location = /custom_error.html {
root {{salt.file.dirname(vars.error_page)}};
internal;
}
{% if vars.get('require_vpn') %}
set $is_allowed no;
if ($is_local_uw_ip = yes) {
set $is_allowed yes;
}
{% endif %}
{% if vars.favicon_source %}
location = /favicon.ico {
alias {{vars.favicon}};
}
{%- endif %}
# hide nginx version headers
server_tokens off;
# max upload size
client_max_body_size 75M;
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/csv text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
location / {
## ** maintenance
# default_type text/plain;
# return 200 "Undergoing some planned maintenance. The service will be restored soon.";
{% if vars.get('require_vpn') %}
if ($is_allowed = no) {
return 307 https://checkvpn.uwaterloo.ca/?callback=https://{{vars.server_name}}$request_uri;
# NOTE: nginx does not have a good way to encode_url for the callback
# SO: A request like .. ?callback=https://x/?x=1&y=2
# will drop y=2 from the callback!
}
{% endif %}
proxy_send_timeout 20;
proxy_read_timeout 130;
keepalive_timeout 120;
# proxy_buffer_size 4096 is not enough for cache key, it should increased at least to 6144,
proxy_buffer_size 6144;
proxy_buffering on;
# fix: 400 Request Header Or Cookie Too Large
#large_client_header_buffers 4 16k;
# max upload size
#client_max_body_size 75M;
# enable chunked responses
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_next_upstream error timeout;
proxy_redirect off;
proxy_pass {{vars.proxy_pass}};
add_header Access-Control-Allow-Origin https://{{vars.server_name}};
add_header Access-Control-Allow-Methods 'GET, POST, DELETE, OPTIONS';
add_header Front-End-Https on;
add_header Strict-Transport-Security "max-age=15768000; includeSubdomains";
#add_header X-Nginx-Cache $upstream_cache_status;
#add_header X-XSS-Protection "1; mode=block";
#add_header X-Content-Type-Options nosniff;
#add_header X-Frame-Options SAMEORIGIN;
}
}
}
# ------ VARS ------
{% from 'uwl/nginx/init.sls' import vars as _nginx %}
{% load_yaml as vars %}
default:
_nginx: {{_nginx}}
name: {{_nginx.config_file}}
source: salt://{{tpldir}}/_data/nginx.conf
server_name: {{grains['fqdn']}}
http_root: /repo_vol/public
user: {{_nginx.user}}
worker_connections: 1024
proxy_pass: 'http://127.0.0.1:8000'
htpasswd_enabled: False
htpasswd_path: /etc/htpasswd
htpasswd_msg: Admin Access
htpasswd_content: 'qa:{PLAIN}qapass'
error_page: /usr/share/nginx/html/custom_error.html
favicon: /usr/share/nginx/html/favicon.ico
favicon_source: salt://{{tpldir}}/_data/favicon.ico
cache_size: {{(grains['mem_total'] * 0.05)|int}}
site_req_limit: 50
site_req_limit_burst: 1500
api_req_limit: 200
api_req_limit_burst: 1500
map_local_uw_ip: |
default no;
127.0.0.0/8 yes;
10.0.0.0/8 yes;
172.16.0.0/12 yes;
192.168.0.0/16 yes;
129.97.0.0/16 yes;
47.252.27.26/32 yes; # Alibaba VPN
app_servers:
- 127.0.0.1
{% endload %}
{% set vars = salt.uwl.solve_vars(vars, tplfile) %}
# ------------------
include:
- {{_nginx.sls}}
{{sls}}:
file.managed:
- name: {{vars.name}}
- source: {{vars.source}}
- template: jinja
- context: { vars: {{vars|json}} }
- mode: '0644'
- watch_in:
- service: {{_nginx.sls}}
{#
{{sls}} geoip packages:
pkg.installed:
- pkgs:
- geoip-database
- libgeoip1
- nginx-full
- require_in:
- file: {{sls}}
- watch_in:
- service: {{_nginx.sls}}
#}
{{sls}} - error_page:
file.managed:
- name: {{vars.error_page}}
- source: salt://{{tpldir}}/_data/custom_error.html
- mode: '0644'
{% if vars.favicon_source %}
{{sls}} - favicon:
file.managed:
- name: {{vars.favicon}}
- source: {{vars.favicon_source}}
- mode: '0644'
- watch_in:
- service: {{_nginx.sls}}
{%- endif %}
{%- if vars.htpasswd_enabled %}
{{sls}} - htpasswd:
file.managed:
- name: {{vars.htpasswd_path}}
- source: salt://{{tpldir}}/_data/htpasswd
- template: jinja
- context:
vars: {{vars|json}}
- mode: '0644'
- watch_in:
- service: {{vars._nginx.sls}}
{%- endif %}
# ------ VARS ------
{% from 'uwl/postgresql/init.sls' import vars as _postgresql %}
{% load_yaml as vars %}
default:
example:
database:
<dbname>:
user: user
password: password
<dbname2>:
user: user
password: password
{% endload %}
{% set vars = salt.uwl.solve_vars(vars, tplfile) %}
# ------------------
include:
- {{_postgresql.sls}}
{{sls}} python package needed for salt postgres modules:
pkg.installed:
- pkgs:
#- python-psycopg2
- python3-psycopg2
- reload_modules: true
{% for d,v in vars.get('database', {}).items() %}
{{sls}} {{d}} user:
postgres_user.present:
- name: {{v.user}}
#- refresh_password: True
- password: {{v.password}}
- createdb: False
- createroles: False
- require:
- pkg: {{sls}} python package needed for salt postgres modules
{{sls}} {{d}} database:
postgres_database.present:
- name: {{d}}
- owner: {{v.user}}
- encoding: UTF8
- lc_collate: en_CA.UTF-8
- lc_ctype: en_CA.UTF-8
- template: template0
- require:
- postgres_user: {{sls}} {{d}} user
- pkg: {{sls}} python package needed for salt postgres modules
{% endfor %}
{{sls}} hba_config:
file.blockreplace:
- name: {{_postgresql.cluster_config_path}}/pg_hba.conf
- marker_start: "### START zone SALT ###"
- marker_end: "### END zone SALT ###"
- content: |
host webapp_t01 all 0.0.0.0/0 md5
- append_if_not_found: True
- show_changes: True
- watch_in:
- service: {{_postgresql.sls}}
#!/bin/bash
# SYNC: {{vars.settings.databases.default.HOST}}/{{vars.settings.databases.default.NAME}}
# USING: {{vars.database_sync.HOST}}/{{vars.database_sync.NAME}}
command -v apt && apt -y install postgresql-client
#echo '{{vars.database_sync.HOST}}:{{vars.database_sync.PORT}}:{{vars.database_sync.NAME}}:{{vars.database_sync.USER}}:{{vars.database_sync.PASSWORD}}' > ./.pgpass
#echo '{{vars.settings.databases.default.HOST}}:{{vars.settings.databases.default.PORT}}:{{vars.settings.databases.default.NAME}}:{{vars.settings.databases.default.USER}}:{{vars.settings.databases.default.PASSWORD}}' >> ./.pgpass
#chmod 0700 ./.pgpass
#export PGPASSFILE=./.pgpass
echo ""
echo "**** WIPE DATABASE (tables) ****"
PGPASSWORD={{vars.settings.databases.default.PASSWORD}} psql \
--host={{vars.settings.databases.default.HOST}} \
--username={{vars.settings.databases.default.USER}} \
--dbname={{vars.settings.databases.default.NAME}} \
-t -c 'DROP SCHEMA public CASCADE; CREATE SCHEMA public; GRANT ALL ON SCHEMA public TO postgres; GRANT ALL ON SCHEMA public TO public;' \
| PGPASSWORD={{vars.settings.databases.default.PASSWORD}} psql \
--host={{vars.settings.databases.default.HOST}} \
--username={{vars.settings.databases.default.USER}} \
--dbname={{vars.settings.databases.default.NAME}}
# another way to drop objects...
# -c "select 'drop table \"' || tablename || '\" cascade;' from pg_tables where schemaname='public'" \
# -c 'DROP SCHEMA public CASCADE; CREATE SCHEMA public; GRANT ALL ON SCHEMA public TO postgres; GRANT ALL ON SCHEMA public TO public;' \
echo ""
echo "**** SYNC DATABASE ****"
PGPASSWORD={{vars.database_sync.PASSWORD}} pg_dump \
--host={{vars.database_sync.HOST}} \
--username={{vars.database_sync.USER}} \
--dbname={{vars.database_sync.NAME}} \
--no-owner --clean \
| PGPASSWORD={{vars.settings.databases.default.PASSWORD}} psql \