Merge pull request #12268 from Security-Onion-Solutions/feature/fleet-artifacts

Feature/fleet artifacts
This commit is contained in:
Josh Brower
2024-02-12 08:58:14 -05:00
committed by GitHub
11 changed files with 160 additions and 10 deletions

View File

@@ -41,9 +41,9 @@ file_roots:
base: base:
- /opt/so/saltstack/local/salt - /opt/so/saltstack/local/salt
- /opt/so/saltstack/default/salt - /opt/so/saltstack/default/salt
- /nsm/elastic-fleet/artifacts
- /opt/so/rules/nids - /opt/so/rules/nids
# The master_roots setting configures a master-only copy of the file_roots dictionary, # The master_roots setting configures a master-only copy of the file_roots dictionary,
# used by the state compiler. # used by the state compiler.
# master_roots: /opt/so/saltstack/salt-master # master_roots: /opt/so/saltstack/salt-master

View File

@@ -180,6 +180,7 @@
'telegraf', 'telegraf',
'firewall', 'firewall',
'logstash', 'logstash',
'nginx',
'healthcheck', 'healthcheck',
'schedule', 'schedule',
'elasticfleet', 'elasticfleet',

View File

@@ -84,6 +84,13 @@ docker:
custom_bind_mounts: [] custom_bind_mounts: []
extra_hosts: [] extra_hosts: []
extra_env: [] extra_env: []
'so-nginx-fleet-node':
final_octet: 31
port_bindings:
- 8443:8443
custom_bind_mounts: []
extra_hosts: []
extra_env: []
'so-playbook': 'so-playbook':
final_octet: 32 final_octet: 32
port_bindings: port_bindings:

View File

@@ -48,6 +48,7 @@ docker:
so-logstash: *dockerOptions so-logstash: *dockerOptions
so-mysql: *dockerOptions so-mysql: *dockerOptions
so-nginx: *dockerOptions so-nginx: *dockerOptions
so-nginx-fleet-node: *dockerOptions
so-playbook: *dockerOptions so-playbook: *dockerOptions
so-redis: *dockerOptions so-redis: *dockerOptions
so-sensoroni: *dockerOptions so-sensoroni: *dockerOptions

View File

@@ -38,12 +38,26 @@ so-elastic-fleet-auto-configure-server-urls:
- retry: True - retry: True
{% endif %} {% endif %}
# Automatically update Fleet Server Elasticsearch URLs # Automatically update Fleet Server Elasticsearch URLs & Agent Artifact URLs
{% if grains.role not in ['so-fleet'] %} {% if grains.role not in ['so-fleet'] %}
so-elastic-fleet-auto-configure-elasticsearch-urls: so-elastic-fleet-auto-configure-elasticsearch-urls:
cmd.run: cmd.run:
- name: /usr/sbin/so-elastic-fleet-es-url-update - name: /usr/sbin/so-elastic-fleet-es-url-update
- retry: True - retry: True
so-elastic-fleet-auto-configure-artifact-urls:
cmd.run:
- name: /usr/sbin/so-elastic-fleet-artifacts-url-update
- retry: True
{% endif %}
# Sync Elastic Agent artifacts to Fleet Node
{% if grains.role in ['so-fleet'] %}
elasticagent_syncartifacts:
file.recurse:
- name: /nsm/elastic-fleet/artifacts/beats
- source: salt://beats
{% endif %} {% endif %}
{% if SERVICETOKEN != '' %} {% if SERVICETOKEN != '' %}

View File

@@ -0,0 +1,90 @@
# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one
# or more contributor license agreements. Licensed under the Elastic License 2.0; you may not use
# this file except in compliance with the Elastic License 2.0.
{% from 'vars/globals.map.jinja' import GLOBALS %}
. /usr/sbin/so-common
# Only run on Managers
if ! is_manager_node; then
printf "Not a Manager Node... Exiting"
exit 0
fi
# Function to check if an array contains a value
array_contains () {
local array="$1[@]"
local seeking=$2
local in=1
for element in "${!array}"; do
if [[ $element == "$seeking" ]]; then
in=0
break
fi
done
return $in
}
# Query for the current Grid Nodes that are running Logstash (which includes Fleet Nodes)
LOGSTASHNODES='{{ salt['pillar.get']('logstash:nodes', {}) | tojson }}'
# Initialize an array for new hosts from Fleet Nodes
declare -a NEW_LIST=()
# Query for Fleet Nodes & add them to the list (Hostname)
if grep -q "fleet" <<< "$LOGSTASHNODES"; then
readarray -t FLEETNODES < <(jq -r '.fleet | keys_unsorted[]' <<< "$LOGSTASHNODES")
for NODE in "${FLEETNODES[@]}"; do
URL="http://$NODE:8443/artifacts/"
NAME="FleetServer_$NODE"
NEW_LIST+=("$URL=$NAME")
done
fi
# Create an array for expected hosts and their names
declare -A expected_urls=(
["http://{{ GLOBALS.url_base }}:8443/artifacts/"]="FleetServer_{{ GLOBALS.hostname }}"
["https://artifacts.elastic.co/downloads/"]="Elastic Artifacts"
)
# Merge NEW_LIST into expected_urls
for entry in "${NEW_LIST[@]}"; do
# Extract URL and Name from each entry
IFS='=' read -r URL NAME <<< "$entry"
# Add to expected_urls, automatically handling URL as key and NAME as value
expected_urls["$URL"]="$NAME"
done
# Fetch the current hosts from the API
current_urls=$(curl -K /opt/so/conf/elasticsearch/curl.config 'http://localhost:5601/api/fleet/agent_download_sources' | jq -r .items[].host)
# Convert current hosts to an array
IFS=$'\n' read -rd '' -a current_urls_array <<<"$current_urls"
# Flag to track if any host was added
any_url_added=0
# Check each expected host
for host in "${!expected_urls[@]}"; do
array_contains current_urls_array "$host" || {
echo "$host (${expected_urls[$host]}) is missing. Adding it..."
# Prepare the JSON payload
JSON_STRING=$( jq -n \
--arg NAME "${expected_urls[$host]}" \
--arg URL "$host" \
'{"name":$NAME,"host":$URL}' )
# Create the missing host
curl -K /opt/so/conf/elasticsearch/curl.config -L -X POST "localhost:5601/api/fleet/agent_download_sources" -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d "$JSON_STRING"
# Flag that an artifact URL was added
any_url_added=1
}
done
if [[ $any_url_added -eq 0 ]]; then
echo "All expected artifact URLs are present. No updates needed."
fi

View File

@@ -95,6 +95,7 @@
{% set NODE_CONTAINERS = [ {% set NODE_CONTAINERS = [
'so-elastic-fleet', 'so-elastic-fleet',
'so-logstash', 'so-logstash',
'so-nginx-fleet-node'
] %} ] %}
{% elif GLOBALS.role == 'so-sensor' %} {% elif GLOBALS.role == 'so-sensor' %}

View File

@@ -610,6 +610,9 @@ up_to_2.4.50() {
mkdir /opt/so/rules/nids/suri mkdir /opt/so/rules/nids/suri
chown socore:socore /opt/so/rules/nids/suri chown socore:socore /opt/so/rules/nids/suri
mv -v /opt/so/rules/nids/*.rules /opt/so/rules/nids/suri/. mv -v /opt/so/rules/nids/*.rules /opt/so/rules/nids/suri/.
echo "Adding /nsm/elastic-fleet/artifacts to file_roots in /etc/salt/master using so-yaml"
so-yaml.py append /etc/salt/master file_roots.base /nsm/elastic-fleet/artifacts
INSTALLEDVERSION=2.4.50 INSTALLEDVERSION=2.4.50
} }

View File

@@ -14,6 +14,9 @@ include:
- nginx.config - nginx.config
- nginx.sostatus - nginx.sostatus
{% if grains.role not in ['so-fleet'] %}
{# if the user has selected to replace the crt and key in the ui #} {# if the user has selected to replace the crt and key in the ui #}
{% if NGINXMERGED.ssl.replace_cert %} {% if NGINXMERGED.ssl.replace_cert %}
@@ -88,6 +91,15 @@ make-rule-dir-nginx:
- recurse: - recurse:
- user - user
- group - group
{% endif %}
{# if this is an so-fleet node then we want to use the port bindings, custom bind mounts defined for fleet #}
{% if GLOBALS.role == 'so-fleet' %}
{% set container_config = 'so-nginx-fleet-node' %}
{% else %}
{% set container_config = 'so-nginx' %}
{% endif %}
so-nginx: so-nginx:
docker_container.running: docker_container.running:
@@ -95,11 +107,11 @@ so-nginx:
- hostname: so-nginx - hostname: so-nginx
- networks: - networks:
- sobridge: - sobridge:
- ipv4_address: {{ DOCKER.containers['so-nginx'].ip }} - ipv4_address: {{ DOCKER.containers[container_config].ip }}
- extra_hosts: - extra_hosts:
- {{ GLOBALS.manager }}:{{ GLOBALS.manager_ip }} - {{ GLOBALS.manager }}:{{ GLOBALS.manager_ip }}
{% if DOCKER.containers['so-nginx'].extra_hosts %} {% if DOCKER.containers[container_config].extra_hosts %}
{% for XTRAHOST in DOCKER.containers['so-nginx'].extra_hosts %} {% for XTRAHOST in DOCKER.containers[container_config].extra_hosts %}
- {{ XTRAHOST }} - {{ XTRAHOST }}
{% endfor %} {% endfor %}
{% endif %} {% endif %}
@@ -119,20 +131,20 @@ so-nginx:
- /nsm/repo:/opt/socore/html/repo:ro - /nsm/repo:/opt/socore/html/repo:ro
- /nsm/rules:/nsm/rules:ro - /nsm/rules:/nsm/rules:ro
{% endif %} {% endif %}
{% if DOCKER.containers['so-nginx'].custom_bind_mounts %} {% if DOCKER.containers[container_config].custom_bind_mounts %}
{% for BIND in DOCKER.containers['so-nginx'].custom_bind_mounts %} {% for BIND in DOCKER.containers[container_config].custom_bind_mounts %}
- {{ BIND }} - {{ BIND }}
{% endfor %} {% endfor %}
{% endif %} {% endif %}
{% if DOCKER.containers['so-nginx'].extra_env %} {% if DOCKER.containers[container_config].extra_env %}
- environment: - environment:
{% for XTRAENV in DOCKER.containers['so-nginx'].extra_env %} {% for XTRAENV in DOCKER.containers[container_config].extra_env %}
- {{ XTRAENV }} - {{ XTRAENV }}
{% endfor %} {% endfor %}
{% endif %} {% endif %}
- cap_add: NET_BIND_SERVICE - cap_add: NET_BIND_SERVICE
- port_bindings: - port_bindings:
{% for BINDING in DOCKER.containers['so-nginx'].port_bindings %} {% for BINDING in DOCKER.containers[container_config].port_bindings %}
- {{ BINDING }} - {{ BINDING }}
{% endfor %} {% endfor %}
- watch: - watch:

View File

@@ -39,6 +39,26 @@ http {
include /etc/nginx/conf.d/*.conf; include /etc/nginx/conf.d/*.conf;
{%- if role in ['fleet'] %}
server {
listen 8443;
server_name {{ GLOBALS.hostname }};
root /opt/socore/html;
location /artifacts/ {
try_files $uri =206;
proxy_read_timeout 90;
proxy_connect_timeout 90;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Proxy "";
proxy_set_header X-Forwarded-Proto $scheme;
}
}
{%- endif %}
{%- if role in ['eval', 'managersearch', 'manager', 'standalone', 'import'] %} {%- if role in ['eval', 'managersearch', 'manager', 'standalone', 'import'] %}
server { server {

View File

@@ -264,6 +264,7 @@ base:
- telegraf - telegraf
- firewall - firewall
- logstash - logstash
- nginx
- elasticfleet - elasticfleet
- elasticfleet.install_agent_grid - elasticfleet.install_agent_grid
- schedule - schedule