bifrost/playbooks/roles/bifrost-ironic-install/tasks/bootstrap.yml
Dmitry Tantsur 3cb96f1b67 Tighten permissions for PXE directories
Make the HTTP directory not world readable by default.
Images may contain secrets, so regular users should not read them.

Add nginx and dnsmasq to the ironic group so that they can read ironic
files that are group accessible.

Change-Id: Iaa8585fb48e5db6c0d5063dca0d84c9d2300f0c9
2022-01-14 12:56:36 +01:00

507 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: "Add nginx and dnsmasq to the ironic group"
user:
name: "{{ item }}"
groups: "ironic"
append: yes
loop:
- "{{ nginx_user }}"
- dnsmasq
- 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: "dnsmasq"
group: "ironic"
mode: 0755
when: dnsmasq_additional_hostsdir is defined
- name: "Setup inventory DHCP hosts directory"
file:
path: "{{ dnsmasq_dhcp_hostsdir }}"
state: directory
owner: "dnsmasq"
group: "ironic"
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: "ironic"
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:
- "{{ file_url_port }}"
- "{{ file_url_port_tls }}"
- 6385
when: not use_firewalld | bool
- name: "Explicitly permit DHCP and TFTP ports"
iptables:
chain: INPUT
action: insert
protocol: udp
destination_port: "{{ item }}"
in_interface: "{{ network_interface }}"
jump: ACCEPT
loop:
- 67
- 69
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