update-nginx-config 3.96 KB
Newer Older
BAIRE Anthony's avatar
BAIRE Anthony committed
1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/python3 -u

import json
import os
import re
import subprocess
import sys

INPUT   = "/vol/ro/config"
OUTPUT  = "/etc/nginx/sites-available/default"

12
13
def die(lineno, fmt, *k):
    sys.stderr.write("error:%s:%s: %s\n" % (INPUT, ((lineno+1) if isinstance(lineno, int) else ""), (fmt % k)))
BAIRE Anthony's avatar
BAIRE Anthony committed
14
15
    sys.exit(1)

BAIRE Anthony's avatar
BAIRE Anthony committed
16

17
18
# ssl config
security_config = lambda fqdn: """
BAIRE Anthony's avatar
BAIRE Anthony committed
19
20
21
22
	ssl on;
	ssl_certificate		/vol/ro/ssl/{fqdn}.crt;
	ssl_certificate_key	/vol/ro/ssl/{fqdn}.key;

23
        include /etc/nginx/ssl.conf;
24
	{hsts}
BAIRE Anthony's avatar
BAIRE Anthony committed
25

26
27
""".format(fqdn=fqdn, hsts="more_clear_headers Strict-Transport-Security;"
                            if os.environ.get("NGINX_DISABLE_HSTS") else "")
BAIRE Anthony's avatar
BAIRE Anthony committed
28
29


30
31
# default server (returns 404)
default_server = lambda **kw: """
BAIRE Anthony's avatar
BAIRE Anthony committed
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

# do not transmit nginx version number
server_tokens off;

server {{
	# Default server (if given an unknown/invalid virtual host)
	listen		80;
	server_name	_;
	return		404;
}}
server {{
	# Default server (if given an unknown/invalid virtual host)
	listen		443;
	server_name	_;
{security}

BAIRE Anthony's avatar
BAIRE Anthony committed
48
49
50
51
	location / {{
		return	404;
	}}

52
{block}
BAIRE Anthony's avatar
BAIRE Anthony committed
53

54
55
}}
""".format(security=security_config("invalid"), **kw)
BAIRE Anthony's avatar
BAIRE Anthony committed
56

BAIRE Anthony's avatar
BAIRE Anthony committed
57

58
allgo_server = lambda **kw: """
BAIRE Anthony's avatar
BAIRE Anthony committed
59
60
61
62
63
64
65

server {{
	# HTTP server
	listen		80;
	server_name	{fqdn};

	# redirect everything to the HTTPS server
66
        rewrite		^(.*)		https://{fqdn}:{port}$1	permanent;
BAIRE Anthony's avatar
BAIRE Anthony committed
67
68
69
70
}}

server {{
	# HTTPS server
71
	listen		{port};
BAIRE Anthony's avatar
BAIRE Anthony committed
72
73
74
75
76
	server_name	{fqdn};

	# security config
{security}

BAIRE Anthony's avatar
BAIRE Anthony committed
77
78
	# allow big uploads
	client_max_body_size 1G;
BAIRE Anthony's avatar
BAIRE Anthony committed
79
80
81
82
83
84
85
86
87
88

        # transform 502 (bad gateway) and 504 (gateway timeout) errors into 503 (service unavailable)
        error_page 502 =503 /503;
        error_page 504 =503 /503;
        location = /503
        {{
                return 503;
        }}

        location /
89
        {{
90
		proxy_pass	http://{env}-django:8080/;
91
92
93
94
                proxy_redirect  off;
		proxy_buffering	off;

                proxy_set_header X-Forwarded-Proto $scheme;
95
96
                proxy_set_header X-Forwarded-Host  $http_host;
                proxy_set_header X-Forwarded-For   $remote_addr;
97
98
        }}

99
100
101
{block}
}}
""".format(security=security_config(kw["fqdn"]), **kw)
BAIRE Anthony's avatar
BAIRE Anthony committed
102

BAIRE Anthony's avatar
BAIRE Anthony committed
103

104
105
106
107
108
109
def read_block(line_reader):
    first_lineno, line = next(line_reader, (None, None))
    if line is None:
        die(lineno, "unexpected EOF (expected: '{'")
    if not line.startswith("{"):
        die(lineno, "expected '{' at the beginnning of line")
BAIRE Anthony's avatar
BAIRE Anthony committed
110

111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
    lst = [line[1:]]
    for lineno, line in line_reader:
        if line.startswith("}"):
            if re.match(r"\}\s*(#|$)", line):
                return "".join(lst)
            die(lineno, "closing '}' must be alone on its line")
        lst.append(line)
    die(first_lineno, "unterminated block")

def main():
    with open(INPUT) as in_file, open(OUTPUT, "w") as out_file:

        default_done = False
        line_reader = enumerate(in_file)

        for lineno, line in line_reader:
            if line.startswith("DEFAULT"):
                # default server
                if not re.match("DEFAULT\s*($|#)", line):
                    die(lineno, "malformatted DEFAULT line")
                if default_done:
                    die(lineno, "duplicated DEFAULT line")

                out_file.write(default_server(block=read_block(line_reader)))
                default_done = True

            elif line.startswith("ALLGO"):
                # allgo server

                if not default_done:
                    default_server(block="")
                    default_done = True

                try:
145
                    env, fqdn, port = (x.strip() for x in re.match(r"ALLGO\(([^)]*)\)\s*($|#)", line).group(1).split(","))
146
147
148
149
                    int(port)
                except Exception:
                    die(lineno, "malformatted ALLGO line")

150
                sys.stderr.write("Allgo instance: environment %s  frontend %s:%s\n" % (env, fqdn, port))
151

152
153
                out_file.write(allgo_server(env=env, fqdn=fqdn,
                    port=port, block=read_block(line_reader)))
154
155
156
157

            else:
                # ordinary line
                out_file.write(line)
BAIRE Anthony's avatar
BAIRE Anthony committed
158

159
main()
BAIRE Anthony's avatar
BAIRE Anthony committed
160