mirror of
https://github.com/Security-Onion-Solutions/securityonion.git
synced 2026-10-08 23:35:37 +02:00
Adding a user failed with "Password does not meet the minimum requirements" for passwords that were well over the eight character minimum. so-user reads the password from stdin in updatePassword, but verifyEnvironment runs first and calls kratosCurl, which ran docker exec with -i. That attaches stdin and drains the pipe the caller sent the password on, so the later read -rs saw EOF, password was empty, and expr length "" tripped the minimum length check. No kratosCurl or hydraCurl call sends a body on stdin; every one passes it as a -d argument, so -i was never needed. Dropping it leaves the admin API responses unchanged. so-client had the same wrapper for the Hydra admin API. It does not currently read stdin, but the flag drains its caller's pipe just the same, so it is dropped there too.
417 lines
10 KiB
Bash
Executable File
417 lines
10 KiB
Bash
Executable File
#!/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 <<USAGE_EOF
|
|
Usage: $0 <operation> [supporting parameters]
|
|
|
|
where <operation> 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 <name>
|
|
Optional parameters:
|
|
--note <note> (defaults to blank)
|
|
--json output as JSON
|
|
|
|
delete: Deletes a client from the oauth2 system
|
|
Required parameters:
|
|
--id <id>
|
|
|
|
addperm: Grants a permission to an existing client
|
|
Required parameters:
|
|
--id <id>
|
|
--permission <permission>
|
|
|
|
delperm: Removes a permission from an existing client
|
|
Required parameters:
|
|
--id <id>
|
|
--permission <permission>
|
|
|
|
update: Updates a client name and note.
|
|
Required parameters:
|
|
--id <id>
|
|
--name <name>
|
|
--note <note>
|
|
--searchusername <run-as username>
|
|
|
|
generate-secret: Regenerates a client's secret and outputs the new secret.
|
|
Required parameters:
|
|
--id <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
|
|
|
|
hydraContainer=${HYDRA_CONTAINER:-so-hydra}
|
|
hydraUrl=${HYDRA_URL:-http://localhost: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 hydraCurl() {
|
|
docker exec "$hydraContainer" curl "$@"
|
|
}
|
|
|
|
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 "docker"
|
|
response=$(hydraCurl -Ss -L ${hydraUrl}/health/alive)
|
|
[[ "$response" != '{"status":"ok"}' ]] && 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=$(hydraCurl -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 <<EOF
|
|
{
|
|
"access_token_strategy": "opaque",
|
|
"client_id": "$id",
|
|
"client_secret": "$secret",
|
|
"client_name": "$name",
|
|
"grant_types": [ "client_credentials" ],
|
|
"response_types": [ "code" ],
|
|
"metadata": {
|
|
"note": "$note",
|
|
"searchUsername": ""
|
|
}
|
|
}
|
|
EOF
|
|
)
|
|
|
|
response=$(hydraCurl -Ss -L --fail-with-body -X POST ${hydraUrl}/admin/clients -d "$body")
|
|
if [[ $? != 0 ]]; then
|
|
error=$(echo $response | jq .error)
|
|
fail "Failed to submit request to Hydra: $error"
|
|
fi
|
|
}
|
|
|
|
function update() {
|
|
clientId=$1
|
|
name=$2
|
|
note=$3
|
|
username=$4
|
|
|
|
body=$(cat <<EOF
|
|
[
|
|
{
|
|
"op": "replace",
|
|
"path": "/client_name",
|
|
"value": "$name"
|
|
},
|
|
{
|
|
"op": "replace",
|
|
"path": "/metadata",
|
|
"value": {
|
|
"note": "$note",
|
|
"searchUsername": "$username"
|
|
}
|
|
}
|
|
]
|
|
EOF
|
|
)
|
|
|
|
response=$(hydraCurl -Ss -L --fail-with-body -X PATCH ${hydraUrl}/admin/clients/$id -d "$body")
|
|
if [[ $? != 0 ]]; then
|
|
error=$(echo $response | jq .error)
|
|
fail "Failed to submit request to Hydra: $error"
|
|
fi
|
|
}
|
|
|
|
function generateSecret() {
|
|
clientId=$1
|
|
|
|
secret=$(get_random_value)
|
|
body=$(cat <<EOF
|
|
[
|
|
{
|
|
"op": "replace",
|
|
"path": "/client_secret",
|
|
"value": "$secret"
|
|
}
|
|
]
|
|
EOF
|
|
)
|
|
|
|
response=$(hydraCurl -Ss -L --fail-with-body -X PATCH ${hydraUrl}/admin/clients/$id -d "$body")
|
|
if [[ $? != 0 ]]; then
|
|
error=$(echo $response | jq .error)
|
|
fail "Failed to submit request to Hydra: $error"
|
|
fi
|
|
}
|
|
|
|
function deleteClient() {
|
|
identityId=$1
|
|
|
|
[[ ${identityId} == "" ]] && fail "Client not found"
|
|
|
|
response=$(hydraCurl -Ss -XDELETE -L --fail-with-body "${hydraUrl}/admin/clients/$identityId")
|
|
if [[ $? != 0 ]]; then
|
|
error=$(echo $response | jq .error)
|
|
fail "Failed to submit request to Hydra: $error"
|
|
fi
|
|
|
|
rolesTmpFile="${socRolesFile}.tmp"
|
|
createFile "$rolesTmpFile" "$soUID" "$soGID"
|
|
grep -v "$identityId" "$socRolesFile" > "$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
|