From f3084131a8fbcead0881bfae16862607f363b36d Mon Sep 17 00:00:00 2001 From: Daniel Neuberger Date: Fri, 11 Aug 2023 17:10:26 +0200 Subject: [PATCH 1/3] start restructuring for better handling; #227 --- README.md | 1 + roles/elasticsearch/defaults/main.yml | 4 + .../tasks/elasticsearch-configuration.yml | 42 ++++ .../tasks/elasticsearch-keystore.yml | 184 ++++++++++++++++++ .../tasks/elasticsearch-security.yml | 10 +- roles/elasticsearch/tasks/main.yml | 63 +----- 6 files changed, 246 insertions(+), 58 deletions(-) create mode 100644 roles/elasticsearch/tasks/elasticsearch-configuration.yml create mode 100644 roles/elasticsearch/tasks/elasticsearch-keystore.yml diff --git a/README.md b/README.md index a1387a73..66938d74 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ We test the collection on the following Linux distributions. Each one with Elast * Ubuntu 20.04 LTS * Ubuntu 22.04 LTS * Debian 11 +* RHEL 9 ( Elastic Stack 8) We know from personal experience, that the collections work in following combinations. Missing tests mostly come from incompatibilties between the distribution and our testing environment, not from problems with the collection itself. diff --git a/roles/elasticsearch/defaults/main.yml b/roles/elasticsearch/defaults/main.yml index 5e8de21c..b7c690aa 100644 --- a/roles/elasticsearch/defaults/main.yml +++ b/roles/elasticsearch/defaults/main.yml @@ -23,6 +23,10 @@ elasticsearch_config_jvm: "jvm.options.j2" elasticsearch_user: elasticsearch elasticsearch_group: elasticsearch +# elasticsearh security and api +elasticsearch_api_basic_auth_username: 'elastic' # +elasticsearch_api_basic_auth_password: '' # after starting the cluster for the first time you will find that password in /usr/share/elasticsearch/initial_passwords + # JVM custom parameters elasticsearch_java_home: '' elasticsearch_jvm_custom_parameters: '' diff --git a/roles/elasticsearch/tasks/elasticsearch-configuration.yml b/roles/elasticsearch/tasks/elasticsearch-configuration.yml new file mode 100644 index 00000000..8f000284 --- /dev/null +++ b/roles/elasticsearch/tasks/elasticsearch-configuration.yml @@ -0,0 +1,42 @@ +--- +- name: Configure Elasticsearch + template: + src: elasticsearch.yml.j2 + dest: /etc/elasticsearch/elasticsearch.yml + owner: root + group: root + mode: 0644 + backup: "{{ elasticsearch_config_backup }}" + notify: + - Restart Elasticsearch + when: elasticsearch_manage_yaml | bool + +- name: Create Elasticsearch directory + file: + path: "{{ item.path }}" + state: directory + owner: elasticsearch + group: elasticsearch + mode: "2750" + when: item.create | bool + loop: + - {create: "{{elasticsearch_create_logpath}}", path: "{{ elasticsearch_logpath }}" } + - {create: "{{elasticsearch_create_datapath}}", path: "{{ elasticsearch_datapath }}" } + +- name: Copy jvm.options File + become: yes + template: + src: "{{ elasticsearch_config_jvm }}" + dest: "{{ elasticsearch_conf_dir }}/jvm.options" + owner: root + group: "{{ elasticsearch_group }}" + mode: "660" + force: yes + notify: Restart Elasticsearch + +- name: Start Elasticsearch + service: + name: elasticsearch + state: started + enabled: yes + failed_when: false \ No newline at end of file diff --git a/roles/elasticsearch/tasks/elasticsearch-keystore.yml b/roles/elasticsearch/tasks/elasticsearch-keystore.yml new file mode 100644 index 00000000..8f7ff6ac --- /dev/null +++ b/roles/elasticsearch/tasks/elasticsearch-keystore.yml @@ -0,0 +1,184 @@ +--- + +- name: Create keystore + command: /usr/share/elasticsearch/bin/elasticsearch-keystore create + args: + creates: /etc/elasticsearch/elasticsearch.keystore + +- name: Check for bootstrap password + command: /usr/share/elasticsearch/bin/elasticsearch-keystore list + changed_when: false + register: elasticsearch_keystore + +- name: Set bootstrap password # noqa: risky-shell-pipe + shell: > + if test -n "$(ps -p $$ | grep bash)"; then set -o pipefail; fi; + echo "{{ elasticsearch_bootstrap_pw }}" | + /usr/share/elasticsearch/bin/elasticsearch-keystore + add -x 'bootstrap.password' + when: "'bootstrap.password' not in elasticsearch_keystore.stdout_lines" + changed_when: false + no_log: true + notify: + - Restart Elasticsearch + ignore_errors: "{{ ansible_check_mode }}" + +- name: Get xpack.security.http.ssl.keystore.secure_password # noqa: risky-shell-pipe + shell: > + if test -n "$(ps -p $$ | grep bash)"; then set -o pipefail; fi; + /usr/share/elasticsearch/bin/elasticsearch-keystore + show 'xpack.security.http.ssl.keystore.secure_password' + when: + - "'xpack.security.http.ssl.keystore.secure_password' in elasticsearch_keystore.stdout_lines" + - elasticsearch_http_security + register: elasticsearch_http_ssl_keystore_secure_password + ignore_errors: "{{ ansible_check_mode }}" + no_log: true + changed_when: false + +- name: Set xpack.security.http.ssl.keystore.secure_password # noqa: risky-shell-pipe + shell: > + if test -n "$(ps -p $$ | grep bash)"; then set -o pipefail; fi; + echo "{{ elasticsearch_tls_key_passphrase }}" | + /usr/share/elasticsearch/bin/elasticsearch-keystore + add -f -x 'xpack.security.http.ssl.keystore.secure_password' + changed_when: false + no_log: true + when: + - elasticsearch_http_ssl_keystore_secure_password.stdout is undefined or elasticsearch_tls_key_passphrase != elasticsearch_http_ssl_keystore_secure_password.stdout + - elasticsearch_http_security + notify: + - Restart Elasticsearch + +- name: Remove xpack.security.http.ssl.keystore.secure_password # noqa: risky-shell-pipe + shell: > + if test -n "$(ps -p $$ | grep bash)"; then set -o pipefail; fi; + /usr/share/elasticsearch/bin/elasticsearch-keystore + remove 'xpack.security.http.ssl.keystore.secure_password' + changed_when: false + no_log: true + when: + - "'xpack.security.http.ssl.keystore.secure_password' in elasticsearch_keystore.stdout_lines" + - not elasticsearch_http_security + notify: + - Restart Elasticsearch + +- name: Get xpack.security.http.ssl.truststore.secure_password # noqa: risky-shell-pipe + shell: > + if test -n "$(ps -p $$ | grep bash)"; then set -o pipefail; fi; + /usr/share/elasticsearch/bin/elasticsearch-keystore + show 'xpack.security.http.ssl.truststore.secure_password' + when: + - "'xpack.security.http.ssl.truststore.secure_password' in elasticsearch_keystore.stdout_lines" + - elasticsearch_http_security + register: elasticsearch_http_ssl_truststore_secure_password + ignore_errors: "{{ ansible_check_mode }}" + no_log: true + changed_when: false + +- name: Set xpack.security.http.ssl.truststore.secure_password # noqa: risky-shell-pipe + shell: > + if test -n "$(ps -p $$ | grep bash)"; then set -o pipefail; fi; + echo "{{ elasticsearch_tls_key_passphrase }}" | + /usr/share/elasticsearch/bin/elasticsearch-keystore + add -f -x 'xpack.security.http.ssl.truststore.secure_password' + changed_when: false + no_log: true + when: + - elasticsearch_http_ssl_truststore_secure_password.stdout is undefined or elasticsearch_tls_key_passphrase != elasticsearch_http_ssl_truststore_secure_password.stdout + - elasticsearch_http_security + notify: + - Restart Elasticsearch + +- name: Remove xpack.security.http.ssl.truststore.secure_password # noqa: risky-shell-pipe + shell: > + if test -n "$(ps -p $$ | grep bash)"; then set -o pipefail; fi; + /usr/share/elasticsearch/bin/elasticsearch-keystore + remove 'xpack.security.http.ssl.truststore.secure_password' + changed_when: false + no_log: true + when: + - "'xpack.security.http.ssl.truststore.secure_password' in elasticsearch_keystore.stdout_lines" + - not elasticsearch_http_security + notify: + - Restart Elasticsearch + +- name: Get xpack.security.transport.ssl.keystore.secure_password # noqa: risky-shell-pipe + shell: > + if test -n "$(ps -p $$ | grep bash)"; then set -o pipefail; fi; + /usr/share/elasticsearch/bin/elasticsearch-keystore + show 'xpack.security.transport.ssl.keystore.secure_password' + when: + - "'xpack.security.transport.ssl.keystore.secure_password' in elasticsearch_keystore.stdout_lines" + - elasticsearch_security + register: elasticsearch_transport_ssl_keystore_secure_password + ignore_errors: "{{ ansible_check_mode }}" + no_log: true + changed_when: false + +- name: Set xpack.security.transport.ssl.keystore.secure_password # noqa: risky-shell-pipe + shell: > + if test -n "$(ps -p $$ | grep bash)"; then set -o pipefail; fi; + echo "{{ elasticsearch_tls_key_passphrase }}" | + /usr/share/elasticsearch/bin/elasticsearch-keystore + add -f -x 'xpack.security.transport.ssl.keystore.secure_password' + changed_when: false + no_log: true + when: + - elasticsearch_transport_ssl_keystore_secure_password.stdout is undefined or elasticsearch_tls_key_passphrase != elasticsearch_transport_ssl_keystore_secure_password.stdout + - elasticsearch_security + notify: + - Restart Elasticsearch + +- name: Remove xpack.security.transport.ssl.keystore.secure_password # noqa: risky-shell-pipe + shell: > + if test -n "$(ps -p $$ | grep bash)"; then set -o pipefail; fi; + /usr/share/elasticsearch/bin/elasticsearch-keystore + remove 'xpack.security.transport.ssl.keystore.secure_password' + changed_when: false + no_log: true + when: + - "'xpack.security.transport.ssl.keystore.secure_password' in elasticsearch_keystore.stdout_lines" + - not elasticsearch_security + notify: + - Restart Elasticsearch + +- name: Get xpack.security.transport.ssl.truststore.secure_password # noqa: risky-shell-pipe + shell: > + if test -n "$(ps -p $$ | grep bash)"; then set -o pipefail; fi; + /usr/share/elasticsearch/bin/elasticsearch-keystore + show 'xpack.security.transport.ssl.truststore.secure_password' + when: + - "'xpack.security.transport.ssl.truststore.secure_password' in elasticsearch_keystore.stdout_lines" + - elasticsearch_security + register: elasticsearch_transport_ssl_truststore_secure_password + ignore_errors: "{{ ansible_check_mode }}" + no_log: true + changed_when: false + +- name: Set xpack.security.transport.ssl.truststore.secure_password # noqa: risky-shell-pipe + shell: > + if test -n "$(ps -p $$ | grep bash)"; then set -o pipefail; fi; + echo "{{ elasticsearch_tls_key_passphrase }}" | + /usr/share/elasticsearch/bin/elasticsearch-keystore + add -f -x 'xpack.security.transport.ssl.truststore.secure_password' + changed_when: false + no_log: true + when: + - elasticsearch_transport_ssl_truststore_secure_password.stdout is undefined or elasticsearch_tls_key_passphrase != elasticsearch_transport_ssl_truststore_secure_password.stdout + - elasticsearch_security + notify: + - Restart Elasticsearch + +- name: Remove xpack.security.transport.ssl.truststore.secure_password # noqa: risky-shell-pipe + shell: > + if test -n "$(ps -p $$ | grep bash)"; then set -o pipefail; fi; + /usr/share/elasticsearch/bin/elasticsearch-keystore + remove 'xpack.security.transport.ssl.truststore.secure_password' + changed_when: false + no_log: true + when: + - "'xpack.security.transport.ssl.truststore.secure_password' in elasticsearch_keystore.stdout_lines" + - not elasticsearch_security + notify: + - Restart Elasticsearch \ No newline at end of file diff --git a/roles/elasticsearch/tasks/elasticsearch-security.yml b/roles/elasticsearch/tasks/elasticsearch-security.yml index 316176cc..5c2ad97d 100644 --- a/roles/elasticsearch/tasks/elasticsearch-security.yml +++ b/roles/elasticsearch/tasks/elasticsearch-security.yml @@ -80,13 +80,13 @@ state: absent when: elasticsearch_move_ca_directory.changed - - name: Check the existance of ca on Ansible controler + - name: Check the existance of ca on Ansible controller stat: path: /tmp/ca.crt register: elasticsearch_check_temporary_ca delegate_to: localhost - - name: Move temporary ca file on Ansible controler + - name: Move temporary ca file on Ansible controller copy: src: /tmp/ca.crt dest: "/tmp/ca.crt_{{ ansible_date_time.iso8601_micro }}" @@ -95,7 +95,7 @@ delegate_to: localhost register: elasticsearch_move_ca_file - - name: Remove temporary ca file on Ansible controler + - name: Remove temporary ca file on Ansible controller file: path: /tmp/ca.crt state: absent @@ -521,7 +521,7 @@ state: started failed_when: false -- name: Wait for all instances to start +- name: Wait for all instances to startelasticstack_initial_passwords include_tasks: wait_for_instance.yml loop: "{{ groups['elasticsearch'] }}" tags: notest @@ -542,7 +542,7 @@ - renew_ca - renew_es_cert -- name: Check for passwords being set +- name: Check for if path for initial passwords being set stat: path: "{{ elasticstack_initial_passwords }}" delegate_to: "{{ elasticstack_ca }}" diff --git a/roles/elasticsearch/tasks/main.yml b/roles/elasticsearch/tasks/main.yml index b5928eb2..7c560bb5 100644 --- a/roles/elasticsearch/tasks/main.yml +++ b/roles/elasticsearch/tasks/main.yml @@ -127,35 +127,22 @@ when: - ansible_os_family == "Debian" -- name: Configure Elasticsearch - template: - src: elasticsearch.yml.j2 - dest: /etc/elasticsearch/elasticsearch.yml - owner: root - group: root - mode: 0644 - backup: "{{ elasticsearch_config_backup }}" - notify: - - Restart Elasticsearch - when: elasticsearch_manage_yaml | bool - -- name: Create Elasticsearch directory - file: - path: "{{ item.path }}" - state: directory - owner: elasticsearch - group: elasticsearch - mode: "2750" - when: item.create | bool - loop: - - {create: "{{elasticsearch_create_logpath}}", path: "{{ elasticsearch_logpath }}" } - - {create: "{{elasticsearch_create_datapath}}", path: "{{ elasticsearch_datapath }}" } +- name: Import Tasks elasticsearch-configuration.yml + import_tasks: elasticsearch-configuration.yml + when: + - elasticsearch_initial_setup | bool + - elasticsearch_manage_yaml | bool + +- name: Import Tasks elasticsearch-keystore.yml + import_tasks: elasticsearch-keystore.yml + when: elasticsearch_initial_setup | bool - name: Import Tasks elasticsearch-security.yml import_tasks: elasticsearch-security.yml when: - elasticsearch_security | bool - elasticstack_variant == "elastic" + - elasticsearch_initial_setup | bool tags: - certificates - renew_ca @@ -170,24 +157,6 @@ - Restart Elasticsearch when: elasticsearch_jna_workaround | bool -- name: Copy jvm.options File - become: yes - template: - src: "{{ elasticsearch_config_jvm }}" - dest: "{{ elasticsearch_conf_dir }}/jvm.options" - owner: root - group: "{{ elasticsearch_group }}" - mode: "660" - force: yes - notify: Restart Elasticsearch - -- name: Start Elasticsearch - service: - name: elasticsearch - state: started - enabled: yes - failed_when: false - - name: Handle cluster setup without security when: not elasticsearch_security | bool block: @@ -222,18 +191,6 @@ # See https://github.com/NETWAYS/ansible-collection-elasticstack/issues/137 # for details why we have this task again here # -- name: Configure Elasticsearch - template: - src: elasticsearch.yml.j2 - dest: /etc/elasticsearch/elasticsearch.yml - owner: root - group: root - mode: 0644 - backup: "{{ elasticsearch_config_backup }}" - notify: - - Restart Elasticsearch - when: elasticsearch_manage_yaml | bool - - name: Show Info about heap debug: msg: "Using {{ elasticsearch_heap | int * 1024 }} of {{ ansible_memtotal_mb }} MB as heap for Elasticsearch" From 883cc10536596182b1567f2cc00306722a582abd Mon Sep 17 00:00:00 2001 From: Daniel Neuberger Date: Mon, 14 Aug 2023 07:32:01 +0200 Subject: [PATCH 2/3] add variable for elasticsearch_initial_setup ; #227 --- roles/elasticsearch/defaults/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/roles/elasticsearch/defaults/main.yml b/roles/elasticsearch/defaults/main.yml index b7c690aa..6e893186 100644 --- a/roles/elasticsearch/defaults/main.yml +++ b/roles/elasticsearch/defaults/main.yml @@ -22,6 +22,7 @@ elasticsearch_conf_dir: "/etc/elasticsearch/" elasticsearch_config_jvm: "jvm.options.j2" elasticsearch_user: elasticsearch elasticsearch_group: elasticsearch +elasticsearch_initial_setup: true # elasticsearh security and api elasticsearch_api_basic_auth_username: 'elastic' # From 47862f7313c4b4652b98fc2662134ac2b754f3b6 Mon Sep 17 00:00:00 2001 From: Daniel Neuberger Date: Mon, 28 Aug 2023 13:52:33 +0200 Subject: [PATCH 3/3] include security handling from offical archived role; #227 --- .../security/elasticsearch-security-file.yml | 110 ++++++++++ .../elasticsearch-security-native.yml | 200 ++++++++++++++++++ .../tasks/security/elasticsearch-security.yml | 91 ++++++++ .../elasticsearch-xpack-activation.yml | 21 ++ .../elasticsearch-xpack-trial-activation.yml | 18 ++ 5 files changed, 440 insertions(+) create mode 100644 roles/elasticsearch/tasks/security/elasticsearch-security-file.yml create mode 100644 roles/elasticsearch/tasks/security/elasticsearch-security-native.yml create mode 100644 roles/elasticsearch/tasks/security/elasticsearch-security.yml create mode 100644 roles/elasticsearch/tasks/security/elasticsearch-xpack-activation.yml create mode 100644 roles/elasticsearch/tasks/security/elasticsearch-xpack-trial-activation.yml diff --git a/roles/elasticsearch/tasks/security/elasticsearch-security-file.yml b/roles/elasticsearch/tasks/security/elasticsearch-security-file.yml new file mode 100644 index 00000000..b169b9ee --- /dev/null +++ b/roles/elasticsearch/tasks/security/elasticsearch-security-file.yml @@ -0,0 +1,110 @@ +--- +- set_fact: manage_file_users=false + +- set_fact: manage_file_users=true + when: es_users is defined and es_users.file is defined and es_users.file.keys() | list | length > 0 + +# Users migration from elasticsearch < 6.3 versions +- name: Check if old users file exists + become: yes + stat: + path: '{{ es_conf_dir }}/x-pack/users' + register: old_users_file + check_mode: no + +- name: Copy the old users file from the old deprecated location + become: yes + copy: + remote_src: yes + force: no # only copy it if the new path doesn't exist yet + src: "{{ es_conf_dir }}/x-pack/users" + dest: "{{ es_conf_dir }}/users" + group: "{{ es_group }}" + owner: root + when: old_users_file.stat.exists +# End of users migrations + +#List current users +- name: List Users + become: yes + shell: cat {{ es_conf_dir }}/users | awk -F':' '{print $1}' + register: current_file_users + when: manage_file_users + changed_when: False + check_mode: no + +- name: set fact users_to_remove + set_fact: users_to_remove={{ current_file_users.stdout_lines | difference (es_users.file.keys() | list) }} + when: manage_file_users and es_delete_unmanaged_file + +#Remove users +- name: Remove Users + become: yes + command: > + {{es_home}}/bin/elasticsearch-users userdel {{item}} + with_items: "{{users_to_remove | default([])}}" + when: manage_file_users + environment: + CONF_DIR: "{{ es_conf_dir }}" + ES_PATH_CONF: "{{ es_conf_dir }}" + ES_HOME: "{{es_home}}" + +- name: set fact users_to_add + set_fact: users_to_add={{ es_users.file.keys() | list | difference (current_file_users.stdout_lines) }} + when: manage_file_users and es_delete_unmanaged_file + +#Add users +- name: Add Users + become: yes + command: > + {{es_home}}/bin/elasticsearch-users useradd {{item}} -p {{es_users.file[item].password}} + with_items: "{{ users_to_add | default([]) }}" + when: manage_file_users + no_log: True + environment: + CONF_DIR: "{{ es_conf_dir }}" + ES_PATH_CONF: "{{ es_conf_dir }}" + ES_HOME: "{{es_home}}" + +#Set passwords for all users declared - Required as the useradd will not change existing user passwords +- name: Set User Passwords + become: yes + command: > + {{es_home}}/bin/elasticsearch-users passwd {{ item }} -p {{es_users.file[item].password}} + with_items: "{{ es_users.file.keys() | list }}" + when: manage_file_users + #Currently no easy way to figure out if the password has changed or to know what it currently is so we can skip. + changed_when: False + no_log: True + environment: + CONF_DIR: "{{ es_conf_dir }}" + ES_PATH_CONF: "{{ es_conf_dir }}" + ES_HOME: "{{es_home}}" + +- name: set fact users_roles + set_fact: users_roles={{es_users.file | extract_role_users () }} + when: manage_file_users + +#Copy Roles files +- name: Copy roles.yml File for Instance + become: yes + template: + src: security/roles.yml.j2 + dest: "{{ es_conf_dir }}/roles.yml" + owner: root + group: "{{ es_group }}" + mode: "0660" + force: yes + when: es_roles is defined and es_roles.file is defined + +#Overwrite users_roles file +- name: Copy User Roles + become: yes + template: + src: security/users_roles.j2 + dest: "{{ es_conf_dir }}/users_roles" + owner: root + group: "{{ es_group }}" + mode: "0660" + force: yes + when: manage_file_users and users_roles | length > 0 diff --git a/roles/elasticsearch/tasks/security/elasticsearch-security-native.yml b/roles/elasticsearch/tasks/security/elasticsearch-security-native.yml new file mode 100644 index 00000000..f8502640 --- /dev/null +++ b/roles/elasticsearch/tasks/security/elasticsearch-security-native.yml @@ -0,0 +1,200 @@ +--- +- name: set fact change_api_password to false + set_fact: change_api_password=false + +- name: set fact manage_native_users to false + set_fact: manage_native_users=false + +- name: set fact manage_native_users to true + set_fact: manage_native_users=true + when: es_users is defined and es_users.native is defined and es_users.native.keys() | list | length > 0 + +- name: set fact manage_native_role to false + set_fact: manage_native_roles=false + +- name: set fact manage_native_roles to true + set_fact: manage_native_roles=true + when: es_roles is defined and es_roles.native is defined and es_roles.native.keys() | list | length > 0 + +#If the node has just has security installed it maybe either stopped or started 1. if stopped, we need to start to load native realms 2. if started, we need to restart to load + +#List current users +- name: List Native Users + uri: + url: "{{ es_api_uri }}/{{ es_security_api }}/user" + method: GET + user: "{{es_api_basic_auth_username}}" + password: "{{es_api_basic_auth_password}}" + force_basic_auth: yes + status_code: 200 + validate_certs: "{{ es_validate_certs }}" + register: user_list_response + when: manage_native_users + check_mode: no + +- name: set fact reserved_users equals user_list_response.json + set_fact: reserved_users={{ user_list_response.json | filter_reserved }} + when: manage_native_users + +#Current users not inc. those reserved +- name: set fact current_users equals user_list_response.json.keys not including reserved + set_fact: current_users={{ user_list_response.json.keys() | list | difference (reserved_users) }} + when: manage_native_users + +#We are changing the es_api_basic_auth_username password, so we need to do it first and update the param +- name: set fact native_users + set_fact: native_users={{ es_users.native }} + when: manage_native_users + +- name: set fact change_api_password to true + set_fact: change_api_password=true + when: manage_native_users and es_api_basic_auth_username in native_users and native_users[es_api_basic_auth_username].password is defined + +- name: Update API User Password + uri: + url: "{{ es_api_uri }}/{{ es_security_api }}/user/{{es_api_basic_auth_username}}/_password" + method: POST + body_format: json + body: "{ \"password\":\"{{native_users[es_api_basic_auth_username].password}}\" }" + status_code: 200 + user: "{{es_api_basic_auth_username}}" + password: "{{es_api_basic_auth_password}}" + force_basic_auth: yes + validate_certs: "{{ es_validate_certs }}" + when: change_api_password + +- name: set fact es_api_basic_auth_password + set_fact: es_api_basic_auth_password={{native_users[es_api_basic_auth_username].password}} + when: change_api_password + +#Identify users that are present in ES but not declared and thus should be removed +- name: set fact users_to_remove + set_fact: users_to_remove={{ current_users | difference ( native_users.keys() | list) }} + when: manage_native_users + +#Delete all non required users NOT inc. reserved +- name: Delete Native Users + uri: + url: "{{ es_api_uri }}/{{ es_security_api }}/user/{{item}}" + method: DELETE + status_code: 200 + user: "{{es_api_basic_auth_username}}" + password: "{{es_api_basic_auth_password}}" + force_basic_auth: yes + validate_certs: "{{ es_validate_certs }}" + when: manage_native_users and es_delete_unmanaged_native + with_items: "{{ users_to_remove | default([]) }}" + +- name: set fact users_to_ignore + set_fact: users_to_ignore={{ native_users.keys() | list | intersect (reserved_users) }} + when: manage_native_users + +- name: debug message + debug: + msg: "WARNING: YOU CAN ONLY CHANGE THE PASSWORD FOR RESERVED USERS IN THE NATIVE REALM. ANY ROLE CHANGES WILL BE IGNORED: {{users_to_ignore}}" + when: manage_native_users and users_to_ignore | length > 0 + +#Update password on all reserved users +- name: Update Reserved User Passwords + uri: + url: "{{ es_api_uri }}/{{ es_security_api }}/user/{{ item | urlencode }}/_password" + method: POST + body_format: json + body: "{ \"password\":\"{{native_users[item].password}}\" }" + status_code: 200 + user: "{{es_api_basic_auth_username}}" + password: "{{es_api_basic_auth_password}}" + force_basic_auth: yes + validate_certs: "{{ es_validate_certs }}" + when: native_users[item].password is defined + no_log: True + with_items: "{{ users_to_ignore | default([]) }}" + +- name: set fact users_to_modify + set_fact: users_to_modify={{ native_users.keys() | list | difference (reserved_users) }} + when: manage_native_users + +#Overwrite all other users NOT inc. those reserved +- name: Update Non-Reserved Native User Details + uri: + url: "{{ es_api_uri }}/{{ es_security_api }}/user/{{ item | urlencode }}" + method: POST + body_format: json + body: "{{ native_users[item] | to_json }}" + status_code: 200 + user: "{{es_api_basic_auth_username}}" + password: "{{es_api_basic_auth_password}}" + force_basic_auth: yes + validate_certs: "{{ es_validate_certs }}" + when: manage_native_users + no_log: True + with_items: "{{ users_to_modify | default([]) }}" + +## ROLE CHANGES + +#List current roles not. inc those reserved +- name: List Native Roles + uri: + url: "{{ es_api_uri }}/{{ es_security_api }}/role" + method: GET + user: "{{es_api_basic_auth_username}}" + password: "{{es_api_basic_auth_password}}" + force_basic_auth: yes + status_code: 200 + validate_certs: "{{ es_validate_certs }}" + register: role_list_response + when: manage_native_roles + check_mode: no + +- name: set fact reserved roles + set_fact: reserved_roles={{ role_list_response.json | filter_reserved }} + when: manage_native_roles + +- name: set fact current roles + set_fact: current_roles={{ role_list_response.json.keys() | list | difference (reserved_roles) }} + when: manage_native_roles + +- name: set fact roles to ignore + set_fact: roles_to_ignore={{ es_roles.native.keys() | list | intersect (reserved_roles) | default([]) }} + when: manage_native_roles + +- name: debug message + debug: + msg: "WARNING: YOU CANNOT CHANGE RESERVED ROLES. THE FOLLOWING WILL BE IGNORED: {{roles_to_ignore}}" + when: manage_native_roles and roles_to_ignore | length > 0 + +- name: set fact roles_to_remove + set_fact: roles_to_remove={{ current_roles | difference ( es_roles.native.keys() | list) }} + when: manage_native_roles + +#Delete all non required roles NOT inc. reserved +- name: Delete Native Roles + uri: + url: "{{ es_api_uri }}/{{ es_security_api }}/role/{{ item | urlencode }}" + method: DELETE + status_code: 200 + user: "{{es_api_basic_auth_username}}" + password: "{{es_api_basic_auth_password}}" + force_basic_auth: yes + validate_certs: "{{ es_validate_certs }}" + when: manage_native_roles and es_delete_unmanaged_native + with_items: "{{roles_to_remove | default([]) }}" + +- name: set fact roles_to_modify + set_fact: roles_to_modify={{ es_roles.native.keys() | list | difference (reserved_roles) }} + when: manage_native_roles + +#Update other roles - NOT inc. reserved roles +- name: Update Native Roles + uri: + url: "{{ es_api_uri }}/{{ es_security_api }}/role/{{ item | urlencode }}" + method: POST + body_format: json + body: "{{ es_roles.native[item] | to_json}}" + status_code: 200 + user: "{{es_api_basic_auth_username}}" + password: "{{es_api_basic_auth_password}}" + force_basic_auth: yes + validate_certs: "{{ es_validate_certs }}" + when: manage_native_roles + with_items: "{{ roles_to_modify | default([]) }}" diff --git a/roles/elasticsearch/tasks/security/elasticsearch-security.yml b/roles/elasticsearch/tasks/security/elasticsearch-security.yml new file mode 100644 index 00000000..184ab442 --- /dev/null +++ b/roles/elasticsearch/tasks/security/elasticsearch-security.yml @@ -0,0 +1,91 @@ +--- +#Security specific configuration done here + +#TODO: 1. Skip users with no password defined or error 2. Passwords | length > 6 + +#-----------------------------Create Bootstrap User----------------------------------- +### START BLOCK elasticsearch keystore ### +- name: create the elasticsearch keystore + block: + - name: create the keystore if it doesn't exist yet + become: yes + command: > + {{es_home}}/bin/elasticsearch-keystore create + args: + creates: "{{ es_conf_dir }}/elasticsearch.keystore" + environment: + ES_PATH_CONF: "{{ es_conf_dir }}" + + - name: Check if bootstrap password is set + become: yes + command: > + {{es_home}}/bin/elasticsearch-keystore list + register: list_keystore + changed_when: False + environment: + ES_PATH_CONF: "{{ es_conf_dir }}" + check_mode: no + + - name: Create Bootstrap password for elastic user + become: yes + shell: echo {{ es_api_basic_auth_password | quote }} | {{ es_home }}/bin/elasticsearch-keystore add -x 'bootstrap.password' + when: + - es_api_basic_auth_username is defined and list_keystore is defined and es_api_basic_auth_username == 'elastic' and 'bootstrap.password' not in list_keystore.stdout_lines + environment: + ES_PATH_CONF: "{{ es_conf_dir }}" + no_log: true + + - name: Remove keystore entries + become: yes + command: > + {{ es_home }}/bin/elasticsearch-keystore remove '{{ item.key }}' + with_items: "{{ es_keystore_entries }}" + when: + - es_keystore_entries is defined and es_keystore_entries | length > 0 + - item.state is defined and item.state == 'absent' + - item.key in list_keystore.stdout_lines + - ('bootstrap.password' not in item.key) + no_log: true + + - name: Reload keystore entries + become: yes + command: > + {{es_home}}/bin/elasticsearch-keystore list + register: list_keystore + changed_when: False + environment: + ES_PATH_CONF: "{{ es_conf_dir }}" + check_mode: no + + - name: Add keystore entries + become: yes + shell: echo {{ item.value | quote }} | {{ es_home }}/bin/elasticsearch-keystore add -x -f {{ item.key }} + with_items: "{{ es_keystore_entries }}" + when: + - es_keystore_entries is defined and es_keystore_entries | length > 0 + - item.state is undefined or item.state == 'present' + - item.force|default(False) or ( not item.force|default(False) and item.key not in list_keystore.stdout_lines ) + - ('bootstrap.password' not in item.key) + no_log: true + + +### END BLOCK elasticsearch keystore ### + +#-----------------------------FILE BASED REALM---------------------------------------- + +- include: elasticsearch-security-file.yml + when: (es_users is defined and es_users.file is defined) or (es_roles is defined and es_roles.file is defined) + +#-----------------------------ROLE MAPPING ---------------------------------------- + +#Copy Roles files +- name: Copy role_mapping.yml file for instance + become: yes + template: + src: security/role_mapping.yml.j2 + dest: "{{ es_conf_dir }}/role_mapping.yml" + owner: root + group: "{{ es_group }}" + mode: "0660" + force: yes + when: es_role_mapping is defined diff --git a/roles/elasticsearch/tasks/security/elasticsearch-xpack-activation.yml b/roles/elasticsearch/tasks/security/elasticsearch-xpack-activation.yml new file mode 100644 index 00000000..5bf08cb6 --- /dev/null +++ b/roles/elasticsearch/tasks/security/elasticsearch-xpack-activation.yml @@ -0,0 +1,21 @@ +--- +- name: Activate ES license (with security authentication) + uri: + method: PUT + url: "{{ es_api_uri }}/{{ es_license_api }}?acknowledge=true" + user: "{{es_api_basic_auth_username | default(omit)}}" + password: "{{es_api_basic_auth_password | default(omit)}}" + body_format: json + body: "{{ es_xpack_license }}" + return_content: yes + force_basic_auth: yes + validate_certs: "{{ es_validate_certs }}" + register: license_activated + no_log: True + failed_when: > + license_activated.status != 200 or + license_activated.json.license_status is not defined or + license_activated.json.license_status != 'valid' + +- name: License + debug: msg={{ license_activated }} diff --git a/roles/elasticsearch/tasks/security/elasticsearch-xpack-trial-activation.yml b/roles/elasticsearch/tasks/security/elasticsearch-xpack-trial-activation.yml new file mode 100644 index 00000000..e0cc73e0 --- /dev/null +++ b/roles/elasticsearch/tasks/security/elasticsearch-xpack-trial-activation.yml @@ -0,0 +1,18 @@ +--- +- name: Activate ES trial license (with security authentication) + uri: + method: POST + url: "{{ es_api_uri }}/{{ es_license_api }}/start_trial?acknowledge=true" + user: "{{es_api_basic_auth_username | default(omit)}}" + password: "{{es_api_basic_auth_password | default(omit)}}" + return_content: yes + force_basic_auth: yes + status_code: + - 200 + - 403 + validate_certs: "{{ es_validate_certs }}" + register: trial_license_activated + when: es_xpack_trial + +- name: Trial license + debug: msg={{ trial_license_activated }} \ No newline at end of file