Skip to content
Snippets Groups Projects
Commit cfa37d67 authored by Michael Loipführer's avatar Michael Loipführer
Browse files

networking automation

parent 5912be62
No related branches found
No related tags found
No related merge requests found
......@@ -13,7 +13,7 @@ Dabei werden nötige Pakete installiert, alle Konfigurationsfiles sowohl auf dem
Außerdem wird im container **openssh-server** installiert und gestartet. Der Port wird dynamisch auf den ersten
freien Port ab **10022** in Inkrementen von **1000** gesetzt.
Die templates für Konfigurationsfiles liegen im directory **container**.
Die templates für Konfigurationsfiles liegen im directory **templates**.
### Container VERNICHTEN
```bash
......@@ -26,4 +26,4 @@ Der Container und alle Konfigurationsfiles auf dem Host werden gelöscht.
python3 lustmolch.py install-ssh-key <container-name> <path-to-ssh-key>
```
Der angegeben SSH key wird in `/root/.ssh/authorized_keys` kopiert. Es ist möglich den key als String-Parameter
zu übergeben, dabei muss das Flag `--key-string` gesetzt sein.
\ No newline at end of file
zu übergeben, dabei muss das Flag `--key-string` gesetzt sein.
[Match]
Virtualization=container
Name=host0
[Network]
DHCP=no
DNS={{ dns_server }}
Address={{ ip_address_container }}
Gateway={{ gateway }}
LLDP=yes
EmitLLDP=customer-bridge
# iptable v4 rules for lustmolch container {{name}}
*filter
-A INPUT -p tcp -m tcp --dport {{ssh_port}} 10.150.0.0/17 -j ACCEPT
-A INPUT -p tcp -m tcp --dport {{ssh_port}} 141.84.69.0/24 -j ACCEPT
COMMIT
# iptable v6 rules for lustmolch container {{name}}
*filter
-A INPUT -p tcp -m tcp --dport {{ssh_port}} 2001:4ca0:200::/48 -j ACCEPT
COMMIT
......@@ -8,26 +8,36 @@ from jinja2 import Environment, PackageLoader
import click
env = Environment(loader=PackageLoader('lustmolch', 'container'))
env = Environment(loader=PackageLoader('lustmolch', 'templates'))
cfg_template = namedtuple('cfg_template', ['source', 'path', 'filename'])
template_files_host = [
cfg_template('nginx', Path('/etc/nginx/sites-available'), '{name}'),
cfg_template('nspawn', Path('/etc/systemd/nspawn'), '{name}.nspawn')
cfg_template('nspawn', Path('/etc/systemd/nspawn'), '{name}.nspawn'),
cfg_template('80-container-ve.network', Path('/etc/systemd/network'),
'80-container.ve-{name}.network')
]
template_files_container = [
cfg_template('sshd_config', Path('/etc/ssh'), 'sshd_config')
cfg_template('sshd_config', Path('/etc/ssh'), 'sshd_config'),
cfg_template('80-container.host0.network', Path('/etc/systemd/network'),
'80-container.host0.network')
]
FLAVOUR = 'buster'
DEBIAN_MIRROR = 'http://mirror.stusta.de/debian'
IP_RANGES = ['10.150.0.0/17', '141.84.69.0/24']
SSN_IP_RANGES = ['10.150.0.0/17', '141.84.69.0/24']
www_root = Path('/var/www')
SSH_START_PORT = 10022
SSH_PORT_INCREMENT = 1000
IP_LUSTMOLCH = '141.84.69.235' # TODO: find out dynamically
IP_START_CONTAINER = (192, 168, 0, 1)
IP_START_HOST = (192, 168, 128, 1)
IP_SUBNET_LENGTH = 16
def next_ssh_port(config_file, name):
"""
......@@ -39,7 +49,6 @@ def next_ssh_port(config_file, name):
name: Container name
Returns: SSH port
"""
if not Path(config_file).exists():
cfg = {}
......@@ -57,6 +66,56 @@ def next_ssh_port(config_file, name):
return port
def next_ip_address(config_file, name):
"""
Return the next available (local) IP address to be assigned to the
container and the host interfaces.
Args:
config_file: Path to container configuration file (containers.json)
name: Container name
Returns (tuple): host_ip, container_ip
"""
if not Path(config_file).exists():
cfg = {}
else:
with open(config_file, 'r') as f:
cfg = json.load(f)
if name in cfg:
return (cfg[name].get('ip_address_host').split('/')[0],
cfg[name].get('ip_address_container').split('/')[0])
ip_host = list(IP_START_HOST)
ip_container = list(IP_START_CONTAINER)
for name, container in cfg.items():
if 'ip_address_host' not in container or 'ip_address_container' not in container:
continue
ip_h = container.get('ip_address_host').split('/')[0].split('.')
ip_c = container.get('ip_address_container').split('/')[0].split('.')
if ip_h[2] > ip_host[2]:
ip_host = ip_h
ip_host[3] += 1
elif ip_h[2] == ip_host[2] and ip_h[3] > ip_host[3]:
if ip_h[3] == 254:
ip_host[2] += 1
ip_host[3] = 1
else:
ip_host[3] = ip_h[3] + 1
if ip_c[2] > ip_container[2]:
ip_container = ip_c
ip_container[3] += 1
elif ip_c[2] == ip_container[2] and ip_c[3] > ip_container[3]:
if ip_c[3] == 254:
ip_container[2] += 1
ip_container[3] = 1
else:
ip_container[3] = ip_c[3] + 1
return (ip_host.join('.'), ip_container.join('.'))
def update_config(config_file, name, container):
if not Path(config_file).exists():
with open(config_file, 'w+') as f:
......@@ -90,13 +149,18 @@ def create_container(dry_run, config_file, name):
www_dir.mkdir(parents=True, exist_ok=True)
# place configuration files
ip_address_container, ip_address_host = next_ip_address(config_file, name)
ssh_port = next_ssh_port(config_file, name)
context = {
'name': name,
'ssh_port': next_ssh_port(config_file, name),
'ssh_port': ssh_port,
'ip_address_host': ip_address_host,
'ip_address_container': ip_address_container,
'ip_subnet_length': IP_SUBNET_LENGTH,
'url': f'{name}.stusta.de'
}
for cfg in template_files_host:
template = env.get_template(cfg.source)
template = env.get_template('host/' + cfg.source)
file_name = cfg.path / (cfg.filename.format(**context))
click.echo(f'Placing config file {file_name}')
if not dry_run:
......@@ -115,24 +179,30 @@ def create_container(dry_run, config_file, name):
script_location = '/opt/bootstrap.sh'
script_location_host = str(machine_path) + script_location
shutil.copy('container/bootstrap.sh', script_location_host)
shutil.copy('templates/container/bootstrap.sh', script_location_host)
Path(script_location_host).chmod(0o755)
run(['systemd-nspawn', '-D', str(machine_path), script_location], check=True)
click.echo('Copying config files into container')
if not dry_run:
for cfg in template_files_container:
template = env.get_template(cfg.source)
template = env.get_template('container/' + cfg.source)
file_name = cfg.filename.format(**context)
click.echo(f'Placing config file {file_name}')
if not dry_run:
with open(Path(f'{machine_path}{cfg.path}/{file_name}'), 'w+') as f:
f.write(template.render(context))
click.echo(f'Updating Iptable rules for port {context["ssh_port"]}')
click.echo(f'Updating Iptable rules (filter, nat)')
if not dry_run:
for ip_range in IP_RANGES:
run(['iptables', '-A', 'INPUT', '-p', 'tcp', '-m', 'tcp', '--dport', context['ssh_port'], '-s', ip_range, '-j', 'ACCEPT'])
for ip_range in SSN_IP_RANGES:
run(['iptables', '-A', 'INPUT', '-p', 'tcp', '-m', 'tcp',
'--dport', ssh_port, '-s', ip_range, '-j', 'ACCEPT'])
run(['iptables', '-t' , 'nat', '-A', 'PREROUTING', '-p', 'tcp',
'-m' ,'tcp', '--dport', ssh_port, '-s', ip_range, '-j', 'DNAT',
'--to-destination', f'{ip_address_container}:22'])
run(['iptables', '-t', 'nat', '-A', 'POSTROUTING', '-o', f've-{name}',
'-j', 'SNAT', '--to-source', IP_LUSTMOLCH])
click.echo('Starting container')
if not dry_run:
......@@ -142,7 +212,8 @@ def create_container(dry_run, config_file, name):
if not dry_run:
update_config(config_file, name, container=context)
click.echo(f'All done, ssh server running on port {context["ssh_port"]}\nTo finish please run "iptables-save".')
click.echo(f'All done, ssh server running on port {ssh_port}\n'
'To finish please run "iptables-save".')
@cli.command()
......@@ -167,11 +238,16 @@ def install_ssh_key(config_file, key_string, name, key):
@cli.command()
@click.option('--dry-run', is_flag=True, default=False)
@click.option('--config-file', default='containers.json', help='Container configuration file')
@click.argument('name')
def remove_container(config_file, name):
def remove_container(dry_run, config_file, name):
machine_path = Path('/var/lib/machines', name)
click.echo(f'Stopping container')
if not dry_run:
run(['machinectl', 'stop', name], capture_output=True, check=False)
# removing shared folder
www_dir = www_root / name
click.echo(f'Removing shared www folder')
......
# SPDX-License-Identifier: LGPL-2.1+
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
# This network file matches the container-side of the virtual Ethernet link
# created by systemd-nspawn's --network-veth switch. See systemd-nspawn(1) for
# details.
[Match]
Virtualization=container
Name=host0
[Network]
DHCP=no
DNS={{ ip_address_host }}
Address={{ ip_address_container }}/{{ ip_subnet_length }}
Gateway={{ ip_address_host }}
LLDP=yes
EmitLLDP=customer-bridge
......@@ -2,4 +2,5 @@
apt-get update
apt-get -y upgrade
apt-get -y install dbus openssh-server vim
\ No newline at end of file
apt-get -y install dbus openssh-server vim systemd-networkd
systemctl enable systemd-networkd.service
\ No newline at end of file
......@@ -10,7 +10,7 @@
# possible, but leave them commented. Uncommented options override the
# default value.
Port {{ssh_port}}
Port 22
#AddressFamily any
#ListenAddress 0.0.0.0
#ListenAddress ::
......@@ -29,7 +29,7 @@ Port {{ssh_port}}
# Authentication:
#LoginGraceTime 2m
PermitRootLogin without-password
PermitRootLogin prohibit-password
StrictModes yes
#MaxAuthTries 6
#MaxSessions 10
......
......@@ -16,7 +16,7 @@ Name=ve-{{ name }}
Driver=veth
[Network]
Address={{ ip_address_host }}
Address={{ ip_address_host }}/{{ ip_subnet_length }}
IPMasquerade=yes
LLDP=yes
EmitLLDP=customer-bridge
......
File moved
......@@ -6,3 +6,4 @@ Bind=/var/www/{{name}}:/var/www
[Exec]
PrivateUsers=off
Hostname={{name}}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment