mirror of
https://github.com/Security-Onion-Solutions/securityonion.git
synced 2026-10-08 15:25:26 +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.
720 lines
23 KiB
Bash
Executable File
720 lines
23 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
|
|
|
|
|
|
DEFAULT_ROLE=analyst
|
|
|
|
function usage() {
|
|
cat <<USAGE_EOF
|
|
Usage: $0 <operation> [supporting parameters]
|
|
|
|
where <operation> is one of the following:
|
|
|
|
list: Lists all user email addresses currently defined in the identity system
|
|
|
|
add: Adds a new user to the identity system
|
|
Required parameters:
|
|
--email <email>
|
|
Optional parameters:
|
|
--role <role> (defaults to $DEFAULT_ROLE)
|
|
--firstName <firstName> (defaults to blank)
|
|
--lastName <lastName> (defaults to blank)
|
|
--note <note> (defaults to blank)
|
|
--skip-sync (defers the Elastic sync until the next scheduled time)
|
|
|
|
addrole: Grants a role to an existing user
|
|
Required parameters:
|
|
--email <email>
|
|
--role <role>
|
|
Optional parameters:
|
|
--skip-sync (defers the Elastic sync until the next scheduled time)
|
|
|
|
delrole: Removes a role from an existing user
|
|
Required parameters:
|
|
--email <email>
|
|
--role <role>
|
|
Optional parameters:
|
|
--skip-sync (defers the Elastic sync until the next scheduled time)
|
|
|
|
password: Updates a user's password and disables MFA
|
|
Required parameters:
|
|
--email <email>
|
|
Optional parameters:
|
|
--skip-sync (defers the Elastic sync until the next scheduled time)
|
|
|
|
profile: Updates a user's profile information
|
|
Required parameters:
|
|
--email <email>
|
|
Optional parameters:
|
|
--role <role> (defaults to $DEFAULT_ROLE)
|
|
--firstName <firstName> (defaults to blank)
|
|
--lastName <lastName> (defaults to blank)
|
|
--note <note> (defaults to blank)
|
|
|
|
enable: Enables a user
|
|
Required parameters:
|
|
--email <email>
|
|
Optional parameters:
|
|
--skip-sync (defers the Elastic sync until the next scheduled time)
|
|
|
|
disable: Disables a user
|
|
Required parameters:
|
|
--email <email>
|
|
Optional parameters:
|
|
--skip-sync (defers the Elastic sync until the next scheduled time)
|
|
|
|
validate: Validates that the given email address and password are acceptable
|
|
Required parameters:
|
|
--email <email>
|
|
|
|
valemail: Validates that the given email address is acceptable; requires 'email' parameter
|
|
Required parameters:
|
|
--email <email>
|
|
|
|
valpass: Validates that a password is acceptable
|
|
|
|
Note that the password can be piped into STDIN to avoid prompting for it
|
|
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
|
|
--email)
|
|
email=$(echo $1 | sed 's/"/\\"/g')
|
|
shift
|
|
;;
|
|
--role)
|
|
role=$(echo $1 | sed 's/"/\\"/g')
|
|
shift
|
|
;;
|
|
--firstName)
|
|
firstName=$(echo $1 | sed 's/"/\\"/g')
|
|
shift
|
|
;;
|
|
--lastName)
|
|
lastName=$(echo $1 | sed 's/"/\\"/g')
|
|
shift
|
|
;;
|
|
--note)
|
|
note=$(echo $1 | sed 's/"/\\"/g')
|
|
shift
|
|
;;
|
|
--skip-sync)
|
|
SKIP_SYNC=1
|
|
;;
|
|
*)
|
|
echo "Encountered unexpected parameter: $param"
|
|
usage
|
|
;;
|
|
esac
|
|
done
|
|
|
|
kratosContainer=${KRATOS_CONTAINER:-so-kratos}
|
|
kratosUrl=${KRATOS_URL:-http://localhost:4434/admin}
|
|
databasePath=${KRATOS_DB_PATH:-/nsm/kratos/db/db.sqlite}
|
|
databaseTimeout=${KRATOS_DB_TIMEOUT:-5000}
|
|
bcryptRounds=${BCRYPT_ROUNDS:-12}
|
|
elasticUsersFile=${ELASTIC_USERS_FILE:-/opt/so/saltstack/local/salt/elasticsearch/files/users}
|
|
elasticRolesFile=${ELASTIC_ROLES_FILE:-/opt/so/saltstack/local/salt/elasticsearch/files/users_roles}
|
|
socRolesFile=${SOC_ROLES_FILE:-/opt/so/conf/soc/soc_users_roles}
|
|
esUID=${ELASTIC_UID:-930}
|
|
esGID=${ELASTIC_GID:-930}
|
|
soUID=${SOCORE_UID:-939}
|
|
soGID=${SOCORE_GID:-939}
|
|
|
|
function lock() {
|
|
# Obtain file descriptor lock
|
|
exec 99>/var/tmp/so-user.lock || fail "Unable to create lock descriptor; if the system was not shutdown gracefully you may need to remove /var/tmp/so-user.lock manually."
|
|
flock -w 10 99 || fail "Another process is using so-user; if the system was not shutdown gracefully you may need to remove /var/tmp/so-user.lock manually."
|
|
trap 'rm -f /var/tmp/so-user.lock' EXIT
|
|
}
|
|
|
|
function fail() {
|
|
msg=$1
|
|
echo "$1"
|
|
exit 1
|
|
}
|
|
|
|
function kratosCurl() {
|
|
docker exec "$kratosContainer" 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 "htpasswd"
|
|
require "jq"
|
|
require "docker"
|
|
require "openssl"
|
|
require "sqlite3"
|
|
[[ ! -f $databasePath ]] && fail "Unable to find database file; specify path via KRATOS_DB_PATH environment variable"
|
|
response=$(kratosCurl -Ss -L ${kratosUrl}/)
|
|
[[ "$response" != "404 page not found" ]] && fail "Unable to communicate with Kratos; specify URL via KRATOS_URL environment variable"
|
|
}
|
|
|
|
function findIdByEmail() {
|
|
email=${1,,}
|
|
|
|
response=$(kratosCurl -Ss -L ${kratosUrl}/identities)
|
|
identityId=$(echo "${response}" | jq -r ".[] | select(.verifiable_addresses[0].value == \"$email\") | .id")
|
|
echo $identityId
|
|
}
|
|
|
|
function validatePassword() {
|
|
password=$1
|
|
|
|
len=$(expr length "$password")
|
|
if [[ $len -lt 8 ]]; then
|
|
fail "Password does not meet the minimum requirements"
|
|
fi
|
|
if [[ $len -gt 72 ]]; then
|
|
fail "Password is too long (max: 72)"
|
|
fi
|
|
check_password_and_exit "$password"
|
|
}
|
|
|
|
function validateEmail() {
|
|
email=$1
|
|
requireLower=$2
|
|
# (?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])
|
|
if [[ ! "$email" =~ ^[[:alnum:]._%+-]+@[[:alnum:].-]+\.[[:alpha:]]{2,}$ ]]; then
|
|
fail "Email address is invalid"
|
|
fi
|
|
|
|
if [[ "$requireLower" == "true" && "$email" =~ [A-Z] ]]; then
|
|
fail "Email addresses cannot contain uppercase letters"
|
|
fi
|
|
}
|
|
|
|
function hashPassword() {
|
|
password=$1
|
|
|
|
passwordHash=$(echo "${password}" | htpasswd -niBC $bcryptRounds SOUSER)
|
|
passwordHash=$(echo "$passwordHash" | cut -c 11-)
|
|
passwordHash="\$2a${passwordHash}" # still waiting for https://github.com/elastic/elasticsearch/issues/51132
|
|
echo "$passwordHash"
|
|
}
|
|
|
|
|
|
function updatePassword() {
|
|
identityId=$1
|
|
|
|
if [ -z "$password" ]; then
|
|
# Read password from stdin (show prompt only if no stdin was piped in)
|
|
test -t 0
|
|
if [[ $? == 0 ]]; then
|
|
echo "Enter new password:"
|
|
fi
|
|
read -rs password
|
|
|
|
validatePassword "$password"
|
|
fi
|
|
|
|
if [[ -n "$identityId" ]]; then
|
|
# Generate password hash
|
|
passwordHash=$(hashPassword "$password")
|
|
# Update DB with new hash
|
|
echo "update identity_credentials set config=CAST('{\"hashed_password\":\"$passwordHash\"}' as BLOB), created_at=datetime('now'), updated_at=datetime('now') where identity_id='${identityId}' and identity_credential_type_id=(select id from identity_credential_types where name='password');" | sqlite3 -cmd ".timeout ${databaseTimeout}" "$databasePath"
|
|
[[ $? != 0 ]] && fail "Unable to update password"
|
|
# Deactivate MFA
|
|
echo "delete from identity_credential_identifiers where identity_credential_id in (select id from identity_credentials where identity_id='${identityId}' and identity_credential_type_id in (select id from identity_credential_types where name in ('totp', 'webauthn', 'oidc')));" | sqlite3 -cmd ".timeout ${databaseTimeout}" "$databasePath"
|
|
[[ $? != 0 ]] && fail "Unable to clear aal2 identity IDs"
|
|
echo "delete from identity_credentials where identity_id='${identityId}' and identity_credential_type_id in (select id from identity_credential_types where name in ('totp', 'webauthn', 'oidc'));" | sqlite3 -cmd ".timeout ${databaseTimeout}" "$databasePath"
|
|
[[ $? != 0 ]] && fail "Unable to clear aal2 identity credentials"
|
|
echo "delete from session_devices where session_id in (select id from sessions where identity_id='${identityId}');" | sqlite3 -cmd ".timeout ${databaseTimeout}" "$databasePath"
|
|
[[ $? != 0 ]] && fail "Unable to clear session devices"
|
|
echo "delete from sessions where identity_id='${identityId}';" | sqlite3 -cmd ".timeout ${databaseTimeout}" "$databasePath"
|
|
[[ $? != 0 ]] && fail "Unable to clear sessions"
|
|
echo "update identities set available_aal='aal1' where id='${identityId}';" | sqlite3 -cmd ".timeout ${databaseTimeout}" "$databasePath"
|
|
[[ $? != 0 ]] && fail "Unable to reset aal"
|
|
fi
|
|
}
|
|
|
|
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 users file
|
|
rolesTmpFile="${socRolesFile}.tmp"
|
|
createFile "$rolesTmpFile" "$soUID" "$soGID"
|
|
|
|
if [[ -f "$databasePath" ]]; then
|
|
echo "Migrating roles to new file: $socRolesFile"
|
|
|
|
echo "select 'superuser:' || id from identities;" | sqlite3 -cmd ".timeout ${databaseTimeout}" "$databasePath" \
|
|
>> "$rolesTmpFile"
|
|
[[ $? != 0 ]] && fail "Unable to read identities from database"
|
|
|
|
echo "The following users have all been migrated with the super user role:"
|
|
cat "${rolesTmpFile}"
|
|
else
|
|
echo "Database file does not exist yet, installation is likely not yet complete."
|
|
fi
|
|
|
|
if [[ -d "$socRolesFile" ]]; then
|
|
echo "Removing invalid roles directory created by Docker"
|
|
rm -fr "$socRolesFile"
|
|
fi
|
|
mv "${rolesTmpFile}" "${socRolesFile}"
|
|
fi
|
|
}
|
|
|
|
function syncElasticSystemUser() {
|
|
json=$1
|
|
userid=$2
|
|
usersFile=$3
|
|
|
|
user=$(echo "$json" | jq -r ".local.users.$userid.user")
|
|
pass=$(echo "$json" | jq -r ".local.users.$userid.pass")
|
|
|
|
[[ -z "$user" || -z "$pass" ]] && fail "Elastic auth credentials for system user '$userid' are missing"
|
|
hash=$(hashPassword "$pass")
|
|
|
|
echo "${user}:${hash}" >> "$usersFile"
|
|
}
|
|
|
|
function syncElasticSystemRole() {
|
|
json=$1
|
|
userid=$2
|
|
role=$3
|
|
rolesFile=$4
|
|
|
|
user=$(echo "$json" | jq -r ".local.users.$userid.user")
|
|
|
|
[[ -z "$user" ]] && fail "Elastic auth credentials for system user '$userid' are missing"
|
|
|
|
echo "${role}:${user}" >> "$rolesFile"
|
|
}
|
|
|
|
function syncElastic() {
|
|
[[ -n $SKIP_SYNC ]] && return
|
|
|
|
echo "Syncing users and roles between SOC and Elastic..."
|
|
|
|
usersTmpFile="${elasticUsersFile}.tmp"
|
|
createFile "${usersTmpFile}" "$esUID" "$esGID"
|
|
rolesTmpFile="${elasticRolesFile}.tmp"
|
|
createFile "${rolesTmpFile}" "$esUID" "$esGID"
|
|
|
|
authPillarJson=$(lookup_salt_value "auth" "elasticsearch" "pillar" "json")
|
|
|
|
syncElasticSystemUser "$authPillarJson" "so_elastic_user" "$usersTmpFile"
|
|
syncElasticSystemUser "$authPillarJson" "so_kibana_user" "$usersTmpFile"
|
|
syncElasticSystemUser "$authPillarJson" "so_logstash_user" "$usersTmpFile"
|
|
syncElasticSystemUser "$authPillarJson" "so_beats_user" "$usersTmpFile"
|
|
syncElasticSystemUser "$authPillarJson" "so_monitor_user" "$usersTmpFile"
|
|
|
|
syncElasticSystemRole "$authPillarJson" "so_elastic_user" "superuser" "$rolesTmpFile"
|
|
syncElasticSystemRole "$authPillarJson" "so_kibana_user" "kibana_system" "$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"
|
|
|
|
if [[ -f "$databasePath" && -f "$socRolesFile" ]]; then
|
|
# Append the SOC users
|
|
userData=$(echo "select '{\"user\":\"' || ici.identifier || '\", \"data\":' || ic.config || '}'" \
|
|
"from identity_credential_identifiers ici, identity_credentials ic, identities i, identity_credential_types ict " \
|
|
"where " \
|
|
" ici.identity_credential_id=ic.id " \
|
|
" and ic.identity_id=i.id " \
|
|
" and ict.id=ic.identity_credential_type_id " \
|
|
" and ict.name='password' " \
|
|
" and i.state == 'active' " \
|
|
"order by ici.identifier;" | \
|
|
sqlite3 -cmd ".timeout ${databaseTimeout}" "$databasePath")
|
|
[[ $? != 0 ]] && fail "Unable to read credential hashes from database"
|
|
|
|
user_data_formatted=$(echo "${userData}" | jq -r '.user + ":" + .data.hashed_password')
|
|
if lookup_salt_value "features" "" "pillar" | grep -qx odc; then
|
|
# generate random placeholder salt/hash for users without passwords
|
|
random_crypt=$(get_random_value 53)
|
|
user_data_formatted=$(echo "${user_data_formatted}" | sed -r "s/^(.+:)\$/\\1\$2a\$12${random_crypt}/")
|
|
fi
|
|
echo "${user_data_formatted}" >> "$usersTmpFile"
|
|
|
|
# Append the user roles
|
|
while IFS="" read -r rolePair || [ -n "$rolePair" ]; do
|
|
userId=$(echo "$rolePair" | cut -d: -f2)
|
|
role=$(echo "$rolePair" | cut -d: -f1)
|
|
echo "select '$role:' || ici.identifier " \
|
|
"from identity_credential_identifiers ici, identity_credentials ic, identity_credential_types ict " \
|
|
"where ici.identity_credential_id=ic.id " \
|
|
" and ict.id=ic.identity_credential_type_id " \
|
|
" and ict.name='password' " \
|
|
" and ic.identity_id = '$userId';" | \
|
|
sqlite3 -cmd ".timeout ${databaseTimeout}" "$databasePath" >> "$rolesTmpFile"
|
|
[[ $? != 0 ]] && fail "Unable to read role identities from database"
|
|
done < "$socRolesFile"
|
|
else
|
|
echo "Database file or soc roles file does not exist yet, skipping users export"
|
|
fi
|
|
|
|
if [[ -s "${usersTmpFile}" ]]; then
|
|
mv "${usersTmpFile}" "${elasticUsersFile}"
|
|
mv "${rolesTmpFile}" "${elasticRolesFile}"
|
|
|
|
if [[ -z "$SKIP_STATE_APPLY" ]]; then
|
|
echo "Elastic state will be re-applied to affected minions. This will run in the background and may take several minutes to complete."
|
|
echo "Applying elastic state to elastic minions at $(date)" >> /opt/so/log/soc/sync.log 2>&1
|
|
salt --async -C 'I@elasticsearch:enabled:true' state.apply elasticsearch queue=True >> /opt/so/log/soc/sync.log 2>&1
|
|
fi
|
|
else
|
|
echo "Newly generated users/roles files are incomplete; aborting."
|
|
fi
|
|
}
|
|
|
|
function syncAll() {
|
|
ensureRoleFileExists
|
|
|
|
# Check if a sync is needed. Sync is not needed if the following are true:
|
|
# - user database entries are all older than the elastic users file
|
|
# - soc roles file last modify date is older than the elastic roles file
|
|
if [[ -z "$FORCE_SYNC" && -f "$databasePath" && -f "$elasticUsersFile" ]]; then
|
|
usersFileAgeSecs=$(echo $(($(date +%s) - $(date +%s -r "$elasticUsersFile"))))
|
|
staleCount=$(echo "select count(*) from identity_credentials where updated_at >= Datetime('now', '-${usersFileAgeSecs} seconds');" \
|
|
| sqlite3 -cmd ".timeout ${databaseTimeout}" "$databasePath")
|
|
[[ $? != 0 ]] && fail "Unable to read user count from database"
|
|
if [[ "$staleCount" == "0" && "$elasticRolesFile" -nt "$socRolesFile" ]]; then
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
syncElastic
|
|
|
|
return 0
|
|
}
|
|
|
|
function listUsers() {
|
|
response=$(kratosCurl -Ss -L ${kratosUrl}/identities)
|
|
[[ $? != 0 ]] && fail "Unable to communicate with Kratos"
|
|
|
|
users=$(echo "${response}" | jq -r ".[] | .verifiable_addresses[0].value" | sort)
|
|
for user in $users; do
|
|
roles=$(grep ":$user\$" "$elasticRolesFile" | cut -d: -f1 | tr '\n' ' ')
|
|
echo "$user: $roles"
|
|
done
|
|
}
|
|
|
|
function addUserRole() {
|
|
email=$1
|
|
role=$2
|
|
|
|
adjustUserRole "$email" "$role" "add"
|
|
}
|
|
|
|
function deleteUserRole() {
|
|
email=$1
|
|
role=$2
|
|
|
|
adjustUserRole "$email" "$role" "del"
|
|
}
|
|
|
|
function adjustUserRole() {
|
|
email=$1
|
|
role=$2
|
|
op=$3
|
|
|
|
identityId=$(findIdByEmail "$email")
|
|
[[ ${identityId} == "" ]] && fail "User not found"
|
|
|
|
ensureRoleFileExists
|
|
|
|
filename="$socRolesFile"
|
|
hasRole=0
|
|
grep "^$role:" "$socRolesFile" | grep -q "$identityId" && hasRole=1
|
|
if [[ "$op" == "add" ]]; then
|
|
if [[ "$hasRole" == "1" ]]; then
|
|
echo "User '$email' already has the role: $role"
|
|
return 1
|
|
else
|
|
echo "$role:$identityId" >> "$filename"
|
|
fi
|
|
elif [[ "$op" == "del" ]]; then
|
|
if [[ "$hasRole" -ne 1 ]]; then
|
|
fail "User '$email' 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 createUser() {
|
|
email=$1
|
|
role=$2
|
|
firstName=$3
|
|
lastName=$4
|
|
note=$5
|
|
|
|
now=$(date -u +%FT%TZ)
|
|
addUserJson=$(cat <<EOF
|
|
{
|
|
"traits": {
|
|
"email": "${email}",
|
|
"firstName": "${firstName}",
|
|
"lastName": "${lastName}",
|
|
"note": "${note}"
|
|
},
|
|
"schema_id": "default"
|
|
}
|
|
EOF
|
|
)
|
|
|
|
response=$(kratosCurl -Ss -L ${kratosUrl}/identities -d "$addUserJson")
|
|
[[ $? != 0 ]] && fail "Unable to communicate with Kratos"
|
|
|
|
identityId=$(echo "${response}" | jq -r ".id")
|
|
if [[ "${identityId}" == "null" ]]; then
|
|
code=$(echo "${response}" | jq ".error.code")
|
|
[[ "${code}" == "409" ]] && fail "User already exists"
|
|
|
|
reason=$(echo "${response}" | jq ".error.message")
|
|
[[ $? == 0 ]] && fail "Unable to add user: ${reason}"
|
|
else
|
|
updatePassword "$identityId"
|
|
addUserRole "$email" "$role"
|
|
fi
|
|
}
|
|
|
|
function updateStatus() {
|
|
email=$1
|
|
status=$2
|
|
|
|
identityId=$(findIdByEmail "$email")
|
|
[[ ${identityId} == "" ]] && fail "User not found"
|
|
|
|
response=$(kratosCurl -Ss -L "${kratosUrl}/identities/$identityId")
|
|
[[ $? != 0 ]] && fail "Unable to communicate with Kratos"
|
|
|
|
schemaId=$(echo "$response" | jq -r .schema_id)
|
|
|
|
# Capture traits and remove obsolete 'status' trait if exists
|
|
traitBlock=$(echo "$response" | jq -c .traits | sed -re 's/,?"status":".*?"//')
|
|
|
|
state="active"
|
|
if [[ "$status" == "locked" ]]; then
|
|
state="inactive"
|
|
fi
|
|
body="{ \"schema_id\": \"$schemaId\", \"state\": \"$state\", \"traits\": $traitBlock }"
|
|
response=$(kratosCurl -fSsL -XPUT -H "Content-Type: application/json" "${kratosUrl}/identities/$identityId" -d "$body")
|
|
[[ $? != 0 ]] && fail "Unable to update user"
|
|
}
|
|
|
|
function updateUserPassword() {
|
|
email=$1
|
|
|
|
identityId=$(findIdByEmail "$email")
|
|
[[ ${identityId} == "" ]] && fail "User not found"
|
|
|
|
updatePassword "$identityId"
|
|
}
|
|
|
|
function updateUserProfile() {
|
|
email=$1
|
|
|
|
identityId=$(findIdByEmail "$email")
|
|
[[ ${identityId} == "" ]] && fail "User not found"
|
|
|
|
response=$(kratosCurl -Ss -L "${kratosUrl}/identities/$identityId")
|
|
[[ $? != 0 ]] && fail "Unable to communicate with Kratos"
|
|
|
|
schemaId=$(echo "$response" | jq -r .schema_id)
|
|
state=$(echo "$response" | jq -r .state)
|
|
|
|
traitBlock="{\"email\":\"$email\",\"firstName\":\"$firstName\",\"lastName\":\"$lastName\",\"note\":\"$note\"}"
|
|
|
|
body="{ \"schema_id\": \"$schemaId\", \"state\": \"$state\", \"traits\": $traitBlock }"
|
|
response=$(kratosCurl -fSsL -XPUT -H "Content-Type: application/json" "${kratosUrl}/identities/$identityId" -d "$body")
|
|
[[ $? != 0 ]] && fail "Unable to update user"
|
|
}
|
|
|
|
function deleteUser() {
|
|
email=$1
|
|
|
|
identityId=$(findIdByEmail "$email")
|
|
[[ ${identityId} == "" ]] && fail "User not found"
|
|
|
|
response=$(kratosCurl -Ss -XDELETE -L "${kratosUrl}/identities/$identityId")
|
|
[[ $? != 0 ]] && fail "Unable to communicate with Kratos"
|
|
|
|
rolesTmpFile="${socRolesFile}.tmp"
|
|
createFile "$rolesTmpFile" "$soUID" "$soGID"
|
|
grep -v "$identityId" "$socRolesFile" > "$rolesTmpFile"
|
|
cat "$rolesTmpFile" > "$socRolesFile"
|
|
}
|
|
|
|
case "${operation}" in
|
|
"add")
|
|
verifyEnvironment
|
|
[[ "$email" == "" ]] && fail "Email address must be provided"
|
|
|
|
lock
|
|
validateEmail "$email" true
|
|
updatePassword
|
|
createUser "$email" "${role:-$DEFAULT_ROLE}" "${firstName}" "${lastName}" "${note}"
|
|
syncAll
|
|
echo "Successfully added new user to SOC"
|
|
echo "$password" | so-influxdb-manage useradd "$email"
|
|
if [[ "$role" == "superuser" ]]; then
|
|
echo "$password" | so-influxdb-manage userpromote "$email"
|
|
fi
|
|
;;
|
|
|
|
"list")
|
|
verifyEnvironment
|
|
listUsers
|
|
;;
|
|
|
|
"addrole")
|
|
verifyEnvironment
|
|
[[ "$email" == "" ]] && fail "Email address must be provided"
|
|
[[ "$role" == "" ]] && fail "Role must be provided"
|
|
|
|
lock
|
|
validateEmail "$email"
|
|
if addUserRole "$email" "$role"; then
|
|
syncElastic
|
|
echo "Successfully added role to user"
|
|
if [[ "$role" == "superuser" ]]; then
|
|
echo "$password" | so-influxdb-manage userpromote "$email"
|
|
fi
|
|
fi
|
|
;;
|
|
|
|
"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"
|
|
if [[ "$role" == "superuser" ]]; then
|
|
echo "$password" | so-influxdb-manage userdemote "$email"
|
|
fi
|
|
;;
|
|
|
|
"password")
|
|
verifyEnvironment
|
|
[[ "$email" == "" ]] && fail "Email address must be provided"
|
|
|
|
lock
|
|
updateUserPassword "$email"
|
|
syncAll
|
|
echo "Successfully updated user password"
|
|
echo "$password" | so-influxdb-manage userpass "$email"
|
|
;;
|
|
|
|
"profile")
|
|
verifyEnvironment
|
|
[[ "$email" == "" ]] && fail "Email address must be provided"
|
|
|
|
lock
|
|
updateUserProfile "$email"
|
|
echo "Successfully updated user profile"
|
|
;;
|
|
|
|
"enable")
|
|
verifyEnvironment
|
|
[[ "$email" == "" ]] && fail "Email address must be provided"
|
|
|
|
lock
|
|
updateStatus "$email" 'active'
|
|
syncAll
|
|
echo "Successfully enabled user"
|
|
so-influxdb-manage userenable "$email"
|
|
;;
|
|
|
|
"disable")
|
|
verifyEnvironment
|
|
[[ "$email" == "" ]] && fail "Email address must be provided"
|
|
|
|
lock
|
|
updateStatus "$email" 'locked'
|
|
syncAll
|
|
echo "Successfully disabled user"
|
|
so-influxdb-manage userdisable "$email"
|
|
;;
|
|
|
|
"delete")
|
|
verifyEnvironment
|
|
[[ "$email" == "" ]] && fail "Email address must be provided"
|
|
|
|
lock
|
|
deleteUser "$email"
|
|
syncAll
|
|
echo "Successfully deleted user"
|
|
so-influxdb-manage userdel "$email"
|
|
;;
|
|
|
|
"sync")
|
|
lock
|
|
syncAll
|
|
;;
|
|
|
|
"validate")
|
|
validateEmail "$email" true
|
|
updatePassword
|
|
echo "Email and password are acceptable"
|
|
;;
|
|
|
|
"valemail")
|
|
validateEmail "$email" true
|
|
echo "Email is acceptable"
|
|
;;
|
|
|
|
"valpass")
|
|
updatePassword
|
|
echo "Password is acceptable"
|
|
;;
|
|
|
|
*)
|
|
fail "Unsupported operation: $operation"
|
|
usage
|
|
;;
|
|
esac
|
|
|
|
exit 0
|