From 6b92dba1d5628fcd796565d4bee292863ae8cf72 Mon Sep 17 00:00:00 2001 From: Kevin Carter Date: Sun, 20 Jan 2019 23:57:46 -0600 Subject: [PATCH] Update skydive to use TLS This change implements TLS configurations for skydive. All servers will get their own certificates signed by the CA as setup on the `skydive_service_setup_host` node. > Documentation for TLS configuration can be found here: http://skydive.network/documentation/ Change-Id: I890b4a5d9076b0474ffe5649f9361cb6018c19fe Signed-off-by: Kevin Carter --- skydive/installSkydive.yml | 1 + .../roles/skydive_common/defaults/main.yml | 9 +- skydive/roles/skydive_common/tasks/main.yml | 14 +- .../skydive_common/tasks/skydive_keystone.yml | 12 ++ .../skydive_common/tasks/skydive_setup.yml | 13 +- .../skydive_common/tasks/skydive_ssl.yml | 131 ++++++++++++++++++ .../templates/skydive-openssl.cnf.j2 | 43 ++++++ .../skydive_common/templates/skydive.yml.j2 | 31 +++-- .../vars/{ubuntu.yml => debian.yml} | 3 + skydive/roles/skydive_common/vars/main.yml | 16 +++ skydive/roles/skydive_common/vars/redhat.yml | 2 + skydive/roles/skydive_common/vars/suse.yml | 3 + skydive/validateSkydive.yml | 38 +---- 13 files changed, 267 insertions(+), 49 deletions(-) create mode 100644 skydive/roles/skydive_common/tasks/skydive_ssl.yml create mode 100644 skydive/roles/skydive_common/templates/skydive-openssl.cnf.j2 rename skydive/roles/skydive_common/vars/{ubuntu.yml => debian.yml} (92%) diff --git a/skydive/installSkydive.yml b/skydive/installSkydive.yml index d9f86a1f..fa97e94b 100644 --- a/skydive/installSkydive.yml +++ b/skydive/installSkydive.yml @@ -139,5 +139,6 @@ become: yes roles: - role: skydive_agent + skydive_service_setup_host: "{{ openstack_service_setup_host | default(groups['skydive_analyzers'][0]) }}" tags: - skydive-agent-setup diff --git a/skydive/roles/skydive_common/defaults/main.yml b/skydive/roles/skydive_common/defaults/main.yml index 84ed14f9..91a09909 100644 --- a/skydive/roles/skydive_common/defaults/main.yml +++ b/skydive/roles/skydive_common/defaults/main.yml @@ -58,7 +58,6 @@ skydive_elasticsearch_port: "9200" skydive_etcd_embedded: yes skydive_etcd_port: 12379 skydive_etcd_listen_uri: "0.0.0.0:{{ skydive_etcd_port }}" -skydive_etcd_scheme: http # If embedded etcd is disabled the etcd server must be defined. # skydive_etcd_servers: @@ -137,3 +136,11 @@ skydive_os_service_insecure: true # `config_template` provides an interface that will inser any # option into the compatible configuration file using a deep merge. skydive_config_overrides: {} + +# Skydive SSL certificates will be generated when they do not exist. If the +# vertificates need to be regenerated set this option to true to force +# regenerate certificates. +skydive_ssl_regen: false + +# Set the log level used by skydive +skydive_log_level: INFO diff --git a/skydive/roles/skydive_common/tasks/main.yml b/skydive/roles/skydive_common/tasks/main.yml index 39d4db3b..c3b45e5d 100644 --- a/skydive/roles/skydive_common/tasks/main.yml +++ b/skydive/roles/skydive_common/tasks/main.yml @@ -70,6 +70,7 @@ # NOTE(cloudnull): Locate a clouds.yaml file on the service setup host or localhost. - name: Check for OpenStack deployment + run_once: true block: - name: Slurp clouds file slurp: @@ -82,7 +83,7 @@ src: "{{ skydive_os_cloud_file }}" register: clouds_file delegate_to: "localhost" - failed_when: false + ignore_errors: yes when: - not (skydive_service_setup_host in ['localhost', '127.0.0.1']) @@ -91,25 +92,26 @@ msg: >- No clouds file found, running without OpenStack integration. when: - - not (clouds_file is success) + - clouds_file['content'] is undefined -# NOTE(cloudnull): If a clouds file is found the facts for the clouds file will be delegated +# NOTE(cloudnull): If a clouds file is found the facts for the clouds file will be delegated # to all hosts throughout the skydive deployment. - name: Run OpenStack ingetration deployment + run_once: true block: - name: Enable OpenStack integration set_fact: clouds_yaml: "{{ clouds_file['content'] | b64decode | from_yaml }}" skydive_auth_type: mykeystone skydive_openstack_enabled: true - run_once: true delegate_to: "{{ item }}" delegate_facts: true with_items: "{{ ansible_play_hosts }}" - include_tasks: skydive_keystone.yml - run_once: true when: - - clouds_file is success + - clouds_file['content'] is defined - include_tasks: skydive_setup.yml + +- include_tasks: skydive_ssl.yml diff --git a/skydive/roles/skydive_common/tasks/skydive_keystone.yml b/skydive/roles/skydive_common/tasks/skydive_keystone.yml index 9c5ecdfc..3c86a93f 100644 --- a/skydive/roles/skydive_common/tasks/skydive_keystone.yml +++ b/skydive/roles/skydive_common/tasks/skydive_keystone.yml @@ -64,6 +64,18 @@ vars: ansible_python_interpreter: "{{ skydive_service_setup_host_python_interpreter }}" block: + - name: Ensure clouds file directory + file: + path: "{{ skydive_os_cloud_file | dirname }}" + state: directory + mode: "0750" + + - name: Install binary skydive + copy: + content: "{{ clouds_yaml | to_nice_yaml }}" + dest: "{{ skydive_os_cloud_file }}" + mode: "0640" + - name: Add skydive project os_project: cloud: "{{ skydive_os_cloud }}" diff --git a/skydive/roles/skydive_common/tasks/skydive_setup.yml b/skydive/roles/skydive_common/tasks/skydive_setup.yml index 21f04913..86546681 100644 --- a/skydive/roles/skydive_common/tasks/skydive_setup.yml +++ b/skydive/roles/skydive_common/tasks/skydive_setup.yml @@ -36,8 +36,19 @@ group: "skydive" mode: "0755" with_items: - - "/var/lib/skydive" - "/etc/skydive" + - "/var/lib/skydive" + - "/var/lib/skydive/etcd" + +- name: Create skydive ssl path + file: + path: "{{ item }}" + state: directory + owner: "skydive" + group: "skydive" + mode: "0700" + with_items: + - "/var/lib/skydive/ssl" - name: Check for ovsdb stat: diff --git a/skydive/roles/skydive_common/tasks/skydive_ssl.yml b/skydive/roles/skydive_common/tasks/skydive_ssl.yml new file mode 100644 index 00000000..df45c4e0 --- /dev/null +++ b/skydive/roles/skydive_common/tasks/skydive_ssl.yml @@ -0,0 +1,131 @@ +--- +# Copyright 2019, Rackspace US, Inc. +# +# 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: Purge skydive ssl certificates + file: + path: "{{ item.path }}" + state: absent + with_items: "{{ skydive_ssl_bundle }}" + when: + - skydive_ssl_regen | bool + +- name: SSL Block + run_once: true + delegate_to: "{{ skydive_service_setup_host }}" + block: + - name: Create CNF + template: + src: "skydive-openssl.cnf.j2" + dest: "{{ skydive_ssl_cnf }}" + owner: skydive + group: skydive + + - name: Create CA cert + command: >- + openssl req + -new + -nodes + -x509 + -subj "{{ skydive_ssl_ca_subject }}" + -days 3650 + -keyout {{ skydive_ssl_ca_key }} + -out {{ skydive_ssl_ca_cert }} + args: + creates: "{{ skydive_ssl_ca_cert }}" + + - name: Create CSR + command: >- + openssl req + -new + -nodes + -sha256 + -subj "{{ skydive_ssl_signed_subject }}" + -days 3650 + -keyout {{ skydive_ssl_key }} + -out {{ skydive_ssl_csr }} + -config {{ skydive_ssl_cnf }} + args: + creates: "{{ skydive_ssl_csr }}" + +- name: Create SSL signed cert + command: >- + openssl x509 + -req + -days 3650 + -in {{ skydive_ssl_csr }} + -CA {{ skydive_ssl_ca_cert }} + -CAkey {{ skydive_ssl_ca_key }} + -out {{ skydive_ssl_cert }} + -set_serial 01 + -extensions v3_req + -extfile {{ skydive_ssl_cnf }} + args: + creates: "{{ skydive_ssl_cert }}" + delegate_to: "{{ skydive_service_setup_host }}" + +- name: Fetch skydive ssl csr + fetch: + src: "{{ skydive_ssl_csr }}" + dest: "/tmp/skydive/ssl/{{ skydive_ssl_csr | basename }}" + flat: true + run_once: true + delegate_to: "{{ skydive_service_setup_host }}" + +- name: Fetch skydive ssl cert + fetch: + src: "{{ skydive_ssl_cert }}" + dest: "/tmp/skydive/ssl/{{ skydive_ssl_cert | basename }}" + flat: true + delegate_to: "{{ skydive_service_setup_host }}" + +- name: Fetch skydive ssl key + fetch: + src: "{{ skydive_ssl_key }}" + dest: "/tmp/skydive/ssl/{{ skydive_ssl_key | basename }}" + flat: true + run_once: true + delegate_to: "{{ skydive_service_setup_host }}" + +- name: Fetch skydive ca cert + fetch: + src: "{{ skydive_ssl_ca_cert }}" + dest: "/tmp/skydive/ssl/{{ skydive_ssl_ca_cert | basename }}" + flat: true + run_once: true + delegate_to: "{{ skydive_service_setup_host }}" + +- name: Copy certifactes over + copy: + src: "/tmp/skydive/ssl/{{ item.path | basename }}" + dest: "{{ item.path }}" + owner: skydive + group: skydive + with_items: "{{ skydive_ssl_bundle }}" + +- name: Cleanup system + delegate_to: "localhost" + run_once: true + block: + - name: Find temp skydive ssl certificates + find: + paths: "/tmp/skydive/ssl" + recurse: no + register: files_to_purge + + - name: Purge temp skydive host ssl + file: + path: "{{ item.path }}" + state: absent + with_items: "{{ files_to_purge.files }}" diff --git a/skydive/roles/skydive_common/templates/skydive-openssl.cnf.j2 b/skydive/roles/skydive_common/templates/skydive-openssl.cnf.j2 new file mode 100644 index 00000000..a5b5e91b --- /dev/null +++ b/skydive/roles/skydive_common/templates/skydive-openssl.cnf.j2 @@ -0,0 +1,43 @@ +[req] +distinguished_name = req_distinguished_name +req_extensions = v3_req + +[req_distinguished_name] +countryName = Country Name (2 letter code) +countryName_default = XX +stateOrProvinceName = State or Province Name (full name) +stateOrProvinceName_default = XX +localityName = Locality Name (eg, city) +localityName_default = XX +organizationalUnitName = Organizational Unit Name (eg, section) +organizationalUnitName_default = OpenStack-Ansible +commonName = {{ ((ansible_domain | length) > 0) | ternary(ansible_domain, ansible_hostname) }} +commonName_max = 64 + +[v3_req] +basicConstraints = CA:TRUE +keyUsage = digitalSignature, keyEncipherment, keyCertSign +extendedKeyUsage = serverAuth,clientAuth +subjectAltName = @alt_names + +[alt_names] +{% set ips = [] %} +{% set hostnames = [] %} +{% for node in groups['skydive_all'] %} +{% set _ansible_interface_name = hostvars[node]['skydive_network_device'] | default(hostvars[node]['ansible_default_ipv4']['interface']) | replace('-', '_') %} +{% set _skydive_ip = hostvars[node]['skydive_bind_address'] | default(hostvars[node]["ansible_" ~ _ansible_interface_name]['ipv4']['address']) %} +{% set _skydive_ansible_domain = hostvars[node]['ansible_domain'] | default(hostvars[node]['ansible_hostname'] ) %} +{% set _skydive_dns_name = ((_skydive_ansible_domain | length) > 0) | ternary(_skydive_ansible_domain, hostvars[node]['ansible_hostname']) %} +{% set _ = ips.append(_skydive_ip) %} +{% set _ = hostnames.append(_skydive_dns_name) %} +IP.{{ loop.index }} = {{ _skydive_ip }} +DNS.{{ loop.index }} = {{ _skydive_dns_name }} +{% endfor %} + +{% set localhost_index = (groups['skydive_all'] | length) + 1 %} +{% if '127.0.0.1' not in ips %} +IP.{{ localhost_index }} = 127.0.0.1 +{% endif %} +{% if 'localhost' not in hostnames %} +DNS.{{ localhost_index }} = localhost +{% endif %} diff --git a/skydive/roles/skydive_common/templates/skydive.yml.j2 b/skydive/roles/skydive_common/templates/skydive.yml.j2 index b0b28eff..6dc43314 100644 --- a/skydive/roles/skydive_common/templates/skydive.yml.j2 +++ b/skydive/roles/skydive_common/templates/skydive.yml.j2 @@ -8,13 +8,17 @@ host_id: {{ ansible_hostname }} tls: # File path to X509 Certificate and Private Key to enable TLS communication # Unique certificate per agent is recommended - # client_cert: /etc/ssl/certs/agent.domain.com.crt - # client_key: /etc/ssl/certs/agent.domain.com.key +{% if inventory_hostname in groups['skydive_agents'] %} + client_cert: {{ skydive_ssl_cert }} + client_key: {{ skydive_ssl_key }} +{% endif %} - # server_cert: /etc/ssl/certs/analyzer.domain.com.crt - # server_key: /etc/ssl/certs/analyzer.domain.com.key +{% if inventory_hostname in groups['skydive_analyzers'] %} + server_cert: {{ skydive_ssl_cert }} + server_key: {{ skydive_ssl_key }} +{% endif %} - # ca_cert: /etc/ssl/certs/ca.domain.com.crt + ca_cert: {{ skydive_ssl_ca_cert }} http: # define the Cookie HTTP Request Header @@ -358,7 +362,7 @@ storage: {% endif %} logging: - # level: INFO + level: {{ skydive_log_level }} # Default backend used: stderr backends: @@ -368,8 +372,15 @@ logging: # - syslog # configuration of the 'file' backend +{% if (inventory_hostname in groups['skydive_analyzers']) and (inventory_hostname in groups['skydive_agents']) %} +{% set _log_segment = 'aio' %} +{% elif (inventory_hostname in groups['skydive_analyzers']) %} +{% set _log_segment = 'analyzer' %} +{% else %} +{% set _log_segment = 'agent' %} +{% endif %} file: - path: /var/log/skydive.log + path: /var/log/skydive-{{ _log_segment }}.log # configuration encoder could be for all backends or for specific one # encoder: json @@ -415,7 +426,7 @@ etcd: # max_snap_files: 0 # path where the etcd files will be stored. - # data_dir: /var/lib/skydive/etcd + data_dir: /var/lib/skydive/etcd # client parameters {% if skydive_etcd_servers %} @@ -467,13 +478,14 @@ flow: # 1194: OPENVPN {% endif %} +{% if inventory_hostname in groups['skydive_analyzers'] %} ui: # Specify the extra assets folder. Javascript and CSS files present in this # folder will be added to the WebUI. # extra_assets: /usr/share/skydive/assets # select between light, dark themes - # theme: dark + theme: light # Settings specific to the topology view topology: @@ -530,3 +542,4 @@ rbac: # additional RBAC policy: # - p, myuser, capture, write, deny # - g, myuser, myrole +{% endif %} diff --git a/skydive/roles/skydive_common/vars/ubuntu.yml b/skydive/roles/skydive_common/vars/debian.yml similarity index 92% rename from skydive/roles/skydive_common/vars/ubuntu.yml rename to skydive/roles/skydive_common/vars/debian.yml index f0c451d8..ec63a97d 100644 --- a/skydive/roles/skydive_common/vars/ubuntu.yml +++ b/skydive/roles/skydive_common/vars/debian.yml @@ -14,4 +14,7 @@ # limitations under the License. sykdive_distro_packages: + - openssl + - python3-openssl + - python-openssl - python-passlib diff --git a/skydive/roles/skydive_common/vars/main.yml b/skydive/roles/skydive_common/vars/main.yml index d57ba1e3..c46c727c 100644 --- a/skydive/roles/skydive_common/vars/main.yml +++ b/skydive/roles/skydive_common/vars/main.yml @@ -45,3 +45,19 @@ skydive_fabric: |- # Inject the required basic authentication information _skydive_basic_auth_users: "{{ skydive_username }}": "{{ skydive_password }}" + +skydive_ssl_cnf: "/var/lib/skydive/ssl/skydive-openssl.cnf" +skydive_ssl_key: "/var/lib/skydive/ssl/skydive.key" +skydive_ssl_csr: "/var/lib/skydive/ssl/skydive.csr" +skydive_ssl_cert: "/var/lib/skydive/ssl/skydive-{{ inventory_hostname | replace('_', '-') | replace(' ', '-') }}.crt" +skydive_ssl_signed_subject: "/C=XX/L=OpenStack-Cloud/O=OpenStack/OU=IT/CN={{ ((ansible_domain | length) > 0) | ternary(ansible_domain, ansible_hostname) }}" + +skydive_ssl_ca_key: "/var/lib/skydive/ssl/skydive-ca.key" +skydive_ssl_ca_cert: "/var/lib/skydive/ssl/skydive-ca.crt" +skydive_ssl_ca_subject: "/C=XX/L=OpenStack-Cloud/O=OpenStack" + +skydive_ssl_bundle: + - path: "{{ skydive_ssl_csr }}" + - path: "{{ skydive_ssl_cert }}" + - path: "{{ skydive_ssl_key }}" + - path: "{{ skydive_ssl_ca_cert }}" \ No newline at end of file diff --git a/skydive/roles/skydive_common/vars/redhat.yml b/skydive/roles/skydive_common/vars/redhat.yml index eb50180e..9f8879b4 100644 --- a/skydive/roles/skydive_common/vars/redhat.yml +++ b/skydive/roles/skydive_common/vars/redhat.yml @@ -14,4 +14,6 @@ # limitations under the License. sykdive_distro_packages: + - openssl - python2-passlib + - pyOpenSSL diff --git a/skydive/roles/skydive_common/vars/suse.yml b/skydive/roles/skydive_common/vars/suse.yml index f0c451d8..80c5e764 100644 --- a/skydive/roles/skydive_common/vars/suse.yml +++ b/skydive/roles/skydive_common/vars/suse.yml @@ -14,4 +14,7 @@ # limitations under the License. sykdive_distro_packages: + - openssl + - python2-pyOpenSSL + - python3-pyOpenSSL - python-passlib diff --git a/skydive/validateSkydive.yml b/skydive/validateSkydive.yml index 6c842ee9..1076aa39 100644 --- a/skydive/validateSkydive.yml +++ b/skydive/validateSkydive.yml @@ -17,40 +17,14 @@ hosts: skydive_analyzers[0] vars: skydive_username: skydive - skydive_analyzer_port: 8082 - skydive_network_device: "{{ ansible_default_ipv4['interface'] | replace('-', '_') }}" - skydive_analyzer_uri: "{{ (skydive_bind_address | default(hostvars[inventory_hostname]['ansible_' ~ skydive_network_device]['ipv4']['address'])) ~ ':' ~ skydive_analyzer_port }}" tasks: - - name: Check API login - uri: - url: "http://{{ skydive_analyzer_uri }}/login" - status_code: "200" - method: POST - body: "username={{ skydive_username }}&password={{ skydive_password }}" - headers: - Content-Type: "application/x-www-form-urlencoded" - register: skydive_login + - name: Check client status + command: skydive client status --username {{ skydive_username }} --password {{ skydive_password }} + register: skydive_client until: - - skydive_login is success + - skydive_client is success retries: 10 delay: 10 - - name: Check API status - uri: - url: "http://{{ skydive_analyzer_uri }}/api/status" - method: GET - return_content: true - headers: - Cookie: "{{ skydive_login.set_cookie | regex_replace(',', ';') }}" - register: skydive_response - changed_when: false - until: - - skydive_response is success - - skydive_response.json is defined - retries: 10 - delay: 5 - run_once: true - - - name: Show Skydive agents - debug: - var: "{{ skydive_response.json | to_json }}" + - name: Show Skydive client + debug: var=skydive_client