From c2a398b5e08dcb1646075b0f8c993ccd6b39f476 Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Thu, 11 Mar 2021 11:58:02 -0500 Subject: [PATCH] Add TLS OpenStack API endpoints A new config.tls.generate-cert option is added that defaults to true. When true, a self-signed certificate will be generated and OpenStack API endpoints will be configured to use TLS with that self-signed certificate. The following config options are added: snap get microstack config.tls.generate-self-signed snap get microstack config.tls.cacert-path snap get microstack config.tls.cert-path snap get microstack config.tls.key-path Users can provide their own certificate by setting generate-cert to false and storing their own certificates/key at the paths specified by cacert-path, cert-path, and key-path. 'snap set' can also be used to change the cert/key file names. If using clustering, the certificates/key will be copied from the control node to the compute nodes. The config for cacert-path, cert-path, and key-path will be set to the same values as on the control node. Other notable changes: * The existing generate_selfsigned() function is modified to change the subject alternative name to be made up of the hostname and optionally an IP. The controller hostname and IP are used when generating the certificate for self-signed TLS endpoints. The hostname is now used instead of 'microstack.run' when generating the clustering certificate. * This change also aligns logging for nginx and corresponding sites and moves all nginx sites to {snap_common}/etc/nginx/sites-enabled. Change-Id: Iceea3127822404a3275fcf8a221cbedc4b52c217 --- DEMO.md | 10 +-- snap-overlay/bin/set-default-config.py | 10 ++- snap-overlay/snap-openstack.yaml | 26 +++++--- snap-overlay/templates/05_snap_tweaks.j2 | 11 +++- snap-overlay/templates/cinder-nginx.conf.j2 | 6 +- .../templates/cinder.keystone.conf.j2 | 5 +- snap-overlay/templates/glance-nginx.conf.j2 | 18 ++++++ snap-overlay/templates/glance-snap.conf.j2 | 1 + .../templates/glance.conf.d.keystone.conf.j2 | 5 +- snap-overlay/templates/horizon-nginx.conf.j2 | 13 ++-- snap-overlay/templates/keystone-nginx.conf.j2 | 10 ++- snap-overlay/templates/microstack.json.j2 | 11 +++- snap-overlay/templates/microstack.rc.j2 | 5 +- snap-overlay/templates/neutron-nginx.conf.j2 | 15 +++++ snap-overlay/templates/neutron-snap.conf.j2 | 1 + .../templates/neutron.keystone.conf.j2 | 5 +- snap-overlay/templates/neutron.nova.conf.j2 | 3 +- .../templates/neutron.placement.conf.j2 | 3 +- snap-overlay/templates/nginx.conf.j2 | 2 +- snap-overlay/templates/nova-nginx.conf.j2 | 13 ++-- snap-overlay/templates/nova-snap.conf.j2 | 2 + .../templates/nova.conf.d.cinder.conf.j2 | 5 ++ .../templates/nova.conf.d.glance.conf.j2 | 1 + .../templates/nova.conf.d.keystone.conf.j2 | 5 +- .../templates/nova.conf.d.neutron.conf.j2 | 5 +- .../templates/nova.conf.d.placement.conf.j2 | 5 +- .../templates/placement-nginx.conf.j2 | 10 ++- .../placement.conf.d.keystone.conf.j2 | 5 +- snap/hooks/post-refresh | 2 +- tests/basic-test.sh | 2 +- tests/configure-the-things.sh | 6 +- tests/framework.py | 2 +- tools/cluster/cluster/add_compute.py | 13 +++- tools/cluster/cluster/client.py | 14 +++++ tools/cluster/cluster/daemon.py | 29 +++++++-- tools/init/init/main.py | 1 + tools/init/init/questions/__init__.py | 61 ++++++++++++++++--- tools/init/init/{cluster_tls.py => tls.py} | 29 ++++++--- tools/launch/launch/main.py | 4 +- 39 files changed, 287 insertions(+), 87 deletions(-) create mode 100644 snap-overlay/templates/glance-nginx.conf.j2 create mode 100644 snap-overlay/templates/neutron-nginx.conf.j2 create mode 100644 snap-overlay/templates/nova.conf.d.cinder.conf.j2 rename tools/init/init/{cluster_tls.py => tls.py} (79%) diff --git a/DEMO.md b/DEMO.md index 2b279d3..9c99e0f 100644 --- a/DEMO.md +++ b/DEMO.md @@ -31,8 +31,8 @@ kubernetes, respectively. ``` sudo snap install microstack --beta --devmode -sudo snap install --classic juju -sudo snap install --classic kubectl +sudo snap install juju --classic +sudo snap install kubectl --classic ``` To make sure that you can use the snaps we've installed, add /snap/bin @@ -158,11 +158,11 @@ Answer the questions as follows: - + - +
cloud type: openstack
endpoint: http://10.20.20.1:5000/v3
endpoint: https://10.20.20.1:5000/v3
cert path: none
auth type: userpass
region: microstack
region endpoint: http://10.20.20.1:5000/v3
region endpoint: https://10.20.20.1:5000/v3
add another region?: N
@@ -182,7 +182,7 @@ images in your microstack cloud. Here's how to set that up. ``` mkdir simplestreams -juju metadata generate-image -d ~/simplestreams -i $IMAGE -s bionic -r microstack -u http://10.20.20.1:5000/v3 +juju metadata generate-image -d ~/simplestreams -i $IMAGE -s bionic -r microstack -u https://10.20.20.1:5000/v3 ``` (If you don't still have an `IMAGE` variable in your env, you can find diff --git a/snap-overlay/bin/set-default-config.py b/snap-overlay/bin/set-default-config.py index e3c195a..a36a02f 100755 --- a/snap-overlay/bin/set-default-config.py +++ b/snap-overlay/bin/set-default-config.py @@ -35,7 +35,7 @@ def _get_default_config(): '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.dashboard': 443, 'config.network.ports.mysql': 3306, 'config.network.ports.rabbit': 5672, 'config.network.external-bridge-name': 'br-ex', @@ -75,6 +75,14 @@ def _get_default_config(): 'config.nova.cpu-mode': 'host-model', # Do not override cpu-models by default. 'config.nova.cpu-models': '', + + 'config.tls.generate-self-signed': True, + 'config.tls.cacert-path': + f'{snap_common}/etc/ssl/certs/cacert.pem', + 'config.tls.cert-path': + f'{snap_common}/etc/ssl/certs/cert.pem', + 'config.tls.key-path': + f'{snap_common}/etc/ssl/private/key.pem', } diff --git a/snap-overlay/snap-openstack.yaml b/snap-overlay/snap-openstack.yaml index 1d3a4f5..2c879dc 100644 --- a/snap-overlay/snap-openstack.yaml +++ b/snap-overlay/snap-openstack.yaml @@ -8,7 +8,6 @@ setup: - "{snap_common}/etc/neutron/policy.d" - "{snap_common}/etc/neutron/rootwrap.d" - "{snap_common}/etc/nginx/sites-enabled" - - "{snap_common}/etc/nginx/snap/sites-enabled" - "{snap_common}/etc/glance/glance.conf.d" - "{snap_common}/etc/placement/placement.conf.d" - "{snap_common}/etc/horizon/horizon.conf.d" @@ -22,6 +21,8 @@ setup: - "{snap_common}/etc/cluster/tls" - "{snap_common}/etc/cluster/uwsgi/snap" - "{snap_common}/etc/rabbitmq" + - "{snap_common}/etc/ssl/certs" + - "{snap_common}/etc/ssl/private" - "{snap_common}/fernet-keys" - "{snap_common}/lib" - "{snap_common}/lib/images" @@ -33,24 +34,25 @@ setup: - "{snap_common}/etc/iscsi" - "{snap_common}/etc/target" templates: - cluster-nginx.conf.j2: "{snap_common}/etc/nginx/snap/sites-enabled/cluster.conf" - keystone-nginx.conf.j2: "{snap_common}/etc/nginx/snap/sites-enabled/keystone.conf" + cluster-nginx.conf.j2: "{snap_common}/etc/nginx/sites-enabled/cluster.conf" + keystone-nginx.conf.j2: "{snap_common}/etc/nginx/sites-enabled/keystone.conf" keystone-snap.conf.j2: "{snap_common}/etc/keystone/keystone.conf.d/keystone-snap.conf" neutron-snap.conf.j2: "{snap_common}/etc/neutron/neutron.conf.d/neutron-snap.conf" nginx.conf.j2: "{snap_common}/etc/nginx/snap/nginx.conf" nova-snap.conf.j2: "{snap_common}/etc/nova/nova.conf.d/nova-snap.conf" - nova-nginx.conf.j2: "{snap_common}/etc/nginx/snap/sites-enabled/nova.conf" + nova-nginx.conf.j2: "{snap_common}/etc/nginx/sites-enabled/nova.conf" glance-snap.conf.j2: "{snap_common}/etc/glance/glance.conf.d/glance-snap.conf" - placement-nginx.conf.j2: "{snap_common}/etc/nginx/snap/sites-enabled/placement.conf" + glance-nginx.conf.j2: "{snap_common}/etc/nginx/sites-enabled/glance.conf" + placement-nginx.conf.j2: "{snap_common}/etc/nginx/sites-enabled/placement.conf" placement-snap.conf.j2: "{snap_common}/etc/placement/placement.conf.d/placement-snap.conf" - cinder-nginx.conf.j2: "{snap_common}/etc/nginx/snap/sites-enabled/cinder.conf" + cinder-nginx.conf.j2: "{snap_common}/etc/nginx/sites-enabled/cinder.conf" cinder-snap.conf.j2: "{snap_common}/etc/cinder/cinder.conf.d/cinder-snap.conf" cinder.database.conf.j2: "{snap_common}/etc/cinder/cinder.conf.d/database.conf" cinder.rabbitmq.conf.j2: "{snap_common}/etc/cinder/cinder.conf.d/rabbitmq.conf" cinder.keystone.conf.j2: "{snap_common}/etc/cinder/cinder.conf.d/keystone.conf" cinder-rootwrap.conf.j2: "{snap_common}/etc/cinder/rootwrap.conf" horizon-snap.conf.j2: "{snap_common}/etc/horizon/horizon.conf.d/horizon-snap.conf" - horizon-nginx.conf.j2: "{snap_common}/etc/nginx/snap/sites-enabled/horizon.conf" + horizon-nginx.conf.j2: "{snap_common}/etc/nginx/sites-enabled/horizon.conf" 05_snap_tweaks.j2: "{snap_common}/etc/horizon/local_settings.d/_05_snap_tweaks.py" libvirtd.conf.j2: "{snap_common}/etc/libvirt/libvirtd.conf" qemu.conf.j2: "{snap_common}/etc/libvirt/qemu.conf" @@ -62,10 +64,12 @@ setup: nova.conf.d.keystone.conf.j2: "{snap_common}/etc/nova/nova.conf.d/keystone.conf" nova.conf.d.database.conf.j2: "{snap_common}/etc/nova/nova.conf.d/database.conf" nova.conf.d.rabbitmq.conf.j2: "{snap_common}/etc/nova/nova.conf.d/rabbitmq.conf" + nova.conf.d.cinder.conf.j2: "{snap_common}/etc/nova/nova.conf.d/cinder.conf" nova.conf.d.glance.conf.j2: "{snap_common}/etc/nova/nova.conf.d/glance.conf" nova.conf.d.neutron.conf.j2: "{snap_common}/etc/nova/nova.conf.d/neutron.conf" nova.conf.d.placement.conf.j2: "{snap_common}/etc/nova/nova.conf.d/placement.conf" nova.conf.d.console.conf.j2: "{snap_common}/etc/nova/nova.conf.d/console.conf" + nova-nginx.conf.j2: "{snap_common}/etc/nginx/sites-enabled/nova.conf" keystone.database.conf.j2: "{snap_common}/etc/keystone/keystone.conf.d/database.conf" glance.database.conf.j2: "{snap_common}/etc/glance/glance.conf.d/database.conf" placement.conf.d.database.conf.j2: "{snap_common}/etc/placement/placement.conf.d/database.conf" @@ -75,6 +79,7 @@ setup: neutron.database.conf.j2: "{snap_common}/etc/neutron/neutron.conf.d/database.conf" neutron.conf.d.rabbitmq.conf.j2: "{snap_common}/etc/neutron/neutron.conf.d/rabbitmq.conf" neutron_ovn_metadata_agent.ini.j2: "{snap_common}/etc/neutron/neutron_ovn_metadata_agent.ini" + neutron-nginx.conf.j2: "{snap_common}/etc/nginx/sites-enabled/neutron.conf" rabbitmq.conf.j2: "{snap_common}/etc/rabbitmq/rabbitmq.config" iscsid.conf.j2: "{snap_common}/etc/iscsi/iscsid.conf" lvm.conf.j2: "{snap_common}/etc/lvm/lvm.conf" @@ -84,6 +89,9 @@ setup: nrpe.cfg.j2: "{snap_common}/etc/nrpe/nrpe-microstack.cfg" filebeat.yaml.j2: "{snap_common}/etc/filebeat/filebeat-microstack.yaml" chmod: + "{snap_common}/etc/ssl": 0755 + "{snap_common}/etc/ssl/certs": 0755 + "{snap_common}/etc/ssl/private": 0700 "{snap_common}/instances": 0755 "{snap_common}/etc/microstack.rc": 0644 "{snap_common}/etc/microstack.json": 0644 @@ -124,6 +132,10 @@ setup: virt_type: 'config.nova.virt-type' cpu_mode: 'config.nova.cpu-mode' cpu_models: 'config.nova.cpu-models' + tls_generate_self_signed: 'config.tls.generate-self-signed' + tls_cacert_path: 'config.tls.cacert-path' + tls_cert_path: 'config.tls.cert-path' + tls_key_path: 'config.tls.key-path' entry_points: keystone-manage: binary: "{snap}/bin/keystone-manage" diff --git a/snap-overlay/templates/05_snap_tweaks.j2 b/snap-overlay/templates/05_snap_tweaks.j2 index 6a2149b..6c59d07 100644 --- a/snap-overlay/templates/05_snap_tweaks.j2 +++ b/snap-overlay/templates/05_snap_tweaks.j2 @@ -23,7 +23,7 @@ AVAILABLE_THEMES = [ # Point us at keystone. OPENSTACK_HOST = "10.20.20.1" -OPENSTACK_KEYSTONE_URL = "http://%s:5000/v3" % OPENSTACK_HOST +OPENSTACK_KEYSTONE_URL = "https://%s:5000/v3" % OPENSTACK_HOST OPENSTACK_KEYSTONE_DEFAULT_ROLE = "_member_" # Turn off external access for now. (This should be turned on once we @@ -40,4 +40,11 @@ CACHES = { } SESSION_ENGINE='django.contrib.sessions.backends.cache' - +# SSL config +CSRF_COOKIE_SECURE = True +SESSION_COOKIE_SECURE = True +{% if tls_generate_self_signed|lower == "false" %} +OPENSTACK_SSL_CACERT = "{{ tls_cacert_path }}" +{% else %} +OPENSTACK_SSL_NO_VERIFY = True +{% endif %} diff --git a/snap-overlay/templates/cinder-nginx.conf.j2 b/snap-overlay/templates/cinder-nginx.conf.j2 index 47e6c7f..5134741 100644 --- a/snap-overlay/templates/cinder-nginx.conf.j2 +++ b/snap-overlay/templates/cinder-nginx.conf.j2 @@ -1,5 +1,9 @@ server { - listen 8776; + listen 8776 ssl; + ssl_certificate {{ tls_cert_path }}; + ssl_certificate_key {{ tls_key_path }}; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; access_log {{ snap_common }}/log/nginx-access.log; error_log {{ snap_common }}/log/nginx-error.log; location / { diff --git a/snap-overlay/templates/cinder.keystone.conf.j2 b/snap-overlay/templates/cinder.keystone.conf.j2 index e4144f5..5c56735 100644 --- a/snap-overlay/templates/cinder.keystone.conf.j2 +++ b/snap-overlay/templates/cinder.keystone.conf.j2 @@ -2,8 +2,8 @@ auth_strategy = keystone [keystone_authtoken] -auth_uri = http://{{ control_ip }}:5000 -auth_url = http://{{ control_ip }}:5000 +auth_uri = https://{{ control_ip }}:5000/v3 +auth_url = https://{{ control_ip }}:5000/v3 memcached_servers = {{ compute_ip }}:11211 auth_type = password project_domain_name = default @@ -11,3 +11,4 @@ user_domain_name = default project_name = service username = cinder password = {{ cinder_password }} +cafile = {{ tls_cacert_path }} diff --git a/snap-overlay/templates/glance-nginx.conf.j2 b/snap-overlay/templates/glance-nginx.conf.j2 new file mode 100644 index 0000000..05c9457 --- /dev/null +++ b/snap-overlay/templates/glance-nginx.conf.j2 @@ -0,0 +1,18 @@ +server { + listen 9292 ssl; + ssl_certificate {{ tls_cert_path }}; + ssl_certificate_key {{ tls_key_path }}; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; + access_log {{ snap_common }}/log/nginx-access.log; + error_log {{ snap_common }}/log/nginx-error.log; + # Disabled for glance image uploads. The maximum size will be + # determined by glance settings. + client_max_body_size 0; + location / { + proxy_set_header X-Forwarded-Host $host:$server_port; + proxy_set_header X-Forwarded-Server $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass http://127.0.0.1:9282; + } +} diff --git a/snap-overlay/templates/glance-snap.conf.j2 b/snap-overlay/templates/glance-snap.conf.j2 index 1e1c610..0802727 100644 --- a/snap-overlay/templates/glance-snap.conf.j2 +++ b/snap-overlay/templates/glance-snap.conf.j2 @@ -3,6 +3,7 @@ state_path = {{ snap_common }}/lib # Log to systemd journal use_journal = True +bind_port = 9282 log_file = {{ snap_common }}/log/glance.log debug = {{ logging_debug }} diff --git a/snap-overlay/templates/glance.conf.d.keystone.conf.j2 b/snap-overlay/templates/glance.conf.d.keystone.conf.j2 index 9857898..f593dff 100644 --- a/snap-overlay/templates/glance.conf.d.keystone.conf.j2 +++ b/snap-overlay/templates/glance.conf.d.keystone.conf.j2 @@ -1,6 +1,6 @@ [keystone_authtoken] -auth_uri = http://{{ control_ip }}:5000 -auth_url = http://{{ control_ip }}:5000 +auth_uri = https://{{ control_ip }}:5000/v3 +auth_url = https://{{ control_ip }}:5000/v3 memcached_servers = {{ compute_ip }}:11211 auth_type = password project_domain_name = default @@ -8,6 +8,7 @@ user_domain_name = default project_name = service username = glance password = {{ glance_password }} +cafile = {{ tls_cacert_path }} [paste_deploy] flavor = keystone diff --git a/snap-overlay/templates/horizon-nginx.conf.j2 b/snap-overlay/templates/horizon-nginx.conf.j2 index 394e4e9..3f9b36d 100644 --- a/snap-overlay/templates/horizon-nginx.conf.j2 +++ b/snap-overlay/templates/horizon-nginx.conf.j2 @@ -1,10 +1,11 @@ -# If the OpenStack service has an API that runs behind uwsgi+nginx, you'll need -# to define this template. Be sure to update "listen" with the port number and -# also update "api-name" for the socket. server { - listen {{ dashboard_port }}; - error_log syslog:server=unix:/dev/log; - access_log syslog:server=unix:/dev/log; + listen {{ dashboard_port }} ssl; + ssl_certificate {{ tls_cert_path }}; + ssl_certificate_key {{ tls_key_path }}; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; + access_log {{ snap_common }}/log/nginx-access.log; + error_log {{ snap_common }}/log/nginx-error.log; location / { include {{ snap }}/usr/conf/uwsgi_params; uwsgi_param SCRIPT_NAME ''; diff --git a/snap-overlay/templates/keystone-nginx.conf.j2 b/snap-overlay/templates/keystone-nginx.conf.j2 index 413e923..62f6e1a 100644 --- a/snap-overlay/templates/keystone-nginx.conf.j2 +++ b/snap-overlay/templates/keystone-nginx.conf.j2 @@ -1,7 +1,11 @@ server { - listen 5000; - error_log syslog:server=unix:/dev/log; - access_log syslog:server=unix:/dev/log; + listen 5000 ssl; + ssl_certificate {{ tls_cert_path }}; + ssl_certificate_key {{ tls_key_path }}; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; + access_log {{ snap_common }}/log/nginx-access.log; + error_log {{ snap_common }}/log/nginx-error.log; location / { include {{ snap }}/usr/conf/uwsgi_params; uwsgi_param SCRIPT_NAME ''; diff --git a/snap-overlay/templates/microstack.json.j2 b/snap-overlay/templates/microstack.json.j2 index 587d62d..c3df4fd 100644 --- a/snap-overlay/templates/microstack.json.j2 +++ b/snap-overlay/templates/microstack.json.j2 @@ -13,12 +13,19 @@ "version": 3 } }, - "auth_url": "http://{{ control_ip }}:5000", + "auth_url": "https://{{ control_ip }}:5000/v3", "endpoint_type": null, +{% if tls_generate_self_signed|lower == "false" %} + "https_cacert": "{{ tls_cacert_path }}", + "https_cert": "{{ tls_cert_path }}", + "https_key": "{{ tls_key_path }}", + "https_insecure": false, +{% else %} "https_cacert": "", "https_cert": "", - "https_insecure": false, "https_key": "", + "https_insecure": true, +{% endif %} "profiler_conn_str": null, "profiler_hmac_key": null, "region_name": "" diff --git a/snap-overlay/templates/microstack.rc.j2 b/snap-overlay/templates/microstack.rc.j2 index 1545f2e..f1e9df8 100644 --- a/snap-overlay/templates/microstack.rc.j2 +++ b/snap-overlay/templates/microstack.rc.j2 @@ -3,7 +3,8 @@ export OS_USER_DOMAIN_NAME=default export OS_PROJECT_NAME=admin export OS_USERNAME=admin export OS_PASSWORD={{ keystone_password }} -export OS_AUTH_URL=http://{{ control_ip }}:5000 +export OS_AUTH_PROTOCOL=https +export OS_AUTH_URL=https://{{ control_ip }}:5000/v3 export OS_IDENTITY_API_VERSION=3 export OS_IMAGE_API_VERSION=2 - +export OS_CACERT={{ tls_cacert_path }} diff --git a/snap-overlay/templates/neutron-nginx.conf.j2 b/snap-overlay/templates/neutron-nginx.conf.j2 new file mode 100644 index 0000000..af38190 --- /dev/null +++ b/snap-overlay/templates/neutron-nginx.conf.j2 @@ -0,0 +1,15 @@ +server { + listen 9696 ssl; + ssl_certificate {{ tls_cert_path }}; + ssl_certificate_key {{ tls_key_path }}; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; + access_log {{ snap_common }}/log/nginx-access.log; + error_log {{ snap_common }}/log/nginx-error.log; + location / { + proxy_set_header X-Forwarded-Host $host:$server_port; + proxy_set_header X-Forwarded-Server $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass http://127.0.0.1:9686; + } +} diff --git a/snap-overlay/templates/neutron-snap.conf.j2 b/snap-overlay/templates/neutron-snap.conf.j2 index 3f72448..e2f9420 100644 --- a/snap-overlay/templates/neutron-snap.conf.j2 +++ b/snap-overlay/templates/neutron-snap.conf.j2 @@ -3,6 +3,7 @@ state_path = {{ snap_common }}/lib # Log to systemd journal use_journal = True +bind_port = 9686 log_file = {{ snap_common }}/log/neutron.log debug = {{ logging_debug }} diff --git a/snap-overlay/templates/neutron.keystone.conf.j2 b/snap-overlay/templates/neutron.keystone.conf.j2 index fef46ab..5ae9e96 100644 --- a/snap-overlay/templates/neutron.keystone.conf.j2 +++ b/snap-overlay/templates/neutron.keystone.conf.j2 @@ -2,8 +2,8 @@ auth_strategy = keystone [keystone_authtoken] -auth_uri = http://{{ control_ip }}:5000 -auth_url = http://{{ control_ip }}:5000 +auth_uri = https://{{ control_ip }}:5000/v3 +auth_url = https://{{ control_ip }}:5000/v3 memcached_servers = {{ compute_ip }}:11211 auth_type = password project_domain_name = default @@ -11,3 +11,4 @@ user_domain_name = default project_name = service username = neutron password = {{ neutron_password }} +cafile = {{ tls_cacert_path }} diff --git a/snap-overlay/templates/neutron.nova.conf.j2 b/snap-overlay/templates/neutron.nova.conf.j2 index 5cba31a..02d5ac5 100644 --- a/snap-overlay/templates/neutron.nova.conf.j2 +++ b/snap-overlay/templates/neutron.nova.conf.j2 @@ -3,7 +3,7 @@ notify_nova_on_port_status_changes = True notify_nova_on_port_data_changes = True [nova] -auth_url = http://{{ control_ip }}:5000 +auth_url = https://{{ control_ip }}:5000/v3 auth_type = password project_domain_name = default user_domain_name = default @@ -11,3 +11,4 @@ region_name = {{ region_name }} project_name = service username = nova password = {{ nova_password }} +cafile = {{ tls_cacert_path }} diff --git a/snap-overlay/templates/neutron.placement.conf.j2 b/snap-overlay/templates/neutron.placement.conf.j2 index e1dff73..741355b 100644 --- a/snap-overlay/templates/neutron.placement.conf.j2 +++ b/snap-overlay/templates/neutron.placement.conf.j2 @@ -1,5 +1,5 @@ [placement] -auth_url = http://{{ control_ip }}:5000 +auth_url = https://{{ control_ip }}:5000/v3 auth_type = password project_domain_name = default user_domain_name = default @@ -7,3 +7,4 @@ region_name = {{ region_name }} project_name = service username = placement password = {{ placement_password }} +cafile = {{ tls_cacert_path }} diff --git a/snap-overlay/templates/nginx.conf.j2 b/snap-overlay/templates/nginx.conf.j2 index 9c5762c..5c102cf 100644 --- a/snap-overlay/templates/nginx.conf.j2 +++ b/snap-overlay/templates/nginx.conf.j2 @@ -45,5 +45,5 @@ http { gzip_disable "msie6"; include {{ snap_common }}/etc/nginx/conf.d/*.conf; - include {{ snap_common }}/etc/nginx/snap/sites-enabled/*; + include {{ snap_common }}/etc/nginx/sites-enabled/*; } diff --git a/snap-overlay/templates/nova-nginx.conf.j2 b/snap-overlay/templates/nova-nginx.conf.j2 index 91c43a6..4263742 100644 --- a/snap-overlay/templates/nova-nginx.conf.j2 +++ b/snap-overlay/templates/nova-nginx.conf.j2 @@ -1,10 +1,15 @@ server { - listen 8778; + listen 8774 ssl; + ssl_certificate {{ tls_cert_path }}; + ssl_certificate_key {{ tls_key_path }}; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; access_log {{ snap_common }}/log/nginx-access.log; error_log {{ snap_common }}/log/nginx-error.log; location / { - include {{ snap }}/usr/conf/uwsgi_params; - uwsgi_param SCRIPT_NAME ''; - uwsgi_pass unix://{{ snap_common }}/run/placement-api.sock; + proxy_set_header X-Forwarded-Host $host:$server_port; + proxy_set_header X-Forwarded-Server $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass http://127.0.0.1:8764; } } diff --git a/snap-overlay/templates/nova-snap.conf.j2 b/snap-overlay/templates/nova-snap.conf.j2 index d9ecde9..9d0db12 100644 --- a/snap-overlay/templates/nova-snap.conf.j2 +++ b/snap-overlay/templates/nova-snap.conf.j2 @@ -7,6 +7,8 @@ state_path = {{ snap_common }}/lib # Log to systemd journal use_journal = True +osapi_compute_listen_port = 8764 + # 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 }} diff --git a/snap-overlay/templates/nova.conf.d.cinder.conf.j2 b/snap-overlay/templates/nova.conf.d.cinder.conf.j2 new file mode 100644 index 0000000..77bd1c1 --- /dev/null +++ b/snap-overlay/templates/nova.conf.d.cinder.conf.j2 @@ -0,0 +1,5 @@ +[cinder] +service_type = volume +service_name = cinder +region_name = {{ region_name }} +cafile = {{ tls_cacert_path }} diff --git a/snap-overlay/templates/nova.conf.d.glance.conf.j2 b/snap-overlay/templates/nova.conf.d.glance.conf.j2 index 973442d..99abdaa 100644 --- a/snap-overlay/templates/nova.conf.d.glance.conf.j2 +++ b/snap-overlay/templates/nova.conf.d.glance.conf.j2 @@ -2,3 +2,4 @@ service_type = image service_name = glance region_name = {{ region_name }} +cafile = {{ tls_cacert_path }} diff --git a/snap-overlay/templates/nova.conf.d.keystone.conf.j2 b/snap-overlay/templates/nova.conf.d.keystone.conf.j2 index c259341..93ab25d 100644 --- a/snap-overlay/templates/nova.conf.d.keystone.conf.j2 +++ b/snap-overlay/templates/nova.conf.d.keystone.conf.j2 @@ -1,6 +1,6 @@ [keystone_authtoken] -auth_uri = http://{{ control_ip }}:5000 -auth_url = http://{{ control_ip }}:5000 +auth_uri = https://{{ control_ip }}:5000/v3 +auth_url = https://{{ control_ip }}:5000/v3 memcached_servers = {{ compute_ip }}:11211 auth_type = password project_domain_name = default @@ -8,6 +8,7 @@ user_domain_name = default project_name = service username = nova password = {{ nova_password }} +cafile = {{ tls_cacert_path }} [paste_deploy] flavor = keystone diff --git a/snap-overlay/templates/nova.conf.d.neutron.conf.j2 b/snap-overlay/templates/nova.conf.d.neutron.conf.j2 index 1a717cb..41c65f4 100644 --- a/snap-overlay/templates/nova.conf.d.neutron.conf.j2 +++ b/snap-overlay/templates/nova.conf.d.neutron.conf.j2 @@ -1,6 +1,6 @@ [neutron] -url = http://{{ control_ip }}:9696 -auth_url = http://{{ control_ip }}:5000 +url = https://{{ control_ip }}:9696 +auth_url = https://{{ control_ip }}:5000/v3 memcached_servers = {{ compute_ip }}:11211 auth_type = password project_domain_name = default @@ -11,3 +11,4 @@ username = neutron password = {{ neutron_password }} service_metadata_proxy = True metadata_proxy_shared_secret = {{ ovn_metadata_proxy_shared_secret }} +cafile = {{ tls_cacert_path }} diff --git a/snap-overlay/templates/nova.conf.d.placement.conf.j2 b/snap-overlay/templates/nova.conf.d.placement.conf.j2 index 065d21c..b749277 100644 --- a/snap-overlay/templates/nova.conf.d.placement.conf.j2 +++ b/snap-overlay/templates/nova.conf.d.placement.conf.j2 @@ -1,6 +1,6 @@ [placement] -auth_uri = http://{{ control_ip }}:5000 -auth_url = http://{{ control_ip }}:5000 +auth_uri = https://{{ control_ip }}:5000/v3 +auth_url = https://{{ control_ip }}:5000/v3 memcached_servers = {{ compute_ip }}:11211 auth_type = password project_domain_name = default @@ -9,3 +9,4 @@ project_name = service username = nova password = {{ nova_password }} region_name = {{ region_name }} +cafile = {{ tls_cacert_path }} diff --git a/snap-overlay/templates/placement-nginx.conf.j2 b/snap-overlay/templates/placement-nginx.conf.j2 index c60dc9d..d84daaa 100644 --- a/snap-overlay/templates/placement-nginx.conf.j2 +++ b/snap-overlay/templates/placement-nginx.conf.j2 @@ -1,7 +1,11 @@ server { - listen 8778; - error_log syslog:server=unix:/dev/log; - access_log syslog:server=unix:/dev/log; + listen 8778 ssl; + ssl_certificate {{ tls_cert_path }}; + ssl_certificate_key {{ tls_key_path }}; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; + access_log {{ snap_common }}/log/nginx-access.log; + error_log {{ snap_common }}/log/nginx-error.log; location / { include {{ snap }}/usr/conf/uwsgi_params; uwsgi_param SCRIPT_NAME ''; diff --git a/snap-overlay/templates/placement.conf.d.keystone.conf.j2 b/snap-overlay/templates/placement.conf.d.keystone.conf.j2 index ed05bee..dfd1438 100644 --- a/snap-overlay/templates/placement.conf.d.keystone.conf.j2 +++ b/snap-overlay/templates/placement.conf.d.keystone.conf.j2 @@ -1,6 +1,6 @@ [keystone_authtoken] -auth_uri = http://{{ control_ip }}:5000 -auth_url = http://{{ control_ip }}:5000 +auth_uri = https://{{ control_ip }}:5000/v3 +auth_url = https://{{ control_ip }}:5000/v3 memcached_servers = {{ compute_ip }}:11211 auth_type = password project_domain_name = default @@ -8,6 +8,7 @@ user_domain_name = default project_name = service username = placement password = {{ placement_password }} +cafile = {{ tls_cacert_path }} [paste_deploy] flavor = keystone diff --git a/snap/hooks/post-refresh b/snap/hooks/post-refresh index 327a9bd..ff75d4a 100755 --- a/snap/hooks/post-refresh +++ b/snap/hooks/post-refresh @@ -22,7 +22,7 @@ fi # Add default ports for mysql, rabbit and dashboard services. # [2019-11-21] build 171 (beta) -> master if [ -z "$(snapctl get config.network.ports.dashboard)" ]; then - snapctl set config.network.ports.dashboard=80 + snapctl set config.network.ports.dashboard=443 fi if [ -z "$(snapctl get config.network.ports.mysql)" ]; then diff --git a/tests/basic-test.sh b/tests/basic-test.sh index d4099f5..19a9971 100755 --- a/tests/basic-test.sh +++ b/tests/basic-test.sh @@ -190,7 +190,7 @@ echo "++++++++++++++++++++++++++++++++++++++++++++++++++" export HORIZON_IP if [[ $PREFIX == *"multipass"* ]]; then - echo "Opening $HORIZON_IP:80 up to the outside world." + echo "Opening $HORIZON_IP:443 up to the outside world." cat< /tmp/_10_hosts.py # Allow all hosts to connect to this machine ALLOWED_HOSTS = ['*',] diff --git a/tests/configure-the-things.sh b/tests/configure-the-things.sh index 7ffe635..36dc616 100644 --- a/tests/configure-the-things.sh +++ b/tests/configure-the-things.sh @@ -26,9 +26,9 @@ sudo systemctl restart snap.microstack.* microstack.openstack user show admin || { sudo microstack.keystone-manage bootstrap \ --bootstrap-password $OS_PASSWORD \ - --bootstrap-admin-url http://10.20.20.1:5000/v3/ \ - --bootstrap-internal-url http://10.20.20.1:5000/v3/ \ - --bootstrap-public-url http://10.20.20.1:5000/v3/ \ + --bootstrap-admin-url https://10.20.20.1:5000/v3/ \ + --bootstrap-internal-url https://10.20.20.1:5000/v3/ \ + --bootstrap-public-url https://10.20.20.1:5000/v3/ \ --bootstrap-region-id microstack } diff --git a/tests/framework.py b/tests/framework.py index 17241c2..a893dad 100644 --- a/tests/framework.py +++ b/tests/framework.py @@ -434,7 +434,7 @@ class Framework(unittest.TestCase): 'microstack', 'config.credentials.keystone-password' ]).decode('utf-8') - self.driver.get(f'http://{control_ip}:{dashboard_port}/') + self.driver.get(f'https://{control_ip}:{dashboard_port}/') # Login to horizon! self.driver.find_element(By.ID, "id_username").click() self.driver.find_element(By.ID, "id_username").send_keys("admin") diff --git a/tools/cluster/cluster/add_compute.py b/tools/cluster/cluster/add_compute.py index e88e09c..8c6f22f 100644 --- a/tools/cluster/cluster/add_compute.py +++ b/tools/cluster/cluster/add_compute.py @@ -29,14 +29,23 @@ def _create_credential(): domain_name = 'default' # TODO: add support for TLS-terminated Keystone once this is supported. auth = v3.password.Password( - auth_url="http://localhost:5000/v3", + auth_url="https://localhost:5000/v3", username='nova', password=config_get('config.credentials.nova-password'), user_domain_name=domain_name, project_domain_name=domain_name, project_name=project_name ) - sess = session.Session(auth=auth) + if config_get('config.tls.generate-self-signed'): + sess = session.Session( + auth=auth, + verify=False, + ) + else: + sess = session.Session( + auth=auth, + verify=config_get('config.tls.cacert-path'), + ) keystone_client = client.Client(session=sess) # Only allow this credential to list the Keystone catalog. After it diff --git a/tools/cluster/cluster/client.py b/tools/cluster/cluster/client.py index 249a8f0..7378767 100755 --- a/tools/cluster/cluster/client.py +++ b/tools/cluster/cluster/client.py @@ -98,6 +98,20 @@ def join(): control_ip = response_dict['config']['network']['control-ip'] shell.config_set(**{'config.network.control-ip': control_ip}) + # Write controller's TLS certificate data to compute node + config = json.loads(shell.check_output('snapctl', 'get', 'config')) + tls_path_map = { + 'cacert-path': 'tls_cacert', + 'cert-path': 'tls_cert', + 'key-path': 'tls_key', + } + for tls_config, tls_file in tls_path_map.items(): + tls_path = response_dict['config']['tls'][tls_config] + shell.config_set(**{'config.tls.{}'.format(tls_config): tls_path}) + with open(tls_path, "w") as f: + f.write(response_dict[tls_file]) + shell.config_set(**{'config.tls.generate-self-signed': False}) + if __name__ == '__main__': join() diff --git a/tools/cluster/cluster/daemon.py b/tools/cluster/cluster/daemon.py index b462f6c..9f903cb 100644 --- a/tools/cluster/cluster/daemon.py +++ b/tools/cluster/cluster/daemon.py @@ -10,6 +10,7 @@ from werkzeug.exceptions import BadRequest from cluster.shell import check_output +from cluster.shell import config_get from keystoneauth1.identity import v3 from keystoneauth1 import session @@ -153,8 +154,20 @@ def handle_unexpected_error(error): def join_info(): """Generate the configuration information to return to a client.""" # TODO: be selective about what we return. For now, we just get everything. + info = {} config = json.loads(check_output('snapctl', 'get', 'config')) - info = {'config': config} + info['config'] = config + + # Add the controller's TLS certificate data + tls_path_map = { + 'cacert-path': 'tls_cacert', + 'cert-path': 'tls_cert', + 'key-path': 'tls_key', + } + for tls_config, tls_file in tls_path_map.items(): + with open(config_get('config.tls.{}'.format(tls_config)), "r") as f: + info[tls_file] = f.read() + return info @@ -210,8 +223,7 @@ def join(): ' authentication data in the request.') return MissingAuthDataInRequest() - # TODO: handle https here when TLS termination support is added. - keystone_base_url = 'http://localhost:5000/v3' + keystone_base_url = 'https://localhost:5000/v3' # In an unlikely event of failing to construct an auth object # treat it as if invalid data got passed in terms of responding @@ -231,7 +243,16 @@ def join(): try: # Use the auth object with the app credential to create a session # which the Keystone client will use. - sess = session.Session(auth=auth) + if config_get('config.tls.generate-self-signed'): + sess = session.Session( + auth=auth, + verify=False, + ) + else: + sess = session.Session( + auth=auth, + verify=config_get('config.tls.cacert-path'), + ) except Exception: logger.exception('An exception has occurred while trying to build' ' a Session object with auth data' diff --git a/tools/init/init/main.py b/tools/init/init/main.py index fa175b9..1658bb2 100644 --- a/tools/init/init/main.py +++ b/tools/init/init/main.py @@ -182,6 +182,7 @@ def init() -> None: # The following are not yet implemented: # questions.VmSwappiness(), # questions.FileHandleLimits(), + questions.TlsCertificates(), questions.DashboardAccess(), questions.RabbitMq(), questions.DatabaseSetup(), diff --git a/tools/init/init/questions/__init__.py b/tools/init/init/questions/__init__.py index 7b2555d..a227f89 100644 --- a/tools/init/init/questions/__init__.py +++ b/tools/init/init/questions/__init__.py @@ -26,14 +26,16 @@ limitations under the License. import json from time import sleep from os import path +from pathlib import Path +from shutil import copyfile from init import shell from init.shell import (check, call, check_output, sql, nc_wait, log_wait, restart, download, disable, enable) from init.config import Env, log -from init import cluster_tls +from init import tls from init.questions.question import Question -from init.questions import clustering, network, uninstall # noqa F401 +from init.questions import clustering, network, tls, uninstall # noqa F401 _env = Env().get_env() @@ -102,7 +104,13 @@ class Clustering(Question): 'config.services.hypervisor': 'true', }) # Generate a self-signed certificate for the clustering service. - cluster_tls.generate_selfsigned() + cert_path, key_path = ( + Path(shell.config_get('config.cluster.tls-cert-path')), + Path(shell.config_get('config.cluster.tls-key-path')), + ) + tls.generate_self_signed( + cert_path, key_path, + fingerprint_config='config.cluster.fingerprint') # Write templates check('snap-openstack', 'setup') @@ -288,6 +296,36 @@ class DashboardAccess(ConfigQuestion): hosts=answer)) +class TlsCertificates(Question): + + _type = 'boolean' + _question = 'Do you wish to generate a self-signed certificate for TLS?' + config_key = 'config.tls.generate-self-signed' + + def yes(self, answer: str) -> None: + role = shell.config_get('config.cluster.role') + + if role == 'control': + log.info('Generating TLS Certificate and Key') + cert_path, key_path = ( + Path(shell.config_get('config.tls.cert-path')), + Path(shell.config_get('config.tls.key-path')), + ) + tls.generate_self_signed(cert_path, key_path, ip=_env['control_ip']) + copyfile(Path(shell.config_get('config.tls.cert-path')), + Path(shell.config_get('config.tls.cacert-path'))) + restart('nginx') + + elif role == 'compute': + log.warning('TLS certificate generation can only be performed on ' + 'control node') + + def no(self, answer: str): + log.info('TLS certificates must be provided: config.tls.cacert-path, ' + 'config.tls.cert-path, and config.tls.key-path.') + restart('nginx') + + class RabbitMq(Question): """Wait for Rabbit to start, then setup permissions.""" @@ -362,7 +400,7 @@ class DatabaseSetup(Question): if call('openstack', 'user', 'show', 'admin'): return - bootstrap_url = 'http://{control_ip}:5000/v3/'.format(**_env) + bootstrap_url = 'https://{control_ip}:5000/v3/'.format(**_env) check('snap-openstack', 'launch', 'keystone-manage', 'bootstrap', '--bootstrap-password', _env['keystone_password'], @@ -401,7 +439,7 @@ class DatabaseSetup(Question): log.info('Creating service project ...') if not call('openstack', 'project', 'show', 'service'): check('openstack', 'project', 'create', '--domain', - 'default', '--description', 'Service Project', + 'default', '--description', '"Service Project"', 'service') log.info('Keystone configured!') @@ -536,7 +574,7 @@ class PlacementSetup(Question): for endpoint in ['public', 'internal', 'admin']: call('openstack', 'endpoint', 'create', '--region', 'microstack', 'placement', endpoint, - 'http://{control_ip}:8778'.format(**_env)) + 'https://{control_ip}:8778'.format(**_env)) log.info('Running Placement DB migrations...') check('snap-openstack', 'launch', 'placement-manage', 'db', 'sync') @@ -612,6 +650,7 @@ class NovaControlPlane(Question): enable('nova-api') restart('nova-compute') + restart('nginx') for service in [ 'nova-api-metadata', @@ -630,7 +669,7 @@ class NovaControlPlane(Question): for endpoint in ['public', 'internal', 'admin']: call('openstack', 'endpoint', 'create', '--region', 'microstack', 'compute', endpoint, - 'http://{control_ip}:8774/v2.1'.format(**_env)) + 'https://{control_ip}:8774/v2.1'.format(**_env)) log.info('Creating default flavors...') @@ -682,7 +721,7 @@ class CinderSetup(Question): check( 'openstack', 'endpoint', 'create', '--region', 'microstack', f'volume{api_version}', endpoint, - f'http://{control_ip}:8776/{api_version}/' + f'https://{control_ip}:8776/{api_version}/' '$(project_id)s' ) log.info('Running Cinder DB migrations...') @@ -754,12 +793,13 @@ class NeutronControlPlane(Question): for endpoint in ['public', 'internal', 'admin']: call('openstack', 'endpoint', 'create', '--region', 'microstack', 'network', endpoint, - 'http://{control_ip}:9696'.format(**_env)) + 'https://{control_ip}:9696'.format(**_env)) check('snap-openstack', 'launch', 'neutron-db-manage', 'upgrade', 'head') enable('neutron-api') enable('neutron-ovn-metadata-agent') + restart('nginx') nc_wait(_env['control_ip'], '9696') @@ -864,10 +904,11 @@ class GlanceSetup(Question): for endpoint in ['internal', 'admin', 'public']: check('openstack', 'endpoint', 'create', '--region', 'microstack', 'image', endpoint, - 'http://{compute_ip}:9292'.format(**_env)) + 'https://{compute_ip}:9292'.format(**_env)) check('snap-openstack', 'launch', 'glance-manage', 'db_sync') enable('glance-api') + restart('nginx') nc_wait(_env['compute_ip'], '9292') diff --git a/tools/init/init/cluster_tls.py b/tools/init/init/tls.py similarity index 79% rename from tools/init/init/cluster_tls.py rename to tools/init/init/tls.py index 95e4c98..3724f7c 100644 --- a/tools/init/init/cluster_tls.py +++ b/tools/init/init/tls.py @@ -1,9 +1,12 @@ #!/usr/bin/env python3 -from pathlib import Path + +from init.shell import check from datetime import datetime from dateutil.relativedelta import relativedelta +import ipaddress +import socket from cryptography.hazmat.primitives import hashes from cryptography.hazmat.backends import default_backend @@ -15,7 +18,7 @@ from cryptography.x509.oid import NameOID from init import shell -def generate_selfsigned(): +def generate_self_signed(cert_path, key_path, ip=None, fingerprint_config=None): """Generate a self-signed certificate with associated keys. The certificate will have a fake CNAME and subjAltName since @@ -28,26 +31,29 @@ def generate_selfsigned(): via a secure channel. https://owasp.org/www-community/controls/Certificate_and_Public_Key_Pinning """ - cert_path, key_path = ( - Path(shell.config_get('config.cluster.tls-cert-path')), - Path(shell.config_get('config.cluster.tls-key-path')), - ) # Do not generate a new certificate and key if there is already an existing # pair. TODO: improve this check and allow renewal. if cert_path.exists() and key_path.exists(): return - dummy_cn = 'microstack.run' key = rsa.generate_private_key( public_exponent=65537, key_size=2048, backend=default_backend(), ) + cn = socket.gethostname() common_name = x509.Name([ - x509.NameAttribute(NameOID.COMMON_NAME, dummy_cn) + x509.NameAttribute(NameOID.COMMON_NAME, cn) ]) - san = x509.SubjectAlternativeName([x509.DNSName(dummy_cn)]) + if ip: + san = x509.SubjectAlternativeName( + [x509.DNSName(cn), x509.IPAddress(ipaddress.ip_address(ip))] + ) + else: + san = x509.SubjectAlternativeName([x509.DNSName(cn)]) + basic_contraints = x509.BasicConstraints(ca=True, path_length=0) + now = datetime.utcnow() cert = ( x509.CertificateBuilder() @@ -63,7 +69,8 @@ def generate_selfsigned(): ) cert_fprint = cert.fingerprint(hashes.SHA256()).hex() - shell.config_set(**{'config.cluster.fingerprint': cert_fprint}) + if fingerprint_config: + shell.config_set(**{fingerprint_config: cert_fprint}) serialized_cert = cert.public_bytes(encoding=serialization.Encoding.PEM) serialized_key = key.private_bytes( @@ -73,3 +80,5 @@ def generate_selfsigned(): ) cert_path.write_bytes(serialized_cert) key_path.write_bytes(serialized_key) + check('chmod', '644', str(cert_path)) + check('chmod', '600', str(key_path)) diff --git a/tools/launch/launch/main.py b/tools/launch/launch/main.py index c60fcc1..47a067c 100644 --- a/tools/launch/launch/main.py +++ b/tools/launch/launch/main.py @@ -207,8 +207,8 @@ Access it with `ssh -i {key_path} {username}@{ip}`\ gate = check_output('snapctl', 'get', 'config.network.ext-gateway') port = check_output('snapctl', 'get', 'config.network.ports.dashboard') - print('You can also visit the OpenStack dashboard at http://{}:{}'.format( - gate, port)) + print('You can also visit the OpenStack dashboard at https://{}:{}'.format( + gate, port)) def main():