From cacd5b06435b573e918cedffb96508a6f7580abd Mon Sep 17 00:00:00 2001 From: Jason Ertel Date: Thu, 24 Oct 2024 09:36:09 -0400 Subject: [PATCH] connect --- salt/manager/sync_es_users.sls | 4 + salt/manager/tools/sbin/so-client | 331 ++++++++++++++++++++++++++++++ salt/soc/enabled.sls | 2 +- 3 files changed, 336 insertions(+), 1 deletion(-) create mode 100644 salt/manager/tools/sbin/so-client diff --git a/salt/manager/sync_es_users.sls b/salt/manager/sync_es_users.sls index 3aebef993..c46b58ce2 100644 --- a/salt/manager/sync_es_users.sls +++ b/salt/manager/sync_es_users.sls @@ -6,6 +6,10 @@ so-user.lock: file.missing: - name: /var/tmp/so-user.lock +so-client.lock: + file.missing: + - name: /var/tmp/so-client.lock + # Must run before elasticsearch docker container is started! sync_es_users: cmd.run: diff --git a/salt/manager/tools/sbin/so-client b/salt/manager/tools/sbin/so-client new file mode 100644 index 000000000..04e540465 --- /dev/null +++ b/salt/manager/tools/sbin/so-client @@ -0,0 +1,331 @@ +#!/bin/bash + +# 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 as shown at +# https://securityonion.net/license; you may not use this file except in compliance with the +# Elastic License 2.0. + +if [[ -f /usr/sbin/so-common ]]; then + source /usr/sbin/so-common +else + source $(dirname $0)/../../../common/tools/sbin/so-common +fi + + +DEFAULT_ROLE=limited-auditor + +function usage() { + cat < [supporting parameters] + + where is one of the following: + + list: Lists all client IDs and roles currently defined in the oauth2 system + + add: Adds a new client to the oauth2 system and outputs the generated secret + Required parameters: + --name + Optional parameters: + --role (defaults to $DEFAULT_ROLE) + --note (defaults to blank) + + delete: Deletes a client from the oauth2 system + Required parameters: + --id + + addrole: Grants a role to an existing client + Required parameters: + --id + --role + + delrole: Removes a role from an existing client + Required parameters: + --id + --role + + generate-secret: Regenerates a client's secret and outputs the new secret. + Required parameters: + --id + Optional parameters: + --skip-sync (defers the Elastic sync until the next scheduled time) + +USAGE_EOF + exit 1 +} + +if [[ $# -lt 1 || $1 == --help || $1 == -h || $1 == -? || $1 == --h ]]; then + usage +fi + +operation=$1 +shift + +while [[ $# -gt 0 ]]; do + param=$1 + shift + case "$param" in + --id) + id=$1 + shift + ;; + --role) + role=$1 + shift + ;; + --name) + name=$1 + shift + ;; + --note) + note=$1 + shift + ;; + *) + echo "Encountered unexpected parameter: $param" + usage + ;; + esac +done + +hydraUrl=${HYDRA_URL:-http://127.0.0.1:4445/admin} +socRolesFile=${SOC_ROLES_FILE:-/opt/so/conf/soc/soc_clients_roles} +soUID=${SOCORE_UID:-939} +soGID=${SOCORE_GID:-939} + +function lock() { + # Obtain file descriptor lock + exec 99>/var/tmp/so-client.lock || fail "Unable to create lock descriptor; if the system was not shutdown gracefully you may need to remove /var/tmp/so-client.lock manually." + flock -w 10 99 || fail "Another process is using so-client; if the system was not shutdown gracefully you may need to remove /var/tmp/so-client.lock manually." + trap 'rm -f /var/tmp/so-client.lock' EXIT +} + +function fail() { + msg=$1 + echo "$1" + exit 1 +} + +function require() { + cmd=$1 + which "$1" 2>&1 > /dev/null + [[ $? != 0 ]] && fail "This script requires the following command be installed: ${cmd}" +} + +# Verify this environment is capable of running this script +function verifyEnvironment() { + require "jq" + require "curl" + response=$(curl -Ss -L ${hydraUrl}/) + [[ "$response" != "404 page not found" ]] && fail "Unable to communicate with Hydra; specify URL via HYDRA_URL environment variable" +} + +function createFile() { + filename=$1 + uid=$2 + gid=$3 + + mkdir -p $(dirname "$filename") + truncate -s 0 "$filename" + chmod 600 "$filename" + chown "${uid}:${gid}" "$filename" +} + +function ensureRoleFileExists() { + if [[ ! -f "$socRolesFile" || ! -s "$socRolesFile" ]]; then + # Generate the new roles file + rolesTmpFile="${socRolesFile}.tmp" + createFile "$rolesTmpFile" "$soUID" "$soGID" + + if [[ -d "$socRolesFile" ]]; then + echo "Removing invalid roles directory created by Docker" + rm -fr "$socRolesFile" + fi + mv "${rolesTmpFile}" "${socRolesFile}" + fi +} + +function listClients() { + response=$(curl -Ss -L ${hydraUrl}/admin/clients) + [[ $? != 0 ]] && fail "Unable to communicate with Hydra" + + clientIds=$(echo "${response}" | jq -r ".[] | .client_id" | sort) + for clientId in $clientIds; do + roles=$(grep ":$clientId\$" "$socRolesFile" | cut -d: -f1 | tr '\n' ' ') + echo "$clientId: $roles" + done +} + +function addClientRole() { + id=$1 + role=$2 + + adjustClientRole "$id" "$role" "add" +} + +function deleteClientRole() { + id=$1 + role=$2 + + adjustClientRole "$id" "$role" "del" +} + +function adjustClientRole() { + identityId=$1 + role=$2 + op=$3 + + [[ ${identityId} == "" ]] && fail "Client not found" + + ensureRoleFileExists + + filename="$socRolesFile" + hasRole=0 + grep "^$role:" "$socRolesFile" | grep -q "$identityId" && hasRole=1 + if [[ "$op" == "add" ]]; then + if [[ "$hasRole" == "1" ]]; then + echo "Client '$identityId' already has the role: $role" + return 1 + else + echo "$role:$identityId" >> "$filename" + fi + elif [[ "$op" == "del" ]]; then + if [[ "$hasRole" -ne 1 ]]; then + fail "Client '$identityId' does not have the role: $role" + else + sed "/^$role:$identityId\$/d" "$filename" > "$filename.tmp" + cat "$filename".tmp > "$filename" + rm -f "$filename".tmp + fi + else + fail "Unsupported role adjustment operation: $op" + fi + return 0 +} + +function convertNameToId() { + name=$1 + + name=${name//[^[:alnum:]]/_} + echo "$name" | tr '[:upper:]' '[:lower:]' +} + +function createClient() { + name=$1 + role=$2 + note=$3 + + id=$(convertNameToId "$name") + now=$(date -u +%FT%TZ) + body=$(cat < "$rolesTmpFile" + cat "$rolesTmpFile" > "$socRolesFile" +} + +case "${operation}" in + "add") + verifyEnvironment + [[ "$name" == "" ]] && fail "A short client name must be provided" + + lock + createClient "$name" "${role:-$DEFAULT_ROLE}" "${note}" + echo "Successfully added new client to SOC. Run 'so-user sync' to sync with Elasticsearch." + ;; + + "list") + verifyEnvironment + listClients + ;; + + "addrole") + verifyEnvironment + [[ "$id" == "" ]] && fail "Id must be provided" + [[ "$role" == "" ]] && fail "Role must be provided" + + lock + if addClientRole "$email" "$role"; then + echo "Successfully added role to client" + fi + ;; + + "delrole") + verifyEnvironment + [[ "$id" == "" ]] && fail "Id must be provided" + [[ "$role" == "" ]] && fail "Role must be provided" + + lock + deleteClientRole "$email" "$role" + echo "Successfully removed role from client" + ;; + + "generate-secret") + verifyEnvironment + [[ "$id" == "" ]] && fail "Id must be provided" + + lock + generateSecret "$id" + echo "Successfully generated secret" + ;; + + "delete") + verifyEnvironment + [[ "$id" == "" ]] && fail "Id must be provided" + + lock + deleteClient "$email" + echo "Successfully deleted client. Run 'so-user sync' to sync with Elasticsearch." + ;; + *) + fail "Unsupported operation: $operation" + usage + ;; +esac + +exit 0 diff --git a/salt/soc/enabled.sls b/salt/soc/enabled.sls index d1582721d..32437bd99 100644 --- a/salt/soc/enabled.sls +++ b/salt/soc/enabled.sls @@ -83,7 +83,7 @@ so-soc: - file: soccustom - file: soccustomroles - file: socusersroles - - file: socclientroles + - file: socclientsroles delete_so-soc_so-status.disabled: file.uncomment: