Auth enhancements

This commit is contained in:
Jason Ertel
2021-09-02 09:44:57 -04:00
parent 84ecc3cba7
commit 10126bb7ef
9 changed files with 269 additions and 39 deletions

View File

@@ -0,0 +1,57 @@
#!/bin/bash
# Copyright 2014,2015,2016,2017,2018,2019,2020,2021 Security Onion Solutions, LLC
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
{%- set mainint = salt['pillar.get']('host:mainint') %}
{%- set MYIP = salt['grains.get']('ip_interfaces:' ~ mainint)[0] %}
default_conf_dir=/opt/so/conf
ELASTICSEARCH_HOST="{{ MYIP }}"
ELASTICSEARCH_PORT=9200
# Define a default directory to load roles from
ELASTICSEARCH_ROLES="$default_conf_dir/elasticsearch/roles/"
# Wait for ElasticSearch to initialize
echo -n "Waiting for ElasticSearch..."
COUNT=0
ELASTICSEARCH_CONNECTED="no"
while [[ "$COUNT" -le 240 ]]; do
{{ ELASTICCURL }} -k --output /dev/null --silent --head --fail -L https://"$ELASTICSEARCH_HOST":"$ELASTICSEARCH_PORT"
if [ $? -eq 0 ]; then
ELASTICSEARCH_CONNECTED="yes"
echo "connected!"
break
else
((COUNT+=1))
sleep 1
echo -n "."
fi
done
if [ "$ELASTICSEARCH_CONNECTED" == "no" ]; then
echo
echo -e "Connection attempt timed out. Unable to connect to ElasticSearch. \nPlease try: \n -checking log(s) in /var/log/elasticsearch/\n -running 'sudo docker ps' \n -running 'sudo so-elastic-restart'"
echo
fi
cd ${ELASTICSEARCH_ROLES}
echo "Loading templates..."
for role in *; do
name=$(echo "$role" | cut -d. -f1)
so-elasticsearch-query security/roles/$name -XPUT -d @"$role"
done
cd - >/dev/null

View File

@@ -18,11 +18,17 @@
source $(dirname $0)/so-common source $(dirname $0)/so-common
DEFAULT_ROLE=analyst
if [[ $# -lt 1 || $# -gt 2 ]]; then if [[ $# -lt 1 || $# -gt 2 ]]; then
echo "Usage: $0 <list|add|update|enable|disable|validate|valemail|valpass> [email]" echo "Usage: $0 <operation> [email] [role]"
echo ""
echo " where <operation> is one of the following:"
echo "" echo ""
echo " list: Lists all user email addresses currently defined in the identity system" echo " list: Lists all user email addresses currently defined in the identity system"
echo " add: Adds a new user to the identity system; requires 'email' parameter" echo " add: Adds a new user to the identity system; requires 'email' parameter"
echo " addrole: Grants a role to an existing user; requires 'email' and 'role' parameters"
echo " delrole: Removes a role from an existing user; requires 'email' and 'role' parameters"
echo " update: Updates a user's password; requires 'email' parameter" echo " update: Updates a user's password; requires 'email' parameter"
echo " enable: Enables a user; requires 'email' parameter" echo " enable: Enables a user; requires 'email' parameter"
echo " disable: Disables a user; requires 'email' parameter" echo " disable: Disables a user; requires 'email' parameter"
@@ -36,6 +42,7 @@ fi
operation=$1 operation=$1
email=$2 email=$2
role=$3
kratosUrl=${KRATOS_URL:-http://127.0.0.1:4434} kratosUrl=${KRATOS_URL:-http://127.0.0.1:4434}
databasePath=${KRATOS_DB_PATH:-/opt/so/conf/kratos/db/db.sqlite} databasePath=${KRATOS_DB_PATH:-/opt/so/conf/kratos/db/db.sqlite}
@@ -138,10 +145,9 @@ function updatePassword() {
function createElasticFile() { function createElasticFile() {
filename=$1 filename=$1
tmpFile=${filename} truncate -s 0 "$filename"
truncate -s 0 "$tmpFile" chmod 600 "$filename"
chmod 600 "$tmpFile" chown "${esUID}:${esGID}" "$filename"
chown "${esUID}:${esGID}" "$tmpFile"
} }
function syncElasticSystemUser() { function syncElasticSystemUser() {
@@ -174,28 +180,15 @@ function syncElasticSystemRole() {
function syncElastic() { function syncElastic() {
echo "Syncing users between SOC and Elastic..." echo "Syncing users between SOC and Elastic..."
usersTmpFile="${elasticUsersFile}.tmp" usersTmpFile="${elasticUsersFile}.tmp"
rolesTmpFile="${elasticRolesFile}.tmp"
createElasticFile "${usersTmpFile}" createElasticFile "${usersTmpFile}"
createElasticFile "${rolesTmpFile}"
authPillarJson=$(lookup_salt_value "auth" "elasticsearch" "pillar" "json") authPillarJson=$(lookup_salt_value "auth" "elasticsearch" "pillar" "json")
syncElasticSystemUser "$authPillarJson" "so_elastic_user" "$usersTmpFile" syncElasticSystemUser "$authPillarJson" "so_elastic_user" "$usersTmpFile"
syncElasticSystemRole "$authPillarJson" "so_elastic_user" "superuser" "$rolesTmpFile"
syncElasticSystemUser "$authPillarJson" "so_kibana_user" "$usersTmpFile" syncElasticSystemUser "$authPillarJson" "so_kibana_user" "$usersTmpFile"
syncElasticSystemRole "$authPillarJson" "so_kibana_user" "superuser" "$rolesTmpFile"
syncElasticSystemUser "$authPillarJson" "so_logstash_user" "$usersTmpFile" syncElasticSystemUser "$authPillarJson" "so_logstash_user" "$usersTmpFile"
syncElasticSystemRole "$authPillarJson" "so_logstash_user" "superuser" "$rolesTmpFile"
syncElasticSystemUser "$authPillarJson" "so_beats_user" "$usersTmpFile" syncElasticSystemUser "$authPillarJson" "so_beats_user" "$usersTmpFile"
syncElasticSystemRole "$authPillarJson" "so_beats_user" "superuser" "$rolesTmpFile"
syncElasticSystemUser "$authPillarJson" "so_monitor_user" "$usersTmpFile" syncElasticSystemUser "$authPillarJson" "so_monitor_user" "$usersTmpFile"
syncElasticSystemRole "$authPillarJson" "so_monitor_user" "remote_monitoring_collector" "$rolesTmpFile"
syncElasticSystemRole "$authPillarJson" "so_monitor_user" "remote_monitoring_agent" "$rolesTmpFile"
syncElasticSystemRole "$authPillarJson" "so_monitor_user" "monitoring_user" "$rolesTmpFile"
if [[ -f "$databasePath" ]]; then if [[ -f "$databasePath" ]]; then
# Generate the new users file # Generate the new users file
@@ -207,23 +200,12 @@ function syncElastic() {
jq -r '.user + ":" + .data.hashed_password' \ jq -r '.user + ":" + .data.hashed_password' \
>> "$usersTmpFile" >> "$usersTmpFile"
[[ $? != 0 ]] && fail "Unable to read credential hashes from database" [[ $? != 0 ]] && fail "Unable to read credential hashes from database"
# Generate the new users_roles file
echo "select 'superuser:' || ici.identifier " \
"from identity_credential_identifiers ici, identity_credentials ic " \
"where ici.identity_credential_id=ic.id and instr(ic.config, 'hashed_password') " \
"order by ici.identifier;" | \
sqlite3 "$databasePath" \
>> "$rolesTmpFile"
[[ $? != 0 ]] && fail "Unable to read credential IDs from database"
else else
echo "Database file does not exist yet, skipping users export" echo "Database file does not exist yet, skipping users export"
fi fi
if [[ -s "${usersTmpFile}" ]]; then if [[ -s "${usersTmpFile}" ]]; then
mv "${usersTmpFile}" "${elasticUsersFile}" mv "${usersTmpFile}" "${elasticUsersFile}"
mv "${rolesTmpFile}" "${elasticRolesFile}"
if [[ -z "$SKIP_STATE_APPLY" ]]; then if [[ -z "$SKIP_STATE_APPLY" ]]; then
echo "Elastic state will be re-applied to affected minions. This may take several minutes..." echo "Elastic state will be re-applied to affected minions. This may take several minutes..."
@@ -252,11 +234,73 @@ function listUsers() {
response=$(curl -Ss -L ${kratosUrl}/identities) response=$(curl -Ss -L ${kratosUrl}/identities)
[[ $? != 0 ]] && fail "Unable to communicate with Kratos" [[ $? != 0 ]] && fail "Unable to communicate with Kratos"
echo "${response}" | jq -r ".[] | .verifiable_addresses[0].value" | sort users=$(echo "${response}" | jq -r ".[] | .verifiable_addresses[0].value" | sort)
for user in $users; do
roles=$(grep "$user" users_roles | cut -d: -f1 | tr '\n' ' ')
echo "$user: $roles"
done
}
function addUserRole() {
email=$1
role=$2
return adjustUserRole "$email" "$role" "add"
}
function deleteUserRole() {
email=$1
role=$2
return adjustUserRole "$email" "$role" "del"
}
function adjustUserRole() {
email=$1
role=$2
op=$3
identityId=$(findIdByEmail "$email")
[[ ${identityId} == "" ]] && fail "User not found"
if [ ! -f "$filename" ]; then
rolesTmpFile="${elasticRolesFile}.tmp"
createElasticFile "${rolesTmpFile}"
authPillarJson=$(lookup_salt_value "auth" "elasticsearch" "pillar" "json")
syncElasticSystemRole "$authPillarJson" "so_elastic_user" "superuser" "$rolesTmpFile"
syncElasticSystemRole "$authPillarJson" "so_kibana_user" "superuser" "$rolesTmpFile"
syncElasticSystemRole "$authPillarJson" "so_logstash_user" "superuser" "$rolesTmpFile"
syncElasticSystemRole "$authPillarJson" "so_beats_user" "superuser" "$rolesTmpFile"
syncElasticSystemRole "$authPillarJson" "so_monitor_user" "remote_monitoring_collector" "$rolesTmpFile"
syncElasticSystemRole "$authPillarJson" "so_monitor_user" "remote_monitoring_agent" "$rolesTmpFile"
syncElasticSystemRole "$authPillarJson" "so_monitor_user" "monitoring_user" "$rolesTmpFile"
mv "${rolesTmpFile}" "${elasticRolesFile}"
fi
filename="$elasticRolesFile"
grep "$role:" "$elasticRolesFile" | grep "$email" && hasRole=1
if [[ "$op" == "add" ]]; then
if [[ "$hasRole" -eq 1 ]]; then
fail "User '$email' already has the role: $role"
else
echo "$role:$email" >> "$filename"
fi
elif [[ "$op" == "del" ]]; then
if [[ "$hasRole" -ne 1 ]]; then
fail "User '$email' does not have the role: $role"
else
sed -i "/^$role:$email\$/d" "$filename"
fi
else
echo "Unsupported role adjustment operation: $op"
exit 1
fi
return 0
} }
function createUser() { function createUser() {
email=$1 email=$1
role=$1
now=$(date -u +%FT%TZ) now=$(date -u +%FT%TZ)
addUserJson=$(cat <<EOF addUserJson=$(cat <<EOF
@@ -277,6 +321,8 @@ EOF
reason=$(echo "${response}" | jq ".error.message") reason=$(echo "${response}" | jq ".error.message")
[[ $? == 0 ]] && fail "Unable to add user: ${reason}" [[ $? == 0 ]] && fail "Unable to add user: ${reason}"
addUserRole "$email" "$role"
fi fi
updatePassword $identityId updatePassword $identityId
@@ -339,7 +385,7 @@ case "${operation}" in
lock lock
validateEmail "$email" validateEmail "$email"
updatePassword updatePassword
createUser "$email" createUser "$email" "${role:-$DEFAULT_ROLE}"
syncAll syncAll
echo "Successfully added new user to SOC" echo "Successfully added new user to SOC"
check_container thehive && echo "$password" | so-thehive-user-add "$email" check_container thehive && echo "$password" | so-thehive-user-add "$email"
@@ -351,6 +397,30 @@ case "${operation}" in
listUsers listUsers
;; ;;
"addrole")
verifyEnvironment
[[ "$email" == "" ]] && fail "Email address must be provided"
[[ "$role" == "" ]] && fail "Role must be provided"
lock
validateEmail "$email"
addUserRole "$email" "$role"
syncElastic
echo "Successfully added role to user"
;;
"delrole")
verifyEnvironment
[[ "$email" == "" ]] && fail "Email address must be provided"
[[ "$role" == "" ]] && fail "Role must be provided"
lock
validateEmail "$email"
deleteUserRole "$email" "$role"
syncElastic
echo "Successfully removed role from user"
;;
"update") "update")
verifyEnvironment verifyEnvironment
[[ "$email" == "" ]] && fail "Email address must be provided" [[ "$email" == "" ]] && fail "Email address must be provided"

View File

@@ -35,6 +35,7 @@
{% endif %} {% endif %}
{% set TEMPLATES = salt['pillar.get']('elasticsearch:templates', {}) %} {% set TEMPLATES = salt['pillar.get']('elasticsearch:templates', {}) %}
{% set ROLES = salt['pillar.get']('elasticsearch:roles', {}) %}
{% from 'elasticsearch/auth.map.jinja' import ELASTICAUTH with context %} {% from 'elasticsearch/auth.map.jinja' import ELASTICAUTH with context %}
@@ -119,6 +120,13 @@ estemplatedir:
- group: 939 - group: 939
- makedirs: True - makedirs: True
esrolesdir:
file.directory:
- name: /opt/so/conf/elasticsearch/roles
- user: 930
- group: 939
- makedirs: True
esingestconf: esingestconf:
file.recurse: file.recurse:
- name: /opt/so/conf/elasticsearch/ingest - name: /opt/so/conf/elasticsearch/ingest
@@ -157,6 +165,15 @@ es_template_{{TEMPLATE.split('.')[0] | replace("/","_") }}:
- group: 939 - group: 939
{% endfor %} {% endfor %}
esroles:
file.recurse:
- source: salt://elasticsearch/roles/
- name: /opt/so/conf/elasticsearch/roles/
- clean: True
- template: jinja
- user: 930
- group: 939
nsmesdir: nsmesdir:
file.directory: file.directory:
- name: /nsm/elasticsearch - name: /nsm/elasticsearch
@@ -193,7 +210,7 @@ auth_users_inode:
require: require:
- file: auth_users - file: auth_users
cmd.run: cmd.run:
- name: cat /opt/so/conf/elasticsearch/users.tmp > /opt/so/conf/elasticsearch/users && chown 930:930 /opt/so/conf/elasticsearch/users && chmod 600 /opt/so/conf/elasticsearch/users - name: cat /opt/so/conf/elasticsearch/users.tmp > /opt/so/conf/elasticsearch/users && chown 930:939 /opt/so/conf/elasticsearch/users && chmod 660 /opt/so/conf/elasticsearch/users
- onchanges: - onchanges:
- file: /opt/so/conf/elasticsearch/users.tmp - file: /opt/so/conf/elasticsearch/users.tmp
@@ -201,7 +218,7 @@ auth_users_roles_inode:
require: require:
- file: auth_users_roles - file: auth_users_roles
cmd.run: cmd.run:
- name: cat /opt/so/conf/elasticsearch/users_roles.tmp > /opt/so/conf/elasticsearch/users_roles && chown 930:930 /opt/so/conf/elasticsearch/users_roles && chmod 600 /opt/so/conf/elasticsearch/users_roles - name: cat /opt/so/conf/elasticsearch/users_roles.tmp > /opt/so/conf/elasticsearch/users_roles && chown 930:939 /opt/so/conf/elasticsearch/users_roles && chmod 660 /opt/so/conf/elasticsearch/users_roles
- onchanges: - onchanges:
- file: /opt/so/conf/elasticsearch/users_roles.tmp - file: /opt/so/conf/elasticsearch/users_roles.tmp
@@ -283,7 +300,7 @@ so-elasticsearch-pipelines:
- file: esyml - file: esyml
- file: so-elasticsearch-pipelines-file - file: so-elasticsearch-pipelines-file
{% if grains['role'] in ['so-manager', 'so-eval', 'so-managersearch', 'so-standalone', 'so-heavynode', 'so-node', 'so-import'] and TEMPLATES %} {% if TEMPLATES %}
so-elasticsearch-templates: so-elasticsearch-templates:
cmd.run: cmd.run:
- name: /usr/sbin/so-elasticsearch-templates-load - name: /usr/sbin/so-elasticsearch-templates-load
@@ -291,6 +308,12 @@ so-elasticsearch-templates:
- template: jinja - template: jinja
{% endif %} {% endif %}
so-elasticsearch-roles-load:
cmd.run:
- name: /usr/sbin/so-elasticsearch-roles-load
- cwd: /opt/so
- template: jinja
{% endif %} {# if grains['role'] != 'so-helix' #} {% endif %} {# if grains['role'] != 'so-helix' #}
{% else %} {% else %}

View File

@@ -0,0 +1,45 @@
{
"elasticsearch": {
"cluster": [
"cancel_task",
"create_snapshot",
"monitor",
"monitor_data_frame_transforms",
"monitor_ml",
"monitor_rollup",
"monitor_snapshot",
"monitor_text_structure",
"monitor_transform",
"monitor_watcher",
"read_ccr",
"read_ilm",
"read_pipeline",
"read_slm"
],
"indices": [
{
"names": [
"so-*"
],
"privileges": [
"read",
"read_cross_cluster",
"monitor",
"view_index_metadata"
]
}
],
"run_as": []
},
"kibana": [
{
"spaces": [
"*"
],
"base": [
"read"
],
"feature": {}
}
]
}

View File

@@ -31,10 +31,6 @@
"type": "string", "type": "string",
"title": "Last Name" "title": "Last Name"
}, },
"role": {
"type": "string",
"title": "Role"
},
"status": { "status": {
"type": "string", "type": "string",
"title": "Status" "title": "Status"

View File

@@ -0,0 +1,20 @@
# Define custom business role mappings, or remove mappings that come with
# the default SOC deployment.
#
# IMPORTANT: This file should be copied from the salt/default tree into
# the salt/local tree (preserving the same directory structure).
# Failure to do this will result in the customizations being
# overwritten on future upgrades.
#
# Syntax => prebuiltRoleX: customRoleY: op
# Explanation => roleY and roleZ are adjusted permissions of roleX, op is:
# + add the new permissions/role mappings (default)
# - remove existing prebuilt permissions
#
# In the example below, we will define a new role for junior analysts,
# that is nearly identical to the analyst role that comes with SOC, with the
# exception that it removes their ability to obtain details about other
# analysts in the system.
#
# analyst: jr_analyst
# user-monitor: jr_analyst:-

View File

@@ -85,6 +85,14 @@
"statickeyauth": { "statickeyauth": {
"anonymousCidr": "{{ DNET }}/24", "anonymousCidr": "{{ DNET }}/24",
"apiKey": "{{ SENSORONIKEY }}" "apiKey": "{{ SENSORONIKEY }}"
},
"staticrbac": {
"roleFiles": [
"rbac/permissions",
"rbac/roles",
"rbac/users_roles",
"rbac/custom_roles"
]
} }
}, },
"client": { "client": {

View File

@@ -62,6 +62,15 @@ soccustom:
- mode: 600 - mode: 600
- template: jinja - template: jinja
soccustomroles:
file.managed:
- name: /opt/so/conf/soc/custom_roles
- source: salt://soc/files/soc/custom_roles
- user: 939
- group: 939
- mode: 600
- template: jinja
# we dont want this added too early in setup, so we add the onlyif to verify 'startup_states: highstate' # we dont want this added too early in setup, so we add the onlyif to verify 'startup_states: highstate'
# is in the minion config. That line is added before the final highstate during setup # is in the minion config. That line is added before the final highstate during setup
sosyncusers: sosyncusers:
@@ -81,6 +90,8 @@ so-soc:
- /opt/so/conf/soc/motd.md:/opt/sensoroni/html/motd.md:ro - /opt/so/conf/soc/motd.md:/opt/sensoroni/html/motd.md:ro
- /opt/so/conf/soc/banner.md:/opt/sensoroni/html/login/banner.md:ro - /opt/so/conf/soc/banner.md:/opt/sensoroni/html/login/banner.md:ro
- /opt/so/conf/soc/custom.js:/opt/sensoroni/html/js/custom.js:ro - /opt/so/conf/soc/custom.js:/opt/sensoroni/html/js/custom.js:ro
- /opt/so/conf/soc/custom_roles:/opt/sensoroni/rbac/custom_roles:ro
- /opt/so/conf/elasticsearch/users_roles:/opt/sensoroni/rbac/users_roles:ro
- /opt/so/log/soc/:/opt/sensoroni/logs/:rw - /opt/so/log/soc/:/opt/sensoroni/logs/:rw
{%- if salt['pillar.get']('nodestab', {}) %} {%- if salt['pillar.get']('nodestab', {}) %}
- extra_hosts: - extra_hosts:

View File

@@ -121,7 +121,7 @@ add_web_user() {
{ {
echo "Attempting to add administrator user for web interface..."; echo "Attempting to add administrator user for web interface...";
export SKIP_STATE_APPLY=true export SKIP_STATE_APPLY=true
echo "$WEBPASSWD1" | /usr/sbin/so-user add "$WEBUSER"; echo "$WEBPASSWD1" | /usr/sbin/so-user add "$WEBUSER" "superuser";
unset SKIP_STATE_APPLY unset SKIP_STATE_APPLY
echo "Add user result: $?"; echo "Add user result: $?";
} >> "/root/so-user-add.log" 2>&1 } >> "/root/so-user-add.log" 2>&1