From 85c2359b3725cba622ef5ccadada9972a5817a49 Mon Sep 17 00:00:00 2001 From: Igor Duarte Date: Tue, 5 Sep 2023 11:45:39 -0300 Subject: [PATCH] ansible-scylla-node: Add support for system resources encryption System encryption is applied to semi-transient on-disk data, such as commit logs, batch logs, and hinted handoff data. This patch adds support for it by adding the variables system_info_encryption_local and system_info_encryption_kmip. The former should be used for a LocalKeyProvider and the latter for a KMIPKeyProvider. If the user is using a KMIPKeyProvider the variable kmip_hosts, also added in this patch, must be set. The details of how these variables must be used are described in ansible-scylla-node/defaults/main.yml along with the other variables from the node role. Fixes #88 --- ansible-scylla-node/defaults/main.yml | 48 ++++++++++++++ ansible-scylla-node/tasks/common.yml | 14 ++++ ansible-scylla-node/tasks/enable_ear.yml | 69 ++++++++++++++++++++ ansible-scylla-node/templates/scylla.yaml.j2 | 16 ++++- 4 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 ansible-scylla-node/tasks/enable_ear.yml diff --git a/ansible-scylla-node/defaults/main.yml b/ansible-scylla-node/defaults/main.yml index 2de4fd14..b4684d2a 100644 --- a/ansible-scylla-node/defaults/main.yml +++ b/ansible-scylla-node/defaults/main.yml @@ -274,6 +274,54 @@ scylla_ssl: client: enabled: true +# Local keys are used for encrypting user data, such as SSTables. +# To use your own key, copy it to {{ localhost_key_dir }} with the name {{ key_name }} and then this key will be copied to all the nodes. +# If you don't provide your own key, the role will automatically generate a new one for you based on the variables under data_encryption_local and at +# the end of the process the role will create {{ localhost_key_dir }} (if it's not already created) and copy the key to it, so you can save it +# somewhere else as a backup. +# Note that the role is responsible only for copying the keys to the nodes. In order to actually encrypt your data you need +# to manually configure your tables as explained in the documentation -> https://enterprise.docs.scylladb.com/stable/operating-scylla/security/encryption-at-rest.html +data_encryption_local: + enabled: false # mandatory + localhost_key_dir: "{{ inventory_dir }}/data_encryption_keys" # mandatory + key_dir: /etc/scylla/data_encryption_keys # mandatory + key_name: data_key # mandatory + cipher_algorithm: AES # mandatory + secret_key_strength: 128 # mandatory + secret_key_block_mode: CBC # mandatory if you're not providing your own key + secret_key_padding: PKCS5 # mandatory if you're not providing your own key + +# Encrypt system resources: +# System encryption is applied to semi-transient on-disk data, such as commit logs, batch logs, and hinted handoff data. +# If you'd like to use a LocalKeyProvider, please use the system_info_encrypt_local config +# If you'd like to use a KMIPKeyProvider, please use the system_info_encryption_kmip config + +# LocalKeyProvider: +# To use your own key, copy it to {{ localhost_key_dir }} with the name {{ key_name }} and then this key will be copied to all the nodes. +# If you don't provide your own key, the role will automatically generate a new one for you based on the variables under system_info_encryption_local and at +# the end of the process the role will create {{ localhost_key_dir }} (if it's not already created) and copy the key to it, so you can save it +# somewhere else as a backup. +system_info_encryption_local: + enabled: false # mandatory + localhost_key_dir: "{{ inventory_dir }}/system_encryption_keys" # mandatory + key_dir: /etc/scylla/system_encryption_keys # mandatory + key_name: system_key # mandatory + cipher_algorithm: AES # mandatory + secret_key_strength: 128 # mandatory + secret_key_block_mode: CBC # mandatory if you're not providing your own key + secret_key_padding: PKCS5 # mandatory if you're not providing your own key + # complete_cipher_algorithm must is the value that is going to be set as cipher_algorithm in system_info_encryption. It needs to include cipher_algorithm, block_mode, padding and it has to be in the below format. + complete_cipher_algorithm: "AES/CBC/PKCS5Padding" # mandatory + +# KMIPKeyProvider: +# All the variables under system_info_encryption_kmip are mandatory +# Make sure you set kmip_hosts accordingly +system_info_encryption_kmip: + enabled: false + cipher_algorithm: AES + secret_key_strength: 128 + kmip_host: yourkmipServerIP.com + # If Scylla Manager will be used, set the following variables scylla_manager_enabled: true # deprecated in favour of below, it will be dropped soon, below should be used diff --git a/ansible-scylla-node/tasks/common.yml b/ansible-scylla-node/tasks/common.yml index 531b0575..8694381c 100644 --- a/ansible-scylla-node/tasks/common.yml +++ b/ansible-scylla-node/tasks/common.yml @@ -292,6 +292,20 @@ - (scylla_ssl.internode.enabled|bool) or (scylla_ssl.client.enabled|bool) +- name: Set up LocalKeyProvider for system info encryption + include_tasks: enable_ear.yml + vars: + _type: "system_info" + _encryption_vars: "{{ system_info_encryption_local }}" + when: system_info_encryption_local.enabled|bool + +- name: Copy keys for sstables encryption + include_tasks: enable_ear.yml + vars: + _type: "sstables" + _encryption_vars: "{{ data_encryption_local }}" + when: data_encryption_local.enabled|bool + # scylla_listen_address is a composite indirect value that depends on another per-host value - scylla_nic. # Therefore in order to be able to get the corresponding value via hostvars[item] later in the play we need to # have an actual value resolved. diff --git a/ansible-scylla-node/tasks/enable_ear.yml b/ansible-scylla-node/tasks/enable_ear.yml new file mode 100644 index 00000000..7e3e59e3 --- /dev/null +++ b/ansible-scylla-node/tasks/enable_ear.yml @@ -0,0 +1,69 @@ +--- +- set_fact: + _remote_key_path: "{{ _encryption_vars.key_dir }}/{{ _encryption_vars.key_name }}" + +- set_fact: + _localhost_key_path: "{{ _encryption_vars.localhost_key_dir }}/{{ _encryption_vars.key_name }}" + +- name: Check if the keys already exist in the nodes + stat: + path: "{{ _remote_key_path }}" + become: true + register: remote_key_file + +- name: Check if the user provided a key + stat: + path: "{{ _localhost_key_path }}" + delegate_to: localhost + register: localhost_key_file + +- fail: + msg: "This node already has a key in '{{ _remote_key_path }}'" + when: remote_key_file.stat.exists|bool and localhost_key_file.stat.exists|bool == false + +- fail: + msg: "This node already has a key in '{{ _remote_key_path }}' and it's different from the one provided by the user in '{{ _localhost_key_path }}'" + when: + - remote_key_file.stat.exists|bool and localhost_key_file.stat.exists|bool + - remote_key_file.stat.checksum != localhost_key_file.stat.checksum + +- name: Create keys dir + file: + path: "{{ _encryption_vars.key_dir }}" + state: directory + owner: scylla + group: scylla + mode: '700' + become: true + +- name: Generate key and copy it to localhost + block: + - name: Generate key + shell: "/bin/local_file_key_generator -a {{ _encryption_vars.cipher_algorithm }} -m {{ _encryption_vars.secret_key_block_mode }} -p {{ _encryption_vars.secret_key_padding }} -l {{ _encryption_vars.secret_key_strength }} {{ _remote_key_path }}" + become: true + + - name: Create localhost keys dir + file: + path: "{{ _encryption_vars.localhost_key_dir }}" + state: directory + mode: '700' + delegate_to: localhost + + - name: Copy key from remote to localhost + fetch: + src: "{{ _remote_key_path }}" + dest: "{{ _localhost_key_path }}" + flat: true + validate_checksum: true + become: true + run_once: true + when: localhost_key_file.stat.exists|bool == false + +- name: Copy key from localhost to all nodes + copy: + src: "{{ _localhost_key_path }}" + dest: "{{ _remote_key_path }}" + owner: scylla + group: scylla + mode: '600' + become: true diff --git a/ansible-scylla-node/templates/scylla.yaml.j2 b/ansible-scylla-node/templates/scylla.yaml.j2 index 32fb8ecf..ca54f983 100644 --- a/ansible-scylla-node/templates/scylla.yaml.j2 +++ b/ansible-scylla-node/templates/scylla.yaml.j2 @@ -53,7 +53,6 @@ client_encryption_options: keyfile: {{ scylla_ssl.cert_path }}/{{ inventory_hostname }}.pem truststore: {{ scylla_ssl.cert_path }}/{{ scylla_cluster_name }}-ca.crt {% endif %} - {% if scylla_ssl is defined and scylla_ssl.internode.enabled %} server_encryption_options: internode_encryption: {{ scylla_ssl.internode.internode_encryption }} @@ -61,3 +60,18 @@ server_encryption_options: keyfile: {{ scylla_ssl.cert_path }}/{{ inventory_hostname }}.pem truststore: {{ scylla_ssl.cert_path }}/{{ scylla_cluster_name }}-ca.crt {% endif %} +{% if system_info_encryption_local.enabled|bool %} +system_info_encryption: + enabled: True + cipher_algorithm: {{ system_info_encryption_local.complete_cipher_algorithm }} + secret_key_strength: {{ system_info_encryption_local.secret_key_strength }} + key_provider: LocalFileSystemKeyProviderFactory + secret_key_file: {{ system_info_encryption_local.key_dir }}/{{ system_info_encryption_local.key_name }} +{% elif system_info_encryption_kmip.enabled|bool %} +system_info_encryption: + enabled: True + cipher_algorithm: {{ system_info_encryption_kmip.cipher_algorithm }} + secret_key_strength: {{ system_info_encryption_kmip.secret_key_strength }} + key_provider: KmipKeyProviderFactory + kmip_host: {{ system_info_encryption_kmip.kmip_host }} +{% endif %}