TLS support for API services

Change-Id: I084da313eda17435c095ade7cb1b92981f5341dc
This commit is contained in:
Dmitry Tantsur 2020-08-25 14:51:44 +02:00
parent e4d0325b2f
commit ddafc94e30
37 changed files with 549 additions and 39 deletions

View File

@ -154,6 +154,8 @@ def cmd_install(args):
install_dib='true',
network_interface=args.network_interface,
enable_keystone=args.enable_keystone,
enable_tls=args.enable_tls,
generate_tls=args.enable_tls,
noauth_mode='false',
enabled_hardware_types=args.hardware_types,
cleaning_disk_erase=args.cleaning_disk_erase,
@ -220,6 +222,8 @@ def parse_args():
help='the network interface to use')
install.add_argument('--enable-keystone', action='store_true',
help='enable keystone and use authentication')
install.add_argument('--enable-tls', action='store_true',
help='enable self-signed TLS on API endpoints')
install.add_argument('--hardware-types',
# only generic types are enabled in the simple CI
default='ipmi,redfish,manual-management',

View File

@ -215,6 +215,11 @@ Additionally, the following parameters can be useful:
A comma separated list of hardware types to enable.
``--enable-keystone``
Whether to enable authentication with Keystone_.
``--enable-tls``
Enable self-signed TLS on API endpoints.
.. warning::
If using Keystone_, see :ref:`keystone-tls` for important notes.
See the built-in documentation for more details:
@ -307,6 +312,30 @@ If you are running the installation behind a proxy, export the
environment variables ``http_proxy``, ``https_proxy`` and ``no_proxy``
so that ansible will use these proxy settings.
TLS support
-----------
Bifrost supports TLS for API services with two options:
* A self-signed certificate can be generated automatically. Set
``enable_tls=true`` and ``generate_tls=true``.
.. note:: This is equivalent to the ``--enable-tls`` flag of ``bifrost-cli``.
* Certificate paths can be provided via:
``tls_certificate_path``
Path to the TLS certificate (must be world-readable).
``tls_private_key_path``
Path to the private key (must not be password protected).
``tls_csr_path``
Path to the certificate signing request file.
Set ``enable_tls=true`` and do not set ``generate_tls`` to use this option.
.. warning::
If using Keystone, see :ref:`keystone-tls` for important notes.
Dependencies
============

View File

@ -30,6 +30,25 @@ See the following files for more settings that can be overridden:
* ``playbooks/roles/bifrost-ironic-install/defaults/main.yml``
* ``playbooks/roles/bifrost-keystone-install/defaults/main.yml``
.. _keystone-tls:
TLS notes
---------
There are two important limitations to keep in mind when using Keystone with
TLS:
* It's not possible to enable TLS on upgrade from Bifrost < 9.0 (Ussuri
and early Victoria). First do an upgrade to Bifrost >= 9.0, then enable TLS
in a separate step.
* Automatically updating from a TLS environment to a non-TLS one may not be
possible if using custom TLS certificates in a non-standard location
(``/etc/bifrost/bifrost.crt``). You need to manually change identity
endpoints in the catalog from ``https`` to ``http`` directly before
an update. The ``public`` endpoint **must** be updated **last** or you may
lock yourself out of keystone.
Using an existing Keystone
--------------------------

View File

@ -15,3 +15,4 @@
BOOT_MODE: "{{ boot_mode | default('') }}"
TEST_VM_NODE_DRIVER: "{{ test_driver | default('ipmi') }}"
NOAUTH_MODE: "{{ noauth_mode | default(false) | bool | lower }}"
ENABLE_TLS: "{{ enable_tls | default(false) | bool | lower }}"

View File

@ -45,6 +45,10 @@ overridden in no-auth mode.
Ironic endpoint to use. If the fact is already defined, it is not overridden.
`tls_certificate_path`
Path to the TLS certificate. Only set if TLS is used.
Notes
-----

View File

@ -5,5 +5,6 @@ network_interface: "virbr0"
ans_network_interface: "{{ network_interface | replace('-', '_') }}"
internal_ip: "{{ hostvars[inventory_hostname]['ansible_' + ans_network_interface]['ipv4']['address'] }}"
api_protocol: http
enable_tls: false
api_protocol: "{{ 'https' if enable_tls | bool else 'http' }}"
ironic_api_url: "{{ api_protocol }}://{{ internal_ip }}:6385"

View File

@ -38,6 +38,14 @@
- openstack_cloud is defined
no_log: yes
- name: "Set the TLS certificate if present"
set_fact:
tls_certificate_path: "{{ openstack_cloud.cacert }}"
when:
- tls_certificate_path is undefined
- openstack_cloud is defined
- openstack_cloud.cacert is defined
- name: "If in noauth mode and no clouds.yaml, unset authentication parameters."
set_fact:
auth_type: None

View File

@ -23,6 +23,7 @@
cloud: "{{ cloud_name | default(omit) }}"
auth_type: "{{ auth_type | default(omit) }}"
auth: "{{ auth | default(omit) }}"
ca_cert: "{{ tls_certificate_path | default(omit) }}"
ironic_url: "{{ ironic_url | default(omit) }}"
uuid: "{{ uuid | default() }}"
name: "{{ name | default() }}"

View File

@ -56,6 +56,7 @@
cloud: "{{ cloud_name | default(omit) }}"
auth_type: "{{ auth_type | default(omit) }}"
auth: "{{ auth | default(omit) }}"
ca_cert: "{{ tls_certificate_path | default(omit) }}"
ironic_url: "{{ ironic_url | default(omit) }}"
uuid: "{{ uuid }}"
state: present
@ -85,6 +86,7 @@
cloud: "{{ cloud_name | default(omit) }}"
auth_type: "{{ auth_type | default(omit) }}"
auth: "{{ auth | default(omit) }}"
ca_cert: "{{ tls_certificate_path | default(omit) }}"
ironic_url: "{{ ironic_url | default(omit) }}"
uuid: "{{ uuid }}"
state: present

View File

@ -260,7 +260,7 @@ noauth_mode: true
enable_keystone: false
# Service URLs used for communication with them.
api_protocol: http
api_protocol: "{{ 'https' if enable_tls | bool else 'http' }}"
ironic_api_url: "{{ api_protocol }}://{{ internal_ip }}:6385"
ironic_inspector_api_url: "{{ api_protocol }}://{{ internal_ip }}:5050"
keystone_api_url: "{{ api_protocol }}://{{ internal_ip }}:5000/v3"
@ -339,3 +339,10 @@ pip_opts: "{{ lookup('env', 'PIP_OPTS') | default('') }}"
# Timeout for gathering facts.
fact_gather_timeout: "{{ lookup('config', 'DEFAULT_GATHER_TIMEOUT', on_missing='skip') | default(omit, true) }}"
# Enable TLS support.
enable_tls: false
tls_root: /etc/bifrost
tls_certificate_path: "{{ tls_root }}/bifrost.crt"
ironic_private_key_path: /etc/ironic/ironic.pem
ironic_inspector_private_key_path: /etc/ironic-inspector/inspector.pem

View File

@ -159,6 +159,15 @@
- not noauth_mode | bool
- not enable_keystone | bool
- name: "Generate TLS parameters"
include_role:
name: bifrost-tls
vars:
dest_private_key_path: "{{ ironic_private_key_path }}"
dest_private_key_owner: ironic
dest_private_key_group: ironic
when: enable_tls | bool
- name: "Populate keystone for Bifrost"
include: keystone_setup.yml
when:

View File

@ -92,6 +92,15 @@
- not noauth_mode | bool
- not enable_keystone | bool
- name: "Generate TLS parameters"
include_role:
name: bifrost-tls
vars:
dest_private_key_path: "{{ ironic_inspector_private_key_path }}"
dest_private_key_owner: ironic
dest_private_key_group: ironic
when: enable_tls | bool
- name: "Populate keystone for ironic-inspector "
include: keystone_setup_inspector.yml
when: enable_keystone | bool

View File

@ -53,6 +53,7 @@
domain_id: "default"
enabled: yes
auth: "{{ keystone_auth }}"
ca_cert: "{{ tls_certificate_path | default(omit) }}"
environment: "{{ bifrost_venv_env }}"
no_log: true
@ -66,6 +67,7 @@
auth: "{{ keystone_auth }}"
update_password: always
wait: yes
ca_cert: "{{ tls_certificate_path | default(omit) }}"
environment: "{{ bifrost_venv_env }}"
no_log: true
@ -76,6 +78,7 @@
project: "{{ ironic.service_catalog.project_name }}"
auth: "{{ keystone_auth }}"
wait: yes
ca_cert: "{{ tls_certificate_path | default(omit) }}"
environment: "{{ bifrost_venv_env }}"
no_log: true
@ -87,6 +90,7 @@
description: OpenStack Baremetal Service
auth: "{{ keystone_auth }}"
wait: yes
ca_cert: "{{ tls_certificate_path | default(omit) }}"
environment: "{{ bifrost_venv_env }}"
register: baremetal_catalog_service
no_log: true
@ -99,6 +103,7 @@
url: "{{ ironic.keystone.admin_url | default(ironic_api_url) }}"
region: "{{ keystone.bootstrap.region_name | default('RegionOne') }}"
auth: "{{ keystone_auth }}"
ca_cert: "{{ tls_certificate_path | default(omit) }}"
no_log: true
environment: "{{ bifrost_venv_env }}"
@ -115,6 +120,7 @@
url: "{{ ironic.keystone.public_url | default(ironic_public_url) | default(ironic_api_url) }}"
region: "{{ keystone.bootstrap.region_name | default('RegionOne') }}"
auth: "{{ keystone_auth }}"
ca_cert: "{{ tls_certificate_path | default(omit) }}"
no_log: true
environment: "{{ bifrost_venv_env }}"
@ -131,6 +137,7 @@
url: "{{ ironic.keystone.internal_url | default(ironic_private_url) | default(ironic_api_url) }}"
region: "{{ keystone.bootstrap.region_name | default('RegionOne') }}"
auth: "{{ keystone_auth }}"
ca_cert: "{{ tls_certificate_path | default(omit) }}"
no_log: true
environment: "{{ bifrost_venv_env }}"
@ -139,6 +146,7 @@
name: "baremetal_admin"
state: present
auth: "{{ keystone_auth }}"
ca_cert: "{{ tls_certificate_path | default(omit) }}"
environment: "{{ bifrost_venv_env }}"
no_log: true
@ -147,6 +155,7 @@
name: "baremetal_observer"
state: present
auth: "{{ keystone_auth }}"
ca_cert: "{{ tls_certificate_path | default(omit) }}"
environment: "{{ bifrost_venv_env }}"
no_log: true
@ -158,6 +167,7 @@
domain_id: "default"
enabled: yes
auth: "{{ keystone_auth }}"
ca_cert: "{{ tls_certificate_path | default(omit) }}"
environment: "{{ bifrost_venv_env }}"
no_log: true
@ -169,6 +179,7 @@
domain: "default"
auth: "{{ keystone_auth }}"
wait: yes
ca_cert: "{{ tls_certificate_path | default(omit) }}"
environment: "{{ bifrost_venv_env }}"
no_log: true
@ -179,5 +190,6 @@
project: "baremetal"
auth: "{{ keystone_auth }}"
wait: yes
ca_cert: "{{ tls_certificate_path | default(omit) }}"
environment: "{{ bifrost_venv_env }}"
no_log: true

View File

@ -53,6 +53,7 @@
default_project: "{{ ironic_inspector.service_catalog.project_name | default('service') }}"
auth: "{{ keystone_auth }}"
wait: yes
ca_cert: "{{ tls_certificate_path | default(omit) }}"
environment: "{{ bifrost_venv_env }}"
no_log: true
@ -63,6 +64,7 @@
project: "{{ ironic_inspector.service_catalog.project_name | default('service') }}"
auth: "{{ keystone_auth }}"
wait: yes
ca_cert: "{{ tls_certificate_path | default(omit) }}"
environment: "{{ bifrost_venv_env }}"
no_log: true
@ -74,6 +76,7 @@
description: OpenStack Baremetal Introspection Service
auth: "{{ keystone_auth }}"
wait: yes
ca_cert: "{{ tls_certificate_path | default(omit) }}"
environment: "{{ bifrost_venv_env }}"
register: introspection_catalog_service
no_log: true
@ -86,6 +89,7 @@
url: "{{ ironic_inspector.keystone.admin_url | default(ironic_inspector_api_url) }}"
region: "{{ keystone.bootstrap.region_name | default('RegionOne') }}"
auth: "{{ keystone_auth }}"
ca_cert: "{{ tls_certificate_path | default(omit) }}"
no_log: true
environment: "{{ bifrost_venv_env }}"
@ -102,6 +106,7 @@
url: "{{ ironic_inspector.keystone.public_url | default(ironic_inspector_public_url) | default(ironic_inspector_api_url) }}"
region: "{{ keystone.bootstrap.region_name | default('RegionOne') }}"
auth: "{{ keystone_auth }}"
ca_cert: "{{ tls_certificate_path | default(omit) }}"
no_log: true
environment: "{{ bifrost_venv_env }}"
@ -118,6 +123,7 @@
url: "{{ ironic_inspector.keystone.internal_url | default(ironic_inspector_private_url) | default(ironic_inspector_api_url) }}"
region: "{{ keystone.bootstrap.region_name | default('RegionOne') }}"
auth: "{{ keystone_auth }}"
ca_cert: "{{ tls_certificate_path | default(omit) }}"
no_log: true
environment: "{{ bifrost_venv_env }}"
@ -130,6 +136,7 @@
auth: "{{ keystone_auth }}"
update_password: always
wait: yes
ca_cert: "{{ tls_certificate_path | default(omit) }}"
environment: "{{ bifrost_venv_env }}"
no_log: true
@ -140,5 +147,6 @@
project: baremetal
auth: "{{ keystone_auth }}"
wait: yes
ca_cert: "{{ tls_certificate_path | default(omit) }}"
environment: "{{ bifrost_venv_env }}"
no_log: true

View File

@ -44,6 +44,11 @@
- not enable_keystone | bool
no_log: yes
- name: "Set OS_CACERT if required"
set_fact:
testing_env: "{{ testing_env | combine({'OS_CACERT': tls_certificate_path}) }}"
when: enable_tls | bool
- name: "Validate API access and at least one conductor"
command: baremetal conductor list -f value -c Hostname
environment: "{{ testing_env | combine(bifrost_venv_env) }}"
@ -77,6 +82,11 @@
- enable_inspector | bool
no_log: yes
- name: "Set OS_CACERT if required"
set_fact:
testing_env: "{{ testing_env | combine({'OS_CACERT': tls_certificate_path}) }}"
when: enable_tls | bool
- name: "Validate introspection API access"
command: baremetal introspection list
environment: "{{ testing_env | combine(bifrost_venv_env) }}"

View File

@ -5,6 +5,6 @@ dhcp || reboot
goto introspect
:introspect
kernel {{ ipa_kernel_url }} ipa-inspection-callback-url=http://{{ internal_ip }}:5050/v1/continue {% if fast_track | bool %}ipa-api-url=http://{{ internal_ip }}:6385{% endif %} systemd.journald.forward_to_console=yes BOOTIF=${mac} nofb nomodeset vga=normal console=ttyS0 {{ inspector_extra_kernel_options | default('') }} initrd={{ ipa_ramdisk_url | basename }}
kernel {{ ipa_kernel_url }} ipa-inspection-callback-url={{ api_protocol }}://{{ internal_ip }}:5050/v1/continue {% if fast_track | bool %}ipa-api-url={{ api_protocol }}://{{ internal_ip }}:6385{% endif %} systemd.journald.forward_to_console=yes BOOTIF=${mac} nofb nomodeset vga=normal console=ttyS0 {{ inspector_extra_kernel_options | default('') }} ipa-insecure=1 initrd={{ ipa_ramdisk_url | basename }}
initrd {{ ipa_ramdisk_url }}
boot

View File

@ -21,6 +21,14 @@ transport_url = rabbit://ironic:{{ironic_db_password }}@{{ message_queue_host |
transport_url = fake://
{% endif %}
{% if enable_tls | bool %}
use_ssl = True
[ssl]
cert_file = {{ tls_certificate_path }}
key_file = {{ ironic_inspector_private_key_path }}
{% endif %}
[database]
connection=mysql+pymysql://{{ ironic_inspector.database.username }}:{{ ironic_inspector.database.password }}@{{ ironic_inspector.database.host }}/{{ ironic_inspector.database.name }}?charset=utf8
@ -50,6 +58,9 @@ endpoint_override = {{ ironic_api_url }}
username = {{ admin_username }}
password = {{ admin_password }}
{% endif %}
{% if enable_tls | bool %}
cafile = {{ tls_certificate_path }}
{% endif %}
{% if enable_keystone is defined and enable_keystone | bool == true %}
[keystone_authtoken]
@ -60,7 +71,9 @@ password = {{ ironic_inspector.service_catalog.password }}
user_domain_id = default
project_name = service
project_domain_id = default
{% if enable_tls | bool %}
cafile = {{ tls_certificate_path }}
{% endif %}
{% endif %}
[processing]

View File

@ -40,6 +40,15 @@ http_basic_auth_user_file = /etc/ironic/htpasswd
log_dir = {{ ironic_log_dir }}
{% endif %}
{% if enable_tls | bool %}
[api]
enable_ssl_api = True
[ssl]
cert_file = {{ tls_certificate_path }}
key_file = {{ ironic_private_key_path }}
{% endif %}
[agent]
{% if ironic_store_ramdisk_logs | bool %}
deploy_logs_collect = always
@ -50,9 +59,9 @@ deploy_logs_local_path = {{ ironic_agent_deploy_logs_local_path }}
[pxe]
{% if testing | bool %}
pxe_append_params = console=ttyS0
pxe_append_params = console=ttyS0 ipa-insecure=1
{% else %}
pxe_append_params = systemd.journald.forward_to_console=yes {{ extra_kernel_options | default('') }}
pxe_append_params = systemd.journald.forward_to_console=yes ipa-insecure=1 {{ extra_kernel_options | default('') }}
{% endif %}
pxe_config_template = $pybasedir/drivers/modules/ipxe_config.template
tftp_server = {{ internal_ip }}
@ -108,7 +117,7 @@ use_web_server_for_images = true
{% if enable_inspector | bool == true %}
[inspector]
power_off = {{ power_off_after_inspection }}
extra_kernel_params = {{ inspector_extra_kernel_options | default('') }}
extra_kernel_params = ipa-insecure=1 {{ inspector_extra_kernel_options | default('') }}
{% if enable_keystone | bool %}
auth_type = password
auth_url = {{ ironic.service_catalog.auth_url }}
@ -118,16 +127,20 @@ user_domain_id = default
project_name = {{ ironic.service_catalog.project_name }}
project_domain_id = default
region_name = {{ keystone.bootstrap.region_name | default('RegionOne')}}
callback_endpoint_override = http://{{ internal_ip }}:5050
# NOTE(dtantsur): this has to be on internal IP even if public IPs are used
callback_endpoint_override = {{ api_protocol }}://{{ internal_ip }}:5050
{% elif noauth_mode | bool %}
auth_type=none
endpoint_override = http://{{ internal_ip }}:5050
endpoint_override = {{ ironic_inspector_api_url }}
{% else %}
auth_type = http_basic
endpoint_override = http://{{ internal_ip }}:5050
endpoint_override = {{ ironic_inspector_api_url }}
username = {{ admin_username }}
password = {{ admin_password }}
{% endif %}
{% if enable_tls | bool %}
cafile = {{ tls_certificate_path }}
{% endif %}
{% endif %}
{% if enable_keystone is defined and enable_keystone | bool == true %}
@ -139,6 +152,9 @@ password = {{ ironic.service_catalog.password }}
user_domain_id = default
project_name = {{ ironic.service_catalog.project_name }}
project_domain_id = default
{% if enable_tls | bool %}
cafile = {{ tls_certificate_path }}
{% endif %}
{% endif %}
[service_catalog]
@ -158,9 +174,14 @@ auth_type = http_basic
username = {{ admin_username }}
password = {{ admin_password }}
{% endif %}
endpoint_override = http://{{ internal_ip }}:6385
# NOTE(dtantsur): this has to be on internal IP even if public IPs are used
endpoint_override = {{ api_protocol }}://{{ internal_ip }}:6385
[json_rpc]
{% if enable_tls | bool %}
use_ssl = True
cafile = {{ tls_certificate_path }}
{% endif %}
{% if enable_keystone | bool %}
auth_strategy = keystone
auth_url = {{ ironic.service_catalog.auth_url }}

View File

@ -4,8 +4,10 @@ network_interface: "virbr0"
ans_network_interface: "{{ network_interface | replace('-', '_') }}"
internal_ip: "{{ hostvars[inventory_hostname]['ansible_' + ans_network_interface]['ipv4']['address'] }}"
enable_tls: false
# Service URLs used for communication with them.
api_protocol: http
api_protocol: "{{ 'https' if enable_tls | bool else 'http' }}"
ironic_api_url: "{{ api_protocol }}://{{ internal_ip }}:6385"
ironic_inspector_api_url: "{{ api_protocol }}://{{ internal_ip }}:5050"

View File

@ -31,6 +31,11 @@
- "{{ config_region_name is defined }}"
- "{{ config_auth_url is defined }}"
- name: "Generate TLS parameters"
include_role:
name: bifrost-tls
when: enable_tls | bool
- name: "Ensure the ~/.config exists"
file:
name: "~{{ user | default('root') }}/.config"

View File

@ -13,16 +13,25 @@ clouds:
project_domain_id: "{{ cloud.1.config_project_domain_id | default('default') }}"
user_domain_id: "{{ cloud.1.config_user_domain_id | default('default') }}"
identity_api_version: "3"
{% if enable_tls | bool %}
cacert: "{{ tls_certificate_path }}"
{% endif %}
{% endfor %}
{% elif noauth_mode | default(true) | bool %}
bifrost:
auth_type: "none"
baremetal_endpoint_override: {{ ironic_api_url }}
baremetal_introspection_endpoint_override: {{ ironic_inspector_api_url }}
{% if enable_tls | bool %}
cacert: "{{ tls_certificate_path }}"
{% endif %}
# Deprecated
bifrost-inspector:
auth_type: "none"
endpoint: {{ ironic_inspector_api_url }}
{% if enable_tls | bool %}
cacert: "{{ tls_certificate_path }}"
{% endif %}
{% else %}
bifrost:
auth_type: "http_basic"
@ -31,6 +40,9 @@ clouds:
password: "{{ default_password }}"
endpoint: {{ ironic_api_url }}
baremetal_introspection_endpoint_override: {{ ironic_inspector_api_url }}
{% if enable_tls | bool %}
cacert: "{{ tls_certificate_path }}"
{% endif %}
bifrost-admin:
auth_type: "http_basic"
auth:
@ -38,4 +50,7 @@ clouds:
password: "{{ admin_password }}"
endpoint: {{ ironic_api_url }}
baremetal_introspection_endpoint_override: {{ ironic_inspector_api_url }}
{% if enable_tls | bool %}
cacert: "{{ tls_certificate_path }}"
{% endif %}
{% endif %}

View File

@ -36,3 +36,7 @@ case "${1:-bifrost}" in
*) echo -e "\nERROR unsupported or unspecified profile: $1\nMust be one of bifrost, bifrost-admin";;
esac
{% endif %}
{% if enable_tls | bool %}
export OS_CACERT="{{ tls_certificate_path }}"
{% endif %}

View File

@ -35,7 +35,7 @@ network_interface: "virbr0"
ans_network_interface: "{{ network_interface | replace('-', '_') }}"
internal_ip: "{{ hostvars[inventory_hostname]['ansible_' + ans_network_interface]['ipv4']['address'] }}"
api_protocol: http
api_protocol: "{{ 'https' if enable_tls | bool else 'http' }}"
keystone_api_url: "{{ api_protocol }}://{{ internal_ip }}:5000/v3"
# Defaults required by this role that are normally inherited via
@ -85,3 +85,9 @@ keystone:
host: localhost
pip_opts: "{{ lookup('env', 'PIP_OPTS') | default('') }}"
# Enable TLS support.
enable_tls: false
tls_root: /etc/bifrost
tls_certificate_path: "{{ tls_root }}/bifrost.crt"
nginx_private_key_path: /etc/nginx/keystone.pem

View File

@ -77,6 +77,15 @@
login_password: "{{ mysql_password | default(None) }}"
when: keystone.database.host == 'localhost'
- name: "Generate TLS parameters"
include_role:
name: bifrost-tls
vars:
dest_private_key_path: "{{ nginx_private_key_path }}"
dest_private_key_owner: "{{ nginx_user }}"
dest_private_key_group: "{{ nginx_user }}"
when: enable_tls | bool
- name: "Create an keystone service group"
group:
name: "keystone"

View File

@ -45,34 +45,15 @@
- enable_keystone | bool
- not skip_bootstrap | bool
- name: "Upgrade existing installation"
include: upgrade.yml
when:
- enable_keystone | bool
- not skip_bootstrap | bool
- not skip_start | bool
- name: "Start Keystone services"
include: start.yml
when:
- enable_keystone | bool
- not skip_start | bool
- name: "Change the bootstrap password from the static value on upgrade"
os_user:
name: "{{ keystone.bootstrap.username }}"
password: "{{ keystone.bootstrap.password }}"
update_password: always
state: present
domain: "default"
default_project: "{{ keystone.bootstrap.project_name }}"
auth:
auth_url: "{{ ironic.service_catalog.auth_url | default('http://127.0.0.1:5000/') }}"
username: "{{ keystone.bootstrap.username }}"
password: "ChangeThisPa55w0rd"
project_name: "{{ keystone.bootstrap.project_name | default('admin') }}"
project_domain_id: "default"
user_domain_id: "default"
wait: yes
environment: "{{ bifrost_venv_env }}"
no_log: true
ignore_errors: true
when:
- enable_keystone | bool
- not skip_bootstrap | bool
- test_created_keystone_db is undefined or not test_created_keystone_db.changed | bool
- keystone.bootstrap.enabled | bool
- keystone.database.host == 'localhost'

View File

@ -0,0 +1,121 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
---
# TODO(dtantsur): can be removed in W
- name: "Change the bootstrap password from the static value on upgrade"
openstack.cloud.identity_user:
name: "{{ keystone.bootstrap.username }}"
password: "{{ keystone.bootstrap.password }}"
update_password: always
state: present
domain: "default"
default_project: "{{ keystone.bootstrap.project_name }}"
auth:
auth_url: "{{ ironic.service_catalog.auth_url | default('http://127.0.0.1:5000/') }}"
username: "{{ keystone.bootstrap.username }}"
password: "ChangeThisPa55w0rd"
project_name: "{{ keystone.bootstrap.project_name | default('admin') }}"
project_domain_id: "default"
user_domain_id: "default"
wait: yes
ca_cert: "{{ tls_certificate_path | default(omit) }}"
environment: "{{ bifrost_venv_env }}"
no_log: true
ignore_errors: true
when:
- test_created_keystone_db is undefined or not test_created_keystone_db.changed | bool
- keystone.bootstrap.enabled | bool
- keystone.database.host == 'localhost'
# NOTE(dtantsur): these tasks are required for update from HTTP to HTTPS
- name: "Configure keystone auth with http"
set_fact:
keystone_auth:
auth_url: "{{ ironic.service_catalog.auth_url | default(keystone_api_url) | replace('https:', 'http:') }}"
username: "{{ keystone.bootstrap.username }}"
password: "{{ keystone.bootstrap.password }}"
project_name: "{{ keystone.bootstrap.project_name | default('admin') }}"
project_domain_id: "default"
user_domain_id: "default"
no_log: true
when: api_protocol == 'https'
- name: "Configure keystone auth with https"
set_fact:
keystone_auth:
auth_url: "{{ ironic.service_catalog.auth_url | default(keystone_api_url) | replace('http:', 'https:') }}"
username: "{{ keystone.bootstrap.username }}"
password: "{{ keystone.bootstrap.password }}"
project_name: "{{ keystone.bootstrap.project_name | default('admin') }}"
project_domain_id: "default"
user_domain_id: "default"
# NOTE(dtantsur): we cannot use tls_certificate_path as it won't be present
# on an upgrade to non-TLS.
keystone_ca_cert: /etc/bifrost/bifrost.crt
no_log: true
when: api_protocol == 'http'
- name: "Ensure keystone service record for keystone"
openstack.cloud.catalog_service:
state: present
name: "keystone"
service_type: "identity"
auth: "{{ keystone_auth }}"
wait: yes
ca_cert: "{{ keystone_ca_cert | default(omit) }}"
environment: "{{ bifrost_venv_env }}"
register: identity_catalog_service
ignore_errors: true
no_log: true
- name: "Update identity internal endpoint"
openstack.cloud.endpoint:
state: present
service: "{{ identity_catalog_service.id }}"
endpoint_interface: internal
url: "{{ keystone.bootstrap.internal_url | default(keystone_private_url) | default(keystone_api_url) }}"
region: "{{ keystone.bootstrap.region_name | default('RegionOne') }}"
auth: "{{ keystone_auth }}"
ca_cert: "{{ keystone_ca_cert | default(omit) }}"
ignore_errors: true
no_log: true
when: identity_catalog_service.id is defined
- name: "Update identity admin endpoint"
openstack.cloud.endpoint:
state: present
service: "{{ identity_catalog_service.id }}"
endpoint_interface: admin
url: "{{ keystone.bootstrap.admin_url | default(keystone_api_url) }}"
region: "{{ keystone.bootstrap.region_name | default('RegionOne') }}"
auth: "{{ keystone_auth }}"
ca_cert: "{{ keystone_ca_cert | default(omit) }}"
ignore_errors: true
no_log: true
when: identity_catalog_service.id is defined
# NOTE(dtantsur): the public endpoint MUST go last, otherwise the other
# endpoints will fail to update.
- name: "Update identity public endpoint"
openstack.cloud.endpoint:
state: present
service: "{{ identity_catalog_service.id }}"
endpoint_interface: public
url: "{{ keystone.bootstrap.public_url | default(keystone_public_url) | default(keystone_api_url) }}"
region: "{{ keystone.bootstrap.region_name | default('RegionOne') }}"
auth: "{{ keystone_auth }}"
ca_cert: "{{ keystone_ca_cert | default(omit) }}"
ignore_errors: true
no_log: true
when: identity_catalog_service.id is defined

View File

@ -1,6 +1,12 @@
# {{ ansible_managed }}
server {
{% if enable_tls | bool %}
listen 5000 ssl;
ssl_certificate {{ tls_certificate_path }};
ssl_certificate_key {{ nginx_private_key_path }};
{% else %}
listen 5000;
{% endif %}
access_log /var/log/nginx/keystone/access.log;
error_log /var/log/nginx/keystone/error.log;
location / {
@ -10,7 +16,13 @@ server {
}
}
server {
{% if enable_tls | bool %}
listen 35357 ssl;
ssl_certificate {{ tls_certificate_path }};
ssl_certificate_key {{ nginx_private_key_path }};
{% else %}
listen 35357;
{% endif %}
access_log /var/log/nginx/keystone/access.log;
error_log /var/log/nginx/keystone/error.log;
location / {

View File

@ -0,0 +1,86 @@
bifrost-tls
===========
This role generates TLS certificates for Bifrost and copies the private key to
a predefined location.
Requirements
------------
This role requires:
- Ansible 2.9
Role Variables
--------------
generate_tls: Whether the generate new certificates or use existing ones.
If the latter, this role only handles copying the private key,
all files have to exist. Defaults to `false` to avoid overwriting
operator's files.
network_interface: Network interface services are listening on.
tls_common_name: The common name of the certificate. Defaults to the host's
full domain name (FQDN).
tls_hosts: A list of valid IP addresses for the generated certificate. Defaults
to `public_ip` (if set), `private_ip` (if set), `internal_ip` and
127.0.0.1. The host `localhost` is always added.
tls_host_names: A list of valid host names for the generated certificate.
Defaults to the host's FQDN + `localhost`.
tls_certificate_path: Path to the TLS certificate. Can be generated.
tls_private_key_path: Path to the private key. Can be generated.
tls_csr_path: Path to the signing request. Can be generated.
tls_force_regenerate: Boolean, whether to regenerate existing certificates.
Defaults to `false`.
dest_private_key_path: Destination to copy the private key to. Defaults to
undefined (not copying).
dest_private_key_owner: Owner of the destination private key. Defaults to root.
dest_private_key_group: Group of the destination private key. Defaults to root.
Dependencies
------------
None at this time.
Example Playbook
----------------
- hosts: localhost
connection: local
name: "Generate TLS parameters"
become: yes
gather_facts: yes
roles:
- role: bifrost-tls
generate_tls: true
tls_common_name: example.com
License
-------
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Author Information
------------------
Ironic Developers

View File

@ -0,0 +1,34 @@
---
generate_tls: false
# NOTE(dtantsur): we don't want to make our generated certificates accepted
# system-wide, hence storing them here.
tls_root: /etc/bifrost
tls_certificate_path: "{{ tls_root }}/bifrost.crt"
tls_private_key_path: "{{ tls_root }}/bifrost.pem"
tls_csr_path: "{{ tls_root }}/bifrost.csr"
# Force re-generating of certificates.
tls_force_regenerate: false
# Copy the resulting key to:
#dest_private_key_path:
dest_private_key_owner: root
dest_private_key_group: root
# Don't change this unless you really know what you're doing.
dest_private_key_mode: 0600
network_interface: "virbr0"
ans_network_interface: "{{ network_interface | replace('-', '_') }}"
internal_interface: "{{ hostvars[inventory_hostname]['ansible_' + ans_network_interface]['ipv4'] }}"
internal_ip: "{{ internal_interface['address'] }}"
# Common name for the certificate.
tls_common_name: "{{ ansible_fqdn }}"
tls_hosts: >-
{{ [internal_ip, '127.0.0.1']
+ ([public_ip] if public_ip is defined else [])
+ ([private_ip] if private_ip is defined else []) }}
tls_host_names:
- localhost
- "{{ ansible_fqdn }}"

View File

@ -0,0 +1,54 @@
---
- name: "Ensure the certificate root directory"
file:
path: "{{ tls_root }}"
state: directory
owner: root
group: root
mode: 0755
when: generate_tls | bool
- name: "Generate private key"
openssl_privatekey:
path: "{{ tls_private_key_path }}"
force: "{{ tls_force_regenerate | bool }}"
owner: root
group: root
mode: 0600
when: generate_tls | bool
- name: "Generate certificate signing request"
openssl_csr:
path: "{{ tls_csr_path }}"
privatekey_path: "{{ tls_private_key_path }}"
force: "{{ tls_force_regenerate | bool }}"
owner: root
group: root
mode: 0600
common_name: "{{ tls_common_name }}"
subject_alt_name: >-
{{ (tls_hosts | map('regex_replace', '^', 'IP:') | list)
+ (tls_host_names | map('regex_replace', '^', 'DNS:') | list) }}
when: generate_tls | bool
- name: "Generate self-signed TLS certificates"
openssl_certificate:
provider: selfsigned
path: "{{ tls_certificate_path }}"
privatekey_path: "{{ tls_private_key_path }}"
csr_path: "{{ tls_csr_path }}"
force: "{{ tls_force_regenerate | bool }}"
owner: root
group: root
mode: 0644
when: generate_tls | bool
- name: "Copy the key to the destination"
copy:
src: "{{ tls_private_key_path }}"
dest: "{{ dest_private_key_path }}"
remote_src: yes
owner: "{{ dest_private_key_owner }}"
group: "{{ dest_private_key_group }}"
mode: "{{ dest_private_key_mode }}"
when: dest_private_key_path is defined

View File

@ -20,6 +20,7 @@
cloud: "{{ cloud_name | default(omit) }}"
auth_type: "{{ auth_type | default(omit) }}"
auth: "{{ auth | default(omit) }}"
ca_cert: "{{ tls_certificate_path | default(omit) }}"
ironic_url: "{{ ironic_url | default(omit) }}"
uuid: "{{ uuid | default() }}"
name: "{{ name | default() }}"

View File

@ -20,6 +20,7 @@
cloud: "{{ cloud_name | default(omit) }}"
auth_type: "{{ auth_type | default(omit) }}"
auth: "{{ auth | default(omit) }}"
ca_cert: "{{ tls_certificate_path | default(omit) }}"
ironic_url: "{{ ironic_url | default(omit) }}"
driver: ""
uuid: "{{ uuid | default() }}"

View File

@ -24,6 +24,7 @@
cloud: "{{ cloud_name | default(omit) }}"
auth_type: "{{ auth_type | default(omit) }}"
auth: "{{ auth | default(omit) }}"
ca_cert: "{{ tls_certificate_path | default(omit) }}"
ironic_url: "{{ ironic_url | default(omit) }}"
driver: "{{ driver }}"
uuid: "{{ uuid | default() }}"

View File

@ -49,6 +49,7 @@
cloud: "{{ cloud_name | default(omit) }}"
auth_type: "{{ auth_type | default(omit) }}"
auth: "{{ auth | default(omit) }}"
ca_cert: "{{ tls_certificate_path | default(omit) }}"
ironic_url: "{{ ironic_url | default(omit) }}"
uuid: "{{ uuid | default('') }}"
name: "{{ name | default('') }}"

View File

@ -0,0 +1,14 @@
---
features:
- |
Supports TLS configuration by setting ``enable_tls=true`` and, optionally,
``generate_tls=true``. The corresponding ``bifrost-cli`` argument is
``--enable-tls`` (auto-generated certificates only).
issues:
- |
When using Keystone for authentication, it may not be possible to disable
TLS after enabling it if the certificate is in a non-standard location.
- |
Due to upgrade limitations, it may not be possible to enable TLS on
upgrading from a previous version. Do an upgrade first, then enable TLS
in a separate installation step.

View File

@ -13,6 +13,7 @@ ENABLE_KEYSTONE="${ENABLE_KEYSTONE:-false}"
ZUUL_BRANCH=${ZUUL_BRANCH:-}
CLI_TEST=${CLI_TEST:-false}
BOOT_MODE=${BOOT_MODE:-}
ENABLE_TLS=${ENABLE_TLS:-false}
# Set defaults for ansible command-line options to drive the different
# tests.
@ -178,6 +179,8 @@ ${ANSIBLE} -vvvv \
-e enable_keystone=${ENABLE_KEYSTONE} \
-e wait_for_node_deploy=${WAIT_FOR_DEPLOY} \
-e not_enrolled_data_file=${BAREMETAL_DATA_FILE}.rest \
-e enable_tls=${ENABLE_TLS} \
-e generate_tls=${ENABLE_TLS} \
-e skip_install=${CLI_TEST} \
-e skip_package_install=${CLI_TEST} \
-e skip_bootstrap=${CLI_TEST} \

View File

@ -83,6 +83,7 @@
- openstack/keystone
vars:
enable_keystone: true
enable_tls: true
test_driver: redfish
- job:
@ -109,6 +110,7 @@
- openstack/keystone
vars:
enable_keystone: true
enable_tls: true
test_driver: redfish
- job: