#!/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 function usage() { cat < [supporting parameters] where is one of the following: list: Lists all client IDs and permissions 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: --note (defaults to blank) --json output as JSON delete: Deletes a client from the oauth2 system Required parameters: --id addperm: Grants a permission to an existing client Required parameters: --id --permission delperm: Removes a permission from an existing client Required parameters: --id --permission update: Updates a client name and note. Required parameters: --id --name --note --searchusername generate-secret: Regenerates a client's secret and outputs the new secret. Required parameters: --id Optional parameters: --json output as JSON USAGE_EOF exit 1 } if [[ $# -lt 1 || $1 == --help || $1 == -h || $1 == -? || $1 == --h ]]; then usage fi operation=$1 shift searchUsername=__MISSING__ note=__MISSING__ while [[ $# -gt 0 ]]; do param=$1 shift case "$param" in --id) id=$(echo $1 | sed 's/"/\\"/g') [[ ${#id} -gt 55 ]] && fail "id cannot be longer than 55 characters" shift ;; --permission) perm=$(echo $1 | sed 's/"/\\"/g') [[ ${#perm} -gt 50 ]] && fail "permission cannot be longer than 50 characters" shift ;; --name) name=$(echo $1 | sed 's/"/\\"/g') [[ ${#name} -gt 50 ]] && fail "name cannot be longer than 50 characters" shift ;; --note) note=$(echo $1 | sed 's/"/\\"/g') [[ ${#note} -gt 100 ]] && fail "note cannot be longer than 100 characters" shift ;; --searchusername) searchUsername=$(echo $1 | sed 's/"/\\"/g') [[ ${#searchUsername} -gt 50 ]] && fail "search username cannot be longer than 50 characters" shift ;; --json) json=1 ;; *) echo "Encountered unexpected parameter: $param" usage ;; esac done hydraUrl=${HYDRA_URL:-http://127.0.0.1:4445} 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" != *"Error 404"* ]] && 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" ]]; 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 -f ${hydraUrl}/admin/clients) [[ $? != 0 ]] && fail "Unable to communicate with Hydra" clientIds=$(echo "${response}" | jq -r ".[] | .client_id" | sort) for clientId in $clientIds; do perms=$(grep ":$clientId\$" "$socRolesFile" | cut -d: -f1 | tr '\n' ' ') echo "$clientId: $perms" done } function addClientPermission() { id=$1 perm=$2 adjustClientPermission "$id" "$perm" "add" } function deleteClientPermission() { id=$1 perm=$2 adjustClientPermission "$id" "$perm" "del" } function adjustClientPermission() { identityId=$1 perm=$2 op=$3 [[ ${identityId} == "" ]] && fail "Client not found" ensureRoleFileExists filename="$socRolesFile" hasPerm=0 grep "^$perm:" "$socRolesFile" | grep -q "$identityId" && hasPerm=1 if [[ "$op" == "add" ]]; then if [[ "$hasPerm" == "1" ]]; then echo "Client '$identityId' already has the permission: $perm" return 1 else echo "$perm:$identityId" >> "$filename" fi elif [[ "$op" == "del" ]]; then if [[ "$hasPerm" -ne 1 ]]; then fail "Client '$identityId' does not have the permission: $perm" else sed -e "\!^$perm:$identityId\$!d" "$filename" > "$filename.tmp" cat "$filename".tmp > "$filename" rm -f "$filename".tmp fi else fail "Unsupported permission adjustment operation: $op" fi return 0 } function convertNameToId() { name=$1 name=${name//[^[:alnum:]]/_} echo "socl_$name" | tr '[:upper:]' '[:lower:]' } function createClient() { name=$1 note=$2 id=$(convertNameToId "$name") now=$(date -u +%FT%TZ) secret=$(get_random_value) body=$(cat < "$rolesTmpFile" cat "$rolesTmpFile" > "$socRolesFile" } case "${operation}" in "add") verifyEnvironment [[ "$name" == "" ]] && fail "A short client name must be provided" lock createClient "$name" "$note" if [[ "$json" == "1" ]]; then echo "{\"id\":\"$id\",\"secret\":\"$secret\"}" else echo "Successfully added client ID $id with generated secret: $secret" fi ;; "list") verifyEnvironment listClients ;; "addperm") verifyEnvironment [[ "$id" == "" ]] && fail "Id must be provided" [[ "$perm" == "" ]] && fail "Permission must be provided" lock if addClientPermission "$id" "$perm"; then echo "Successfully added permission to client" fi ;; "delperm") verifyEnvironment [[ "$id" == "" ]] && fail "Id must be provided" [[ "$perm" == "" ]] && fail "Permission must be provided" lock deleteClientPermission "$id" "$perm" echo "Successfully removed permission from client" ;; "update") verifyEnvironment [[ "$id" == "" ]] && fail "Id must be provided" [[ "$name" == "" ]] && fail "Name must be provided" [[ "$note" == "__MISSING__" ]] && fail "Note must be provided" [[ "$searchUsername" == "__MISSING__" ]] && fail "Search Username must be provided" lock update "$id" "$name" "$note" "$searchUsername" echo "Successfully updated client" ;; "generate-secret") verifyEnvironment [[ "$id" == "" ]] && fail "Id must be provided" lock generateSecret "$id" if [[ "$json" == "1" ]]; then echo "{\"secret\":\"$secret\"}" else echo "Successfully generated secret: $secret" fi ;; "delete") verifyEnvironment [[ "$id" == "" ]] && fail "Id must be provided" lock deleteClient "$id" echo "Successfully deleted client." ;; *) fail "Unsupported operation: $operation" usage ;; esac exit 0