123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120 |
- import argparse
- import MySQLdb
- import os
- import re
- import secrets
- import string
- import sys
- from collections import OrderedDict
- from pki_utils import PKI
- host = 'dbmaster'
- user = 'replication-manager'
- def validate_name(value):
- pattern = re.compile("^([a-z0-9._-]+[a-z0-9]+)$")
- if not pattern.match(value):
- raise ValueError(f'Name does not match pattern {pattern}')
- return value
- def add_slave(cursor, **kwargs):
- name = validate_name(kwargs['name'])
- alphabet = string.ascii_letters + string.digits
- passwd = ''.join(secrets.choice(alphabet) for i in range(32))
- # Create key and certificate
- with open('certs/ca.pem', 'r') as ca_crt_file, open('certs/ca-key.pem', 'r') as ca_key_file:
- ca_crt_pem = ca_crt_file.read().encode('ascii')
- ca_key_pem = ca_key_file.read().encode('ascii')
- pki = PKI(ca_crt_pem=ca_crt_pem, ca_key_pem=ca_key_pem)
- pki.initialize_key()
- pki.create_certificate(common_name=name, days=365*10)
- subject = ''
- for label, attributes in pki.subject_attributes.items():
- assert len(attributes) == 1
- value = attributes[0]
- subject += f'/{label}={value}'
- # Configure slave in database
- username = name.replace('.', '_') # allows using username as binlog on the slave
- print(f'Creating slave user {username} with subject {subject} ...')
- cursor.execute("CREATE USER %s@'%%' IDENTIFIED BY %s", (username, passwd,))
- cursor.execute("GRANT RELOAD, REPLICATION CLIENT, REPLICATION SLAVE ON *.* TO %s@'%%' REQUIRE SUBJECT %s", (username, subject,))
- cursor.execute("GRANT SELECT ON pdns.* TO %s@'%%' REQUIRE SUBJECT %s", (username, subject,))
- cursor.execute("FLUSH PRIVILEGES")
- print(f'Password is {passwd}')
- # Write key and certificate
- umask = os.umask(0o077)
- key_filename = f'certs/{name}-key.pem'
- crt_filename = f'certs/{name}-crt.pem'
- with open(key_filename, 'wb') as key_file, open(crt_filename, 'wb') as crt_file:
- key_file.write(pki.key_pem)
- crt_file.write(pki.crt_pem)
- os.umask(umask)
- print(key_filename, '(key)')
- print(crt_filename, '(certificate)')
- def list_slaves(cursor):
- cursor.execute("SELECT User, x509_subject FROM mysql.user WHERE x509_subject != ''")
- for row in cursor.fetchall():
- print(row[0], row[1].decode('utf-8'))
- def remove_slave(cursor, **kwargs):
- slavename = validate_name(kwargs['name'])
- cursor.execute("DROP USER %s@'%%'", (slavename,))
- cursor.execute("FLUSH PRIVILEGES")
- def main():
- parser = argparse.ArgumentParser(description='List, add, and remove pdns database replication slaves.')
- subparsers = parser.add_subparsers(dest='action', required=True)
- actions = {}
- # add
- description = 'Add a slave and generate TLS key/certificate. The slave replication password is read from stdin (first line).'
- subparser = subparsers.add_parser('add', help='Add a slave and generate TLS key/certificate', description=description)
- subparser.add_argument('--name', type=str, help='Slave identifier (usually hostname)', required=True)
- actions['add'] = add_slave
- # list
- subparser = subparsers.add_parser('list', help='List slaves', description='List slaves.')
- actions['list'] = list_slaves
- # remove
- subparser = subparsers.add_parser('remove', help='Remove a slave', description='Remove a slave.')
- subparser.add_argument('--name', type=str, help='Slave identifier (usually hostname)', required=True)
- actions['remove'] = remove_slave
- # Validate and extract arguments (errors out if insufficient arguments are given)
- args = parser.parse_args()
- kwargs = vars(args).copy()
- # Initialize database
- db = MySQLdb.connect(host=host, user=user, passwd=os.environ['DESECSTACK_DBMASTER_PASSWORD_replication_manager'])
- # Action!
- action = kwargs.pop('action')
- action_func = actions[action]
- try:
- action_func(db.cursor(), **kwargs)
- except Exception as e:
- raise e
- finally:
- db.close()
- if __name__ == "__main__":
- main()
|