553 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			553 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
import json
 | 
						|
import re
 | 
						|
import socket
 | 
						|
import os
 | 
						|
import io
 | 
						|
import sys
 | 
						|
 | 
						|
 | 
						|
def forward(
 | 
						|
    input_json,
 | 
						|
    output_conf,
 | 
						|
):
 | 
						|
    with io.open(
 | 
						|
        input_json,
 | 
						|
        'r'
 | 
						|
    ) as f:
 | 
						|
        forward_nginx = json.load(f)
 | 
						|
 | 
						|
    with io.open(
 | 
						|
        output_conf,
 | 
						|
        'w'
 | 
						|
    ) as f:
 | 
						|
        names = [o['app_name'] for o in forward_nginx]
 | 
						|
 | 
						|
        if not '' in names:
 | 
						|
            forward_nginx.append(
 | 
						|
                dict(
 | 
						|
                    app_name='',
 | 
						|
                    redirect_url='https://product-development-service.blogspot.com',
 | 
						|
                )
 | 
						|
            )
 | 
						|
 | 
						|
        sections = dict()
 | 
						|
 | 
						|
        for entry in forward_nginx:
 | 
						|
            location_path = None
 | 
						|
 | 
						|
            if entry['app_name'] != '':
 | 
						|
                location_path = '/%s/' % entry['app_name']
 | 
						|
            else:
 | 
						|
                location_path = '/'
 | 
						|
 | 
						|
            if 'server_name' in entry:
 | 
						|
                server_name = entry['server_name']
 | 
						|
            else:
 | 
						|
                server_name = 'default_server'
 | 
						|
 | 
						|
            if (
 | 
						|
                not server_name in sections
 | 
						|
            ):
 | 
						|
                sections[server_name] = []
 | 
						|
 | 
						|
                if 'client_max_body_size' in entry:
 | 
						|
                    client_max_body_size = entry['client_max_body_size']
 | 
						|
                else:
 | 
						|
                    client_max_body_size = '50M'
 | 
						|
 | 
						|
                assert isinstance(client_max_body_size, str)
 | 
						|
 | 
						|
                sections[server_name].append(
 | 
						|
                    r'''
 | 
						|
  client_max_body_size %s;
 | 
						|
                    ''' % client_max_body_size
 | 
						|
                )
 | 
						|
 | 
						|
            location_get = lambda location_body, location_path2, prefix=None,: (
 | 
						|
                r'''
 | 
						|
  location {location} {
 | 
						|
    {location_body}
 | 
						|
  }
 | 
						|
                '''.replace(
 | 
						|
                    '{location_body}', location_body,
 | 
						|
                ).replace(
 | 
						|
                    '{location}', '%s  %s' % (
 | 
						|
                        (
 | 
						|
                            '^~'
 | 
						|
                            if prefix is None
 | 
						|
                            else prefix
 | 
						|
                        ),
 | 
						|
                        location_path2
 | 
						|
                    ),
 | 
						|
                )
 | 
						|
            )
 | 
						|
 | 
						|
            if 'target_endpoint' in entry:
 | 
						|
                location_body_get = lambda target_endpoint: \
 | 
						|
                    r'''
 | 
						|
    proxy_set_header Host $http_host;
 | 
						|
    proxy_http_version 1.1;
 | 
						|
    proxy_set_header X-Forwarded-For $t1;
 | 
						|
    proxy_set_header X-Forwarded-Proto $scheme;
 | 
						|
    proxy_set_header Upgrade $http_upgrade;
 | 
						|
    proxy_set_header Connection $connection_upgrade;
 | 
						|
    proxy_redirect off;
 | 
						|
    proxy_buffering off;
 | 
						|
    proxy_pass {target_endpoint};
 | 
						|
                    '''.replace(
 | 
						|
                        '{target_endpoint}', target_endpoint,
 | 
						|
                    )
 | 
						|
 | 
						|
                if 'fallback_endpoint' in entry:
 | 
						|
                    fallback_name = '@fallback-%s' % os.urandom(10).hex()
 | 
						|
 | 
						|
                    sections[server_name].append(
 | 
						|
                        location_get(
 | 
						|
                            location_body_get(entry['target_endpoint']) + r'''
 | 
						|
    proxy_intercept_errors on;
 | 
						|
    error_page 502 =200 {fallback_name};
 | 
						|
                            '''.replace(
 | 
						|
                                '{fallback_name}', fallback_name,
 | 
						|
                            ),
 | 
						|
                            location_path,
 | 
						|
                        )
 | 
						|
                    )
 | 
						|
 | 
						|
                    sections[server_name].append(
 | 
						|
                        location_get(
 | 
						|
                            location_body_get(entry['fallback_endpoint']),
 | 
						|
                            fallback_name,
 | 
						|
                            '',
 | 
						|
                        )
 | 
						|
                    )
 | 
						|
                else:
 | 
						|
                    sections[server_name].append(
 | 
						|
                        location_get(
 | 
						|
                            location_body_get(entry['target_endpoint']),
 | 
						|
                            location_path,
 | 
						|
                        )
 | 
						|
                    )
 | 
						|
            elif 'redirect_url' in entry:
 | 
						|
                sections[server_name].append(
 | 
						|
                    location_get(
 | 
						|
                        r'''
 | 
						|
      return 302 {redirect_url}$request_uri;
 | 
						|
                        '''.replace(
 | 
						|
                            '{redirect_url}', entry['redirect_url'],
 | 
						|
                        ),
 | 
						|
                        location_path,
 | 
						|
                    )
 | 
						|
                )
 | 
						|
            else:
 | 
						|
                raise NotImplementedError
 | 
						|
 | 
						|
        servers = []
 | 
						|
 | 
						|
        for server_name, current_sections in sections.items():
 | 
						|
            servers.append(
 | 
						|
                r'''
 | 
						|
server {
 | 
						|
  set $t1 $remote_addr;
 | 
						|
  if ($http_x_forwarded_for)
 | 
						|
  {
 | 
						|
    set $t1 $http_x_forwarded_for;
 | 
						|
  }
 | 
						|
 | 
						|
  server_name {server_name};
 | 
						|
  listen 80 {default_server};
 | 
						|
  #client_max_body_size 50M;
 | 
						|
 | 
						|
  {sections_config}
 | 
						|
}
 | 
						|
                '''.replace(
 | 
						|
                  '{sections_config}', '\n'.join(current_sections)
 | 
						|
                ).replace(
 | 
						|
                  '{server_name}',
 | 
						|
                  (
 | 
						|
                    '_'
 | 
						|
                    if server_name == 'default_server'
 | 
						|
                    else server_name
 | 
						|
                  ),
 | 
						|
                ).replace(
 | 
						|
                  '{default_server}',
 | 
						|
                  (
 | 
						|
                    ''
 | 
						|
                    if not server_name == 'default_server'
 | 
						|
                    else server_name
 | 
						|
                  )
 | 
						|
                )
 | 
						|
            )
 | 
						|
 | 
						|
        f.write(r'''
 | 
						|
events {
 | 
						|
  multi_accept on;
 | 
						|
  worker_connections 64;
 | 
						|
}
 | 
						|
 | 
						|
http {
 | 
						|
  log_format main
 | 
						|
    '[$time_local][$remote_addr:$remote_port, $http_x_forwarded_for, $t1, $http_host]'
 | 
						|
    '[$request_length,$bytes_sent,$request_time]'
 | 
						|
    '[$status][$request]'
 | 
						|
    '[$http_user_agent][$http_referer]';
 | 
						|
 | 
						|
  access_log /dev/null combined;
 | 
						|
  access_log /dev/stderr main;
 | 
						|
  gzip on;
 | 
						|
  server_tokens off;
 | 
						|
 | 
						|
  {servers_config}
 | 
						|
 | 
						|
  map $http_upgrade $connection_upgrade {
 | 
						|
    default upgrade;
 | 
						|
    '' close;
 | 
						|
  }
 | 
						|
}
 | 
						|
        '''.replace(
 | 
						|
          '{servers_config}', '\n'.join(servers)
 | 
						|
        ))
 | 
						|
 | 
						|
def ssl(input_json, output_conf):
 | 
						|
    with io.open(
 | 
						|
        input_json,
 | 
						|
        'r'
 | 
						|
    ) as f:
 | 
						|
        ssl_nginx = json.load(f)
 | 
						|
 | 
						|
    servers = []
 | 
						|
 | 
						|
    if 'stream_server' in ssl_nginx:
 | 
						|
        upstream_servers = []
 | 
						|
        server_names = []
 | 
						|
 | 
						|
        ssh_proxy_download_rate = ssl_nginx['stream_server'].get(
 | 
						|
          'ssh_proxy_download_rate',
 | 
						|
          128 * 1024,
 | 
						|
        )
 | 
						|
        ssh_proxy_upload_rate = ssl_nginx['stream_server'].get(
 | 
						|
          'ssh_proxy_upload_rate',
 | 
						|
          128 * 1024,
 | 
						|
        )
 | 
						|
        web_proxy_download_rate = ssl_nginx['stream_server'].get(
 | 
						|
          'web_proxy_download_rate',
 | 
						|
          128 * 1024 * 1024,
 | 
						|
        )
 | 
						|
        web_proxy_upload_rate = ssl_nginx['stream_server'].get(
 | 
						|
          'web_proxy_upload_rate',
 | 
						|
          128 * 1024 * 1024,
 | 
						|
        )
 | 
						|
 | 
						|
        if 'by_server_name' in ssl_nginx['stream_server']:
 | 
						|
            for k, v in ssl_nginx['stream_server']['by_server_name'].items():
 | 
						|
                upstream_servers.append(
 | 
						|
                    'upstream %s { server %s; }' % (
 | 
						|
                        v['upstream_name'],
 | 
						|
                        v['url'],
 | 
						|
                    )
 | 
						|
                )
 | 
						|
                server_names.append(
 | 
						|
                    '"%s" %s;' % (
 | 
						|
                        v['server_name'], v['upstream_name'],
 | 
						|
                    )
 | 
						|
                )
 | 
						|
            
 | 
						|
 | 
						|
        if 'ssh' in ssl_nginx['stream_server']:
 | 
						|
            ssh_section = 'upstream ssh { server {ssh}; }'.replace(
 | 
						|
                '{ssh}',
 | 
						|
                ssl_nginx['stream_server']['ssh'],
 | 
						|
            )
 | 
						|
        else:
 | 
						|
            ssh_section = ''
 | 
						|
 | 
						|
        ssl_port = 444
 | 
						|
        stream_server = r'''
 | 
						|
stream {
 | 
						|
    upstream web {
 | 
						|
        server 127.0.0.1:444;
 | 
						|
    }
 | 
						|
 | 
						|
{upstream_servers}
 | 
						|
 | 
						|
    {ssh_section}
 | 
						|
 | 
						|
    map $ssl_preread_protocol $upstream_protocol {
 | 
						|
        default ssh;
 | 
						|
        "TLSv1.2" $upstream_server_name;
 | 
						|
        "TLSv1.3" $upstream_server_name;
 | 
						|
    }
 | 
						|
 | 
						|
    map $upstream_protocol $proxy_download_rate {
 | 
						|
      web {web_proxy_download_rate};
 | 
						|
      ssh {ssh_proxy_download_rate};
 | 
						|
    }
 | 
						|
    map $upstream_protocol $proxy_upload_rate {
 | 
						|
      web {web_proxy_upload_rate};
 | 
						|
      ssh {ssh_proxy_upload_rate};
 | 
						|
    }
 | 
						|
 | 
						|
    map $ssl_preread_server_name $upstream_server_name {
 | 
						|
        default web;
 | 
						|
{server_names}
 | 
						|
    }
 | 
						|
 | 
						|
    # SSH and SSL on the same port
 | 
						|
    server {
 | 
						|
        listen 443;
 | 
						|
 | 
						|
        ssl_preread on;
 | 
						|
 | 
						|
        proxy_pass $upstream_protocol;
 | 
						|
 | 
						|
        proxy_download_rate $proxy_download_rate;
 | 
						|
        proxy_upload_rate $proxy_upload_rate;
 | 
						|
        # proxy_upload_rate 10k;
 | 
						|
    }
 | 
						|
}
 | 
						|
        '''.replace(
 | 
						|
            '{upstream_servers}', ''.join([
 | 
						|
                '    ' + o + '\n'
 | 
						|
                for o in upstream_servers
 | 
						|
            ]),
 | 
						|
        ).replace(
 | 
						|
            '{ssh_section}', ssh_section,
 | 
						|
        ).replace(
 | 
						|
            '{web_proxy_download_rate}', '%d' % web_proxy_download_rate,
 | 
						|
        ).replace(
 | 
						|
            '{ssh_proxy_download_rate}', '%d' % ssh_proxy_download_rate,
 | 
						|
        ).replace(
 | 
						|
            '{web_proxy_upload_rate}', '%d' % web_proxy_upload_rate,
 | 
						|
        ).replace(
 | 
						|
            '{ssh_proxy_upload_rate}', '%d' % ssh_proxy_upload_rate,
 | 
						|
        ).replace(
 | 
						|
            '{server_names}', ''.join([
 | 
						|
                '        ' + o + '\n'
 | 
						|
                for o in server_names
 | 
						|
            ]),
 | 
						|
        )
 | 
						|
    else:
 | 
						|
        stream_server = ''
 | 
						|
        ssl_port = 443
 | 
						|
 | 
						|
    if 'default_server' in ssl_nginx:
 | 
						|
        server = ssl_nginx['default_server']
 | 
						|
 | 
						|
        if 'metrics_allowed' in server:
 | 
						|
            metrics_allowed_ip = socket.gethostbyname(server['metrics_allowed'])
 | 
						|
        else:
 | 
						|
            metrics_allowed_ip = '127.0.0.1'
 | 
						|
 | 
						|
        servers.append(
 | 
						|
            r'''
 | 
						|
server {
 | 
						|
  server_name _;
 | 
						|
  listen 80 default_server;
 | 
						|
 | 
						|
  location = /_metrics {
 | 
						|
    stub_status;
 | 
						|
    access_log off;
 | 
						|
    # allow 172.0.0.0/8;
 | 
						|
    allow {metrics_allowed_ip};
 | 
						|
    # allow 127.0.0.1;
 | 
						|
    deny all;
 | 
						|
  }
 | 
						|
 | 
						|
  location ~ ^/.well-known/acme-challenge/ {
 | 
						|
    alias /var/www/;
 | 
						|
    try_files $uri =404;
 | 
						|
  }
 | 
						|
 | 
						|
  location ~ {
 | 
						|
    deny all;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
server {
 | 
						|
  set $t1 $remote_addr;
 | 
						|
  if ($http_x_forwarded_for)
 | 
						|
  {
 | 
						|
    set $t1 $http_x_forwarded_for;
 | 
						|
  }
 | 
						|
 | 
						|
  listen {ssl_port} ssl default_server;
 | 
						|
  server_name _;
 | 
						|
 | 
						|
  client_max_body_size {client_max_body_size};
 | 
						|
 | 
						|
  ssl_certificate {signed_chain_cert};
 | 
						|
  ssl_certificate_key {domain_key};
 | 
						|
 | 
						|
  return 444;
 | 
						|
}
 | 
						|
            '''.replace(
 | 
						|
                  '{signed_chain_cert}', server['signed_chain_cert'],
 | 
						|
              ).replace(
 | 
						|
                  '{client_max_body_size}', server['client_max_body_size'],
 | 
						|
              ).replace(
 | 
						|
                  '{domain_key}', server['domain_key'],
 | 
						|
              ).replace(
 | 
						|
                  '{ssl_port}', '%d' % ssl_port,
 | 
						|
              ).replace(
 | 
						|
                  '{metrics_allowed_ip}', metrics_allowed_ip
 | 
						|
              )
 | 
						|
        )
 | 
						|
 | 
						|
    for server in ssl_nginx['servers']:
 | 
						|
        location_proxy_app = r'''
 | 
						|
  location ^~ / {
 | 
						|
    proxy_set_header Host $http_host;
 | 
						|
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 | 
						|
    proxy_set_header X-Forwarded-Proto $scheme;
 | 
						|
    proxy_set_header Upgrade $http_upgrade;
 | 
						|
    proxy_set_header Connection $connection_upgrade;
 | 
						|
    proxy_redirect off;
 | 
						|
    proxy_buffering off;
 | 
						|
    proxy_http_version 1.1;
 | 
						|
    proxy_pass http://app:80;
 | 
						|
  }
 | 
						|
        '''
 | 
						|
 | 
						|
        location_forward_ssl = r'''
 | 
						|
  location ~ {
 | 
						|
    #return 444;
 | 
						|
    return 301 https://$host$request_uri;
 | 
						|
  }
 | 
						|
        '''
 | 
						|
 | 
						|
        if server.get('allow_http') in [True]:
 | 
						|
          http_location = location_proxy_app
 | 
						|
        else:
 | 
						|
          http_location = location_forward_ssl
 | 
						|
 | 
						|
        drop_by_user_agent = ''
 | 
						|
 | 
						|
        if not server.get('drop_by_user_agent') is None:
 | 
						|
          r = re.compile('^([a-zA-Z0-9\s\.\,\(\)]+)$')
 | 
						|
          user_agent_list = [
 | 
						|
            r.match(o)[1]
 | 
						|
            for o in server.get('drop_by_user_agent')
 | 
						|
          ]
 | 
						|
          drop_by_user_agent = r'''
 | 
						|
          if ( $http_user_agent ~ ({user_agent_list}) ) {
 | 
						|
            return 444;
 | 
						|
          }
 | 
						|
          '''.replace(
 | 
						|
            '{user_agent_list}',
 | 
						|
            '|'.join(user_agent_list)
 | 
						|
          )
 | 
						|
 | 
						|
        servers.append(
 | 
						|
            r'''
 | 
						|
server {
 | 
						|
  set $t1 $remote_addr;
 | 
						|
  if ($http_x_forwarded_for)
 | 
						|
  {
 | 
						|
    set $t1 $http_x_forwarded_for;
 | 
						|
  }
 | 
						|
 | 
						|
  listen 80;
 | 
						|
  server_name {server_names};
 | 
						|
  client_max_body_size {client_max_body_size};
 | 
						|
 | 
						|
  location ~ ^/.well-known/acme-challenge/ {
 | 
						|
    alias /var/www/;
 | 
						|
    try_files $uri =404;
 | 
						|
  }
 | 
						|
 | 
						|
  {http_location}
 | 
						|
}
 | 
						|
 | 
						|
server {
 | 
						|
  set $t1 $remote_addr;
 | 
						|
  if ($http_x_forwarded_for)
 | 
						|
  {
 | 
						|
    set $t1 $http_x_forwarded_for;
 | 
						|
  }
 | 
						|
 | 
						|
  listen {ssl_port} ssl;
 | 
						|
  server_name {server_names};
 | 
						|
 | 
						|
  client_max_body_size {client_max_body_size};
 | 
						|
 | 
						|
  ssl_certificate {signed_chain_cert};
 | 
						|
  ssl_certificate_key {domain_key};
 | 
						|
 | 
						|
  {drop_by_user_agent}
 | 
						|
 | 
						|
  location ^~ / {
 | 
						|
    proxy_set_header Host $http_host;
 | 
						|
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 | 
						|
    proxy_set_header X-Forwarded-Proto $scheme;
 | 
						|
    proxy_set_header Upgrade $http_upgrade;
 | 
						|
    proxy_set_header Connection $connection_upgrade;
 | 
						|
    proxy_redirect off;
 | 
						|
    proxy_buffering off;
 | 
						|
    proxy_http_version 1.1;
 | 
						|
    proxy_pass http://app:80;
 | 
						|
  }
 | 
						|
}
 | 
						|
            '''.replace(
 | 
						|
                '{server_names}', ' '.join(server['server_names'])
 | 
						|
              ).replace(
 | 
						|
                  '{signed_chain_cert}', server['signed_chain_cert'],
 | 
						|
              ).replace(
 | 
						|
                  '{client_max_body_size}', server['client_max_body_size'],
 | 
						|
              ).replace(
 | 
						|
                  '{domain_key}', server['domain_key'],
 | 
						|
              ).replace(
 | 
						|
                  '{drop_by_user_agent}', drop_by_user_agent,
 | 
						|
              ).replace(
 | 
						|
                  '{ssl_port}', '%d' % ssl_port,
 | 
						|
              ).replace(
 | 
						|
                  '{http_location}', http_location
 | 
						|
              )
 | 
						|
        )
 | 
						|
 | 
						|
 | 
						|
    with io.open(
 | 
						|
        output_conf,
 | 
						|
        'w'
 | 
						|
    ) as f:
 | 
						|
        f.write(
 | 
						|
            r'''
 | 
						|
load_module "modules/ngx_stream_module.so";
 | 
						|
 | 
						|
events {
 | 
						|
  multi_accept on;
 | 
						|
  worker_connections 64;
 | 
						|
}
 | 
						|
 | 
						|
{stream_server}
 | 
						|
 | 
						|
http {
 | 
						|
  log_format main
 | 
						|
  '[$time_local][$remote_addr:$remote_port, $http_x_forwarded_for, $t1, $http_host]'
 | 
						|
  '[$request_length,$bytes_sent,$request_time]'
 | 
						|
  '[$status][$request]'
 | 
						|
  '[$http_user_agent][$http_referer]';
 | 
						|
 | 
						|
  access_log /dev/null combined;
 | 
						|
  access_log /dev/stderr main;
 | 
						|
  gzip on;
 | 
						|
  server_tokens off;
 | 
						|
 | 
						|
  {servers}
 | 
						|
 | 
						|
 | 
						|
  map $http_upgrade $connection_upgrade {
 | 
						|
    default upgrade;
 | 
						|
    '' close;
 | 
						|
  }
 | 
						|
}
 | 
						|
            '''\
 | 
						|
                .replace('{servers}', '\n'.join(servers)) \
 | 
						|
                .replace('{stream_server}', stream_server)
 | 
						|
        )
 | 
						|
 | 
						|
 | 
						|
if __name__ == '__main__':
 | 
						|
    if len(sys.argv) >= 2 and sys.argv[1] == 'ssl':
 | 
						|
        ssl(*sys.argv[2:])
 | 
						|
    else:
 | 
						|
        forward(*sys.argv[1:])
 |