|
@@ -1,120 +0,0 @@
|
|
|
-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()
|
|
|
-
|