Изучение способов динамического обслуживания пиров VPN-сервера Wireguard
Я развертываю большое количество серверов в разных географических точках (которые будут время от времени перемещаться), и мне нужно получить удаленный доступ и администрировать эти серверы по низкой цене без какого-либо дополнительного оборудования. После некоторого беглого исследования я решил, что Wireguard - моя лучшая ставка из-за его возможности роуминга (пока один из публичных IP-адресов концентратора или говорящих партнеров остается согласованным, туннель будет сохранен). Я написал быстрый и грязный скрипт Python для запуска при загрузке, который создает закрытые и открытые ключи, вызывает интерфейс WG и передает параметры сети в хранилище параметров AWS SSM. Затем WG VPN-концентратор считывает эти значения по мере их добавления и динамически вызывает новых партнеров.
Это решение предлагает быстрое и грязное исправление, но я не программист Python по профессии, и я бы хотел повторить написанный мной скрипт Python. Есть еще множество случаев, когда реестр может не синхронизироваться (файлы открытого / закрытого ключа случайно удаляются).
Это довольно широкий вопрос, но так как этот код будет распространяться повсеместно (мои навыки Python до сих пор распространялись только на быстрые и грязные скрипты), я ищу рекомендации по проектированию, в которых можно было бы использовать макет и читабельность кода. улучшено в соответствии с лучшими практиками Python (использование классов, более эффективное для разработки цикла и т. д.).
import random
import string
import urllib2
import re
import json
from urllib2 import urlopen
#this is a module obtained from https://pypi.org/project/ssm-parameter-store/
from ssm_parameter_store import SSMParameterStore
import os
import subprocess
import logging
import logging.handlers
from botocore.exceptions import *
log = logging.getLogger(__name__)
log.setLevel(logging.DEBUG)
handler = logging.handlers.SysLogHandler(address = '/dev/log')
formatter = logging.Formatter('%(module)s.%(funcName)s: %(message)s')
handler.setFormatter(formatter)
log.addHandler(handler)
ssm_client = boto3.client('ssm',
region_name="us-west-1",
aws_access_key_id="xxxx",
aws_secret_access_key="xxxx"
)
#create a global read only object that parses SSM data with dictionaries
store = SSMParameterStore(ssm_client=ssm_client,prefix='/')
peer_list = store['wg_peers']
hostname = subprocess.Popen("awk -F: '{ print \"server-\" $5 $6 }' /sys/class/net/eth0/address", stdout=subprocess.PIPE, shell=True)
hostname = hostname.stdout.read().strip()
def new_store():
store = SSMParameterStore(ssm_client=ssm_client,prefix='/')
peer_list = store['wg_peers']
return peer_list
def new_aws_client(type):
try:
ssm_client = boto3.client(type,
region_name="us-west-1",
aws_access_key_id="xxxx",
aws_secret_access_key="xxxx"
)
return ssm_client
except Exception as e:
log.critical("Could not instantiate a new AWS boto3 client")
def geolocate(datatypes=['public_ip','region','city']):
url = 'http://ipinfo.io/json'
response = urlopen(url)
data = json.load(response)
data['public_ip'] = data.pop('ip')
for metadata in datatypes:
ssm_put('/'+metadata,data[metadata],True)
return data
def add_ssm_metadata():
geolocate()
for datatype in data[metadata]:
print datatype
def check_wg_exists():
#set wg_missing to 1 if there is no output
wg_missing = subprocess.call("ip link show wg0", shell=True)
#set key_exists to 1 if the public key exists
key_exists = os.path.isfile('/root/publickey')
#query SSM again and assign to a new object since a new private IP may have been added for this server
local_peerlist = new_store()
if key_exists == 0:
print "Creating a new private and public key for wg"
subprocess.check_call("cd /root/ ; wg genkey | tee privatekey | wg pubkey > publickey", shell=True)
publickey = open('/root/publickey', 'r')
ssm_put('/peer_id',publickey.read(),False)
if wg_missing:
print "Adding generating private and public keys and creating wg interface..."
subprocess.check_call("ip link add dev wg0 type wireguard", shell=True)
subprocess.check_call("ip address add dev wg0 {}/24".format(local_peerlist[hostname]['private_ip']), shell=True)
subprocess.check_call("wg set wg0 listen-port 5555 private-key /root/privatekey peer <public_key_of_wg_hub> allowed-ips 10.1.1.1/16 endpoint <wg_hub_public_ip>:5555 persistent-keepalive 30", shell=True)
subprocess.check_call("ip link set up dev wg0", shell=True)
print "wg interface is now active"
else:
print "WG interface and private and public key already exists on this server"
def next_ssm_ip():
aws_ip_list = []
key_list = []
#if the hostname already exists in SSM, return the private IP
if hostname in peer_list.keys():
print "IP already exists in SSM"
return peer_list[hostname]['private_ip']
#find the last used private ip and
for id in peer_list.keys():
for private_ip_record in peer_list[id].keys():
if 'private_ip' in peer_list[id].keys():
aws_ip_list.append(peer_list[id]['private_ip'])
current_ip = max(aws_ip_list)
start = list(map(int, current_ip.split(".")))
temp_ip = start
start[3] += 1
for i in (3, 2, 1):
if temp_ip[i] == 255:
temp_ip[i] = 0
temp_ip[i-1] += 1
new_ip = ".".join(map(str, temp_ip))
return new_ip
def add_ssm_private_ip():
if hostname not in peer_list.keys():
print "adding private key for this host"
#call the next_ssm_ip function to find the next available private IP for this peer
ssm_put('/private_ip', next_ssm_ip(), False)
#function to write parameters to the SSM database
def ssm_put(key, value, overwrite_param):
try:
response = ssm_client.put_parameter(
Name='/wg_peers/' + hostname + key,
Description='string',
Value=value,
Type='String',
#KeyId='string',
Overwrite=overwrite_param,
#AllowedPattern='string')
)
except Exception as e:
print e
if __name__ == "__main__":
#1. check if a private IP already exists for this peer in SSM - if not, add the IP
add_ssm_private_ip()
#2. check if wg private key, public key and interface exists. If not, create and add them
check_wg_exists()
#3. add location based metadata to the peers SSM datastore record
geolocate()