diff --git a/docker-compose.yml b/docker-compose.yml index cd8f90ff..4e859267 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,14 +3,20 @@ version: '3.7' services: wazuh: - build: wazuh + build: wazuh-opendistro/ + image: wazuh-opendistro hostname: wazuh-manager restart: always ports: - - "1514:1514/udp" + - "1514:1514" - "1515:1515" - "514:514/udp" - "55000:55000" + environment: + - ELASTICSEARCH_URL=https://elasticsearch:9200 + - ELASTIC_USERNAME=admin + - ELASTIC_PASSWORD=admin + - FILEBEAT_SSL_VERIFICATION_MODE=none volumes: - ossec_api_configuration:/var/ossec/api/configuration - ossec_etc:/var/ossec/etc @@ -25,46 +31,45 @@ services: - filebeat_var:/var/lib/filebeat elasticsearch: - image: docker.elastic.co/elasticsearch/elasticsearch:7.8.0 + image: amazon/opendistro-for-elasticsearch:1.9.0 hostname: elasticsearch restart: always ports: - "9200:9200" environment: - - "ES_JAVA_OPTS=-Xms1g -Xmx1g" - - bootstrap.memory_lock=true - discovery.type=single-node + - cluster.name=wazuh-cluster + - network.host=0.0.0.0 + - "ES_JAVA_OPTS=-Xms512m -Xmx512m" + - bootstrap.memory_lock=true ulimits: memlock: soft: -1 hard: -1 - volumes: - - ./elastic_conf/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml + nofile: + soft: 65536 + hard: 65536 kibana: - build: kibana + build: kibana-opendistro/ + image: wazuh-kibana-opendistro hostname: kibana restart: always + ports: + - 443:5601 + environment: + - ELASTICSEARCH_USERNAME=admin + - ELASTICSEARCH_PASSWORD=admin + - SERVER_SSL_ENABLED=true + - SERVER_SSL_CERTIFICATE=/usr/share/kibana/config/opendistroforelasticsearch.example.org.cert + - SERVER_SSL_KEY=/usr/share/kibana/config/opendistroforelasticsearch.example.org.key + depends_on: - elasticsearch links: - elasticsearch:elasticsearch - wazuh:wazuh - nginx: - image: nginx:stable - hostname: nginx - restart: always - ports: - - "80:80" - - "443:443" - depends_on: - - kibana - links: - - kibana:kibana - volumes: - - ./nginx_conf:/etc/nginx/conf.d:ro - volumes: ossec_api_configuration: ossec_etc: diff --git a/kibana-opendistro/Dockerfile b/kibana-opendistro/Dockerfile new file mode 100644 index 00000000..82631ca2 --- /dev/null +++ b/kibana-opendistro/Dockerfile @@ -0,0 +1,57 @@ +# Wazuh Docker Copyright (C) 2020 Wazuh Inc. (License GPLv2) +FROM amazon/opendistro-for-elasticsearch-kibana:1.9.0 +USER kibana +ARG ELASTIC_VERSION=7.8.0 +ARG WAZUH_VERSION=4.0.0 +ARG WAZUH_APP_VERSION="${WAZUH_VERSION}_${ELASTIC_VERSION}" + +WORKDIR /usr/share/kibana +RUN ./bin/kibana-plugin install https://packages-dev.wazuh.com/pre-release/ui/kibana/wazuh_kibana-${WAZUH_APP_VERSION}-1.zip + +WORKDIR / +USER root +COPY config/entrypoint.sh ./entrypoint.sh +RUN chmod 755 ./entrypoint.sh + +ENV PATTERN="" \ + CHECKS_PATTERN="" \ + CHECKS_TEMPLATE="" \ + CHECKS_API="" \ + CHECKS_SETUP="" \ + EXTENSIONS_PCI="" \ + EXTENSIONS_GDPR="" \ + EXTENSIONS_AUDIT="" \ + EXTENSIONS_OSCAP="" \ + EXTENSIONS_CISCAT="" \ + EXTENSIONS_AWS="" \ + EXTENSIONS_VIRUSTOTAL="" \ + EXTENSIONS_OSQUERY="" \ + APP_TIMEOUT="" \ + WAZUH_SHARDS="" \ + WAZUH_REPLICAS="" \ + WAZUH_VERSION_SHARDS="" \ + WAZUH_VERSION_REPLICAS="" \ + IP_SELECTOR="" \ + IP_IGNORE="" \ + WAZUH_MONITORING_ENABLED="" \ + WAZUH_MONITORING_FREQUENCY="" \ + WAZUH_MONITORING_SHARDS="" \ + WAZUH_MONITORING_REPLICAS="" \ + ADMIN_PRIVILEGES="" + +USER kibana +RUN NODE_OPTIONS="--max-old-space-size=2048" /usr/local/bin/kibana-docker --optimize + +COPY ./config/custom_welcome /tmp/custom_welcome +COPY --chown=kibana:kibana ./config/welcome_wazuh.sh ./ +RUN chmod +x ./welcome_wazuh.sh +ARG CHANGE_WELCOME="true" +RUN ./welcome_wazuh.sh + +COPY --chown=kibana:kibana ./config/wazuh_app_config.sh ./ +RUN chmod +x ./wazuh_app_config.sh + +COPY --chown=kibana:kibana ./config/kibana_settings.sh ./ +RUN chmod +x ./kibana_settings.sh + +ENTRYPOINT ./entrypoint.sh diff --git a/kibana-opendistro/config/custom_welcome/security-login.style.css b/kibana-opendistro/config/custom_welcome/security-login.style.css new file mode 100644 index 00000000..6648df2b --- /dev/null +++ b/kibana-opendistro/config/custom_welcome/security-login.style.css @@ -0,0 +1,118 @@ +#security-login-app .content { + background: url(./wazuh_wazuh_bg.svg) !important; + width: 100% !important; + height: 100% !important; + background-size: cover !important; +} + +.app-wrapper { + left: 0; +} + +.global-nav.is-global-nav-open+.app-wrapper { + left: 0; +} + +.btn-default { + background-color: #00a9e5!important; + border-color: #00a0e5!important; + color: #ffffff; + padding: 8px; +} + +.btn-default:hover { + background-color: #00a9e5!important; + border-color: #00a0e5!important; + color: #ffffff; +} + +.brand-image-container { + text-align: center; +} + +.brand-image { + display: none; +} + +.login-wrapper { + position: absolute; + width: 430px; + top: 55px; + border-radius: 1px; + padding: 1em; +} + +.login-wrapper .login-title { + text-align: center; + padding-bottom: 10px; + color: #ffffff !important; + font-size: 35px !important; + font-weight: 300; +} + +.login-wrapper .login-subtitle { + text-align: center; + padding-bottom: 15px; + color: #ffffff !important; + font-size: 16px !important; +} + +.login-wrapper .login-form { + padding: 16px; + box-shadow: 0 2px 2px -1px rgba(152, 162, 179, 0.3), 0 1px 5px -2px rgba(152, 162, 179, 0.3); + background-color: #FFF; + border: 1px solid #D3DAE6; + border-radius: 4px; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + margin-top: 32px; +} + +.login-wrapper .login-form .input-group { + margin-bottom: 1em; +} + +.login-wrapper .login-form .kuiTextInput { + cursor: initial; +} + +.login-wrapper .login-form .kuiTextInput:invalid:not(.ng-touched) { + border-color: #D9D9D9; +} + +.login-wrapper .login-form .kuiTextInput.has-error { + border-color: #A30000; +} + +.login-wrapper .login-form .btn-login { + width: 100%; +} + +.login-wrapper .error-message { + color: #b4251d; + font-size: 14px; + margin-top: 16px; + margin-bottom: 0; + background-color: #f8e9e9; + padding: 8px; + font-weight: 400; + border-left: 2px solid #BD271E; +} + +.loginWelcome__logo { + display: inline-block; + width: 80px; + height: 80px; + line-height: 80px; + text-align: center; + background-color: #FFF; + border-radius: 100%; + padding: 16px; + box-shadow: 0 6px 12px -1px rgba(152, 162, 179, 0.2), 0 4px 4px -1px rgba(152, 162, 179, 0.2), 0 2px 2px 0 rgba(152, 162, 179, 0.2); + margin-bottom: 32px; +} + +.loginWelcome__logo { + background: url(./wazuh_logo_circle.svg) center center no-repeat !important; +} \ No newline at end of file diff --git a/kibana-opendistro/config/custom_welcome/template.js.hbs b/kibana-opendistro/config/custom_welcome/template.js.hbs new file mode 100644 index 00000000..54255bca --- /dev/null +++ b/kibana-opendistro/config/custom_welcome/template.js.hbs @@ -0,0 +1,112 @@ +var kbnCsp = JSON.parse(document.querySelector('kbn-csp').getAttribute('data')); +window.__kbnStrictCsp__ = kbnCsp.strictCsp; +window.__kbnDarkMode__ = {{darkMode}}; +window.__kbnPublicPath__ = {{publicPathMap}}; + +if (window.__kbnStrictCsp__ && window.__kbnCspNotEnforced__) { + var legacyBrowserError = document.getElementById('kbn_legacy_browser_error'); + legacyBrowserError.style.display = 'flex'; +} else { + if (!window.__kbnCspNotEnforced__ && window.console) { + window.console.log("^ A single error about an inline script not firing due to content security policy is expected!"); + } + var loadingMessage = document.getElementById('kbn_loading_message'); + loadingMessage.style.display = 'flex'; + + window.onload = function () { + //WAZUH + var interval = setInterval(() => { + var title = document.getElementsByClassName('login-title'); + if ((title || []).length) { + clearInterval(interval); + title[0].textContent = "Welcome to Wazuh"; + var subtitle = document.getElementsByClassName('login-subtitle'); + subtitle[0].textContent = "The Open Source Security Platform"; + var logo = document.getElementsByClassName('brand-image-container'); + $(logo).append(''); + } + }) + // + + function failure() { + // make subsequent calls to failure() noop + failure = function () {}; + + var err = document.createElement('h1'); + err.style['color'] = 'white'; + err.style['font-family'] = 'monospace'; + err.style['text-align'] = 'center'; + err.style['background'] = '#F44336'; + err.style['padding'] = '25px'; + err.innerText = document.querySelector('[data-error-message]').dataset.errorMessage; + + document.body.innerHTML = err.outerHTML; + } + +var stylesheetTarget = document.querySelector('head meta[name="add-styles-here"]') + function loadStyleSheet(url, cb) { + var dom = document.createElement('link'); + dom.rel = 'stylesheet'; + dom.type = 'text/css'; + dom.href = url; + dom.addEventListener('error', failure); + dom.addEventListener('load', cb); + document.head.insertBefore(dom, stylesheetTarget); + } + + var scriptsTarget = document.querySelector('head meta[name="add-scripts-here"]') + function loadScript(url, cb) { + var dom = document.createElement('script'); + {{!-- NOTE: async = false is used to trigger async-download/ordered-execution as outlined here: https://www.html5rocks.com/en/tutorials/speed/script-loading/ --}} + dom.async = false; + dom.src = url; + dom.addEventListener('error', failure); + dom.addEventListener('load', cb); + document.head.insertBefore(dom, scriptsTarget); + } + + function load(urls, cb) { + var pending = urls.length; + urls.forEach(function (url) { + var innerCb = function () { + pending = pending - 1; + if (pending === 0 && typeof cb === 'function') { + cb(); + } + } + + if (typeof url !== 'string') { + load(url, innerCb); + } else if (url.slice(-4) === '.css') { + loadStyleSheet(url, innerCb); + } else { + loadScript(url, innerCb); + } + }); + } + + load([ + {{#each jsDependencyPaths}} + '{{this}}', + {{/each}} + ], function () { + {{#unless legacyBundlePath}} + if (!__kbnBundles__ || !__kbnBundles__['entry/core'] || typeof __kbnBundles__['entry/core'].__kbnBootstrap__ !== 'function') { + console.error('entry/core bundle did not load correctly'); + failure(); + } else { + __kbnBundles__['entry/core'].__kbnBootstrap__() + } + {{/unless}} + + load([ + {{#if legacyBundlePath}} + '{{legacyBundlePath}}', + {{/if}} + {{#each styleSheetPaths}} + '{{this}}', + {{/each}} + ]); + }); + } +} \ No newline at end of file diff --git a/kibana-opendistro/config/custom_welcome/wazuh_logo_circle.svg b/kibana-opendistro/config/custom_welcome/wazuh_logo_circle.svg new file mode 100644 index 00000000..45a61d74 --- /dev/null +++ b/kibana-opendistro/config/custom_welcome/wazuh_logo_circle.svg @@ -0,0 +1 @@ +wazuh_logo_circle \ No newline at end of file diff --git a/kibana-opendistro/config/custom_welcome/wazuh_wazuh_bg.svg b/kibana-opendistro/config/custom_welcome/wazuh_wazuh_bg.svg new file mode 100644 index 00000000..0c49c5c6 --- /dev/null +++ b/kibana-opendistro/config/custom_welcome/wazuh_wazuh_bg.svg @@ -0,0 +1 @@ +wazuh_wazuh_bg \ No newline at end of file diff --git a/kibana-opendistro/config/entrypoint.sh b/kibana-opendistro/config/entrypoint.sh new file mode 100644 index 00000000..f2c017f1 --- /dev/null +++ b/kibana-opendistro/config/entrypoint.sh @@ -0,0 +1,65 @@ +#!/bin/bash +# Wazuh Docker Copyright (C) 2020 Wazuh Inc. (License GPLv2) + +set -e + +############################################################################## +# Waiting for elasticsearch +############################################################################## + +if [ "x${ELASTICSEARCH_URL}" == "x" ]; then + if [[ ${ENABLED_SECURITY} == "false" ]]; then + export el_url="http://elasticsearch:9200" + else + export el_url="https://elasticsearch:9200" + fi +else + export el_url="${ELASTICSEARCH_URL}" +fi + +if [[ ${ENABLED_SECURITY} == "false" || "x${ELASTICSEARCH_USERNAME}" == "x" || "x${ELASTICSEARCH_PASSWORD}" == "x" ]]; then + auth="" + # remove security plugin from kibana if elasticsearch is not using it either + /usr/share/kibana/bin/kibana-plugin remove opendistro_security +else + export auth="--user ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} -k" +fi + +until curl -XGET $el_url ${auth}; do + >&2 echo "Elastic is unavailable - sleeping" + sleep 5 +done + +sleep 2 + +>&2 echo "Elasticsearch is up." + + +############################################################################## +# Waiting for wazuh alerts template +############################################################################## + +strlen=0 + +while [[ $strlen -eq 0 ]] +do + template=$(curl ${auth} $el_url/_cat/templates/wazuh -s) + strlen=${#template} + >&2 echo "Wazuh alerts template not loaded - sleeping." + sleep 2 +done + +sleep 2 + +>&2 echo "Wazuh alerts template is loaded." + + +./wazuh_app_config.sh + +sleep 5 + +./kibana_settings.sh & + +sleep 2 + +/usr/local/bin/kibana-docker diff --git a/kibana-opendistro/config/kibana_settings.sh b/kibana-opendistro/config/kibana_settings.sh new file mode 100644 index 00000000..19cae116 --- /dev/null +++ b/kibana-opendistro/config/kibana_settings.sh @@ -0,0 +1,60 @@ +#!/bin/bash +# Wazuh Docker Copyright (C) 2020 Wazuh Inc. (License GPLv2) + +WAZUH_MAJOR=4 + +############################################################################## +# Wait for the Kibana API to start. It is necessary to do it in this container +# because the others are running Elastic Stack and we can not interrupt them. +# +# The following actions are performed: +# +# Add the wazuh alerts index as default. +# Set the Discover time interval to 24 hours instead of 15 minutes. +# Do not ask user to help providing usage statistics to Elastic. +############################################################################## + +############################################################################## +# Customize elasticsearch ip +############################################################################## +sed -i "s|elasticsearch.hosts:.*|elasticsearch.hosts: $el_url|g" /usr/share/kibana/config/kibana.yml +# disable multitenancy +sed -i "s|opendistro_security.multitenancy.enabled:.*|opendistro_security.multitenancy.enabled: false|g" /usr/share/kibana/config/kibana.yml + +# If KIBANA_INDEX was set, then change the default index in kibana.yml configuration file. If there was an index, then delete it and recreate. +if [ "$KIBANA_INDEX" != "" ]; then + if grep -q 'kibana.index' /usr/share/kibana/config/kibana.yml; then + sed -i '/kibana.index/d' /usr/share/kibana/config/kibana.yml + fi + echo "kibana.index: $KIBANA_INDEX" >> /usr/share/kibana/config/kibana.yml +fi + +while [[ "$(curl -XGET -I -s -o /dev/null -w '%{http_code}' -k https://127.0.0.1:5601/login)" != "200" ]]; do + echo "Waiting for Kibana API. Sleeping 5 seconds" + sleep 5 +done + +# Prepare index selection. +echo "Kibana API is running" + +default_index="/tmp/default_index.json" + +cat > ${default_index} << EOF +{ + "changes": { + "defaultIndex": "wazuh-alerts-${WAZUH_MAJOR}.x-*" + } +} +EOF + +sleep 5 +# Add the wazuh alerts index as default. +curl ${auth} -POST -k https://127.0.0.1:5601/api/kibana/settings -H "Content-Type: application/json" -H "kbn-xsrf: true" -d@${default_index} +rm -f ${default_index} + +sleep 5 +# Configuring Kibana TimePicker. +curl ${auth} -POST -k "https://127.0.0.1:5601/api/kibana/settings" -H "Content-Type: application/json" -H "kbn-xsrf: true" -d \ +'{"changes":{"timepicker:timeDefaults":"{\n \"from\": \"now-12h\",\n \"to\": \"now\",\n \"mode\": \"quick\"}"}}' + +echo "End settings" diff --git a/kibana-opendistro/config/wazuh_app_config.sh b/kibana-opendistro/config/wazuh_app_config.sh new file mode 100644 index 00000000..db1d8f82 --- /dev/null +++ b/kibana-opendistro/config/wazuh_app_config.sh @@ -0,0 +1,67 @@ +#!/bin/bash +# Wazuh Docker Copyright (C) 2020 Wazuh Inc. (License GPLv2) + +wazuh_url="${WAZUH_API_URL:-https://wazuh}" +wazuh_port="${API_PORT:-55000}" +api_user="${API_USER:-wazuh}" +api_password="${API_PASS:-wazuh}" + +kibana_config_file="/usr/share/kibana/optimize/wazuh/config/wazuh.yml" +mkdir -p /usr/share/kibana/optimize/wazuh/config/ +touch $kibana_config_file + +declare -A CONFIG_MAP=( + [pattern]=$PATTERN + [checks.pattern]=$CHECKS_PATTERN + [checks.template]=$CHECKS_TEMPLATE + [checks.api]=$CHECKS_API + [checks.setup]=$CHECKS_SETUP + [extensions.pci]=$EXTENSIONS_PCI + [extensions.gdpr]=$EXTENSIONS_GDPR + [extensions.audit]=$EXTENSIONS_AUDIT + [extensions.oscap]=$EXTENSIONS_OSCAP + [extensions.ciscat]=$EXTENSIONS_CISCAT + [extensions.aws]=$EXTENSIONS_AWS + [extensions.virustotal]=$EXTENSIONS_VIRUSTOTAL + [extensions.osquery]=$EXTENSIONS_OSQUERY + [timeout]=$APP_TIMEOUT + [wazuh.shards]=$WAZUH_SHARDS + [wazuh.replicas]=$WAZUH_REPLICAS + [wazuh-version.shards]=$WAZUH_VERSION_SHARDS + [wazuh-version.replicas]=$WAZUH_VERSION_REPLICAS + [ip.selector]=$IP_SELECTOR + [ip.ignore]=$IP_IGNORE + [wazuh.monitoring.enabled]=$WAZUH_MONITORING_ENABLED + [wazuh.monitoring.frequency]=$WAZUH_MONITORING_FREQUENCY + [wazuh.monitoring.shards]=$WAZUH_MONITORING_SHARDS + [wazuh.monitoring.replicas]=$WAZUH_MONITORING_REPLICAS + [admin]=$ADMIN_PRIVILEGES +) + +for i in "${!CONFIG_MAP[@]}" +do + if [ "${CONFIG_MAP[$i]}" != "" ]; then + sed -i 's/.*#'"$i"'.*/'"$i"': '"${CONFIG_MAP[$i]}"'/' $kibana_config_file + fi +done + +# remove default API entry (new in 3.11.0_7.5.1) +sed -ie '/- default:/,+4d' $kibana_config_file + +CONFIG_CODE=$(curl ${auth} -s -o /dev/null -w "%{http_code}" -XGET $el_url/.wazuh/_doc/1513629884013) + +grep -q 1513629884013 $kibana_config_file +_config_exists=$? + +if [[ "x$CONFIG_CODE" != "x200" && $_config_exists -ne 0 ]]; then +cat << EOF > $kibana_config_file +hosts: + - 1513629884013: + url: $wazuh_url + port: $wazuh_port + username: $api_user + password: $api_password +EOF +else + echo "Wazuh APP already configured" +fi diff --git a/kibana-opendistro/config/welcome_wazuh.sh b/kibana-opendistro/config/welcome_wazuh.sh new file mode 100644 index 00000000..46aaddfa --- /dev/null +++ b/kibana-opendistro/config/welcome_wazuh.sh @@ -0,0 +1,14 @@ +#!/bin/bash +# Wazuh Docker Copyright (C) 2020 Wazuh Inc. (License GPLv2) + +if [[ $CHANGE_WELCOME == "true" ]] +then + echo "Set Wazuh app as the default landing page" + echo "server.defaultRoute: /app/wazuh" >> /usr/share/kibana/config/kibana.yml + + echo "Set custom welcome styles" + cp -f /tmp/custom_welcome/template.js.hbs /usr/share/kibana/src/legacy/ui/ui_render/bootstrap/template.js.hbs + cp -f /tmp/custom_welcome/security-login.style.css /usr/share/kibana/optimize/bundles/security-login.style.css + cp -f /tmp/custom_welcome/*svg /usr/share/kibana/optimize/bundles/ +fi + diff --git a/nginx_conf/kibana-web.conf b/nginx_conf/kibana-web.conf index b3821747..9ac5b667 100644 --- a/nginx_conf/kibana-web.conf +++ b/nginx_conf/kibana-web.conf @@ -10,11 +10,11 @@ server { ssl_certificate /etc/nginx/conf.d/ssl/kibana-access.pem; ssl_certificate_key /etc/nginx/conf.d/ssl/kibana-access.key; location / { - auth_basic "Restricted Access"; - auth_basic_user_file /etc/nginx/conf.d/kibana.htpasswd; + # auth_basic "Restricted Access"; + # auth_basic_user_file /etc/nginx/conf.d/kibana.htpasswd; proxy_pass http://kibana:5601/; proxy_buffer_size 128k; proxy_buffers 4 256k; proxy_busy_buffers_size 256k; } -} +} diff --git a/wazuh-opendistro/Dockerfile b/wazuh-opendistro/Dockerfile new file mode 100644 index 00000000..dd76e266 --- /dev/null +++ b/wazuh-opendistro/Dockerfile @@ -0,0 +1,54 @@ +# Wazuh Docker Copyright (C) 2020 Wazuh Inc. (License GPLv2) +FROM centos:7 + +ARG FILEBEAT_VERSION=7.8.0 +ARG WAZUH_VERSION=4.0.0-1 +ARG TEMPLATE_VERSION="develop" +ARG WAZUH_FILEBEAT_MODULE="wazuh-filebeat-0.2.tar.gz" + +ENV API_USER="foo" \ + API_PASS="bar" + + +# Set repositories. +RUN rpm --import https://packages.wazuh.com/key/GPG-KEY-WAZUH + +COPY config/wazuh.repo /etc/yum.repos.d/wazuh.repo + +RUN yum --enablerepo=updates clean metadata && \ + yum -y install openssl which && yum -y install wazuh-manager-${WAZUH_VERSION} -y && \ + sed -i "s/^enabled=1/enabled=0/" /etc/yum.repos.d/wazuh.repo && \ + yum clean all && rm -rf /var/cache/yum + +RUN curl -L -O https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-oss-${FILEBEAT_VERSION}-x86_64.rpm &&\ + rpm -i filebeat-oss-${FILEBEAT_VERSION}-x86_64.rpm && rm -f filebeat-oss-${FILEBEAT_VERSION}-x86_64.rpm + +RUN curl -s https://packages-dev.wazuh.com/utils/${WAZUH_FILEBEAT_MODULE} | tar -xvz -C /usr/share/filebeat/module + +ARG S6_VERSION="v2.0.0.1" +RUN curl --fail --silent -L https://github.com/just-containers/s6-overlay/releases/download/${S6_VERSION}/s6-overlay-amd64.tar.gz \ + -o /tmp/s6-overlay-amd64.tar.gz && \ + tar xzf /tmp/s6-overlay-amd64.tar.gz -C / --exclude="./bin" && \ + tar xzf /tmp/s6-overlay-amd64.tar.gz -C /usr ./bin && \ + rm /tmp/s6-overlay-amd64.tar.gz + +COPY config/filebeat.yml /etc/filebeat/ + +RUN chmod go-w /etc/filebeat/filebeat.yml + +ADD https://raw.githubusercontent.com/wazuh/wazuh/$TEMPLATE_VERSION/extensions/elasticsearch/7.x/wazuh-template.json /etc/filebeat +RUN chmod go-w /etc/filebeat/wazuh-template.json + +COPY config/etc/ /etc/ + +# Prepare permanent data +# Sync calls are due to https://github.com/docker/docker/issues/9547 +COPY config/permanent_data.env config/permanent_data.sh / +RUN chmod 755 /permanent_data.sh && \ + sync && /permanent_data.sh && \ + sync && rm /permanent_data.sh + +# Services ports +EXPOSE 55000/tcp 1514/tcp 1515/tcp 514/udp 1516/tcp + +ENTRYPOINT [ "/init" ] diff --git a/wazuh-opendistro/config/etc/cont-init.d/0-wazuh-init b/wazuh-opendistro/config/etc/cont-init.d/0-wazuh-init new file mode 100644 index 00000000..1aa78f65 --- /dev/null +++ b/wazuh-opendistro/config/etc/cont-init.d/0-wazuh-init @@ -0,0 +1,164 @@ +#!/usr/bin/with-contenv bash +# Wazuh App Copyright (C) 2020 Wazuh Inc. (License GPLv2) + +# Variables +source /permanent_data.env + +WAZUH_INSTALL_PATH=/var/ossec +WAZUH_CONFIG_MOUNT=/wazuh-config-mount +AUTO_ENROLLMENT_ENABLED=${AUTO_ENROLLMENT_ENABLED:-true} + + +############################################################################## +# Aux functions +############################################################################## +print() { + echo -e $1 +} + +error_and_exit() { + echo "Error executing command: '$1'." + echo 'Exiting.' + exit 1 +} + +exec_cmd() { + eval $1 > /dev/null 2>&1 || error_and_exit "$1" +} + +exec_cmd_stdout() { + eval $1 2>&1 || error_and_exit "$1" +} + + +############################################################################## +# This function will attempt to mount every directory in PERMANENT_DATA +# into the respective path. +# If the path is empty means permanent data volume is also empty, so a backup +# will be copied into it. Otherwise it will not be copied because there is +# already data inside the volume for the specified path. +############################################################################## + +mount_permanent_data() { + for permanent_dir in "${PERMANENT_DATA[@]}"; do + # Check if the path is not empty + if find ${permanent_dir} -mindepth 1 | read; then + print "The path ${permanent_dir} is already mounted" + else + print "Installing ${permanent_dir}" + exec_cmd "cp -a ${WAZUH_INSTALL_PATH}/data_tmp/permanent${permanent_dir}/. ${permanent_dir}" + fi + done +} + +############################################################################## +# This function will replace from the permanent data volume every file +# contained in PERMANENT_DATA_EXCP +# Some files as 'internal_options.conf' are saved as permanent data, but +# they must be updated to work properly if wazuh version is changed. +############################################################################## + +apply_exclusion_data() { + for exclusion_file in "${PERMANENT_DATA_EXCP[@]}"; do + if [ -e ${WAZUH_INSTALL_PATH}/data_tmp/exclusion/${exclusion_file} ] + then + DIR=$(dirname "${exclusion_file}") + if [ ! -e ${DIR} ] + then + mkdir -p ${DIR} + fi + + print "Updating ${exclusion_file}" + exec_cmd "cp -p ${WAZUH_INSTALL_PATH}/data_tmp/exclusion/${exclusion_file} ${exclusion_file}" + fi + done +} + +############################################################################## +# This function will delete from the permanent data volume every file +# contained in PERMANENT_DATA_DEL +############################################################################## + +remove_data_files() { + for del_file in "${PERMANENT_DATA_DEL[@]}"; do + if [ -e ${del_file} ] + then + print "Removing ${del_file}" + exec_cmd "rm ${del_file}" + fi + done +} + +############################################################################## +# Create certificates: Manager +############################################################################## + +create_ossec_key_cert() { + print "Creating ossec-authd key and cert" + exec_cmd "openssl genrsa -out ${WAZUH_INSTALL_PATH}/etc/sslmanager.key 4096" + exec_cmd "openssl req -new -x509 -key ${WAZUH_INSTALL_PATH}/etc/sslmanager.key -out ${WAZUH_INSTALL_PATH}/etc/sslmanager.cert -days 3650 -subj /CN=${HOSTNAME}/" +} + +############################################################################## +# Copy all files from $WAZUH_CONFIG_MOUNT to $WAZUH_INSTALL_PATH and respect +# destination files permissions +# +# For example, to mount the file /var/ossec/data/etc/ossec.conf, mount it at +# $WAZUH_CONFIG_MOUNT/etc/ossec.conf in your container and this code will +# replace the ossec.conf file in /var/ossec/data/etc with yours. +############################################################################## + +mount_files() { + if [ -e "$WAZUH_CONFIG_MOUNT" ] + then + print "Identified Wazuh configuration files to mount..." + exec_cmd_stdout "cp --verbose -r $WAZUH_CONFIG_MOUNT/* $WAZUH_INSTALL_PATH" + else + print "No Wazuh configuration files to mount..." + fi +} + +############################################################################## +# Stop OSSEC +############################################################################## + +function ossec_shutdown(){ + ${WAZUH_INSTALL_PATH}/bin/ossec-control stop; +} + + +############################################################################## +# Main function +############################################################################## + +main() { + # Mount permanent data (i.e. ossec.conf) + mount_permanent_data + + # Restore files stored in permanent data that are not permanent (i.e. internal_options.conf) + apply_exclusion_data + + # Remove some files in permanent_data (i.e. .template.db) + remove_data_files + + # Generate ossec-authd certs if AUTO_ENROLLMENT_ENABLED is true and does not exist + if [ $AUTO_ENROLLMENT_ENABLED == true ] + then + if [ ! -e ${WAZUH_INSTALL_PATH}/etc/sslmanager.key ] + then + create_ossec_key_cert + fi + fi + + # Mount selected files (WAZUH_CONFIG_MOUNT) to container + mount_files + + # Trap exit signals and do a proper shutdown + trap "ossec_shutdown; exit" SIGINT SIGTERM + + # Delete temporary data folder + rm -rf ${WAZUH_INSTALL_PATH}/data_tmp + +} + +main diff --git a/wazuh-opendistro/config/etc/cont-init.d/1-config-filebeat b/wazuh-opendistro/config/etc/cont-init.d/1-config-filebeat new file mode 100644 index 00000000..ce033aa5 --- /dev/null +++ b/wazuh-opendistro/config/etc/cont-init.d/1-config-filebeat @@ -0,0 +1,45 @@ +#!/usr/bin/with-contenv bash +# Wazuh App Copyright (C) 2020 Wazuh Inc. (License GPLv2) + +set -e + +if [ "$ELASTICSEARCH_URL" != "" ]; then + >&2 echo "Customize Elasticsearch ouput IP" + sed -i "s|hosts:.*|hosts: ['$ELASTICSEARCH_URL']|g" /etc/filebeat/filebeat.yml +fi + +# Configure filebeat.yml security settings + +if [ "$ELASTIC_USERNAME" != "" ]; then + >&2 echo "Configuring username." + sed -i "s|#username:.*|username: '$ELASTIC_USERNAME'|g" /etc/filebeat/filebeat.yml +fi + +if [ "$ELASTIC_PASSWORD" != "" ]; then + >&2 echo "Configuring password." + sed -i "s|#password:.*|password: '$ELASTIC_PASSWORD'|g" /etc/filebeat/filebeat.yml +fi + +if [ "$FILEBEAT_SSL_VERIFICATION_MODE" != "" ]; then + >&2 echo "Configuring SSL verification mode." + sed -i "s|#ssl.verification_mode:.*|ssl.verification_mode: $FILEBEAT_SSL_VERIFICATION_MODE|g" /etc/filebeat/filebeat.yml +fi + +if [ "$SSL_CERTIFICATE_AUTHORITIES" != "" ]; then + >&2 echo "Configuring Certificate Authorities." + sed -i "s|#ssl.certificate_authorities:.*|ssl.certificate_authorities: ['$SSL_CERTIFICATE_AUTHORITIES']|g" /etc/filebeat/filebeat.yml +fi + +if [ "$SSL_CERTIFICATE" != "" ]; then + >&2 echo "Configuring SSL Certificate." + sed -i "s|#ssl.certificate:.*|ssl.certificate: '$SSL_CERTIFICATE'|g" /etc/filebeat/filebeat.yml +fi + +if [ "$SSL_KEY" != "" ]; then + >&2 echo "Configuring SSL Key." + sed -i "s|#ssl.key:.*|ssl.key: '$SSL_KEY'|g" /etc/filebeat/filebeat.yml +fi + + +chmod go-w /etc/filebeat/filebeat.yml || true +chown root: /etc/filebeat/filebeat.yml || true diff --git a/wazuh-opendistro/config/etc/cont-init.d/2-manager b/wazuh-opendistro/config/etc/cont-init.d/2-manager new file mode 100644 index 00000000..6ce02ae2 --- /dev/null +++ b/wazuh-opendistro/config/etc/cont-init.d/2-manager @@ -0,0 +1,3 @@ +#!/usr/bin/with-contenv bash + +/var/ossec/bin/ossec-control start diff --git a/wazuh-opendistro/config/etc/services.d/filebeat/finish b/wazuh-opendistro/config/etc/services.d/filebeat/finish new file mode 100644 index 00000000..8813eb67 --- /dev/null +++ b/wazuh-opendistro/config/etc/services.d/filebeat/finish @@ -0,0 +1,6 @@ +#!/usr/bin/env sh +echo >&2 "Filebeat exited. code=${1}" + +# terminate other services to exit from the container +exec s6-svscanctl -t /var/run/s6/services + diff --git a/wazuh-opendistro/config/etc/services.d/filebeat/run b/wazuh-opendistro/config/etc/services.d/filebeat/run new file mode 100644 index 00000000..706ee5af --- /dev/null +++ b/wazuh-opendistro/config/etc/services.d/filebeat/run @@ -0,0 +1,4 @@ +#!/usr/bin/with-contenv sh +echo >&2 "starting Filebeat" + +exec /usr/share/filebeat/bin/filebeat -e -c /etc/filebeat/filebeat.yml -path.home /usr/share/filebeat -path.config /etc/filebeat -path.data /var/lib/filebeat -path.logs /var/log/filebeat diff --git a/wazuh-opendistro/config/filebeat.yml b/wazuh-opendistro/config/filebeat.yml new file mode 100644 index 00000000..8a627bf9 --- /dev/null +++ b/wazuh-opendistro/config/filebeat.yml @@ -0,0 +1,22 @@ + +# Wazuh - Filebeat configuration file +filebeat.modules: + - module: wazuh + alerts: + enabled: true + archives: + enabled: false + +setup.template.json.enabled: true +setup.template.json.path: '/etc/filebeat/wazuh-template.json' +setup.template.json.name: 'wazuh' +setup.template.overwrite: true +setup.ilm.enabled: false +output.elasticsearch: + hosts: ['https://elasticsearch:9200'] + #username: + #password: + #ssl.verification_mode: + #ssl.certificate_authorities: + #ssl.certificate: + #ssl.key: diff --git a/wazuh-opendistro/config/permanent_data.env b/wazuh-opendistro/config/permanent_data.env new file mode 100644 index 00000000..9c2556d8 --- /dev/null +++ b/wazuh-opendistro/config/permanent_data.env @@ -0,0 +1,67 @@ +# Permanent data mounted in volumes +i=0 +PERMANENT_DATA[((i++))]="/var/ossec/api/configuration" +PERMANENT_DATA[((i++))]="/var/ossec/etc" +PERMANENT_DATA[((i++))]="/var/ossec/logs" +PERMANENT_DATA[((i++))]="/var/ossec/queue" +PERMANENT_DATA[((i++))]="/var/ossec/agentless" +PERMANENT_DATA[((i++))]="/var/ossec/var/multigroups" +PERMANENT_DATA[((i++))]="/var/ossec/integrations" +PERMANENT_DATA[((i++))]="/var/ossec/active-response/bin" +PERMANENT_DATA[((i++))]="/var/ossec/wodles" +PERMANENT_DATA[((i++))]="/etc/filebeat" +export PERMANENT_DATA + +# Files mounted in a volume that should not be permanent +i=0 +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/etc/internal_options.conf" +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/integrations/pagerduty" +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/integrations/slack" +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/integrations/slack.py" +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/integrations/virustotal" +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/integrations/virustotal.py" +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/active-response/bin/default-firewall-drop.sh" +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/active-response/bin/disable-account.sh" +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/active-response/bin/firewalld-drop.sh" +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/active-response/bin/firewall-drop.sh" +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/active-response/bin/host-deny.sh" +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/active-response/bin/ip-customblock.sh" +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/active-response/bin/ipfw_mac.sh" +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/active-response/bin/ipfw.sh" +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/active-response/bin/kaspersky.py" +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/active-response/bin/kaspersky.sh" +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/active-response/bin/npf.sh" +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/active-response/bin/ossec-slack.sh" +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/active-response/bin/ossec-tweeter.sh" +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/active-response/bin/pf.sh" +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/active-response/bin/restart-ossec.sh" +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/active-response/bin/restart.sh" +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/active-response/bin/route-null.sh" +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/agentless/sshlogin.exp" +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/agentless/ssh_pixconfig_diff" +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/agentless/ssh_asa-fwsmconfig_diff" +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/agentless/ssh_integrity_check_bsd" +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/agentless/main.exp" +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/agentless/su.exp" +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/agentless/ssh_integrity_check_linux" +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/agentless/register_host.sh" +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/agentless/ssh_generic_diff" +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/agentless/ssh_foundry_diff" +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/agentless/ssh_nopass.exp" +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/agentless/ssh.exp" +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/wodles/aws/aws-s3" +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/wodles/aws/aws-s3.py" +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/wodles/azure/azure-logs" +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/wodles/azure/azure-logs.py" +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/wodles/docker/DockerListener" +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/wodles/docker/DockerListener.py" +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/wodles/gcloud/gcloud" +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/wodles/gcloud/gcloud.py" +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/wodles/gcloud/integration.py" +PERMANENT_DATA_EXCP[((i++))]="/var/ossec/wodles/gcloud/tools.py" +export PERMANENT_DATA_EXCP + +# Files mounted in a volume that should be deleted +i=0 +PERMANENT_DATA_DEL[((i++))]="/var/ossec/queue/db/.template.db" +export PERMANENT_DATA_DEL diff --git a/wazuh-opendistro/config/permanent_data.sh b/wazuh-opendistro/config/permanent_data.sh new file mode 100644 index 00000000..7dfaa647 --- /dev/null +++ b/wazuh-opendistro/config/permanent_data.sh @@ -0,0 +1,40 @@ +#!/bin/bash +# Wazuh App Copyright (C) 2020 Wazuh Inc. (License GPLv2) + +# Variables +source /permanent_data.env + +WAZUH_INSTALL_PATH=/var/ossec +DATA_TMP_PATH=${WAZUH_INSTALL_PATH}/data_tmp +mkdir ${DATA_TMP_PATH} + +# Move exclusion files to EXCLUSION_PATH +EXCLUSION_PATH=${DATA_TMP_PATH}/exclusion +mkdir ${EXCLUSION_PATH} + +for exclusion_file in "${PERMANENT_DATA_EXCP[@]}"; do + # Create the directory for the exclusion file if it does not exist + DIR=$(dirname "${exclusion_file}") + if [ ! -e ${EXCLUSION_PATH}/${DIR} ] + then + mkdir -p ${EXCLUSION_PATH}/${DIR} + fi + + mv ${exclusion_file} ${EXCLUSION_PATH}/${exclusion_file} +done + +# Move permanent files to PERMANENT_PATH +PERMANENT_PATH=${DATA_TMP_PATH}/permanent +mkdir ${PERMANENT_PATH} + +for permanent_dir in "${PERMANENT_DATA[@]}"; do + # Create the directory for the permanent file if it does not exist + DIR=$(dirname "${permanent_dir}") + if [ ! -e ${PERMANENT_PATH}${DIR} ] + then + mkdir -p ${PERMANENT_PATH}${DIR} + fi + + mv ${permanent_dir} ${PERMANENT_PATH}${permanent_dir} + +done diff --git a/wazuh-opendistro/config/wazuh.repo b/wazuh-opendistro/config/wazuh.repo new file mode 100644 index 00000000..15e1b822 --- /dev/null +++ b/wazuh-opendistro/config/wazuh.repo @@ -0,0 +1,7 @@ +[wazuh_repo] +gpgcheck=1 +gpgkey=https://packages.wazuh.com/key/GPG-KEY-WAZUH +enabled=1 +name=Wazuh repository +baseurl=https://packages-dev.wazuh.com/pre-release/yum/ +protect=1