From 32ad5af7f4039115dd5dbb3d93d919ef0d28f143 Mon Sep 17 00:00:00 2001 From: Dmitrii Shcherbakov Date: Wed, 7 Oct 2020 18:35:07 +0000 Subject: [PATCH] Generate random passwords instead of hard-coding * The prototype stage hard-coding of passwords is replaced by random generation of passwords for: * all API services; * RabbitMQ; * MySQL; * OpenStack admin user; * OpenStack service users; * Passwords are not replaced upon successive microstack.init calls to preserve idempotency. Change-Id: Ic3d6108a81d09bdd09e986f80b3040b030605178 --- snap-overlay/bin/set-default-config | 107 ------------------ snap-overlay/bin/set-default-config.py | 88 ++++++++++++++ snap-overlay/bin/setup-rabbit | 4 +- snap-overlay/snap-openstack.yaml | 4 +- .../templates/cinder.database.conf.j2 | 2 +- .../templates/cinder.rabbitmq.conf.j2 | 2 +- .../templates/glance.database.conf.j2 | 2 +- .../templates/keystone.database.conf.j2 | 2 +- snap-overlay/templates/microstack.json.j2 | 2 +- snap-overlay/templates/microstack.rc.j2 | 2 +- .../templates/neutron.conf.d.rabbitmq.conf.j2 | 2 +- .../templates/neutron.database.conf.j2 | 2 +- snap-overlay/templates/nova-snap.conf.j2 | 1 + .../templates/nova.conf.d.database.conf.j2 | 4 +- .../templates/nova.conf.d.glance.conf.j2 | 4 +- .../templates/nova.conf.d.rabbitmq.conf.j2 | 2 +- .../placement.conf.d.database.conf.j2 | 2 +- snap-wrappers/mysql/mysql-start-server | 19 +++- snap/hooks/install | 22 +++- snap/hooks/post-refresh | 2 +- tests/framework.py | 7 +- tools/cluster/cluster/client.py | 13 +-- tools/cluster/cluster/shell.py | 19 ++++ tools/init/init/credentials.py | 9 ++ tools/init/init/main.py | 2 +- tools/init/init/questions/__init__.py | 105 ++++++++++------- tools/init/init/shell.py | 21 +++- 27 files changed, 273 insertions(+), 178 deletions(-) delete mode 100755 snap-overlay/bin/set-default-config create mode 100755 snap-overlay/bin/set-default-config.py create mode 100644 tools/init/init/credentials.py diff --git a/snap-overlay/bin/set-default-config b/snap-overlay/bin/set-default-config deleted file mode 100755 index 05d2678..0000000 --- a/snap-overlay/bin/set-default-config +++ /dev/null @@ -1,107 +0,0 @@ -#!/bin/bash - -set -ex - -# Initialize Config -# TODO: put this in a nice yaml format, and parse it. - -# General snap-wide settings -snapctl set \ - config.clustered=false \ - config.post-setup=true \ - ; - -snapctl set \ - config.keystone.region-name=microstack \ - ; - -# Networking related settings. -snapctl set \ - config.network.dns-servers=1.1.1.1 \ - config.network.dns-domain=microstack.example. \ - config.network.ext-gateway=10.20.20.1 \ - config.network.control-ip=10.20.20.1 \ - config.network.node-fqdn=`hostname -f` \ - config.network.compute-ip=10.20.20.1 \ - config.network.ext-cidr=10.20.20.1/24 \ - config.network.security-rules=true \ - config.network.dashboard-allowed-hosts="*" \ - config.network.ports.dashboard=80 \ - config.network.ports.mysql=3306 \ - config.network.ports.rabbit=5672 \ - config.network.external-bridge-name=br-ex \ - config.network.physnet-name=physnet1 \ - ; - -# Passwords, certs, etc. -snapctl set \ - config.credentials.os-password=keystone \ - config.credentials.key-pair="/home/{USER}/snap/{SNAP_NAME}/common/.ssh/id_microstack" \ - config.credentials.nova-password=nova \ - config.credentials.cinder-password=cinder \ - config.credentials.neutron-password=neutron \ - config.credentials.placement-password=placement \ - config.credentials.glance-password=glance \ - ; - -# Cinder volume backend config. -snapctl set \ - config.cinder.setup-loop-based-cinder-lvm-backend=false \ - config.cinder.loop-device-file-size=32G \ - config.cinder.lvm-backend-volume-group=cinder-volumes \ - ; - -# Host optimizations and fixes. -snapctl set \ - config.host.ip-forwarding=false \ - config.host.check-qemu=true \ - ; - -# Enable or disable groups of services. -snapctl set \ - config.services.control-plane=true \ - config.services.hypervisor=true \ - config.services.spice-console=true \ - ; - -# Clustering roles -snapctl set \ - config.cluster.role=control \ - config.cluster.password=null \ - ; - -# Uninstall stuff -snapctl set \ - config.cleanup.delete-bridge=true \ - config.cleanup.remove=true \ - ; - -# Filebeat -snapctl set \ - config.logging.datatag="" \ - config.logging.host="localhost:5044" \ - config.logging.custom-config="$SNAP_COMMON/etc/filebeat/filebeat-microstack.yaml" \ - config.services.extra.enabled=false \ - config.services.extra.filebeat=false \ - ; - -# services won't start if disabled in install hook -# see https://github.com/snapcore/snapd/blob/53dd10a71d1754610eda2d3d776465b81b3281cd/wrappers/services.go#L139 -snapctl stop --disable ${SNAP_NAME}.filebeat - -# NRPE -snapctl set \ - config.alerting.custom-config="$SNAP_COMMON/etc/nrpe/nrpe-microstack.cfg" \ - config.services.extra.nrpe=false \ - ; - -snapctl stop --disable ${SNAP_NAME}.telegraf - -# Telegraf -snapctl set \ - config.monitoring.ipmi="" \ - config.monitoring.custom-config="$SNAP_COMMON/etc/telegraf/telegraf-microstack.conf" \ - config.services.extra.telegraf=false \ - ; - -snapctl stop --disable ${SNAP_NAME}.nrpe diff --git a/snap-overlay/bin/set-default-config.py b/snap-overlay/bin/set-default-config.py new file mode 100755 index 0000000..eeb86d4 --- /dev/null +++ b/snap-overlay/bin/set-default-config.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 + +import os +import socket + +from init import shell +from init import credentials + + +def _get_default_config(): + snap_common = os.getenv('SNAP_COMMON') + return { + 'config.clustered': False, + 'config.post-setup': True, + 'config.keystone.region-name': 'microstack', + 'config.credentials.key-pair': '/home/{USER}/snap/{SNAP_NAME}' + '/common/.ssh/id_microstack', + 'config.network.node-fqdn': socket.getfqdn(), + 'config.network.dns-servers': '1.1.1.1', + 'config.network.dns-domain': 'microstack.example.', + 'config.network.ext-gateway': '10.20.20.1', + 'config.network.control-ip': '10.20.20.1', + 'config.network.compute-ip': '10.20.20.1', + 'config.network.ext-cidr': '10.20.20.1/24', + 'config.network.security-rules': True, + 'config.network.dashboard-allowed-hosts': '*', + 'config.network.ports.dashboard': 80, + 'config.network.ports.mysql': 3306, + 'config.network.ports.rabbit': 5672, + 'config.network.external-bridge-name': 'br-ex', + 'config.network.physnet-name': 'physnet1', + 'config.cinder.setup-loop-based-cinder-lvm-backend': False, + 'config.cinder.loop-device-file-size': '32G', + 'config.cinder.lvm-backend-volume-group': 'cinder-volumes', + 'config.host.ip-forwarding': False, + 'config.host.check-qemu': True, + 'config.services.control-plane': True, + 'config.services.hypervisor': True, + 'config.services.spice-console': True, + 'config.cluster.role': 'control', + 'config.cluster.password': 'null', + 'config.cleanup.delete-bridge': True, + 'config.cleanup.remove': True, + 'config.logging.custom-config': f'{snap_common}/etc/filebeat' + '/filebeat-microstack.yaml', + 'config.logging.datatag': '', + 'config.logging.host': 'localhost:5044', + 'config.services.extra.enabled': False, + 'config.services.extra.filebeat': False, + 'config.alerting.custom-config': f'{snap_common}/etc/nrpe' + '/nrpe-microstack.cfg', + 'config.services.extra.nrpe': False, + 'config.monitoring.ipmi': '', + 'config.services.extra.telegraf': False, + 'config.monitoring.custom-config': f'{snap_common}/etc/telegraf' + '/telegraf-microstack.conf' + } + + +def _set_default_config(): + shell.config_set(**_get_default_config()) + + +def _setup_secrets(): + # If a user runs init multiple times we do not want to generate + # new credentials to keep the init operation idempotent. + existing_creds = shell.config_get('config.credentials') + if isinstance(existing_creds, dict): + existing_cred_keys = existing_creds.keys() + else: + existing_cred_keys = [] + shell.config_set(**{ + k: credentials.generate_password() for k in [ + 'config.credentials.mysql-root-password', + 'config.credentials.rabbitmq-password', + 'config.credentials.keystone-password', + 'config.credentials.nova-password', + 'config.credentials.cinder-password', + 'config.credentials.neutron-password', + 'config.credentials.placement-password', + 'config.credentials.glance-password', + ] if k not in existing_cred_keys + }) + + +if __name__ == '__main__': + _set_default_config() + _setup_secrets() diff --git a/snap-overlay/bin/setup-rabbit b/snap-overlay/bin/setup-rabbit index f14f713..1066383 100755 --- a/snap-overlay/bin/setup-rabbit +++ b/snap-overlay/bin/setup-rabbit @@ -4,5 +4,7 @@ set -ex export HOME=$SNAP_COMMON/lib/rabbitmq -$SNAP/usr/sbin/rabbitmqctl add_user openstack rabbitmq || true +rabbitmq_password=`snapctl get config.credentials.rabbitmq-password` + +$SNAP/usr/sbin/rabbitmqctl add_user openstack $rabbitmq_password || true $SNAP/usr/sbin/rabbitmqctl set_permissions openstack ".*" ".*" ".*" diff --git a/snap-overlay/snap-openstack.yaml b/snap-overlay/snap-openstack.yaml index aa3ce57..655fc83 100644 --- a/snap-overlay/snap-openstack.yaml +++ b/snap-overlay/snap-openstack.yaml @@ -83,13 +83,13 @@ setup: "{snap_common}/etc/microstack.json": 0644 snap-config-keys: region_name: 'config.keystone.region-name' - ospassword: 'config.credentials.os-password' + keystone_password: 'config.credentials.keystone-password' nova_password: 'config.credentials.nova-password' cinder_password: 'config.credentials.cinder-password' neutron_password: 'config.credentials.neutron-password' - placement_password: 'config.credentials.placement-password' glance_password: 'config.credentials.glance-password' placement_password: 'config.credentials.placement-password' + rabbitmq_password: 'config.credentials.rabbitmq-password' control_ip: 'config.network.control-ip' node_fqdn: 'config.network.node-fqdn' compute_ip: 'config.network.compute-ip' diff --git a/snap-overlay/templates/cinder.database.conf.j2 b/snap-overlay/templates/cinder.database.conf.j2 index 84a8c89..b21ba80 100644 --- a/snap-overlay/templates/cinder.database.conf.j2 +++ b/snap-overlay/templates/cinder.database.conf.j2 @@ -1,2 +1,2 @@ [database] -connection = mysql+pymysql://cinder:cinder@{{ control_ip }}:{{ mysql_port }}/cinder +connection = mysql+pymysql://cinder:{{ cinder_password }}@{{ control_ip }}:{{ mysql_port }}/cinder diff --git a/snap-overlay/templates/cinder.rabbitmq.conf.j2 b/snap-overlay/templates/cinder.rabbitmq.conf.j2 index ef149bc..d2fef47 100644 --- a/snap-overlay/templates/cinder.rabbitmq.conf.j2 +++ b/snap-overlay/templates/cinder.rabbitmq.conf.j2 @@ -1,2 +1,2 @@ [DEFAULT] -transport_url = rabbit://openstack:rabbitmq@{{ control_ip }}:{{ rabbit_port }} +transport_url = rabbit://openstack:{{ rabbitmq_password }}@{{ control_ip }}:{{ rabbit_port }} diff --git a/snap-overlay/templates/glance.database.conf.j2 b/snap-overlay/templates/glance.database.conf.j2 index c4c0d81..06509f2 100644 --- a/snap-overlay/templates/glance.database.conf.j2 +++ b/snap-overlay/templates/glance.database.conf.j2 @@ -1,2 +1,2 @@ [database] -connection = mysql+pymysql://glance:glance@{{ control_ip }}:{{ mysql_port }}/glance +connection = mysql+pymysql://glance:{{ glance_password }}@{{ control_ip }}:{{ mysql_port }}/glance diff --git a/snap-overlay/templates/keystone.database.conf.j2 b/snap-overlay/templates/keystone.database.conf.j2 index a480ec7..ad7675a 100644 --- a/snap-overlay/templates/keystone.database.conf.j2 +++ b/snap-overlay/templates/keystone.database.conf.j2 @@ -1,2 +1,2 @@ [database] -connection = mysql+pymysql://keystone:keystone@{{ control_ip }}:{{ mysql_port }}/keystone +connection = mysql+pymysql://keystone:{{ keystone_password }}@{{ control_ip }}:{{ mysql_port }}/keystone diff --git a/snap-overlay/templates/microstack.json.j2 b/snap-overlay/templates/microstack.json.j2 index e0a8f8a..587d62d 100644 --- a/snap-overlay/templates/microstack.json.j2 +++ b/snap-overlay/templates/microstack.json.j2 @@ -1,7 +1,7 @@ { "openstack": { "admin": { - "password": "{{ ospassword }}", + "password": "{{ keystone_password }}", "project_domain_name": "default", "project_name": "admin", "user_domain_name": "default", diff --git a/snap-overlay/templates/microstack.rc.j2 b/snap-overlay/templates/microstack.rc.j2 index 1522f9b..1545f2e 100644 --- a/snap-overlay/templates/microstack.rc.j2 +++ b/snap-overlay/templates/microstack.rc.j2 @@ -2,7 +2,7 @@ export OS_PROJECT_DOMAIN_NAME=default export OS_USER_DOMAIN_NAME=default export OS_PROJECT_NAME=admin export OS_USERNAME=admin -export OS_PASSWORD={{ ospassword }} +export OS_PASSWORD={{ keystone_password }} export OS_AUTH_URL=http://{{ control_ip }}:5000 export OS_IDENTITY_API_VERSION=3 export OS_IMAGE_API_VERSION=2 diff --git a/snap-overlay/templates/neutron.conf.d.rabbitmq.conf.j2 b/snap-overlay/templates/neutron.conf.d.rabbitmq.conf.j2 index ef149bc..d2fef47 100644 --- a/snap-overlay/templates/neutron.conf.d.rabbitmq.conf.j2 +++ b/snap-overlay/templates/neutron.conf.d.rabbitmq.conf.j2 @@ -1,2 +1,2 @@ [DEFAULT] -transport_url = rabbit://openstack:rabbitmq@{{ control_ip }}:{{ rabbit_port }} +transport_url = rabbit://openstack:{{ rabbitmq_password }}@{{ control_ip }}:{{ rabbit_port }} diff --git a/snap-overlay/templates/neutron.database.conf.j2 b/snap-overlay/templates/neutron.database.conf.j2 index fe12945..eb206cc 100644 --- a/snap-overlay/templates/neutron.database.conf.j2 +++ b/snap-overlay/templates/neutron.database.conf.j2 @@ -1,2 +1,2 @@ [database] -connection = mysql+pymysql://neutron:neutron@{{ control_ip }}:{{ mysql_port }}/neutron +connection = mysql+pymysql://neutron:{{ neutron_password }}@{{ control_ip }}:{{ mysql_port }}/neutron diff --git a/snap-overlay/templates/nova-snap.conf.j2 b/snap-overlay/templates/nova-snap.conf.j2 index 7204053..addb502 100644 --- a/snap-overlay/templates/nova-snap.conf.j2 +++ b/snap-overlay/templates/nova-snap.conf.j2 @@ -7,6 +7,7 @@ use_journal = True # Set a hostname to be an FQDN to avoid issues with port binding for # which a hostname of a Nova node must match a hostname of an OVN chassis. host = {{ node_fqdn }} +my_ip = {{ compute_ip }} [oslo_concurrency] # Oslo Concurrency lock path diff --git a/snap-overlay/templates/nova.conf.d.database.conf.j2 b/snap-overlay/templates/nova.conf.d.database.conf.j2 index 51ded26..f0ed80a 100644 --- a/snap-overlay/templates/nova.conf.d.database.conf.j2 +++ b/snap-overlay/templates/nova.conf.d.database.conf.j2 @@ -1,5 +1,5 @@ [database] -connection = mysql+pymysql://nova:nova@{{ control_ip }}:{{ mysql_port }}/nova +connection = mysql+pymysql://nova:{{ nova_password }}@{{ control_ip }}:{{ mysql_port }}/nova [api_database] -connection = mysql+pymysql://nova_api:nova_api@{{ control_ip }}:{{ mysql_port }}/nova_api +connection = mysql+pymysql://nova:{{ nova_password }}@{{ control_ip }}:{{ mysql_port }}/nova_api diff --git a/snap-overlay/templates/nova.conf.d.glance.conf.j2 b/snap-overlay/templates/nova.conf.d.glance.conf.j2 index 2373f27..973442d 100644 --- a/snap-overlay/templates/nova.conf.d.glance.conf.j2 +++ b/snap-overlay/templates/nova.conf.d.glance.conf.j2 @@ -1,2 +1,4 @@ [glance] -api_servers = http://{{ control_ip }}:9292 +service_type = image +service_name = glance +region_name = {{ region_name }} diff --git a/snap-overlay/templates/nova.conf.d.rabbitmq.conf.j2 b/snap-overlay/templates/nova.conf.d.rabbitmq.conf.j2 index ef149bc..d2fef47 100644 --- a/snap-overlay/templates/nova.conf.d.rabbitmq.conf.j2 +++ b/snap-overlay/templates/nova.conf.d.rabbitmq.conf.j2 @@ -1,2 +1,2 @@ [DEFAULT] -transport_url = rabbit://openstack:rabbitmq@{{ control_ip }}:{{ rabbit_port }} +transport_url = rabbit://openstack:{{ rabbitmq_password }}@{{ control_ip }}:{{ rabbit_port }} diff --git a/snap-overlay/templates/placement.conf.d.database.conf.j2 b/snap-overlay/templates/placement.conf.d.database.conf.j2 index 4100ba1..f487c2d 100644 --- a/snap-overlay/templates/placement.conf.d.database.conf.j2 +++ b/snap-overlay/templates/placement.conf.d.database.conf.j2 @@ -1,2 +1,2 @@ [placement_database] -connection = mysql+pymysql://placement:placement@{{ control_ip }}:{{ mysql_port }}/placement +connection = mysql+pymysql://placement:{{ placement_password }}@{{ control_ip }}:{{ mysql_port }}/placement diff --git a/snap-wrappers/mysql/mysql-start-server b/snap-wrappers/mysql/mysql-start-server index 3f813b6..a1a7574 100755 --- a/snap-wrappers/mysql/mysql-start-server +++ b/snap-wrappers/mysql/mysql-start-server @@ -46,10 +46,25 @@ port=${PORT} EOF } +init_sql() { + # Write out the initial SQL statements to execute on mysql initialization. + mkdir -p "${CONFDIR}" + touch ${INIT_SQL_FILE} + chmod 600 ${INIT_SQL_FILE} + echo "Setting a root user password..." + root_password=`snapctl get config.credentials.mysql-root-password` + cat > ${INIT_SQL_FILE} < span").click() # Verify that we can click something on the dashboard -- e.g., # we're still not sitting at the login screen. diff --git a/tools/cluster/cluster/client.py b/tools/cluster/cluster/client.py index 8fd1091..618a897 100755 --- a/tools/cluster/cluster/client.py +++ b/tools/cluster/cluster/client.py @@ -4,7 +4,8 @@ import json import requests -from cluster.shell import check, check_output +from cluster import shell +from cluster.shell import check_output def join(): @@ -27,12 +28,10 @@ def join(): resp.json)) resp = resp.json() - # TODO: add better error handling to the below - os_password = resp['config']['credentials']['os-password'] - - # Set passwords and such - check('snapctl', 'set', 'config.credentials.os-password={}'.format( - os_password)) + credentials = resp['config']['credentials'] + control_creds = {f'config.credentials.{k}': v + for k, v in credentials.items()} + shell.config_set(**control_creds) if __name__ == '__main__': diff --git a/tools/cluster/cluster/shell.py b/tools/cluster/cluster/shell.py index 919476a..7cdb1e2 100644 --- a/tools/cluster/cluster/shell.py +++ b/tools/cluster/cluster/shell.py @@ -1,6 +1,7 @@ import os import pymysql import subprocess +import json def sql(cmd) -> None: @@ -16,7 +17,9 @@ def sql(cmd) -> None: """ mysql_conf = '${SNAP_USER_COMMON}/etc/mysql/my.cnf'.format(**os.environ) + root_pasword = config_get('config.credentials.mysql-root-password') connection = pymysql.connect(host='localhost', user='root', + password=root_pasword, read_default_file=mysql_conf) with connection.cursor() as cursor: @@ -36,3 +39,19 @@ def check(*args): """ return subprocess.check_call(args, env=os.environ) + + +def config_get(*keys): + """Get snap config keys via snapctl. + + :param keys list[str]: Keys to retrieve from the snap configuration. + """ + return json.loads(check_output('snapctl', 'get', '-t', *keys)) + + +def config_set(**kwargs): + """Get snap config keys via snapctl. + + :param kwargs dict[str, str]: Values to set in the snap configuration. + """ + check_output('snapctl', 'set', *[f'{k}={v}' for k, v in kwargs.items()]) diff --git a/tools/init/init/credentials.py b/tools/init/init/credentials.py new file mode 100644 index 0000000..a9b9fdc --- /dev/null +++ b/tools/init/init/credentials.py @@ -0,0 +1,9 @@ +import string +import secrets + +DEFAULT_PASSWORD_LENGTH = 32 + + +def generate_password(length=DEFAULT_PASSWORD_LENGTH): + return ''.join(secrets.choice( + string.ascii_letters + string.digits) for i in range(length)) diff --git a/tools/init/init/main.py b/tools/init/init/main.py index db15c0a..eaa78b7 100644 --- a/tools/init/init/main.py +++ b/tools/init/init/main.py @@ -150,8 +150,8 @@ def init() -> None: questions.RabbitMq(), questions.DatabaseSetup(), questions.PlacementSetup(), - questions.NovaHypervisor(), questions.NovaControlPlane(), + questions.NovaHypervisor(), questions.NovaSpiceConsoleSetup(), questions.NeutronControlPlane(), questions.GlanceSetup(), diff --git a/tools/init/init/questions/__init__.py b/tools/init/init/questions/__init__.py index ba50ac5..9d39b17 100644 --- a/tools/init/init/questions/__init__.py +++ b/tools/init/init/questions/__init__.py @@ -7,7 +7,7 @@ for now, we're keeping things simple (if a big lengthy) ---------------------------------------------------------------------- -Copyright 2019 Canonical Ltd +Copyright 2020 Canonical Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -27,6 +27,7 @@ import json from time import sleep from os import path +from init import shell from init.shell import (check, call, check_output, sql, nc_wait, log_wait, start, restart, download, disable, enable) from init.config import Env, log @@ -247,12 +248,10 @@ class NetworkSettings(Question): class OsPassword(ConfigQuestion): _type = 'string' _question = 'Openstack Admin Password' - config_key = 'config.credentials.os-password' + config_key = 'config.credentials.keystone-password' def yes(self, answer): - _env['ospassword'] = answer - - # TODO obfuscate the password! + _env['keystone_password'] = answer class ForceQemu(Question): @@ -367,19 +366,24 @@ class DatabaseSetup(Question): 'mysqld: ready for connections.') def _create_dbs(self) -> None: - # TODO: actually use passwords here. - for db in ('neutron', 'nova', 'nova_api', 'nova_cell0', 'cinder', - 'glance', 'keystone', 'placement'): - sql("CREATE USER IF NOT EXISTS '{db}'@'{control_ip}'" - " IDENTIFIED BY '{db}';".format(db=db, **_env)) - sql("CREATE DATABASE IF NOT EXISTS `{db}`;".format(db=db)) - sql("GRANT ALL PRIVILEGES ON {db}.* TO '{db}'@'{control_ip}';" - "".format(db=db, **_env)) - - # Grant nova user access to cell0 - sql( - "GRANT ALL PRIVILEGES ON nova_cell0.* TO 'nova'@'{control_ip}';" - "".format(**_env)) + db_creds = shell.config_get('config.credentials') + for service_user, db_name in ( + ('neutron', 'neutron'), + ('nova', 'nova'), + ('nova', 'nova_api'), + ('nova', 'nova_cell0'), + ('cinder', 'cinder'), + ('glance', 'glance'), + ('keystone', 'keystone'), + ('placement', 'placement') + ): + db_password = db_creds[f'{service_user}-password'] + sql("CREATE USER IF NOT EXISTS '{user}'@'{control_ip}'" + " IDENTIFIED BY '{db_password}';".format( + user=service_user, db_password=db_password, **_env)) + sql("CREATE DATABASE IF NOT EXISTS `{db}`;".format(db=db_name)) + sql("GRANT ALL PRIVILEGES ON {db}.* TO '{user}'@'{control_ip}';" + "".format(db=db_name, user=service_user, **_env)) def _bootstrap(self) -> None: @@ -389,7 +393,7 @@ class DatabaseSetup(Question): bootstrap_url = 'http://{control_ip}:5000/v3/'.format(**_env) check('snap-openstack', 'launch', 'keystone-manage', 'bootstrap', - '--bootstrap-password', _env['ospassword'], + '--bootstrap-password', _env['keystone_password'], '--bootstrap-admin-url', bootstrap_url, '--bootstrap-internal-url', bootstrap_url, '--bootstrap-public-url', bootstrap_url, @@ -449,17 +453,6 @@ class NovaHypervisor(Question): def yes(self, answer): log.info('Configuring nova compute hypervisor ...') - - if not call('openstack', 'service', 'show', 'compute'): - check('openstack', 'service', 'create', '--name', 'nova', - '--description', '"Openstack Compute"', 'compute') - # TODO make sure that we are the control plane before executing - # TODO if control plane is not hypervisor, still create this - for endpoint in ['public', 'internal', 'admin']: - call('openstack', 'endpoint', 'create', '--region', - 'microstack', 'compute', endpoint, - 'http://{compute_ip}:8774/v2.1'.format(**_env)) - start('nova-compute') def no(self, answer): @@ -492,8 +485,12 @@ class PlacementSetup(Question): log.info('Configuring the Placement service...') if not call('openstack', 'user', 'show', 'placement'): - check('openstack', 'user', 'create', '--domain', 'default', - '--password', 'placement', 'placement') + check( + 'openstack', 'user', 'create', '--domain', 'default', + '--password', + shell.config_get('config.credentials.placement-password'), + 'placement', + ) check('openstack', 'role', 'add', '--project', 'service', '--user', 'placement', 'admin') @@ -549,8 +546,12 @@ class NovaControlPlane(Question): log.info('Configuring nova control plane services ...') if not call('openstack', 'user', 'show', 'nova'): - check('openstack', 'user', 'create', '--domain', - 'default', '--password', 'nova', 'nova') + check( + 'openstack', 'user', 'create', '--domain', 'default', + '--password', + shell.config_get('config.credentials.nova-password'), + 'nova' + ) check('openstack', 'role', 'add', '--project', 'service', '--user', 'nova', 'admin') @@ -561,7 +562,7 @@ class NovaControlPlane(Question): start('nova-api') log.info('Running Nova API DB migrations' - ' (this will take a lot of time)...') + ' (this may take a lot of time)...') check('snap-openstack', 'launch', 'nova-manage', 'api_db', 'sync') if 'cell0' not in check_output('snap-openstack', 'launch', @@ -577,7 +578,7 @@ class NovaControlPlane(Question): 'create_cell', '--name=cell1', '--verbose') log.info('Running Nova DB migrations' - ' (this will take a lot of time)...') + ' (this may take a lot of time)...') check('snap-openstack', 'launch', 'nova-manage', 'db', 'sync') restart('nova-api') @@ -594,7 +595,16 @@ class NovaControlPlane(Question): sleep(5) # TODO: log_wait + if not call('openstack', 'service', 'show', 'compute'): + check('openstack', 'service', 'create', '--name', 'nova', + '--description', '"Openstack Compute"', 'compute') + for endpoint in ['public', 'internal', 'admin']: + call('openstack', 'endpoint', 'create', '--region', + 'microstack', 'compute', endpoint, + 'http://{control_ip}:8774/v2.1'.format(**_env)) + log.info('Creating default flavors...') + self._flavors() def no(self, answer): @@ -618,8 +628,12 @@ class CinderSetup(Question): log.info('Configuring the Cinder services...') if not call('openstack', 'user', 'show', 'cinder'): - check('openstack', 'user', 'create', '--domain', 'default', - '--password', 'cinder', 'cinder') + check( + 'openstack', 'user', 'create', '--domain', 'default', + '--password', + shell.config_get('config.credentials.cinder-password'), + 'cinder' + ) check('openstack', 'role', 'add', '--project', 'service', '--user', 'cinder', 'admin') @@ -699,8 +713,11 @@ class NeutronControlPlane(Question): log.info('Configuring Neutron') if not call('openstack', 'user', 'show', 'neutron'): - check('openstack', 'user', 'create', '--domain', 'default', - '--password', 'neutron', 'neutron') + check( + 'openstack', 'user', 'create', '--domain', 'default', + '--password', + shell.config_get('config.credentials.neutron-password'), + 'neutron') check('openstack', 'role', 'add', '--project', 'service', '--user', 'neutron', 'admin') @@ -810,8 +827,12 @@ class GlanceSetup(Question): log.info('Configuring Glance ...') if not call('openstack', 'user', 'show', 'glance'): - check('openstack', 'user', 'create', '--domain', 'default', - '--password', 'glance', 'glance') + check( + 'openstack', 'user', 'create', '--domain', 'default', + '--password', + shell.config_get('config.credentials.glance-password'), + 'glance' + ) check('openstack', 'role', 'add', '--project', 'service', '--user', 'glance', 'admin') diff --git a/tools/init/init/shell.py b/tools/init/init/shell.py index b331895..be603fd 100644 --- a/tools/init/init/shell.py +++ b/tools/init/init/shell.py @@ -32,6 +32,7 @@ import netifaces import pymysql import socket import wget +import json from init.config import Env, log @@ -105,7 +106,9 @@ def sql(cmd: str) -> None: """ mysql_conf = '{SNAP_COMMON}/etc/mysql/my.cnf'.format(**_env) - connection = pymysql.connect(read_default_file=mysql_conf) + root_pasword = config_get('config.credentials.mysql-root-password') + connection = pymysql.connect(read_default_file=mysql_conf, + password=root_pasword) with connection.cursor() as cursor: cursor.execute(cmd) @@ -169,6 +172,22 @@ def disable(service: str) -> None: check('snapctl', 'stop', '--disable', 'microstack.{}'.format(service)) +def config_get(*keys): + """Get snap config keys via snapctl. + + :param keys list[str]: Keys to retrieve from the snap configuration. + """ + return json.loads(check_output('snapctl', 'get', '-t', *keys)) + + +def config_set(**kwargs): + """Get snap config keys via snapctl. + + :param kwargs dict[str, str]: Values to set in the snap configuration. + """ + check_output('snapctl', 'set', *[f'{k}={v}' for k, v in kwargs.items()]) + + def download(url: str, output: str) -> None: """Download a file to a path""" wget.download(url, output)