diff --git a/elk_metrics_7x/README.rst b/elk_metrics_7x/README.rst
index d08b6af0..db32f2c7 100644
--- a/elk_metrics_7x/README.rst
+++ b/elk_metrics_7x/README.rst
@@ -635,3 +635,84 @@ all `elk_metrics_7x` related services within the local test environment.
 .. code-block:: bash
 
    tests/run-cleanup.sh
+
+
+Enabling ELK security
+---------------------
+
+By default, ELK 7 is deployed without security enabled. This means that all
+service and user interactions are unauthenticated, and communication is
+unencrypted.
+
+If you wish to enable security features, it is recommended to start with a
+deployed cluster with security disabled, before following these steps. Note
+that this is a multi-stage process and requires unavoidable downtime.
+
+https://www.elastic.co/guide/en/elasticsearch/reference/7.17/security-basic-setup.html#generate-certificates
+
+* Generate a certificate authority which is unique to the Elastic cluster.
+  Ensure you set a password against the certificate bundle.
+
+* Generate a key and certificate for ElasticSearch instances. You may use a
+  single bundle for all hosts, or unique bundles if preferred. Again, set a
+  password against these.
+
+* Store the CA bundle securely, and configure the following elasticsearch
+  Ansible role variables. Note that it may be useful to base64 encode and
+  decode the binary certificate bundle files.
+  elastic_security_enabled: True
+  elastic_security_cert_bundle: "cert-bundle-contents"
+  elastic_security_cert_password: "cert-bundle-password"
+
+* Stop all Elasticsearch services.
+
+* Run the 'installElastic.yml' playbook against all cluster nodes. This will
+  enable security features, but will halt log ingest and monitoring tasks
+  due to missing authentication credentials.
+
+https://www.elastic.co/guide/en/elasticsearch/reference/7.17/security-minimal-setup.html#security-create-builtin-users
+
+* Generate usernames and passwords for key ELK services. Store the output
+  securely and set up the following Ansible variables. Note that the
+  credentials for system users are generated for you.
+
+  For Kibana hosts, set the following variables:
+  kibana_system_username
+  kibana_system_password
+  kibana_setup_username (*)
+  kibana_setup_password (*)
+
+  For Logstash hosts, set the following variables:
+  logstash_system_username
+  logstash_system_password
+  logstash_internal_username (*)
+  logstash_internal_password (*)
+
+  For Beats hosts, set the following variables:
+  beats_system_username
+  beats_system_password
+  beats_setup_username (*)
+  beats_setup_password (*)
+
+  (*) Users marked with a star are not generated automatically. These must be
+  set up manually via the Kibana interface once it has been configured. In
+  order for the Kibana playbook to run successfully, the 'elastic' superuser
+  can be used initially as the 'kibana_setup_username/password'.
+
+  kibana_setup - any user which is assigned the built in kibana_admin role
+  logstash_internal - see https://www.elastic.co/guide/en/logstash/7.17/ls-security.html#ls-http-auth-basic
+  beats_setup - see setup role at https://www.elastic.co/guide/en/beats/filebeat/7.17/feature-roles.html
+              - this user must also be assigned the built in ingest_admin role
+
+* Set 'kibana_object_encryption_key' to a string with a minimum length of 32
+  bytes.
+
+* Run the 'installKibana.yml' playbook against Kibana hosts. This will complete
+  their configuration and should allow you to log in to the web interface using
+  the 'elastic' user generated earlier.
+
+* Set up any additional users required by Logstash, Beats or others via the
+  Kibana interface and set their variables as noted above.
+
+* Complete deployment by running the 'installLogstash.yml' and Beat install
+  playbooks.
diff --git a/elk_metrics_7x/fieldRefresh.yml b/elk_metrics_7x/fieldRefresh.yml
index de64c886..651b728f 100644
--- a/elk_metrics_7x/fieldRefresh.yml
+++ b/elk_metrics_7x/fieldRefresh.yml
@@ -35,6 +35,9 @@
         headers:
           Content-Type: "application/json"
           kbn-xsrf: "{{ inventory_hostname | to_uuid }}"
+        url_username: "{{ kibana_setup_username | default(omit) }}"
+        url_password: "{{ kibana_setup_password | default(omit) }}"
+        force_basic_auth: "{{ kibana_setup_username is defined }}"
       register: index_fields_return
       until: index_fields_return is success
       retries: 6
@@ -51,6 +54,9 @@
         headers:
           Content-Type: "application/json"
           kbn-xsrf: "{{ inventory_hostname | to_uuid }}"
+        url_username: "{{ kibana_setup_username | default(omit) }}"
+        url_password: "{{ kibana_setup_password | default(omit) }}"
+        force_basic_auth: "{{ kibana_setup_username is defined }}"
       register: index_fields_format_return
       until: index_fields_format_return is success
       retries: 6
@@ -83,6 +89,9 @@
             headers:
               Content-Type: "application/json"
               kbn-xsrf: "{{ inventory_hostname | to_uuid }}"
+            url_username: "{{ kibana_setup_username | default(omit) }}"
+            url_password: "{{ kibana_setup_password | default(omit) }}"
+            force_basic_auth: "{{ kibana_setup_username is defined }}"
           register: index_fields_return
           until: index_fields_return is success
           retries: 6
diff --git a/elk_metrics_7x/roles/elastic_auditbeat/defaults/main.yml b/elk_metrics_7x/roles/elastic_auditbeat/defaults/main.yml
index 8d9bb8e6..73e8ecb1 100644
--- a/elk_metrics_7x/roles/elastic_auditbeat/defaults/main.yml
+++ b/elk_metrics_7x/roles/elastic_auditbeat/defaults/main.yml
@@ -42,3 +42,7 @@ auditbeat_ignore_socket_data: false
 # set, templates are only pushed when the user is either upgrading the
 # beat version or deploying for the first time in the presence of kibana nodes
 elk_beat_setup: false
+
+# Authentication credentials for monitoring when using ELK security features
+# beats_system_username: ""
+# beats_system_password: ""
diff --git a/elk_metrics_7x/roles/elastic_auditbeat/templates/auditbeat.yml.j2 b/elk_metrics_7x/roles/elastic_auditbeat/templates/auditbeat.yml.j2
index 5fe36e4e..e3cf7b75 100644
--- a/elk_metrics_7x/roles/elastic_auditbeat/templates/auditbeat.yml.j2
+++ b/elk_metrics_7x/roles/elastic_auditbeat/templates/auditbeat.yml.j2
@@ -1155,7 +1155,7 @@ setup.ilm.policy_file: "{{ ilm_policy_file_location }}/{{ ilm_policy_filename }}
 {{ elk_macros.beat_logging('auditbeat', auditbeat_log_level) }}
 
 # ============================= X-Pack Monitoring ==============================
-{{ elk_macros.xpack_monitoring_elasticsearch('auditbeat', inventory_hostname, elasticsearch_data_hosts, ansible_processor_count) }}
+{{ elk_macros.xpack_monitoring_elasticsearch('auditbeat', inventory_hostname, elasticsearch_data_hosts, ansible_processor_count, beats_system_username) }}
 
 # =============================== HTTP Endpoint ================================
 
diff --git a/elk_metrics_7x/roles/elastic_beat_setup/defaults/main.yml b/elk_metrics_7x/roles/elastic_beat_setup/defaults/main.yml
index d6c0b4ee..56adec96 100644
--- a/elk_metrics_7x/roles/elastic_beat_setup/defaults/main.yml
+++ b/elk_metrics_7x/roles/elastic_beat_setup/defaults/main.yml
@@ -23,6 +23,8 @@ elastic_setup_flags:
 elastic_beat_setup_options: >-
   -E 'output.logstash.enabled=false'
   -E 'output.elasticsearch.hosts={{ coordination_nodes | to_json }}'
+  {{ (beats_setup_username is defined) | ternary("-E 'output.elasticsearch.username=" ~ beats_setup_username | default("") ~ "'", "") }}
+  {{ (beats_setup_password is defined) | ternary("-E 'output.elasticsearch.password=" ~ beats_setup_password | default("") ~ "'", "") }}
   -E 'setup.template.enabled=true'
   -E 'setup.template.overwrite=true'
 
@@ -37,3 +39,11 @@ elastic_beat_no_proxy: "{{ elastic_beat_kibana_host }}"
 # set, templates are only pushed when the user is either upgrading the
 # beat version or deploying for the first time in the presence of kibana nodes
 elk_beat_setup: false
+
+# Username and password for beat setup when using ELK security
+# beats_setup_username: ""
+# beats_setup_password: ""
+
+# Authentication credentials for monitoring when using ELK security features
+# beats_system_username: ""
+# beats_system_password: ""
diff --git a/elk_metrics_7x/roles/elastic_beat_setup/tasks/main.yml b/elk_metrics_7x/roles/elastic_beat_setup/tasks/main.yml
index 3628de4e..1d5e2a25 100644
--- a/elk_metrics_7x/roles/elastic_beat_setup/tasks/main.yml
+++ b/elk_metrics_7x/roles/elastic_beat_setup/tasks/main.yml
@@ -58,6 +58,7 @@
   until: templates is success
   retries: 5
   delay: 5
+  no_log: True
   when:
     - (((ansible_local['elastic']['setup'][elastic_beat_name + '_loaded_templates'] is undefined) or
        (not (ansible_local['elastic']['setup'][elastic_beat_name + '_loaded_templates'] | bool))) or
@@ -76,3 +77,13 @@
     - templates is changed
   tags:
     - setup
+
+- name: Set xpack authentication password
+  shell: "echo '{{ beats_system_password }}' | {{ elastic_beat_name }} keystore add {{ beats_system_username }} --stdin --force"
+  no_log: True
+  changed_when: False
+  when:
+    - beats_system_username is defined
+    - beats_system_password is defined
+  tags:
+    - setup
diff --git a/elk_metrics_7x/roles/elastic_filebeat/defaults/main.yml b/elk_metrics_7x/roles/elastic_filebeat/defaults/main.yml
index a28cb810..1f076a38 100644
--- a/elk_metrics_7x/roles/elastic_filebeat/defaults/main.yml
+++ b/elk_metrics_7x/roles/elastic_filebeat/defaults/main.yml
@@ -318,3 +318,7 @@ filebeat_iptables_log_paths: ["var/log/syslog"]
 # set, templates are only pushed when the user is either upgrading the
 # beat version or deploying for the first time in the presence of kibana nodes
 elk_beat_setup: false
+
+# Authentication credentials for monitoring when using ELK security features
+# beats_system_username: ""
+# beats_system_password: ""
diff --git a/elk_metrics_7x/roles/elastic_filebeat/templates/filebeat.yml.j2 b/elk_metrics_7x/roles/elastic_filebeat/templates/filebeat.yml.j2
index 033c9652..24eeeb36 100644
--- a/elk_metrics_7x/roles/elastic_filebeat/templates/filebeat.yml.j2
+++ b/elk_metrics_7x/roles/elastic_filebeat/templates/filebeat.yml.j2
@@ -2048,7 +2048,7 @@ setup.ilm.policy_file: "{{ ilm_policy_file_location }}/{{ ilm_policy_filename }}
 {{ elk_macros.beat_logging('filebeat', filebeat_log_level) }}
 
 # ============================= X-Pack Monitoring ==============================
-{{ elk_macros.xpack_monitoring_elasticsearch('filebeat', inventory_hostname, elasticsearch_data_hosts, ansible_processor_count) }}
+{{ elk_macros.xpack_monitoring_elasticsearch('filebeat', inventory_hostname, elasticsearch_data_hosts, ansible_processor_count, beats_system_username) }}
 
 # =============================== HTTP Endpoint ================================
 
diff --git a/elk_metrics_7x/roles/elastic_heartbeat/defaults/main.yml b/elk_metrics_7x/roles/elastic_heartbeat/defaults/main.yml
index 2fe60e70..a798d6f1 100644
--- a/elk_metrics_7x/roles/elastic_heartbeat/defaults/main.yml
+++ b/elk_metrics_7x/roles/elastic_heartbeat/defaults/main.yml
@@ -41,3 +41,7 @@ heartbeat_log_level: "{{ elastic_beat_log_level | default('info') }}"
 # set, templates are only pushed when the user is either upgrading the
 # beat version or deploying for the first time in the presence of kibana nodes
 elk_beat_setup: false
+
+# Authentication credentials for monitoring when using ELK security features
+# beats_system_username: ""
+# beats_system_password: ""
diff --git a/elk_metrics_7x/roles/elastic_heartbeat/templates/heartbeat.yml.j2 b/elk_metrics_7x/roles/elastic_heartbeat/templates/heartbeat.yml.j2
index 192da36c..f41c6b9a 100644
--- a/elk_metrics_7x/roles/elastic_heartbeat/templates/heartbeat.yml.j2
+++ b/elk_metrics_7x/roles/elastic_heartbeat/templates/heartbeat.yml.j2
@@ -1282,7 +1282,7 @@ setup.ilm.policy_file: "{{ ilm_policy_file_location }}/{{ ilm_policy_filename }}
 {{ elk_macros.beat_logging('heartbeat', heartbeat_log_level) }}
 
 # ============================= X-Pack Monitoring ==============================
-{{ elk_macros.xpack_monitoring_elasticsearch('heartbeat', inventory_hostname, elasticsearch_data_hosts, ansible_processor_count) }}
+{{ elk_macros.xpack_monitoring_elasticsearch('heartbeat', inventory_hostname, elasticsearch_data_hosts, ansible_processor_count, beats_system_username) }}
 
 # =============================== HTTP Endpoint ================================
 
diff --git a/elk_metrics_7x/roles/elastic_ilm/defaults/main.yml b/elk_metrics_7x/roles/elastic_ilm/defaults/main.yml
index c343488f..6ce326fb 100644
--- a/elk_metrics_7x/roles/elastic_ilm/defaults/main.yml
+++ b/elk_metrics_7x/roles/elastic_ilm/defaults/main.yml
@@ -18,3 +18,6 @@ default_ilm_policy_filename: "default-ilm-policy.json"
 default_ilm_policy_file_location: "/tmp"
 
 elastic_beat_no_proxy: "{{ hostvars[groups['elastic'][0]]['ansible_host'] }}"
+
+# beats_setup_username: ""
+# beats_setup_password: ""
diff --git a/elk_metrics_7x/roles/elastic_ilm/tasks/elastic_ilm_update_policy.yml b/elk_metrics_7x/roles/elastic_ilm/tasks/elastic_ilm_update_policy.yml
index 9b30ef8a..1154c5d9 100644
--- a/elk_metrics_7x/roles/elastic_ilm/tasks/elastic_ilm_update_policy.yml
+++ b/elk_metrics_7x/roles/elastic_ilm/tasks/elastic_ilm_update_policy.yml
@@ -4,6 +4,8 @@
     url: "http://{{ elasticsearch_data_node_details[0] }}/_ilm/policy/{{ ilm_policy_name }}"
     method: GET
     status_code: 200,404
+    url_username: "{{ beats_setup_username | default(omit) }}"
+    url_password: "{{ beats_setup_password | default(omit) }}"
   register: check_policy
   when: ilm_policy_name is defined and ilm_policy is defined
 
@@ -14,6 +16,8 @@
     body: "{{ ilm_policy }}"
     status_code: 200
     body_format: json
+    url_username: "{{ beats_setup_username | default(omit) }}"
+    url_password: "{{ beats_setup_password | default(omit) }}"
   when: check_policy.status == 200 and ilm_policy_name is defined and ilm_policy is defined and (elk_package_state | default('present')) != "latest"
 
 
@@ -25,6 +29,8 @@
     body: "{{ ilm_policy }}"
     status_code: 200
     body_format: json
+    url_username: "{{ beats_setup_username | default(omit) }}"
+    url_password: "{{ beats_setup_password | default(omit) }}"
   when: check_policy.status == 404 and ilm_policy_name is defined and ilm_policy is defined and (elk_package_state | default('present')) != "latest"
 
 
@@ -33,6 +39,8 @@
     url: "http://{{ elasticsearch_data_node_details[0] }}/_template/{{ ilm_policy_template }}/"
     method: GET
     status_code: 200,404
+    url_username: "{{ beats_setup_username | default(omit) }}"
+    url_password: "{{ beats_setup_password | default(omit) }}"
   register: template
   when: ilm_policy_template is defined and ilm_policy_name is defined
 
@@ -50,4 +58,6 @@
     headers:
       Content-Type: "application/json"
       kbn-xsrf: "{{ inventory_hostname | to_uuid }}"
+    url_username: "{{ beats_setup_username | default(omit) }}"
+    url_password: "{{ beats_setup_password | default(omit) }}"
   when: template.status == 200 and ilm_policy_template is defined and ilm_policy_name is defined
diff --git a/elk_metrics_7x/roles/elastic_journalbeat/defaults/main.yml b/elk_metrics_7x/roles/elastic_journalbeat/defaults/main.yml
index 15089827..5fe93c83 100644
--- a/elk_metrics_7x/roles/elastic_journalbeat/defaults/main.yml
+++ b/elk_metrics_7x/roles/elastic_journalbeat/defaults/main.yml
@@ -51,3 +51,7 @@ journalbeat_seek: head
 # set, templates are only pushed when the user is either upgrading the
 # beat version or deploying for the first time in the presence of kibana nodes
 elk_beat_setup: false
+
+# Authentication credentials for monitoring when using ELK security features
+# beats_system_username: ""
+# beats_system_password: ""
diff --git a/elk_metrics_7x/roles/elastic_journalbeat/templates/journalbeat.yml.j2 b/elk_metrics_7x/roles/elastic_journalbeat/templates/journalbeat.yml.j2
index 9d68f0c4..f7ed0233 100644
--- a/elk_metrics_7x/roles/elastic_journalbeat/templates/journalbeat.yml.j2
+++ b/elk_metrics_7x/roles/elastic_journalbeat/templates/journalbeat.yml.j2
@@ -1019,7 +1019,7 @@ setup.ilm.policy_file: "{{ ilm_policy_file_location }}/{{ ilm_policy_filename }}
 {{ elk_macros.beat_logging('journalbeat', journalbeat_log_level) }}
 
 # ============================= X-Pack Monitoring ==============================
-{{ elk_macros.xpack_monitoring_elasticsearch('journalbeat', inventory_hostname, elasticsearch_data_hosts, ansible_processor_count) }}
+{{ elk_macros.xpack_monitoring_elasticsearch('journalbeat', inventory_hostname, elasticsearch_data_hosts, ansible_processor_count, beats_system_username) }}
 
 # =============================== HTTP Endpoint ================================
 
diff --git a/elk_metrics_7x/roles/elastic_kibana/defaults/main.yml b/elk_metrics_7x/roles/elastic_kibana/defaults/main.yml
index 8c429108..8e90a4d0 100644
--- a/elk_metrics_7x/roles/elastic_kibana/defaults/main.yml
+++ b/elk_metrics_7x/roles/elastic_kibana/defaults/main.yml
@@ -39,3 +39,9 @@ kibana_elastic_endpoints:
 
 # The URL which users access Kibana from
 # kibana_base_url: ""
+
+# The following are required to grant Kibana access to Elasticsearch
+# when security is enabled. The preferred method for setting the
+# password is to use the Kibana keystore.
+# kibana_system_username: ""
+# kibana_system_password: ""
diff --git a/elk_metrics_7x/roles/elastic_kibana/tasks/main.yml b/elk_metrics_7x/roles/elastic_kibana/tasks/main.yml
index dc5b0dda..727237b4 100644
--- a/elk_metrics_7x/roles/elastic_kibana/tasks/main.yml
+++ b/elk_metrics_7x/roles/elastic_kibana/tasks/main.yml
@@ -82,3 +82,21 @@
     mode: "0666"
   notify:
     - Enable and restart kibana services
+
+- name: Set authentication password
+  shell: "echo '{{ kibana_system_password }}' | /usr/share/kibana/bin/kibana-keystore add elasticsearch.password --stdin --force"
+  no_log: True
+  changed_when: False
+  when:
+    - kibana_system_username is defined
+    - kibana_system_password is defined
+
+- name: Set permissions on keystore
+  file:
+    path: "/etc/kibana/kibana.keystore"
+    group: "kibana"
+    owner: "root"
+    mode: "0660"
+  when:
+    - kibana_system_username is defined
+    - kibana_system_password is defined
diff --git a/elk_metrics_7x/roles/elastic_kibana/templates/kibana.yml.j2 b/elk_metrics_7x/roles/elastic_kibana/templates/kibana.yml.j2
index c7c99642..0f3e7dd4 100644
--- a/elk_metrics_7x/roles/elastic_kibana/templates/kibana.yml.j2
+++ b/elk_metrics_7x/roles/elastic_kibana/templates/kibana.yml.j2
@@ -49,7 +49,9 @@ elasticsearch.hosts: {{ kibana_elastic_endpoints  | to_json }}
 # the username and password that the Kibana server uses to perform maintenance on the Kibana
 # index at startup. Your Kibana users still need to authenticate with Elasticsearch, which
 # is proxied through the Kibana server.
-#elasticsearch.username: "user"
+{% if kibana_system_username is defined %}
+elasticsearch.username: "{{ kibana_system_username }}"
+{% endif %}
 #elasticsearch.password: "pass"
 
 # Enables SSL and paths to the PEM-format SSL certificate and SSL key files, respectively.
@@ -135,3 +137,6 @@ xpack.security.encryptionKey: {{ kibana_security_encryption_key }}
 {% if kibana_reporting_encryption_key is defined %}
 xpack.reporting.encryptionKey: {{ kibana_reporting_encryption_key }}
 {% endif %}
+{% if kibana_object_encryption_key is defined %}
+xpack.encryptedSavedObjects.encryptionKey: {{ kibana_object_encryption_key }}
+{% endif %}
diff --git a/elk_metrics_7x/roles/elastic_logstash/defaults/main.yml b/elk_metrics_7x/roles/elastic_logstash/defaults/main.yml
index 3ddc4d41..eed88d68 100644
--- a/elk_metrics_7x/roles/elastic_logstash/defaults/main.yml
+++ b/elk_metrics_7x/roles/elastic_logstash/defaults/main.yml
@@ -105,3 +105,13 @@ logstash_collectd_security_level: Sign
 
 # Set the descriptive name by which Logstash is identified
 logstash_node_name: "{{ inventory_hostname }}"
+
+# Username and password for XPack monitoring when security
+# is enabled
+# logstash_system_username: ""
+# logstash_system_password: ""
+
+# Username and password for Elasticsearch writes when security
+# is enabled
+# logstash_internal_username: ""
+# logstash_internal_password: ""
diff --git a/elk_metrics_7x/roles/elastic_logstash/tasks/main.yml b/elk_metrics_7x/roles/elastic_logstash/tasks/main.yml
index f42d9359..2bb341de 100644
--- a/elk_metrics_7x/roles/elastic_logstash/tasks/main.yml
+++ b/elk_metrics_7x/roles/elastic_logstash/tasks/main.yml
@@ -223,6 +223,35 @@
     group: "logstash"
     mode: "0750"
 
+- name: Check whether Logstash keystore exists
+  shell: "/usr/share/logstash/bin/logstash-keystore list --path.settings /etc/logstash"
+  changed_when: False
+  failed_when: False
+  register: logstash_keystore
+
+- name: Create Logstash keystore if required
+  expect:
+    command: "/usr/share/logstash/bin/logstash-keystore create --path.settings /etc/logstash"
+    responses:
+      WARNING: y
+  when: logstash_keystore.rc == 1
+
+- name: Set xpack authentication password
+  shell: "echo '{{ logstash_system_password }}' | /usr/share/logstash/bin/logstash-keystore add {{ logstash_system_username }} --stdin --force --path.settings /etc/logstash"
+  no_log: True
+  changed_when: False
+  when:
+    - logstash_system_username is defined
+    - logstash_system_password is defined
+
+- name: Set pipeline authentication password
+  shell: "echo '{{ logstash_internal_password }}' | /usr/share/logstash/bin/logstash-keystore add {{ logstash_internal_username }} --stdin --force  --path.settings /etc/logstash"
+  no_log: True
+  changed_when: False
+  when:
+    - logstash_internal_username is defined
+    - logstash_internal_password is defined
+
 - name: Deploy arcsight collector
   include_tasks: logstash_arcsight.yml
   when:
diff --git a/elk_metrics_7x/roles/elastic_logstash/templates/logstash.yml.j2 b/elk_metrics_7x/roles/elastic_logstash/templates/logstash.yml.j2
index 9e14c7a4..5fc5b542 100644
--- a/elk_metrics_7x/roles/elastic_logstash/templates/logstash.yml.j2
+++ b/elk_metrics_7x/roles/elastic_logstash/templates/logstash.yml.j2
@@ -229,8 +229,10 @@ path.logs: /var/log/logstash
 # X-Pack Monitoring
 # https://www.elastic.co/guide/en/logstash/current/monitoring-logstash.html
 xpack.monitoring.enabled: true
-#xpack.monitoring.elasticsearch.username: logstash_system
-#xpack.monitoring.elasticsearch.password: password
+{% if logstash_system_username is defined %}
+xpack.monitoring.elasticsearch.username: {{ logstash_system_username }}
+xpack.monitoring.elasticsearch.password: ${% raw %}{{% endraw %}{{ logstash_system_username }}{% raw %}}{% endraw +%}
+{% endif %}
 xpack.monitoring.elasticsearch.hosts: {{ logstash_elasticsearch_endpoints | to_json }}
 #xpack.monitoring.elasticsearch.ssl.certificate_authority: [ "/path/to/ca.crt" ]
 #xpack.monitoring.elasticsearch.ssl.truststore.path: path/to/file
diff --git a/elk_metrics_7x/roles/elastic_logstash/vars/redhat.yml b/elk_metrics_7x/roles/elastic_logstash/vars/redhat.yml
index 0d0e66ee..f988d21f 100644
--- a/elk_metrics_7x/roles/elastic_logstash/vars/redhat.yml
+++ b/elk_metrics_7x/roles/elastic_logstash/vars/redhat.yml
@@ -16,5 +16,6 @@
 logstash_distro_packages:
   - logrotate
   - logstash
+  - python3-pexpect
 
 logstash_sysconfig_path: /etc/default/logstash
diff --git a/elk_metrics_7x/roles/elastic_logstash/vars/suse.yml b/elk_metrics_7x/roles/elastic_logstash/vars/suse.yml
index 0d0e66ee..f988d21f 100644
--- a/elk_metrics_7x/roles/elastic_logstash/vars/suse.yml
+++ b/elk_metrics_7x/roles/elastic_logstash/vars/suse.yml
@@ -16,5 +16,6 @@
 logstash_distro_packages:
   - logrotate
   - logstash
+  - python3-pexpect
 
 logstash_sysconfig_path: /etc/default/logstash
diff --git a/elk_metrics_7x/roles/elastic_logstash/vars/ubuntu-18.04.yml b/elk_metrics_7x/roles/elastic_logstash/vars/ubuntu-18.04.yml
new file mode 100644
index 00000000..8ffbbeda
--- /dev/null
+++ b/elk_metrics_7x/roles/elastic_logstash/vars/ubuntu-18.04.yml
@@ -0,0 +1,21 @@
+---
+# Copyright 2018, 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.
+
+logstash_distro_packages:
+  - logrotate
+  - logstash
+  - python-pexpect
+
+logstash_sysconfig_path: /etc/default/logstash
diff --git a/elk_metrics_7x/roles/elastic_logstash/vars/ubuntu.yml b/elk_metrics_7x/roles/elastic_logstash/vars/ubuntu.yml
index 0d0e66ee..f988d21f 100644
--- a/elk_metrics_7x/roles/elastic_logstash/vars/ubuntu.yml
+++ b/elk_metrics_7x/roles/elastic_logstash/vars/ubuntu.yml
@@ -16,5 +16,6 @@
 logstash_distro_packages:
   - logrotate
   - logstash
+  - python3-pexpect
 
 logstash_sysconfig_path: /etc/default/logstash
diff --git a/elk_metrics_7x/roles/elastic_metricbeat/defaults/main.yml b/elk_metrics_7x/roles/elastic_metricbeat/defaults/main.yml
index ede1d4c0..97bf0bcf 100644
--- a/elk_metrics_7x/roles/elastic_metricbeat/defaults/main.yml
+++ b/elk_metrics_7x/roles/elastic_metricbeat/defaults/main.yml
@@ -48,3 +48,7 @@ metricbeat_log_level: "{{ elastic_beat_log_level | default('info') }}"
 # set, templates are only pushed when the user is either upgrading the
 # beat version or deploying for the first time in the presence of kibana nodes
 elk_beat_setup: false
+
+# Authentication credentials for monitoring when using ELK security features
+# beats_system_username: ""
+# beats_system_password: ""
diff --git a/elk_metrics_7x/roles/elastic_metricbeat/templates/metricbeat.yml.j2 b/elk_metrics_7x/roles/elastic_metricbeat/templates/metricbeat.yml.j2
index 0cfdf87f..bd6311e4 100644
--- a/elk_metrics_7x/roles/elastic_metricbeat/templates/metricbeat.yml.j2
+++ b/elk_metrics_7x/roles/elastic_metricbeat/templates/metricbeat.yml.j2
@@ -279,8 +279,10 @@ metricbeat.modules:
     - ml_job
   period: 30s
   hosts: ["localhost:{{ elastic_port }}"]
-  #username: "elastic"
-  #password: "changeme"
+{% if beats_system_username is defined %}
+  username: "{{ beats_system_username }}"
+  password: "${% raw %}{{% endraw %}{{ beats_system_username }}{% raw %}}{% endraw %}"
+{% endif %}
   #ssl.certificate_authorities: ["/etc/pki/root/ca.pem"]
 
   #index_recovery.active_only: true
@@ -483,6 +485,10 @@ metricbeat.modules:
   hosts: ["localhost:5601"]
   basepath: ""
   enabled: true
+{% if beats_system_username is defined %}
+  username: "{{ beats_system_username }}"
+  password: "${% raw %}{{% endraw %}{{ beats_system_username }}{% raw %}}{% endraw %}"
+{% endif %}
 
   # Set to true to send data collected by module to X-Pack
   # Monitoring instead of metricbeat-* indices.
@@ -1910,7 +1916,7 @@ setup.ilm.policy_file: "{{ ilm_policy_file_location }}/{{ ilm_policy_filename }}
 {{ elk_macros.beat_logging('metricbeat', metricbeat_log_level) }}
 
 # ============================= X-Pack Monitoring ==============================
-{{ elk_macros.xpack_monitoring_elasticsearch('metricbeat', inventory_hostname, elasticsearch_data_hosts, ansible_processor_count) }}
+{{ elk_macros.xpack_monitoring_elasticsearch('metricbeat', inventory_hostname, elasticsearch_data_hosts, ansible_processor_count, beats_system_username) }}
 
 # =============================== HTTP Endpoint ================================
 
diff --git a/elk_metrics_7x/roles/elastic_packetbeat/defaults/main.yml b/elk_metrics_7x/roles/elastic_packetbeat/defaults/main.yml
index faa8d6e2..4a76df09 100644
--- a/elk_metrics_7x/roles/elastic_packetbeat/defaults/main.yml
+++ b/elk_metrics_7x/roles/elastic_packetbeat/defaults/main.yml
@@ -41,3 +41,7 @@ packetbeat_log_level: "{{ elastic_beat_log_level | default('info') }}"
 # set, templates are only pushed when the user is either upgrading the
 # beat version or deploying for the first time in the presence of kibana nodes
 elk_beat_setup: false
+
+# Authentication credentials for monitoring when using ELK security features
+# beats_system_username: ""
+# beats_system_password: ""
diff --git a/elk_metrics_7x/roles/elastic_packetbeat/templates/packetbeat.yml.j2 b/elk_metrics_7x/roles/elastic_packetbeat/templates/packetbeat.yml.j2
index dbe7cbd4..2cbff5dd 100644
--- a/elk_metrics_7x/roles/elastic_packetbeat/templates/packetbeat.yml.j2
+++ b/elk_metrics_7x/roles/elastic_packetbeat/templates/packetbeat.yml.j2
@@ -1578,7 +1578,7 @@ setup.ilm.policy_file: "{{ ilm_policy_file_location }}/{{ ilm_policy_filename }}
 {{ elk_macros.beat_logging('packetbeat', packetbeat_log_level) }}
 
 # ============================= X-Pack Monitoring ==============================
-{{ elk_macros.xpack_monitoring_elasticsearch('packetbeat', inventory_hostname, elasticsearch_data_hosts, ansible_processor_count) }}
+{{ elk_macros.xpack_monitoring_elasticsearch('packetbeat', inventory_hostname, elasticsearch_data_hosts, ansible_processor_count, beats_system_username) }}
 
 # =============================== HTTP Endpoint ================================
 
diff --git a/elk_metrics_7x/roles/elasticsearch/defaults/main.yml b/elk_metrics_7x/roles/elasticsearch/defaults/main.yml
index f30f26c5..35683eab 100644
--- a/elk_metrics_7x/roles/elasticsearch/defaults/main.yml
+++ b/elk_metrics_7x/roles/elasticsearch/defaults/main.yml
@@ -50,3 +50,9 @@ elasticsearch_bind_addresses: ["127.0.0.1", "{{ ansible_host }}", "{{ ansible_ho
 # Allow a slow startup before the systemd notifier module kicks in to extend
 # the timeout.
 #elastic_systemd_timeout: 75
+
+# Enable Elastic security, including TLS communication between Elasticsearch
+# nodes
+elastic_security_enabled: False
+# elastic_security_cert_bundle: ""
+# elastic_security_cert_password: ""
diff --git a/elk_metrics_7x/roles/elasticsearch/tasks/main.yml b/elk_metrics_7x/roles/elasticsearch/tasks/main.yml
index 715aa41c..df405295 100644
--- a/elk_metrics_7x/roles/elasticsearch/tasks/main.yml
+++ b/elk_metrics_7x/roles/elasticsearch/tasks/main.yml
@@ -128,6 +128,38 @@
     group: "elasticsearch"
     mode: "0750"
 
+- name: Copy elasticsearch certificate bundle
+  copy:
+    content: "{{ elastic_security_cert_bundle }}"
+    dest: "/etc/elasticsearch/elastic-certificates.p12"
+    owner: root
+    group: elasticsearch
+    mode: 0660
+  when:
+    - elastic_security_enabled
+    - elastic_security_cert_bundle is defined
+
+- name: Set certificate bundle password
+  shell: "echo '{{ elastic_security_cert_password }}' | /usr/share/elasticsearch/bin/elasticsearch-keystore add {{ item }} --stdin --force"
+  no_log: True
+  changed_when: False
+  with_items:
+    - "xpack.security.transport.ssl.keystore.secure_password"
+    - "xpack.security.transport.ssl.truststore.secure_password"
+  when:
+    - elastic_security_enabled
+    - elastic_security_cert_password is defined
+
+- name: Set permissions on keystore
+  file:
+    path: "/etc/elasticsearch/elasticsearch.keystore"
+    group: "elasticsearch"
+    owner: "root"
+    mode: "0660"
+  when:
+    - elastic_security_enabled
+    - elastic_security_cert_password is defined
+
 - include_tasks: "elasticsearch_nfs_setup.yml"
   when:
     - elastic_shared_fs_repos is defined
diff --git a/elk_metrics_7x/roles/elasticsearch/templates/elasticsearch.yml.j2 b/elk_metrics_7x/roles/elasticsearch/templates/elasticsearch.yml.j2
index 98acc122..2a6a9ec9 100644
--- a/elk_metrics_7x/roles/elasticsearch/templates/elasticsearch.yml.j2
+++ b/elk_metrics_7x/roles/elasticsearch/templates/elasticsearch.yml.j2
@@ -159,3 +159,13 @@ indices.recovery.max_bytes_per_sec: {{ elasticsearch_interface_speed }}mb
 xpack.monitoring.collection.enabled: true
 # Set to true to enable machine learning on the node.
 xpack.ml.enabled: false
+# Elastic security
+xpack.security.enabled: {{ elastic_security_enabled | bool | lower }}
+xpack.security.transport.ssl.enabled: {{ elastic_security_enabled | bool | lower }}
+{% if elastic_security_enabled %}
+xpack.security.transport.ssl.verification_mode: certificate
+xpack.security.transport.ssl.client_authentication: required
+xpack.security.transport.ssl.keystore.path: elastic-certificates.p12
+xpack.security.transport.ssl.truststore.path: elastic-certificates.p12
+xpack.security.authc.api_key.enabled: true
+{% endif %}
diff --git a/elk_metrics_7x/templates/_macros.j2 b/elk_metrics_7x/templates/_macros.j2
index e8b565d8..d0b83938 100644
--- a/elk_metrics_7x/templates/_macros.j2
+++ b/elk_metrics_7x/templates/_macros.j2
@@ -532,7 +532,7 @@ logging.files:
 #logging.ecs: false
 {%- endmacro %}
 
-{% macro xpack_monitoring_elasticsearch(beat_name, host, data_hosts, processors) -%}
+{% macro xpack_monitoring_elasticsearch(beat_name, host, data_hosts, processors, username) -%}
 # {{ beat_name | capitalize }} can export internal metrics to a central Elasticsearch monitoring
 # cluster.  This requires xpack monitoring to be enabled in Elasticsearch.  The
 # reporting is disabled by default.
@@ -568,8 +568,10 @@ monitoring.elasticsearch:
 
   # Authentication credentials - either API key or username/password.
   #api_key: "id:api_key"
-  #username: "beats_system"
-  #password: "changeme"
+{% if username is defined %}
+  username: "{{ username }}"
+  password: "${% raw %}{{% endraw %}{{ username }}{% raw %}}{% endraw %}"
+{% endif %}
 
   # Dictionary of HTTP parameters to pass within the URL with index operations.
   #parameters:
diff --git a/elk_metrics_7x/templates/logstash-pipelines.yml.j2 b/elk_metrics_7x/templates/logstash-pipelines.yml.j2
index 748d4ebf..2eedf2d8 100644
--- a/elk_metrics_7x/templates/logstash-pipelines.yml.j2
+++ b/elk_metrics_7x/templates/logstash-pipelines.yml.j2
@@ -571,6 +571,10 @@
             sniffing => {{ (elastic_sniffing_enabled | default(not data_node)) | bool | string | lower }}
             manage_template => {{ (data_node | bool) | lower }}
             index => "%{[@metadata][beat]}-%{[@metadata][version]}"
+{% if logstash_internal_username is defined %}
+            user => {{ logstash_internal_username }}
+            password => "${% raw %}{{% endraw %}{{ logstash_internal_username }}{% raw %}}{% endraw %}"
+{% endif %}
           }
         } else if [@metadata][beat] {
           elasticsearch {
@@ -580,6 +584,10 @@
             sniffing => {{ (elastic_sniffing_enabled | default(not data_node)) | bool | string | lower }}
             manage_template => {{ (data_node | bool) | lower }}
             index => "%{[@metadata][beat]}-%{+YYYY.MM.dd}"
+{% if logstash_internal_username is defined %}
+            user => {{ logstash_internal_username }}
+            password => "${% raw %}{{% endraw %}{{ logstash_internal_username }}{% raw %}}{% endraw %}"
+{% endif %}
           }
         } else if "syslog" in [tags] {
           elasticsearch {
@@ -589,6 +597,10 @@
             sniffing => {{ (elastic_sniffing_enabled | default(not data_node)) | bool | string | lower }}
             manage_template => {{ (data_node | bool) | lower }}
             index => "syslog-%{+YYYY.MM.dd}"
+{% if logstash_internal_username is defined %}
+            user => {{ logstash_internal_username }}
+            password => "${% raw %}{{% endraw %}{{ logstash_internal_username }}{% raw %}}{% endraw %}"
+{% endif %}
           }
         } else if "collectd" in [tags] {
           elasticsearch {
@@ -598,6 +610,10 @@
             sniffing => {{ (elastic_sniffing_enabled | default(not data_node)) | bool | string | lower }}
             manage_template => {{ (data_node | bool) | lower }}
             index => "collectd-%{+YYYY.MM.dd}"
+{% if logstash_internal_username is defined %}
+            user => {{ logstash_internal_username }}
+            password => "${% raw %}{{% endraw %}{{ logstash_internal_username }}{% raw %}}{% endraw %}"
+{% endif %}
           }
         } else {
           elasticsearch {
@@ -607,6 +623,10 @@
             sniffing => {{ (elastic_sniffing_enabled | default(not data_node)) | bool | string | lower }}
             manage_template => {{ (data_node | bool) | lower }}
             index => "undefined-%{+YYYY.MM.dd}"
+{% if logstash_internal_username is defined %}
+            user => {{ logstash_internal_username }}
+            password => "${% raw %}{{% endraw %}{{ logstash_internal_username }}{% raw %}}{% endraw %}"
+{% endif %}
           }
         }
       } else {
@@ -617,6 +637,10 @@
             sniffing => {{ (elastic_sniffing_enabled | default(not data_node)) | bool | string | lower }}
             manage_template => {{ (data_node | bool) | lower }}
             index => "%{[@metadata][beat]}-%{[@metadata][version]}"
+{% if logstash_internal_username is defined %}
+            user => {{ logstash_internal_username }}
+            password => "${% raw %}{{% endraw %}{{ logstash_internal_username }}{% raw %}}{% endraw %}"
+{% endif %}
           }
         } else if [@metadata][beat] {
           elasticsearch {
@@ -625,6 +649,10 @@
             sniffing => {{ (elastic_sniffing_enabled | default(not data_node)) | bool | string | lower }}
             manage_template => {{ (data_node | bool) | lower }}
             index => "%{[@metadata][beat]}-%{+YYYY.MM.dd}"
+{% if logstash_internal_username is defined %}
+            user => {{ logstash_internal_username }}
+            password => "${% raw %}{{% endraw %}{{ logstash_internal_username }}{% raw %}}{% endraw %}"
+{% endif %}
           }
         } else if "syslog" in [tags] {
           elasticsearch {
@@ -633,6 +661,10 @@
             sniffing => {{ (elastic_sniffing_enabled | default(not data_node)) | bool | string | lower }}
             manage_template => {{ (data_node | bool) | lower }}
             index => "syslog-%{+YYYY.MM.dd}"
+{% if logstash_internal_username is defined %}
+            user => {{ logstash_internal_username }}
+            password => "${% raw %}{{% endraw %}{{ logstash_internal_username }}{% raw %}}{% endraw %}"
+{% endif %}
           }
         } else if "collectd" in [tags] {
           elasticsearch {
@@ -641,6 +673,10 @@
             sniffing => {{ (elastic_sniffing_enabled | default(not data_node)) | bool | string | lower }}
             manage_template => {{ (data_node | bool) | lower }}
             index => "collectd-%{+YYYY.MM.dd}"
+{% if logstash_internal_username is defined %}
+            user => {{ logstash_internal_username }}
+            password => "${% raw %}{{% endraw %}{{ logstash_internal_username }}{% raw %}}{% endraw %}"
+{% endif %}
           }
         } else {
           elasticsearch {
@@ -649,6 +685,10 @@
             sniffing => {{ (elastic_sniffing_enabled | default(not data_node)) | bool | string | lower }}
             manage_template => {{ (data_node | bool) | lower }}
             index => "undefined-%{+YYYY.MM.dd}"
+{% if logstash_internal_username is defined %}
+            user => {{ logstash_internal_username }}
+            password => "${% raw %}{{% endraw %}{{ logstash_internal_username }}{% raw %}}{% endraw %}"
+{% endif %}
           }
         }
       }
diff --git a/elk_metrics_7x/vars/variables.yml b/elk_metrics_7x/vars/variables.yml
index 8eea68b7..d43a438e 100644
--- a/elk_metrics_7x/vars/variables.yml
+++ b/elk_metrics_7x/vars/variables.yml
@@ -459,3 +459,6 @@ _elastic_apt_pin_packages:
 
 apt_package_pinning_file_name: "{{ elastic_apt_pin_file_name | default('elasticsearch.pref') }}"
 apt_pinned_packages: "{{ elastic_apt_pin_packages | default(_elastic_apt_pin_packages) }}"
+
+# kibana_setup_username: ""
+# kibana_setup_password: ""