4f2fd6df32
Virtual media images can potentially contain sensitive data, such as password hashes or private keys. This change adds TLS to this traffic. A new HTTP server is now started with Nginx, serving the same /httpboot directory as the old one. If vmedia_enable_tls is true, the /redfish and /ilo directories are only accessible through it. One of the redfish-vmedia CI jobs has been switched to using TLS. Change-Id: I024b81efdbebe08ddb5a20cd0d5e7ae61a180f1b
484 lines
16 KiB
YAML
484 lines
16 KiB
YAML
# Copyright (c) 2015 Hewlett-Packard Development Company, L.P.
|
|
#
|
|
# 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.
|
|
---
|
|
- name: "Check that the provided interface is defined"
|
|
fail:
|
|
msg: >
|
|
Network interface {{ network_interface }} is not known to Ansible.
|
|
If you're testing Bifrost on virtual machines, do not forget to invoke
|
|
"bifrost-cli testenv" or use the "bifrost-create-vm-nodes" role first.
|
|
If you're using Bifrost on real bare metal, you have to provide the
|
|
network interface via the "network_interface" variable or the
|
|
--network-interface argument to "bifrost-cli install".
|
|
when: ('ansible_' + ans_network_interface) not in hostvars[inventory_hostname]
|
|
|
|
- name: "Fail if authentication configuration conflicts."
|
|
fail:
|
|
msg: >
|
|
noauth_mode and enable_keystone are mutually exclusive options.
|
|
Please set one to "false".
|
|
when:
|
|
- noauth_mode | bool
|
|
- enable_keystone | bool
|
|
|
|
- name: "Fail if TLS is inconsistently configured"
|
|
fail:
|
|
msg: Setting vmedia_enable_tls to true requires also enable_tls.
|
|
when:
|
|
- not enable_tls | bool
|
|
- vmedia_enable_tls | bool
|
|
|
|
- name: "Setup firewalld"
|
|
include_tasks: setup_firewalld.yml
|
|
when: use_firewalld | bool
|
|
|
|
# NOTE(sean-k-mooney) only the MySQL database is started during bootstrapping.
|
|
# All other services are started in the Start phase.
|
|
- name: "Start database service"
|
|
service: name={{ mysql_service_name }} state=started enabled=yes
|
|
when: ironic.database.host == 'localhost'
|
|
|
|
- name: "Set mysql_username if environment variable mysql_user is set"
|
|
set_fact:
|
|
mysql_username: "{{ lookup('env', 'mysql_user') }}"
|
|
when: lookup('env', 'mysql_user') | length > 0
|
|
no_log: true
|
|
|
|
- name: "Set mysql_password if environment variable mysql_pass is set"
|
|
set_fact:
|
|
mysql_password: "{{ lookup('env', 'mysql_pass') }}"
|
|
when: lookup('env', 'mysql_pass') | length > 0
|
|
no_log: true
|
|
|
|
- name: "Set MySQL socket fact for Red Hat family"
|
|
set_fact:
|
|
mysql_socket_path: "/var/lib/mysql/mysql.sock"
|
|
when: ansible_os_family | lower == 'redhat'
|
|
|
|
- name: "Set MySQL socket fact for Debian family"
|
|
set_fact:
|
|
mysql_socket_path: "/var/run/mysqld/mysqld.sock"
|
|
when: ansible_os_family | lower == 'debian'
|
|
|
|
- name: "Set MySQL socket fact for other systems"
|
|
set_fact:
|
|
mysql_socket_path: "/var/run/mysql/mysql.sock"
|
|
when: (ansible_os_family | lower) not in ['redhat', 'debian']
|
|
|
|
- name: "MySQL - Creating DB"
|
|
mysql_db:
|
|
login_unix_socket: "{{ mysql_socket_path | default(omit) }}"
|
|
name: "{{ ironic.database.name }}"
|
|
state: present
|
|
encoding: utf8
|
|
login_user: "{{ mysql_username | default(None) }}"
|
|
login_password: "{{ mysql_password | default(None) }}"
|
|
register: test_created_db
|
|
when: ironic.database.host == 'localhost'
|
|
|
|
- name: "MySQL - Creating user for Ironic"
|
|
mysql_user:
|
|
login_unix_socket: "{{ mysql_socket_path | default(omit) }}"
|
|
name: "{{ ironic.database.username }}"
|
|
password: "{{ ironic.database.password }}"
|
|
priv: "{{ ironic.database.name }}.*:ALL"
|
|
state: present
|
|
login_user: "{{ mysql_username | default(None) }}"
|
|
login_password: "{{ mysql_password | default(None) }}"
|
|
when: ironic.database.host == 'localhost'
|
|
|
|
- name: "Create an ironic service group"
|
|
group:
|
|
name: "ironic"
|
|
- name: "Create an ironic service user"
|
|
user:
|
|
name: "ironic"
|
|
group: "ironic"
|
|
- name: "Ensure /etc/ironic exists"
|
|
file:
|
|
name: "/etc/ironic"
|
|
state: directory
|
|
owner: "ironic"
|
|
group: "ironic"
|
|
mode: 0755
|
|
# Note(TheJulia): The rootwrap copies will need to be re-tooled
|
|
# to possibly directly retreive current files if a source install
|
|
# is not utilized.
|
|
- name: "Copy rootwrap.conf from ironic source folder"
|
|
copy:
|
|
src: "{{ ironic_git_folder }}/etc/ironic/rootwrap.conf"
|
|
dest: "/etc/ironic/rootwrap.conf"
|
|
remote_src: yes
|
|
mode: 0644
|
|
owner: root
|
|
group: root
|
|
when: not skip_install | bool
|
|
- name: "Copy rootwrap.d contents from ironic source folder"
|
|
copy:
|
|
src: "{{ ironic_git_folder }}/etc/ironic/rootwrap.d/"
|
|
dest: "/etc/ironic/rootwrap.d/"
|
|
remote_src: yes
|
|
owner: root
|
|
group: root
|
|
when: not skip_install | bool
|
|
- name: "Copy rootwrap.d contents from ironic-lib installation"
|
|
copy:
|
|
src: "{{ bifrost_venv_dir }}/etc/ironic/rootwrap.d/ironic-lib.filters"
|
|
dest: "/etc/ironic/rootwrap.d/"
|
|
remote_src: yes
|
|
owner: root
|
|
group: root
|
|
when: not skip_install | bool
|
|
|
|
- name: "Generate admin htpasswd for ironic"
|
|
htpasswd:
|
|
path: /etc/ironic/htpasswd
|
|
crypt_scheme: bcrypt
|
|
name: "{{ admin_username }}"
|
|
password: "{{ admin_password }}"
|
|
when:
|
|
- not enable_keystone | bool
|
|
|
|
- name: "Generate user htpasswd for ironic"
|
|
htpasswd:
|
|
path: /etc/ironic/htpasswd
|
|
crypt_scheme: bcrypt
|
|
name: "{{ default_username }}"
|
|
password: "{{ default_password }}"
|
|
when:
|
|
- 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: "Generate vmedia TLS parameters"
|
|
include_role:
|
|
name: bifrost-tls
|
|
vars:
|
|
dest_private_key_path: "{{ httpboot_private_key_path }}"
|
|
dest_private_key_owner: "{{ nginx_user }}"
|
|
dest_private_key_group: "{{ nginx_user }}"
|
|
when: vmedia_enable_tls | bool
|
|
|
|
- name: "Populate keystone for Bifrost"
|
|
include: keystone_setup.yml
|
|
when: enable_keystone | bool
|
|
|
|
- name: "Read SSH key if needed"
|
|
import_tasks: ssh_public_key_path.yaml
|
|
when: ipa_add_ssh_key | bool
|
|
|
|
# NOTE(pas-ha) needed to e.g. pick up new interfaces after libvirt install
|
|
- name: "Refresh facts"
|
|
setup:
|
|
gather_timeout: "{{ fact_gather_timeout }}"
|
|
|
|
- name: "Generate ironic Configuration"
|
|
include: ironic_config.yml
|
|
|
|
- name: "Create the log directories (if requested)"
|
|
file:
|
|
path: "{{ item }}"
|
|
state: directory
|
|
mode: 0700
|
|
owner: "ironic"
|
|
group: "ironic"
|
|
loop:
|
|
- "{{ ironic_log_dir | default('') }}"
|
|
- "{{ ironic_agent_deploy_logs_local_path | default('') }}"
|
|
when: item | length > 0
|
|
|
|
- name: "Create ironic DB Schema"
|
|
command: ironic-dbsync --config-file /etc/ironic/ironic.conf create_schema
|
|
environment: "{{ bifrost_venv_env }}"
|
|
when:
|
|
- ironic.database.host == 'localhost'
|
|
- test_created_db.changed
|
|
|
|
- name: "Upgrade ironic DB Schema"
|
|
command: ironic-dbsync --config-file /etc/ironic/ironic.conf upgrade
|
|
environment: "{{ bifrost_venv_env }}"
|
|
when: ironic.database.host != 'localhost'
|
|
or not test_created_db.changed
|
|
|
|
- name: "Create service folder"
|
|
file:
|
|
path: "{{ init_dest_dir }}"
|
|
state: directory
|
|
mode: 0755
|
|
|
|
- name: "Install ironic-inspector to permit use of inspection interface"
|
|
include: inspector_bootstrap.yml
|
|
when: enable_inspector | bool
|
|
|
|
- name: "Get ironic-api & ironic-conductor install location"
|
|
shell: echo $(dirname $(which ironic-api))
|
|
register: ironic_install_prefix
|
|
environment: "{{ bifrost_venv_env }}"
|
|
|
|
- name: "Place ironic services"
|
|
template:
|
|
src: systemd_template.j2
|
|
dest: "{{ init_dest_dir }}{{ item.service_name }}.service"
|
|
owner: "root"
|
|
group: "root"
|
|
loop:
|
|
- service_path: "{{ ironic_install_prefix.stdout | default('') }}"
|
|
service_name: 'ironic-api'
|
|
username: 'ironic'
|
|
args: '--config-file /etc/ironic/ironic.conf'
|
|
- service_path: "{{ ironic_install_prefix.stdout | default('') }}"
|
|
service_name: 'ironic-conductor'
|
|
username: 'ironic'
|
|
args: '--config-file /etc/ironic/ironic.conf'
|
|
|
|
- name: "Create and populate /tftpboot"
|
|
import_tasks: create_tftpboot.yml
|
|
|
|
- name: "Create an ESP image"
|
|
import_tasks: create_esp.yml
|
|
|
|
- name: "Setup additional DHCP hosts directory"
|
|
file:
|
|
path: "{{ dnsmasq_additional_hostsdir }}"
|
|
state: directory
|
|
owner: "root"
|
|
group: "root"
|
|
mode: 0755
|
|
when: dnsmasq_additional_hostsdir is defined
|
|
|
|
- name: "Setup inventory DHCP hosts directory"
|
|
file:
|
|
path: "{{ dnsmasq_dhcp_hostsdir }}"
|
|
state: directory
|
|
owner: "root"
|
|
group: "root"
|
|
mode: 0755
|
|
|
|
- name: "Retrieve interface IP informations"
|
|
set_fact:
|
|
itf_infos: "{{ internal_interface }}"
|
|
dhcp_netaddr: "{{ dhcp_pool_start }}/{{ dhcp_static_mask }}"
|
|
when: include_dhcp_server | bool
|
|
- name: "Compute interface and DHCP network informations"
|
|
set_fact:
|
|
itf_netaddr1: "{{ itf_infos['address'] }}/{{ itf_infos['netmask'] }}"
|
|
itf_netaddr2: "{{ itf_infos['network'] }}/{{ itf_infos['netmask'] }}"
|
|
itf_broadcast: "{{ itf_infos['broadcast'] }}/{{ itf_infos['netmask'] }}"
|
|
dhcp_netaddr: "{{ dhcp_netaddr | ipaddr('network') }}/{{ dhcp_static_mask }}"
|
|
when: include_dhcp_server | bool
|
|
- name: "Validate interface network addresses"
|
|
fail:
|
|
msg: >
|
|
Interface {{ ans_network_interface }} network incoherence
|
|
{{ itf_netaddr1 | ipaddr('network') }}/{{ itf_netaddr1 | ipaddr('prefix') }}
|
|
vs {{ itf_netaddr2 }}/{{ itf_netaddr2 | ipaddr('prefix') }}
|
|
when:
|
|
- include_dhcp_server | bool
|
|
- itf_netaddr1 | ipaddr('network') != itf_netaddr2 | ipaddr('network')
|
|
- name: "Validate interface broadcast addresses"
|
|
fail:
|
|
msg: >
|
|
Interface {{ ans_network_interface }} broadcast incoherence
|
|
{{ itf_netaddr1 | ipaddr('broadcast') }}/{{ itf_netaddr1 | ipaddr('prefix') }}
|
|
vs {{ itf_broadcast | ipaddr('broadcast') }}/{{ itf_broadcast | ipaddr('prefix') }}
|
|
when:
|
|
- include_dhcp_server | bool
|
|
- itf_netaddr1 | ipaddr('broadcast') != itf_broadcast | ipaddr('broadcast')
|
|
- name: "Validate DHCP and interface addresses"
|
|
debug:
|
|
msg: >
|
|
Interface {{ ans_network_interface }} and DHCP networks are incoherent
|
|
{{ itf_netaddr2 | ipaddr('network') }}/{{ itf_netaddr2 | ipaddr('prefix') }}
|
|
{{ dhcp_netaddr | ipaddr('network') }}/{{ dhcp_netaddr | ipaddr('prefix') }}
|
|
overriding DHCP with interface settings"
|
|
when:
|
|
- include_dhcp_server | bool
|
|
- itf_netaddr2 | ipaddr('network') != dhcp_netaddr | ipaddr('network')
|
|
- name: "Computing new DHCP informations"
|
|
set_fact:
|
|
dhcp_start_ip: "{{ dhcp_pool_start.split('.')[-1] }}"
|
|
dhcp_end_ip: "{{ dhcp_pool_end.split('.')[-1] }}"
|
|
dhcp_netaddr: "{{ itf_netaddr1 | ipaddr('network') }}"
|
|
when:
|
|
- include_dhcp_server | bool
|
|
- itf_netaddr2 | ipaddr('network') != dhcp_netaddr | ipaddr('network')
|
|
# Note(olivierbourdon38): we could do much more complex network
|
|
# computation to derive exact (or way closer to exact) range for
|
|
# the new network depending on netmasks and indexes.
|
|
- name: "Computing new DHCP range"
|
|
set_fact:
|
|
dhcp_pool_start: "{{ '.'.join(dhcp_netaddr.split('.')[0:-1]) }}.{{ dhcp_start_ip }}"
|
|
dhcp_pool_end: "{{ '.'.join(dhcp_netaddr.split('.')[0:-1]) }}.{{ dhcp_end_ip }}"
|
|
when:
|
|
- include_dhcp_server | bool
|
|
- itf_netaddr2 | ipaddr('network') != dhcp_netaddr | ipaddr('network')
|
|
- name: "Deploy dnsmasq configuration file"
|
|
template: src=dnsmasq.conf.j2 dest=/etc/dnsmasq.conf
|
|
when: include_dhcp_server | bool
|
|
# NOTE(Shrews) When testing, we want to use our custom dnsmasq.conf file,
|
|
# not the one supplied by libvirt.
|
|
- name: "Look for libvirt dnsmasq config"
|
|
stat: path=/etc/dnsmasq.d/libvirt-bin
|
|
register: test_libvirt_dnsmasq
|
|
when: include_dhcp_server | bool
|
|
- name: "Disable libvirt dnsmasq config"
|
|
command: mv /etc/dnsmasq.d/libvirt-bin /etc/dnsmasq.d/libvirt-bin~
|
|
when:
|
|
- include_dhcp_server | bool
|
|
- test_libvirt_dnsmasq.stat.exists
|
|
- testing | bool
|
|
- name: "Download Ironic Python Agent kernel & image"
|
|
include: download_ipa_image.yml
|
|
when:
|
|
- not create_ipa_image | bool
|
|
- download_ipa | bool
|
|
|
|
- block:
|
|
- name: "Download cirros to use for deployment if requested"
|
|
get_url:
|
|
url: "{{ cirros_deploy_image_upstream_url }}"
|
|
dest: "{{ deploy_image }}"
|
|
owner: ironic
|
|
group: ironic
|
|
mode: 0644
|
|
- name: "Create a checksum file for cirros"
|
|
shell: md5sum {{ deploy_image_filename }} > {{ deploy_image_filename }}.CHECKSUMS
|
|
args:
|
|
chdir: "{{ http_boot_folder }}"
|
|
- name: "Ensure the checksum file is readable"
|
|
file:
|
|
path: "{{ http_boot_folder }}/{{ deploy_image_filename }}.CHECKSUMS"
|
|
owner: ironic
|
|
group: ironic
|
|
mode: 0644
|
|
when: use_cirros | bool
|
|
|
|
- name: "Bootstrap Nginx"
|
|
import_role:
|
|
name: bifrost-nginx-install
|
|
tasks_from: bootstrap
|
|
|
|
- name: "Place nginx configuration for ironic"
|
|
template:
|
|
src: nginx_conf.d_bifrost-httpboot.conf.j2
|
|
dest: /etc/nginx/conf.d/bifrost-httpboot.conf
|
|
owner: "{{ nginx_user }}"
|
|
group: "{{ nginx_user }}"
|
|
mode: 0755
|
|
|
|
- name: "Set permissions for /var/lib/ironic for the ironic user"
|
|
file:
|
|
path: "{{ item }}"
|
|
state: directory
|
|
mode: 0750
|
|
owner: "ironic"
|
|
group: "{{ nginx_user }}"
|
|
loop:
|
|
- "/var/lib/ironic"
|
|
- "/var/lib/ironic/master_images"
|
|
- "/var/lib/ironic/images"
|
|
|
|
- name: >
|
|
"Explicitly permit nginx port (TCP) for file downloads from nodes to be provisioned
|
|
and TCP/6385 for IPA callback"
|
|
iptables:
|
|
chain: INPUT
|
|
action: insert
|
|
protocol: tcp
|
|
destination_port: "{{ item }}"
|
|
in_interface: "{{ network_interface }}"
|
|
jump: ACCEPT
|
|
loop:
|
|
- 68
|
|
- 69
|
|
- "{{ file_url_port }}"
|
|
- "{{ file_url_port_tls }}"
|
|
- 6385
|
|
when: not use_firewalld | bool
|
|
|
|
- name: "Enable services in firewalld"
|
|
firewalld:
|
|
service: "{{ item }}"
|
|
zone: "{{ 'libvirt' if testing | bool else firewalld_internal_zone }}"
|
|
state: enabled
|
|
permanent: yes
|
|
immediate: yes
|
|
loop:
|
|
- dhcp
|
|
- dhcpv6
|
|
- tftp
|
|
when: use_firewalld | bool
|
|
|
|
- name: "Enable ports in firewalld"
|
|
firewalld:
|
|
port: "{{ item }}/tcp"
|
|
zone: "{{ 'libvirt' if testing | bool else firewalld_internal_zone }}"
|
|
state: enabled
|
|
permanent: yes
|
|
immediate: yes
|
|
loop:
|
|
- "{{ file_url_port }}"
|
|
- "{{ file_url_port_tls }}"
|
|
- 6385
|
|
when: use_firewalld | bool
|
|
|
|
- block:
|
|
- name: "Allow nginx, ironic, inspector and IPA ports on SELinux"
|
|
seport:
|
|
ports: "{{ file_url_port }},{{ file_url_port_tls }},6385,5050,9999"
|
|
proto: tcp
|
|
setype: http_port_t
|
|
state: present
|
|
|
|
- name: "Add proper context on created data for tftpboot"
|
|
sefcontext:
|
|
target: "{{ item }}"
|
|
setype: tftpdir_t
|
|
state: present
|
|
loop:
|
|
- "{{ tftp_boot_folder }}"
|
|
- "{{ tftp_boot_folder }}/pxelinux.cfg"
|
|
|
|
- name: "Add proper context on created data for http_boot"
|
|
sefcontext:
|
|
target: "{{ http_boot_folder }}(/.*)?"
|
|
setype: httpd_sys_content_t
|
|
state: present
|
|
|
|
- name: Disable the old ironic policy if it was enabled
|
|
command: semodule -d ironic_policy
|
|
ignore_errors: true
|
|
|
|
- name: Apply the correct SELinux context to the directories
|
|
command: restorecon -iRv {{ item }}
|
|
loop:
|
|
- "{{ http_boot_folder }}"
|
|
- "{{ tftp_boot_folder }}"
|
|
when: (ansible_os_family == 'RedHat' or ansible_os_family == 'Suse') and
|
|
ansible_selinux.status == 'enabled' and ansible_selinux.mode == "enforcing"
|
|
- name: "Configure remote logging"
|
|
template: src=10-rsyslog-remote.conf.j2 dest=/etc/rsyslog.d/10-rsyslog-remote.conf
|
|
when:
|
|
- remote_syslog_server is defined
|
|
- remote_syslog_server | length > 0
|