diff --git a/README.md b/README.md index 172f8524..0e5896ff 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Documentation](https://img.shields.io/badge/docs-view-green.svg)](https://documentation.wazuh.com) [![Documentation](https://img.shields.io/badge/web-view-green.svg)](https://wazuh.com) -These playbooks install and configure Wazuh agent, manager and Elastic Stack. +These playbooks install and configure Wazuh agent, manager and indexer and dashboard. ## Branches * `master` branch contains the latest code, be aware of possible bugs on this branch. @@ -15,7 +15,7 @@ These playbooks install and configure Wazuh agent, manager and Elastic Stack. | Wazuh version | Elastic | ODFE | |---------------|---------|--------| -| v4.3.0 | 7.10.2 | 1.13.2 | +| v4.3.0 | | 1.13.2 | | v4.2.5 | 7.10.2 | 1.13.2 | | v4.2.4 | 7.10.2 | 1.13.2 | | v4.2.3 | 7.10.2 | 1.13.2 | @@ -41,9 +41,9 @@ These playbooks install and configure Wazuh agent, manager and Elastic Stack. │ │ │ ├── ansible-elasticsearch │ │ │ ├── ansible-kibana │ │ - │ │ ├── opendistro - │ │ │ ├── opendistro-elasticsearch - │ │ │ ├── opendistro-kibana + │ │ ├── opensearch + │ │ │ ├── wazuh-dashboard + │ │ │ ├── wazuh-indexer │ │ │ │ ├── wazuh │ │ │ ├── ansible-filebeat @@ -60,10 +60,12 @@ These playbooks install and configure Wazuh agent, manager and Elastic Stack. │ │ ├── wazuh-elastic_stack-distributed.yml │ │ ├── wazuh-elastic_stack-single.yml │ │ ├── wazuh-kibana.yml - │ │ ├── wazuh-manager.yml │ │ ├── wazuh-manager-oss.yml - │ │ ├── wazuh-opendistro.yml - │ │ ├── wazuh-opendistro-kibana.yml + │ │ ├── wazuh-manager.yml + │ │ ├── wazuh-opensearch-dashboard.yml + | | ├── wazuh-opensearch-production-ready + │ │ ├── wazuh-opensearch-single.yml + │ │ ├── wazuh-opensearch.yml │ │ ├── README.md │ ├── VERSION @@ -78,82 +80,97 @@ The hereunder example playbook uses the `wazuh-ansible` role to provision a prod ```yaml --- # Certificates generation - - hosts: es1 + - hosts: wi1 roles: - - role: ../roles/opendistro/opendistro-elasticsearch - elasticsearch_network_host: "{{ private_ip }}" - elasticsearch_cluster_nodes: - - "{{ hostvars.es1.private_ip }}" - - "{{ hostvars.es2.private_ip }}" - - "{{ hostvars.es3.private_ip }}" - elasticsearch_discovery_nodes: - - "{{ hostvars.es1.private_ip }}" - - "{{ hostvars.es2.private_ip }}" - - "{{ hostvars.es3.private_ip }}" + - role: ../roles/opensearch/wazuh-indexer + indexer_network_host: "{{ private_ip }}" + indexer_cluster_nodes: + - "{{ hostvars.wi1.private_ip }}" + - "{{ hostvars.wi2.private_ip }}" + - "{{ hostvars.wi3.private_ip }}" + indexer_discovery_nodes: + - "{{ hostvars.wi1.private_ip }}" + - "{{ hostvars.wi2.private_ip }}" + - "{{ hostvars.wi3.private_ip }}" perform_installation: false - become: yes - become_user: root + become: no vars: - elasticsearch_node_master: true + indexer_node_master: true instances: node1: - name: node-1 # Important: must be equal to elasticsearch_node_name. - ip: "{{ hostvars.es1.private_ip }}" # When unzipping, the node will search for its node name folder to get the cert. + name: node-1 # Important: must be equal to indexer_node_name. + ip: "{{ hostvars.wi1.private_ip }}" # When unzipping, the node will search for its node name folder to get the cert. + role: indexer node2: name: node-2 - ip: "{{ hostvars.es2.private_ip }}" + ip: "{{ hostvars.wi2.private_ip }}" + role: indexer node3: name: node-3 - ip: "{{ hostvars.es3.private_ip }}" + ip: "{{ hostvars.wi3.private_ip }}" + role: indexer node4: name: node-4 ip: "{{ hostvars.manager.private_ip }}" + role: wazuh + node_type: master node5: name: node-5 ip: "{{ hostvars.worker.private_ip }}" + role: wazuh + node_type: worker node6: name: node-6 - ip: "{{ hostvars.kibana.private_ip }}" + ip: "{{ hostvars.dashboard.private_ip }}" + role: dashboard tags: - generate-certs -#ODFE Cluster - - hosts: odfe_cluster +#Wazuh Indexer Cluster + - hosts: wi_cluster strategy: free roles: - - role: ../roles/opendistro/opendistro-elasticsearch - elasticsearch_network_host: "{{ private_ip }}" + - role: ../roles/opensearch/wazuh-indexer + indexer_network_host: "{{ private_ip }}" become: yes become_user: root vars: - elasticsearch_cluster_nodes: - - "{{ hostvars.es1.private_ip }}" - - "{{ hostvars.es2.private_ip }}" - - "{{ hostvars.es3.private_ip }}" - elasticsearch_discovery_nodes: - - "{{ hostvars.es1.private_ip }}" - - "{{ hostvars.es2.private_ip }}" - - "{{ hostvars.es3.private_ip }}" - elasticsearch_node_master: true + indexer_cluster_nodes: + - "{{ hostvars.wi1.private_ip }}" + - "{{ hostvars.wi2.private_ip }}" + - "{{ hostvars.wi3.private_ip }}" + indexer_discovery_nodes: + - "{{ hostvars.wi1.private_ip }}" + - "{{ hostvars.wi2.private_ip }}" + - "{{ hostvars.wi3.private_ip }}" + indexer_node_master: true instances: node1: - name: node-1 # Important: must be equal to elasticsearch_node_name. - ip: "{{ hostvars.es1.private_ip }}" # When unzipping, the node will search for its node name folder to get the cert. + name: node-1 # Important: must be equal to indexer_node_name. + ip: "{{ hostvars.wi1.private_ip }}" # When unzipping, the node will search for its node name folder to get the cert. + role: indexer node2: name: node-2 - ip: "{{ hostvars.es2.private_ip }}" + ip: "{{ hostvars.wi2.private_ip }}" + role: indexer node3: name: node-3 - ip: "{{ hostvars.es3.private_ip }}" + ip: "{{ hostvars.wi3.private_ip }}" + role: indexer node4: name: node-4 ip: "{{ hostvars.manager.private_ip }}" + role: wazuh + node_type: master node5: name: node-5 ip: "{{ hostvars.worker.private_ip }}" + role: wazuh + node_type: worker node6: name: node-6 - ip: "{{ hostvars.kibana.private_ip }}" + ip: "{{ hostvars.dashboard.private_ip }}" + role: dashboard #Wazuh cluster - hosts: manager @@ -180,10 +197,13 @@ The hereunder example playbook uses the `wazuh-ansible` role to provision a prod nodes: - "{{ hostvars.manager.private_ip }}" hidden: 'no' + wazuh_api_users: + - username: custom-user + password: .S3cur3Pa55w0rd*- filebeat_output_indexer_hosts: - - "{{ hostvars.es1.private_ip }}" - - "{{ hostvars.es2.private_ip }}" - - "{{ hostvars.es3.private_ip }}" + - "{{ hostvars.wi1.private_ip }}" + - "{{ hostvars.wi2.private_ip }}" + - "{{ hostvars.wi3.private_ip }}" - hosts: worker roles: @@ -210,57 +230,66 @@ The hereunder example playbook uses the `wazuh-ansible` role to provision a prod - "{{ hostvars.manager.private_ip }}" hidden: 'no' filebeat_output_indexer_hosts: - - "{{ hostvars.es1.private_ip }}" - - "{{ hostvars.es2.private_ip }}" - - "{{ hostvars.es3.private_ip }}" + - "{{ hostvars.wi1.private_ip }}" + - "{{ hostvars.wi2.private_ip }}" + - "{{ hostvars.wi3.private_ip }}" - #ODFE+Kibana node - - hosts: kibana + #Indexer+Dashboard node + - hosts: dashboard roles: - - role: "../roles/opendistro/opendistro-elasticsearch" - - role: "../roles/opendistro/opendistro-kibana" + - role: "../roles/opensearch/wazuh-indexer" + - role: "../roles/opensearch/wazuh-dashboard" become: yes become_user: root vars: - elasticsearch_network_host: "{{ hostvars.kibana.private_ip }}" - elasticsearch_node_name: node-6 - elasticsearch_node_master: false - elasticsearch_node_ingest: false - elasticsearch_node_data: false - elasticsearch_cluster_nodes: - - "{{ hostvars.es1.private_ip }}" - - "{{ hostvars.es2.private_ip }}" - - "{{ hostvars.es3.private_ip }}" - elasticsearch_discovery_nodes: - - "{{ hostvars.es1.private_ip }}" - - "{{ hostvars.es2.private_ip }}" - - "{{ hostvars.es3.private_ip }}" - kibana_node_name: node-6 + indexer_network_host: "{{ hostvars.dashboard.private_ip }}" + indexer_node_name: node-6 + indexer_node_master: false + indexer_node_ingest: false + indexer_node_data: false + indexer_cluster_nodes: + - "{{ hostvars.wi1.private_ip }}" + - "{{ hostvars.wi2.private_ip }}" + - "{{ hostvars.wi3.private_ip }}" + indexer_discovery_nodes: + - "{{ hostvars.wi1.private_ip }}" + - "{{ hostvars.wi2.private_ip }}" + - "{{ hostvars.wi3.private_ip }}" + dashboard_node_name: node-6 wazuh_api_credentials: - id: default url: https://{{ hostvars.manager.private_ip }} port: 55000 - user: foo - password: bar + username: custom-user + password: .S3cur3Pa55w0rd*- instances: node1: - name: node-1 # Important: must be equal to elasticsearch_node_name. - ip: "{{ hostvars.es1.private_ip }}" # When unzipping, the node will search for its node name folder to get the cert. + name: node-1 # Important: must be equal to indexer_node_name. + ip: "{{ hostvars.wi1.private_ip }}" # When unzipping, the node will search for its node name folder to get the cert. + role: indexer node2: name: node-2 - ip: "{{ hostvars.es2.private_ip }}" + ip: "{{ hostvars.wi2.private_ip }}" + role: indexer node3: name: node-3 - ip: "{{ hostvars.es3.private_ip }}" + ip: "{{ hostvars.wi3.private_ip }}" + role: indexer node4: name: node-4 ip: "{{ hostvars.manager.private_ip }}" + role: wazuh + node_type: master node5: name: node-5 ip: "{{ hostvars.worker.private_ip }}" + role: wazuh + node_type: worker node6: name: node-6 - ip: "{{ hostvars.kibana.private_ip }}" + ip: "{{ hostvars.dashboard.private_ip }}" + role: dashboard + ansible_shell_allow_world_readable_temp: true ``` ### Inventory file @@ -271,17 +300,17 @@ The hereunder example playbook uses the `wazuh-ansible` role to provision a prod - The ssh credentials used by Ansible during the provision can be specified in this file too. Another option is including them directly on the playbook. ```ini -es1 ansible_host= private_ip= elasticsearch_node_name=node-1 -es2 ansible_host= private_ip= elasticsearch_node_name=node-2 -es3 ansible_host= private_ip= elasticsearch_node_name=node-3 +wi1 ansible_host= private_ip= elasticsearch_node_name=node-1 +wi2 ansible_host= private_ip= elasticsearch_node_name=node-2 +wi3 ansible_host= private_ip= elasticsearch_node_name=node-3 kibana ansible_host= private_ip= manager ansible_host= private_ip= worker ansible_host= private_ip= -[odfe_cluster] -es1 -es2 -es3 +[wi_cluster] +wi1 +wi2 +wi3 [all:vars] ansible_ssh_user=vagrant diff --git a/playbooks/wazuh-opensearch-production-ready.yml b/playbooks/wazuh-opensearch-production-ready.yml index 76d8ab14..c23d9ff4 100644 --- a/playbooks/wazuh-opensearch-production-ready.yml +++ b/playbooks/wazuh-opensearch-production-ready.yml @@ -13,8 +13,7 @@ - "{{ hostvars.wi2.private_ip }}" - "{{ hostvars.wi3.private_ip }}" perform_installation: false - become: yes - become_user: root + become: no vars: indexer_node_master: true instances: @@ -47,7 +46,7 @@ tags: - generate-certs -#ODFE Cluster +#Wazuh Indexer Cluster - hosts: wi_cluster strategy: free roles: diff --git a/roles/wazuh/ansible-wazuh-manager/files/create_user.py b/roles/wazuh/ansible-wazuh-manager/files/create_user.py index 6bb966fa..5ecb88e2 100644 --- a/roles/wazuh/ansible-wazuh-manager/files/create_user.py +++ b/roles/wazuh/ansible-wazuh-manager/files/create_user.py @@ -3,13 +3,17 @@ import sys import json import random import string -import argparse import os # Set framework path -sys.path.append("/var/ossec/framework") +sys.path.append(os.path.dirname(sys.argv[0]) + "/../framework") + +USER_FILE_PATH = "/var/ossec/api/configuration/admin.json" +SPECIAL_CHARS = "@$!%*?&-_" + try: + from wazuh.rbac.orm import create_rbac_db from wazuh.security import ( create_user, get_users, @@ -22,6 +26,12 @@ except Exception as e: sys.exit(1) +def read_user_file(path=USER_FILE_PATH): + with open(path) as user_file: + data = json.load(user_file) + return data["username"], data["password"] + + def db_users(): users_result = get_users() return {user["username"]: user["id"] for user in users_result.affected_items} @@ -31,15 +41,35 @@ def db_roles(): roles_result = get_roles() return {role["name"]: role["id"] for role in roles_result.affected_items} +def disable_user(uid): + random_pass = "".join( + random.choices( + string.ascii_uppercase + + string.ascii_lowercase + + string.digits + + SPECIAL_CHARS, + k=8, + ) + ) + # assure there must be at least one character from each group + random_pass = random_pass + ''.join([random.choice(chars) for chars in [string.ascii_lowercase, string.digits, string.ascii_uppercase, SPECIAL_CHARS]]) + random_pass = ''.join(random.sample(random_pass,len(random_pass))) + update_user( + user_id=[ + str(uid), + ], + password=random_pass, + ) + if __name__ == "__main__": - parser = argparse.ArgumentParser(description='add_user script') - parser.add_argument('--username', action="store", dest="username") - parser.add_argument('--password', action="store", dest="password") - results = parser.parse_args() + if not os.path.exists(USER_FILE_PATH): + # abort if no user file detected + sys.exit(0) + username, password = read_user_file() - username = results.username - password = results.password + # create RBAC database + create_rbac_db() initial_users = db_users() if username not in initial_users: @@ -66,28 +96,7 @@ if __name__ == "__main__": ], password=password, ) - # set a random password for all other users - for name, id in initial_users.items(): - if name != username: - specials = "@$!%*?&-_" - random_pass = "".join( - [ - random.choice(string.ascii_uppercase), - random.choice(string.ascii_lowercase), - random.choice(string.digits), - random.choice(specials), - ] + - random.choices( - string.ascii_uppercase - + string.ascii_lowercase - + string.digits - + specials, - k=14, - ) - ) - update_user( - user_id=[ - str(id), - ], - password=random_pass, - ) + # disable unused default users + for def_user in ['wazuh', 'wazuh-wui']: + if def_user != username: + disable_user(initial_users[def_user]) \ No newline at end of file diff --git a/roles/wazuh/ansible-wazuh-manager/tasks/Debian.yml b/roles/wazuh/ansible-wazuh-manager/tasks/Debian.yml index 717add8c..eec63592 100644 --- a/roles/wazuh/ansible-wazuh-manager/tasks/Debian.yml +++ b/roles/wazuh/ansible-wazuh-manager/tasks/Debian.yml @@ -16,7 +16,7 @@ become: true shell: | set -o pipefail - curl -s https://packages.wazuh.com/key/GPG-KEY-WAZUH | apt-key add - + curl -s https://packages-dev.wazuh.com/key/GPG-KEY-WAZUH | apt-key add - args: warn: false executable: /bin/bash