Files
securityonion/salt/common/tools/sbin/so-user
2020-03-30 22:15:49 -04:00

201 lines
4.9 KiB
Bash
Executable File

#!/bin/bash
# Copyright 2020 Security Onion Solutions. All rights reserved.
#
# This program is distributed under the terms of version 2 of the
# GNU General Public License. See LICENSE for further details.
#
# 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.
got_root() {
# Make sure you are root
if [ "$(id -u)" -ne 0 ]; then
echo "This script must be run using sudo!"
exit 1
fi
}
# Make sure the user is root
got_root
if [[ $# < 1 || $# > 2 ]]; then
echo "Usage: $0 <list|add|update|delete|checkpw> [email]"
echo "Note that checkpw only checks that the given password meets the minimum requirements, it does not test that it matches for an existing user."
exit 1
fi
operation=$1
email=$2
kratosUrl=${KRATOS_URL:-http://127.0.0.1:4434}
databasePath=${KRATOS_DB_PATH:-/opt/so/conf/kratos/db/db.sqlite}
argon2Iterations=${ARGON2_ITERATIONS:-3}
argon2Memory=${ARGON2_MEMORY:-14}
argon2Parallelism=${ARGON2_PARALLELISM:-2}
argon2HashSize=${ARGON2_HASH_SIZE:-32}
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
require "argon2"
require "jq"
require "curl"
require "openssl"
require "sqlite3"
[[ ! -f $databasePath ]] && fail "Unable to find database file; specify path via KRATOS_DB_PATH environment variable"
response=$(curl -Ss ${kratosUrl}/)
[[ "$response" != "404 page not found" ]] && fail "Unable to communicate with Kratos; specify URL via KRATOS_URL environment variable"
function findIdByEmail() {
email=$1
response=$(curl -Ss ${kratosUrl}/identities)
identityId=$(echo "${response}" | jq ".[] | select(.addresses[0].value == \"$email\") | .id")
echo $identityId
}
function validatePassword() {
password=$1
len=$(expr length "$password")
if [[ $len -lt 6 ]]; then
echo "Password does not meet the minimum requirements"
exit 2
fi
}
function updatePassword() {
identityId=$1
# 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 -s password
validatePassword "$password"
if [[ -n $identityId ]]; then
# Generate password hash
salt=$(openssl rand -hex 8)
passwordHash=$(echo "${password}" | argon2 ${salt} -id -t $argon2Iterations -m $argon2Memory -p $argon2Parallelism -l $argon2HashSize -e)
# Update DB with new hash
echo "update identity_credentials set config=CAST('{\"hashed_password\":\"${passwordHash}\"}' as BLOB) where identity_id=${identityId};" | sqlite3 "$databasePath"
[[ $? != 0 ]] && fail "Unable to update password"
fi
}
function listUsers() {
response=$(curl -Ss ${kratosUrl}/identities)
[[ $? != 0 ]] && fail "Unable to communicate with Kratos"
echo "${response}" | jq -r ".[] | .addresses[0].value" | sort
}
function createUser() {
email=$1
now=$(date -u +%FT%TZ)
addUserJson=$(cat <<EOF
{
"addresses": [
{
"expires_at": "2099-01-31T12:00:00Z",
"value": "${email}",
"verified": true,
"verified_at": "${now}",
"via": "so-add-user"
}
],
"traits": {"email":"${email}"},
"traits_schema_id": "default"
}
EOF
)
response=$(curl -Ss ${kratosUrl}/identities -d "$addUserJson")
[[ $? != 0 ]] && fail "Unable to communicate with Kratos"
identityId=$(echo "${response}" | jq ".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}"
fi
updatePassword $identityId
}
function updateUser() {
email=$1
identityId=$(findIdByEmail "$email")
[[ ${identityId} == "" ]] && fail "User not found"
updatePassword $identityId
}
function deleteUser() {
email=$1
identityId=$(findIdByEmail "$email")
[[ ${identityId} == "" ]] && fail "User not found"
response=$(curl -Ss -XDELETE "${kratosUrl}/identities/$identityId")
[[ $? != 0 ]] && fail "Unable to communicate with Kratos"
}
case "${operation}" in
"add")
[[ "$email" == "" ]] && fail "Email address must be provided"
createUser "$email"
echo "Successfully added new user"
;;
"list")
listUsers
;;
"update")
[[ "$email" == "" ]] && fail "Email address must be provided"
updateUser "$email"
echo "Successfully updated user"
;;
"delete")
[[ "$email" == "" ]] && fail "Email address must be provided"
deleteUser "$email"
echo "Successfully deleted user"
;;
"checkpw")
updatePassword
echo "Password is acceptable"
;;
*)
fail "Unsupported operation: $operation"
;;
esac
exit 0