Add TLS OpenStack API endpoints

This patch provides TLS endpoints secured by a self-signed
certificate. Another patch will provide support for trusted CA-signed
certificates.

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 self-signed certificate by setting
generate-self-signed 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
This commit is contained in:
Corey Bryant 2021-03-11 11:58:02 -05:00
parent abf8af66ef
commit 064aae8458
39 changed files with 292 additions and 86 deletions

View File

@ -158,11 +158,11 @@ Answer the questions as follows:
<table> <table>
<tr><td>cloud type:</td> <td><code>openstack</code></td></tr> <tr><td>cloud type:</td> <td><code>openstack</code></td></tr>
<tr><td>endpoint:</td> <td><code>http://10.20.20.1:5000/v3</code></td></tr> <tr><td>endpoint:</td> <td><code>https://10.20.20.1:5000/v3</code></td></tr>
<tr><td>cert path:</td> <td><code>none</code></td></tr> <tr><td>cert path:</td> <td><code>none</code></td></tr>
<tr><td>auth type:</td> <td><code>userpass</code></td></tr> <tr><td>auth type:</td> <td><code>userpass</code></td></tr>
<tr><td>region:</td> <td><code>microstack</code></td></tr> <tr><td>region:</td> <td><code>microstack</code></td></tr>
<tr><td>region endpoint:</td> <td><code>http://10.20.20.1:5000/v3</code></td></tr> <tr><td>region endpoint:</td> <td><code>https://10.20.20.1:5000/v3</code></td></tr>
<tr><td>add another region?:</td> <td><code>N</code></td></tr> <tr><td>add another region?:</td> <td><code>N</code></td></tr>
</table> </table>
@ -182,7 +182,7 @@ images in your microstack cloud. Here's how to set that up.
``` ```
mkdir simplestreams 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 (If you don't still have an `IMAGE` variable in your env, you can find

View File

@ -35,7 +35,7 @@ def _get_default_config():
'config.network.ext-cidr': '10.20.20.1/24', 'config.network.ext-cidr': '10.20.20.1/24',
'config.network.security-rules': True, 'config.network.security-rules': True,
'config.network.dashboard-allowed-hosts': '*', 'config.network.dashboard-allowed-hosts': '*',
'config.network.ports.dashboard': 80, 'config.network.ports.dashboard': 443,
'config.network.ports.mysql': 3306, 'config.network.ports.mysql': 3306,
'config.network.ports.rabbit': 5672, 'config.network.ports.rabbit': 5672,
'config.network.external-bridge-name': 'br-ex', 'config.network.external-bridge-name': 'br-ex',
@ -75,6 +75,14 @@ def _get_default_config():
'config.nova.cpu-mode': 'host-model', 'config.nova.cpu-mode': 'host-model',
# Do not override cpu-models by default. # Do not override cpu-models by default.
'config.nova.cpu-models': '', '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',
} }

View File

@ -8,7 +8,6 @@ setup:
- "{snap_common}/etc/neutron/policy.d" - "{snap_common}/etc/neutron/policy.d"
- "{snap_common}/etc/neutron/rootwrap.d" - "{snap_common}/etc/neutron/rootwrap.d"
- "{snap_common}/etc/nginx/sites-enabled" - "{snap_common}/etc/nginx/sites-enabled"
- "{snap_common}/etc/nginx/snap/sites-enabled"
- "{snap_common}/etc/glance/glance.conf.d" - "{snap_common}/etc/glance/glance.conf.d"
- "{snap_common}/etc/placement/placement.conf.d" - "{snap_common}/etc/placement/placement.conf.d"
- "{snap_common}/etc/horizon/horizon.conf.d" - "{snap_common}/etc/horizon/horizon.conf.d"
@ -22,6 +21,8 @@ setup:
- "{snap_common}/etc/cluster/tls" - "{snap_common}/etc/cluster/tls"
- "{snap_common}/etc/cluster/uwsgi/snap" - "{snap_common}/etc/cluster/uwsgi/snap"
- "{snap_common}/etc/rabbitmq" - "{snap_common}/etc/rabbitmq"
- "{snap_common}/etc/ssl/certs"
- "{snap_common}/etc/ssl/private"
- "{snap_common}/fernet-keys" - "{snap_common}/fernet-keys"
- "{snap_common}/lib" - "{snap_common}/lib"
- "{snap_common}/lib/images" - "{snap_common}/lib/images"
@ -33,24 +34,25 @@ setup:
- "{snap_common}/etc/iscsi" - "{snap_common}/etc/iscsi"
- "{snap_common}/etc/target" - "{snap_common}/etc/target"
templates: templates:
cluster-nginx.conf.j2: "{snap_common}/etc/nginx/snap/sites-enabled/cluster.conf" cluster-nginx.conf.j2: "{snap_common}/etc/nginx/sites-enabled/cluster.conf"
keystone-nginx.conf.j2: "{snap_common}/etc/nginx/snap/sites-enabled/keystone.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" 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" neutron-snap.conf.j2: "{snap_common}/etc/neutron/neutron.conf.d/neutron-snap.conf"
nginx.conf.j2: "{snap_common}/etc/nginx/snap/nginx.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-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" 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" 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-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.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.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.keystone.conf.j2: "{snap_common}/etc/cinder/cinder.conf.d/keystone.conf"
cinder-rootwrap.conf.j2: "{snap_common}/etc/cinder/rootwrap.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-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" 05_snap_tweaks.j2: "{snap_common}/etc/horizon/local_settings.d/_05_snap_tweaks.py"
libvirtd.conf.j2: "{snap_common}/etc/libvirt/libvirtd.conf" libvirtd.conf.j2: "{snap_common}/etc/libvirt/libvirtd.conf"
qemu.conf.j2: "{snap_common}/etc/libvirt/qemu.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.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.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.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.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.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.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.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" 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" 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" 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.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.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_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" rabbitmq.conf.j2: "{snap_common}/etc/rabbitmq/rabbitmq.config"
iscsid.conf.j2: "{snap_common}/etc/iscsi/iscsid.conf" iscsid.conf.j2: "{snap_common}/etc/iscsi/iscsid.conf"
lvm.conf.j2: "{snap_common}/etc/lvm/lvm.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" nrpe.cfg.j2: "{snap_common}/etc/nrpe/nrpe-microstack.cfg"
filebeat.yaml.j2: "{snap_common}/etc/filebeat/filebeat-microstack.yaml" filebeat.yaml.j2: "{snap_common}/etc/filebeat/filebeat-microstack.yaml"
chmod: chmod:
"{snap_common}/etc/ssl": 0755
"{snap_common}/etc/ssl/certs": 0755
"{snap_common}/etc/ssl/private": 0700
"{snap_common}/instances": 0755 "{snap_common}/instances": 0755
"{snap_common}/etc/microstack.rc": 0644 "{snap_common}/etc/microstack.rc": 0644
"{snap_common}/etc/microstack.json": 0644 "{snap_common}/etc/microstack.json": 0644
@ -124,6 +132,10 @@ setup:
virt_type: 'config.nova.virt-type' virt_type: 'config.nova.virt-type'
cpu_mode: 'config.nova.cpu-mode' cpu_mode: 'config.nova.cpu-mode'
cpu_models: 'config.nova.cpu-models' 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: entry_points:
keystone-manage: keystone-manage:
binary: "{snap}/bin/keystone-manage" binary: "{snap}/bin/keystone-manage"

View File

@ -23,7 +23,7 @@ AVAILABLE_THEMES = [
# Point us at keystone. # Point us at keystone.
OPENSTACK_HOST = "10.20.20.1" 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_" OPENSTACK_KEYSTONE_DEFAULT_ROLE = "_member_"
# Turn off external access for now. (This should be turned on once we # Turn off external access for now. (This should be turned on once we
@ -40,4 +40,13 @@ CACHES = {
} }
SESSION_ENGINE='django.contrib.sessions.backends.cache' SESSION_ENGINE='django.contrib.sessions.backends.cache'
# SSL config
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_SECURE = True
{% if tls_generate_self_signed|lower == "false" %}
# TODO(coreycb): Can we verify cert if self-signed certs
# include a ca-cert and cert?
OPENSTACK_SSL_CACERT = "{{ tls_cacert_path }}"
{% else %}
OPENSTACK_SSL_NO_VERIFY = True
{% endif %}

View File

@ -1,5 +1,9 @@
server { 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; access_log {{ snap_common }}/log/nginx-access.log;
error_log {{ snap_common }}/log/nginx-error.log; error_log {{ snap_common }}/log/nginx-error.log;
location / { location / {

View File

@ -2,8 +2,8 @@
auth_strategy = keystone auth_strategy = keystone
[keystone_authtoken] [keystone_authtoken]
auth_uri = http://{{ control_ip }}:5000 auth_uri = https://{{ control_ip }}:5000/v3
auth_url = http://{{ control_ip }}:5000 auth_url = https://{{ control_ip }}:5000/v3
memcached_servers = {{ compute_ip }}:11211 memcached_servers = {{ compute_ip }}:11211
auth_type = password auth_type = password
project_domain_name = default project_domain_name = default
@ -11,3 +11,4 @@ user_domain_name = default
project_name = service project_name = service
username = cinder username = cinder
password = {{ cinder_password }} password = {{ cinder_password }}
cafile = {{ tls_cacert_path }}

View File

@ -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;
}
}

View File

@ -3,6 +3,7 @@
state_path = {{ snap_common }}/lib state_path = {{ snap_common }}/lib
# Log to systemd journal # Log to systemd journal
use_journal = True use_journal = True
bind_port = 9282
log_file = {{ snap_common }}/log/glance.log log_file = {{ snap_common }}/log/glance.log
debug = {{ logging_debug }} debug = {{ logging_debug }}

View File

@ -1,6 +1,6 @@
[keystone_authtoken] [keystone_authtoken]
auth_uri = http://{{ control_ip }}:5000 auth_uri = https://{{ control_ip }}:5000/v3
auth_url = http://{{ control_ip }}:5000 auth_url = https://{{ control_ip }}:5000/v3
memcached_servers = {{ compute_ip }}:11211 memcached_servers = {{ compute_ip }}:11211
auth_type = password auth_type = password
project_domain_name = default project_domain_name = default
@ -8,6 +8,7 @@ user_domain_name = default
project_name = service project_name = service
username = glance username = glance
password = {{ glance_password }} password = {{ glance_password }}
cafile = {{ tls_cacert_path }}
[paste_deploy] [paste_deploy]
flavor = keystone flavor = keystone

View File

@ -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 { server {
listen {{ dashboard_port }}; listen {{ dashboard_port }} ssl;
error_log syslog:server=unix:/dev/log; ssl_certificate {{ tls_cert_path }};
access_log syslog:server=unix:/dev/log; 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 / { location / {
include {{ snap }}/usr/conf/uwsgi_params; include {{ snap }}/usr/conf/uwsgi_params;
uwsgi_param SCRIPT_NAME ''; uwsgi_param SCRIPT_NAME '';

View File

@ -1,7 +1,11 @@
server { server {
listen 5000; listen 5000 ssl;
error_log syslog:server=unix:/dev/log; ssl_certificate {{ tls_cert_path }};
access_log syslog:server=unix:/dev/log; 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 / { location / {
include {{ snap }}/usr/conf/uwsgi_params; include {{ snap }}/usr/conf/uwsgi_params;
uwsgi_param SCRIPT_NAME ''; uwsgi_param SCRIPT_NAME '';

View File

@ -13,12 +13,19 @@
"version": 3 "version": 3
} }
}, },
"auth_url": "http://{{ control_ip }}:5000", "auth_url": "https://{{ control_ip }}:5000/v3",
"endpoint_type": null, "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_cacert": "",
"https_cert": "", "https_cert": "",
"https_insecure": false,
"https_key": "", "https_key": "",
"https_insecure": true,
{% endif %}
"profiler_conn_str": null, "profiler_conn_str": null,
"profiler_hmac_key": null, "profiler_hmac_key": null,
"region_name": "" "region_name": ""

View File

@ -3,7 +3,8 @@ export OS_USER_DOMAIN_NAME=default
export OS_PROJECT_NAME=admin export OS_PROJECT_NAME=admin
export OS_USERNAME=admin export OS_USERNAME=admin
export OS_PASSWORD={{ keystone_password }} 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_IDENTITY_API_VERSION=3
export OS_IMAGE_API_VERSION=2 export OS_IMAGE_API_VERSION=2
export OS_CACERT={{ tls_cacert_path }}

View File

@ -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;
}
}

View File

@ -3,6 +3,7 @@
state_path = {{ snap_common }}/lib state_path = {{ snap_common }}/lib
# Log to systemd journal # Log to systemd journal
use_journal = True use_journal = True
bind_port = 9686
log_file = {{ snap_common }}/log/neutron.log log_file = {{ snap_common }}/log/neutron.log
debug = {{ logging_debug }} debug = {{ logging_debug }}

View File

@ -2,8 +2,8 @@
auth_strategy = keystone auth_strategy = keystone
[keystone_authtoken] [keystone_authtoken]
auth_uri = http://{{ control_ip }}:5000 auth_uri = https://{{ control_ip }}:5000/v3
auth_url = http://{{ control_ip }}:5000 auth_url = https://{{ control_ip }}:5000/v3
memcached_servers = {{ compute_ip }}:11211 memcached_servers = {{ compute_ip }}:11211
auth_type = password auth_type = password
project_domain_name = default project_domain_name = default
@ -11,3 +11,4 @@ user_domain_name = default
project_name = service project_name = service
username = neutron username = neutron
password = {{ neutron_password }} password = {{ neutron_password }}
cafile = {{ tls_cacert_path }}

View File

@ -3,7 +3,7 @@ notify_nova_on_port_status_changes = True
notify_nova_on_port_data_changes = True notify_nova_on_port_data_changes = True
[nova] [nova]
auth_url = http://{{ control_ip }}:5000 auth_url = https://{{ control_ip }}:5000/v3
auth_type = password auth_type = password
project_domain_name = default project_domain_name = default
user_domain_name = default user_domain_name = default
@ -11,3 +11,4 @@ region_name = {{ region_name }}
project_name = service project_name = service
username = nova username = nova
password = {{ nova_password }} password = {{ nova_password }}
cafile = {{ tls_cacert_path }}

View File

@ -1,5 +1,5 @@
[placement] [placement]
auth_url = http://{{ control_ip }}:5000 auth_url = https://{{ control_ip }}:5000/v3
auth_type = password auth_type = password
project_domain_name = default project_domain_name = default
user_domain_name = default user_domain_name = default
@ -7,3 +7,4 @@ region_name = {{ region_name }}
project_name = service project_name = service
username = placement username = placement
password = {{ placement_password }} password = {{ placement_password }}
cafile = {{ tls_cacert_path }}

View File

@ -45,5 +45,5 @@ http {
gzip_disable "msie6"; gzip_disable "msie6";
include {{ snap_common }}/etc/nginx/conf.d/*.conf; include {{ snap_common }}/etc/nginx/conf.d/*.conf;
include {{ snap_common }}/etc/nginx/snap/sites-enabled/*; include {{ snap_common }}/etc/nginx/sites-enabled/*;
} }

View File

@ -1,10 +1,15 @@
server { 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; access_log {{ snap_common }}/log/nginx-access.log;
error_log {{ snap_common }}/log/nginx-error.log; error_log {{ snap_common }}/log/nginx-error.log;
location / { location / {
include {{ snap }}/usr/conf/uwsgi_params; proxy_set_header X-Forwarded-Host $host:$server_port;
uwsgi_param SCRIPT_NAME ''; proxy_set_header X-Forwarded-Server $host;
uwsgi_pass unix://{{ snap_common }}/run/placement-api.sock; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://127.0.0.1:8764;
} }
} }

View File

@ -7,6 +7,8 @@ state_path = {{ snap_common }}/lib
# Log to systemd journal # Log to systemd journal
use_journal = True use_journal = True
osapi_compute_listen_port = 8764
# Set a hostname to be an FQDN to avoid issues with port binding for # 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. # which a hostname of a Nova node must match a hostname of an OVN chassis.
host = {{ node_fqdn }} host = {{ node_fqdn }}

View File

@ -0,0 +1,5 @@
[cinder]
service_type = volume
service_name = cinder
region_name = {{ region_name }}
cafile = {{ tls_cacert_path }}

View File

@ -2,3 +2,4 @@
service_type = image service_type = image
service_name = glance service_name = glance
region_name = {{ region_name }} region_name = {{ region_name }}
cafile = {{ tls_cacert_path }}

View File

@ -1,6 +1,6 @@
[keystone_authtoken] [keystone_authtoken]
auth_uri = http://{{ control_ip }}:5000 auth_uri = https://{{ control_ip }}:5000/v3
auth_url = http://{{ control_ip }}:5000 auth_url = https://{{ control_ip }}:5000/v3
memcached_servers = {{ compute_ip }}:11211 memcached_servers = {{ compute_ip }}:11211
auth_type = password auth_type = password
project_domain_name = default project_domain_name = default
@ -8,6 +8,7 @@ user_domain_name = default
project_name = service project_name = service
username = nova username = nova
password = {{ nova_password }} password = {{ nova_password }}
cafile = {{ tls_cacert_path }}
[paste_deploy] [paste_deploy]
flavor = keystone flavor = keystone

View File

@ -1,6 +1,6 @@
[neutron] [neutron]
url = http://{{ control_ip }}:9696 url = https://{{ control_ip }}:9696
auth_url = http://{{ control_ip }}:5000 auth_url = https://{{ control_ip }}:5000/v3
memcached_servers = {{ compute_ip }}:11211 memcached_servers = {{ compute_ip }}:11211
auth_type = password auth_type = password
project_domain_name = default project_domain_name = default
@ -11,3 +11,4 @@ username = neutron
password = {{ neutron_password }} password = {{ neutron_password }}
service_metadata_proxy = True service_metadata_proxy = True
metadata_proxy_shared_secret = {{ ovn_metadata_proxy_shared_secret }} metadata_proxy_shared_secret = {{ ovn_metadata_proxy_shared_secret }}
cafile = {{ tls_cacert_path }}

View File

@ -1,6 +1,6 @@
[placement] [placement]
auth_uri = http://{{ control_ip }}:5000 auth_uri = https://{{ control_ip }}:5000/v3
auth_url = http://{{ control_ip }}:5000 auth_url = https://{{ control_ip }}:5000/v3
memcached_servers = {{ compute_ip }}:11211 memcached_servers = {{ compute_ip }}:11211
auth_type = password auth_type = password
project_domain_name = default project_domain_name = default
@ -9,3 +9,4 @@ project_name = service
username = nova username = nova
password = {{ nova_password }} password = {{ nova_password }}
region_name = {{ region_name }} region_name = {{ region_name }}
cafile = {{ tls_cacert_path }}

View File

@ -1,7 +1,11 @@
server { server {
listen 8778; listen 8778 ssl;
error_log syslog:server=unix:/dev/log; ssl_certificate {{ tls_cert_path }};
access_log syslog:server=unix:/dev/log; 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 / { location / {
include {{ snap }}/usr/conf/uwsgi_params; include {{ snap }}/usr/conf/uwsgi_params;
uwsgi_param SCRIPT_NAME ''; uwsgi_param SCRIPT_NAME '';

View File

@ -1,6 +1,6 @@
[keystone_authtoken] [keystone_authtoken]
auth_uri = http://{{ control_ip }}:5000 auth_uri = https://{{ control_ip }}:5000/v3
auth_url = http://{{ control_ip }}:5000 auth_url = https://{{ control_ip }}:5000/v3
memcached_servers = {{ compute_ip }}:11211 memcached_servers = {{ compute_ip }}:11211
auth_type = password auth_type = password
project_domain_name = default project_domain_name = default
@ -8,6 +8,7 @@ user_domain_name = default
project_name = service project_name = service
username = placement username = placement
password = {{ placement_password }} password = {{ placement_password }}
cafile = {{ tls_cacert_path }}
[paste_deploy] [paste_deploy]
flavor = keystone flavor = keystone

View File

@ -22,7 +22,7 @@ fi
# Add default ports for mysql, rabbit and dashboard services. # Add default ports for mysql, rabbit and dashboard services.
# [2019-11-21] build 171 (beta) -> master # [2019-11-21] build 171 (beta) -> master
if [ -z "$(snapctl get config.network.ports.dashboard)" ]; then if [ -z "$(snapctl get config.network.ports.dashboard)" ]; then
snapctl set config.network.ports.dashboard=80 snapctl set config.network.ports.dashboard=443
fi fi
if [ -z "$(snapctl get config.network.ports.mysql)" ]; then if [ -z "$(snapctl get config.network.ports.mysql)" ]; then

View File

@ -190,7 +190,7 @@ echo "++++++++++++++++++++++++++++++++++++++++++++++++++"
export HORIZON_IP export HORIZON_IP
if [[ $PREFIX == *"multipass"* ]]; then 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<<EOF > /tmp/_10_hosts.py cat<<EOF > /tmp/_10_hosts.py
# Allow all hosts to connect to this machine # Allow all hosts to connect to this machine
ALLOWED_HOSTS = ['*',] ALLOWED_HOSTS = ['*',]

View File

@ -26,9 +26,9 @@ sudo systemctl restart snap.microstack.*
microstack.openstack user show admin || { microstack.openstack user show admin || {
sudo microstack.keystone-manage bootstrap \ sudo microstack.keystone-manage bootstrap \
--bootstrap-password $OS_PASSWORD \ --bootstrap-password $OS_PASSWORD \
--bootstrap-admin-url http://10.20.20.1:5000/v3/ \ --bootstrap-admin-url https://10.20.20.1:5000/v3/ \
--bootstrap-internal-url http://10.20.20.1:5000/v3/ \ --bootstrap-internal-url https://10.20.20.1:5000/v3/ \
--bootstrap-public-url http://10.20.20.1:5000/v3/ \ --bootstrap-public-url https://10.20.20.1:5000/v3/ \
--bootstrap-region-id microstack --bootstrap-region-id microstack
} }

View File

@ -434,7 +434,7 @@ class Framework(unittest.TestCase):
'microstack', 'microstack',
'config.credentials.keystone-password' 'config.credentials.keystone-password'
]).decode('utf-8') ]).decode('utf-8')
self.driver.get(f'http://{control_ip}:{dashboard_port}/') self.driver.get(f'https://{control_ip}:{dashboard_port}/')
# Login to horizon! # Login to horizon!
self.driver.find_element(By.ID, "id_username").click() self.driver.find_element(By.ID, "id_username").click()
self.driver.find_element(By.ID, "id_username").send_keys("admin") self.driver.find_element(By.ID, "id_username").send_keys("admin")

View File

@ -27,16 +27,26 @@ VALIDITY_PERIOD = relativedelta(minutes=20)
def _create_credential(): def _create_credential():
project_name = 'service' project_name = 'service'
domain_name = 'default' domain_name = 'default'
# TODO: add support for TLS-terminated Keystone once this is supported.
auth = v3.password.Password( auth = v3.password.Password(
auth_url="http://localhost:5000/v3", auth_url="https://localhost:5000/v3",
username='nova', username='nova',
password=config_get('config.credentials.nova-password'), password=config_get('config.credentials.nova-password'),
user_domain_name=domain_name, user_domain_name=domain_name,
project_domain_name=domain_name, project_domain_name=domain_name,
project_name=project_name project_name=project_name
) )
sess = session.Session(auth=auth) if config_get('config.tls.generate-self-signed'):
# TODO(coreycb): Can we verify cert if self-signed certs
# include a ca-cert and cert?
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) keystone_client = client.Client(session=sess)
# Only allow this credential to list the Keystone catalog. After it # Only allow this credential to list the Keystone catalog. After it

View File

@ -98,6 +98,19 @@ def join():
control_ip = response_dict['config']['network']['control-ip'] control_ip = response_dict['config']['network']['control-ip']
shell.config_set(**{'config.network.control-ip': control_ip}) shell.config_set(**{'config.network.control-ip': control_ip})
# Write controller's TLS certificate data to compute node
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__': if __name__ == '__main__':
join() join()

View File

@ -10,6 +10,7 @@ from werkzeug.exceptions import BadRequest
from cluster.shell import check_output from cluster.shell import check_output
from cluster.shell import config_get
from keystoneauth1.identity import v3 from keystoneauth1.identity import v3
from keystoneauth1 import session from keystoneauth1 import session
@ -153,8 +154,20 @@ def handle_unexpected_error(error):
def join_info(): def join_info():
"""Generate the configuration information to return to a client.""" """Generate the configuration information to return to a client."""
# TODO: be selective about what we return. For now, we just get everything. # TODO: be selective about what we return. For now, we just get everything.
info = {}
config = json.loads(check_output('snapctl', 'get', 'config')) 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 return info
@ -210,8 +223,7 @@ def join():
' authentication data in the request.') ' authentication data in the request.')
return MissingAuthDataInRequest() return MissingAuthDataInRequest()
# TODO: handle https here when TLS termination support is added. keystone_base_url = 'https://localhost:5000/v3'
keystone_base_url = 'http://localhost:5000/v3'
# In an unlikely event of failing to construct an auth object # In an unlikely event of failing to construct an auth object
# treat it as if invalid data got passed in terms of responding # treat it as if invalid data got passed in terms of responding
@ -231,7 +243,18 @@ def join():
try: try:
# Use the auth object with the app credential to create a session # Use the auth object with the app credential to create a session
# which the Keystone client will use. # which the Keystone client will use.
sess = session.Session(auth=auth) if config_get('config.tls.generate-self-signed'):
# TODO(coreycb): Can we verify cert if self-signed certs
# include a ca-cert and cert?
sess = session.Session(
auth=auth,
verify=False,
)
else:
sess = session.Session(
auth=auth,
verify=config_get('config.tls.cacert-path'),
)
except Exception: except Exception:
logger.exception('An exception has occurred while trying to build' logger.exception('An exception has occurred while trying to build'
' a Session object with auth data' ' a Session object with auth data'

View File

@ -182,6 +182,7 @@ def init() -> None:
# The following are not yet implemented: # The following are not yet implemented:
# questions.VmSwappiness(), # questions.VmSwappiness(),
# questions.FileHandleLimits(), # questions.FileHandleLimits(),
questions.TlsCertificates(),
questions.DashboardAccess(), questions.DashboardAccess(),
questions.RabbitMq(), questions.RabbitMq(),
questions.DatabaseSetup(), questions.DatabaseSetup(),

View File

@ -26,14 +26,16 @@ limitations under the License.
import json import json
from time import sleep from time import sleep
from os import path from os import path
from pathlib import Path
from shutil import copyfile
from init import shell from init import shell
from init.shell import (check, call, check_output, sql, nc_wait, log_wait, from init.shell import (check, call, check_output, sql, nc_wait, log_wait,
restart, download, disable, enable) restart, download, disable, enable)
from init.config import Env, log from init.config import Env, log
from init import cluster_tls from init import tls
from init.questions.question import Question 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() _env = Env().get_env()
@ -102,7 +104,13 @@ class Clustering(Question):
'config.services.hypervisor': 'true', 'config.services.hypervisor': 'true',
}) })
# Generate a self-signed certificate for the clustering service. # 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 # Write templates
check('snap-openstack', 'setup') check('snap-openstack', 'setup')
@ -288,6 +296,37 @@ class DashboardAccess(ConfigQuestion):
hosts=answer)) 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): class RabbitMq(Question):
"""Wait for Rabbit to start, then setup permissions.""" """Wait for Rabbit to start, then setup permissions."""
@ -362,7 +401,7 @@ class DatabaseSetup(Question):
if call('openstack', 'user', 'show', 'admin'): if call('openstack', 'user', 'show', 'admin'):
return 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', check('snap-openstack', 'launch', 'keystone-manage', 'bootstrap',
'--bootstrap-password', _env['keystone_password'], '--bootstrap-password', _env['keystone_password'],
@ -401,7 +440,7 @@ class DatabaseSetup(Question):
log.info('Creating service project ...') log.info('Creating service project ...')
if not call('openstack', 'project', 'show', 'service'): if not call('openstack', 'project', 'show', 'service'):
check('openstack', 'project', 'create', '--domain', check('openstack', 'project', 'create', '--domain',
'default', '--description', 'Service Project', 'default', '--description', '"Service Project"',
'service') 'service')
log.info('Keystone configured!') log.info('Keystone configured!')
@ -536,7 +575,7 @@ class PlacementSetup(Question):
for endpoint in ['public', 'internal', 'admin']: for endpoint in ['public', 'internal', 'admin']:
call('openstack', 'endpoint', 'create', '--region', call('openstack', 'endpoint', 'create', '--region',
'microstack', 'placement', endpoint, 'microstack', 'placement', endpoint,
'http://{control_ip}:8778'.format(**_env)) 'https://{control_ip}:8778'.format(**_env))
log.info('Running Placement DB migrations...') log.info('Running Placement DB migrations...')
check('snap-openstack', 'launch', 'placement-manage', 'db', 'sync') check('snap-openstack', 'launch', 'placement-manage', 'db', 'sync')
@ -612,6 +651,7 @@ class NovaControlPlane(Question):
enable('nova-api') enable('nova-api')
restart('nova-compute') restart('nova-compute')
restart('nginx')
for service in [ for service in [
'nova-api-metadata', 'nova-api-metadata',
@ -630,7 +670,7 @@ class NovaControlPlane(Question):
for endpoint in ['public', 'internal', 'admin']: for endpoint in ['public', 'internal', 'admin']:
call('openstack', 'endpoint', 'create', '--region', call('openstack', 'endpoint', 'create', '--region',
'microstack', 'compute', endpoint, 'microstack', 'compute', endpoint,
'http://{control_ip}:8774/v2.1'.format(**_env)) 'https://{control_ip}:8774/v2.1'.format(**_env))
log.info('Creating default flavors...') log.info('Creating default flavors...')
@ -682,7 +722,7 @@ class CinderSetup(Question):
check( check(
'openstack', 'endpoint', 'create', '--region', 'openstack', 'endpoint', 'create', '--region',
'microstack', f'volume{api_version}', endpoint, 'microstack', f'volume{api_version}', endpoint,
f'http://{control_ip}:8776/{api_version}/' f'https://{control_ip}:8776/{api_version}/'
'$(project_id)s' '$(project_id)s'
) )
log.info('Running Cinder DB migrations...') log.info('Running Cinder DB migrations...')
@ -754,12 +794,13 @@ class NeutronControlPlane(Question):
for endpoint in ['public', 'internal', 'admin']: for endpoint in ['public', 'internal', 'admin']:
call('openstack', 'endpoint', 'create', '--region', call('openstack', 'endpoint', 'create', '--region',
'microstack', 'network', endpoint, 'microstack', 'network', endpoint,
'http://{control_ip}:9696'.format(**_env)) 'https://{control_ip}:9696'.format(**_env))
check('snap-openstack', 'launch', 'neutron-db-manage', 'upgrade', check('snap-openstack', 'launch', 'neutron-db-manage', 'upgrade',
'head') 'head')
enable('neutron-api') enable('neutron-api')
enable('neutron-ovn-metadata-agent') enable('neutron-ovn-metadata-agent')
restart('nginx')
nc_wait(_env['control_ip'], '9696') nc_wait(_env['control_ip'], '9696')
@ -864,10 +905,11 @@ class GlanceSetup(Question):
for endpoint in ['internal', 'admin', 'public']: for endpoint in ['internal', 'admin', 'public']:
check('openstack', 'endpoint', 'create', '--region', check('openstack', 'endpoint', 'create', '--region',
'microstack', 'image', endpoint, 'microstack', 'image', endpoint,
'http://{compute_ip}:9292'.format(**_env)) 'https://{compute_ip}:9292'.format(**_env))
check('snap-openstack', 'launch', 'glance-manage', 'db_sync') check('snap-openstack', 'launch', 'glance-manage', 'db_sync')
enable('glance-api') enable('glance-api')
restart('nginx')
nc_wait(_env['compute_ip'], '9292') nc_wait(_env['compute_ip'], '9292')

View File

@ -1,9 +1,12 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from pathlib import Path
from init.shell import check
from datetime import datetime from datetime import datetime
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
import ipaddress
import socket
from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.backends import default_backend from cryptography.hazmat.backends import default_backend
@ -15,7 +18,8 @@ from cryptography.x509.oid import NameOID
from init import shell 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. """Generate a self-signed certificate with associated keys.
The certificate will have a fake CNAME and subjAltName since The certificate will have a fake CNAME and subjAltName since
@ -28,26 +32,29 @@ def generate_selfsigned():
via a secure channel. via a secure channel.
https://owasp.org/www-community/controls/Certificate_and_Public_Key_Pinning 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 # Do not generate a new certificate and key if there is already an existing
# pair. TODO: improve this check and allow renewal. # pair. TODO: improve this check and allow renewal.
if cert_path.exists() and key_path.exists(): if cert_path.exists() and key_path.exists():
return return
dummy_cn = 'microstack.run'
key = rsa.generate_private_key( key = rsa.generate_private_key(
public_exponent=65537, public_exponent=65537,
key_size=2048, key_size=2048,
backend=default_backend(), backend=default_backend(),
) )
cn = socket.gethostname()
common_name = x509.Name([ 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) basic_contraints = x509.BasicConstraints(ca=True, path_length=0)
now = datetime.utcnow() now = datetime.utcnow()
cert = ( cert = (
x509.CertificateBuilder() x509.CertificateBuilder()
@ -63,7 +70,8 @@ def generate_selfsigned():
) )
cert_fprint = cert.fingerprint(hashes.SHA256()).hex() 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_cert = cert.public_bytes(encoding=serialization.Encoding.PEM)
serialized_key = key.private_bytes( serialized_key = key.private_bytes(
@ -73,3 +81,5 @@ def generate_selfsigned():
) )
cert_path.write_bytes(serialized_cert) cert_path.write_bytes(serialized_cert)
key_path.write_bytes(serialized_key) key_path.write_bytes(serialized_key)
check('chmod', '644', str(cert_path))
check('chmod', '600', str(key_path))

View File

@ -207,7 +207,7 @@ Access it with `ssh -i {key_path} {username}@{ip}`\
gate = check_output('snapctl', 'get', 'config.network.ext-gateway') gate = check_output('snapctl', 'get', 'config.network.ext-gateway')
port = check_output('snapctl', 'get', 'config.network.ports.dashboard') port = check_output('snapctl', 'get', 'config.network.ports.dashboard')
print('You can also visit the OpenStack dashboard at http://{}:{}'.format( print('You can also visit the OpenStack dashboard at https://{}:{}'.format(
gate, port)) gate, port))