From 08265fcd959929399bcc92423763adef2929e227 Mon Sep 17 00:00:00 2001 From: Christer Waren Date: Mon, 12 Oct 2026 19:41:22 +0300 Subject: [PATCH] CLI: Update --- wx | 1447 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1447 insertions(+) create mode 100755 wx diff --git a/wx b/wx new file mode 100755 index 0000000..e7590bb --- /dev/null +++ b/wx @@ -0,0 +1,1447 @@ +#!/bin/bash +if [ ! "$BASH_VERSION" ] ; then + echo "Warén Group / Infra / CLI" + echo "This command requires using BASH shell." + exit 1 +fi + +if [[ -L "$0" ]] +then + PWD=$(dirname -- "$(readlink -f -- "$0")") +fi + +wxi-args(){ + declare -gAx args + args=() + + while [[ $# -gt 0 ]]; do + case "$1" in + --*=*) + key="${1%%=*}" + value="${1#*=}" + args["${key#--}"]="$value" + shift + ;; + + --*) + key="$1" + opt="${key#--}" + shift + if [[ $# -gt 0 && "${1}" != -* ]]; then + args["$opt"]="$1" + shift + else + args["$opt"]=true + fi + ;; + + -*=*) + key="${1%%=*}" + value="${1#*=}" + args["${key#-}"]="$value" + shift + ;; + + -*) + key="$1" + opt="${key#-}" + shift + if [[ $# -gt 0 && "${1}" != -* ]]; then + args["$opt"]="$1" + shift + else + args["$opt"]=true + fi + ;; + + *) + args["$((++pos))"]="${1%%=*}" + shift + ;; + esac + done +} + +wxi-config(){ + declare -gAx config + case $1 in + create) + if [[ ! -f $HOME/.warengroup/config.json ]] + then + echo '{}' > $HOME/.warengroup/config.json + fi + ;; + deploy) + if [[ ! $(jq -e . $HOME/.warengroup/config.json.tmp >/dev/null 2>&1) ]] + then + mv $HOME/.warengroup/config.json.tmp $HOME/.warengroup/config.json &> /dev/null + fi + ;; + write) + jq '.'"$2"' = "'$3'"' $HOME/.warengroup/config.json &> $HOME/.warengroup/config.json.tmp + wxi-config deploy + ;; + load) + while IFS=$'\t' read -r k v; do config["$k"]="$v"; done < <( + jq -r ' + paths(scalars) as $p + | "\( $p|map(tostring)|join(".") )\t\( getpath($p) )" + ' $HOME/.warengroup/config.json + ) + ;; + reload) + wxi-config load + ;; + *) + WX_HOME=$HOME/.warengroup + export WX_HOME + + mkdir -p $WX_HOME + chmod 700 -R "$WX_HOME" + + mkdir -p "$WX_HOME/credentials" \ + "$WX_HOME/credentials/ssh" \ + "$WX_HOME/credentials/ssh/eas" \ + "$WX_HOME/credentials/ansible-vault/eas" \ + "$WX_HOME/infra/eas/credentials/ssh" + + find "$WX_HOME" -type d -exec chmod 700 {} + + find "$WX_HOME" -type f -exec chmod 600 {} + + + wxi-config create + if [[ -n ${args['proxy']} ]] + then + wxi-config write proxy "${args['proxy']}" + fi + + wxi-config load + + PROXY_ARGS=() + if [[ -n "${config['proxy']}" ]]; then + PROXY_ARGS=(--proxy "${config['proxy']}") + fi + ;; + esac +} + +wxi-config-legacy(){ + case $1 in + login) + case $2 in + write) + jq '.login.'$ORG'.token = "'$TOKEN'"' $WX_HOME/config.json &> $WX_HOME/config.json.tmp + ;; + read) + if [[ -f "$WX_HOME/config.json" && $(cat $WX_HOME/config.json | jq -r .login.$ORG.token) != 'null' && $(cat $WX_HOME/config.json | jq -r .login.$ORG.token) != '' ]] + then + cat $WX_HOME/config.json | jq -r .login.$ORG.token + else + return 1 + fi + ;; + erase) + TOKEN="" + wxi-config-legacy login write + ;; + esac + ;; + *) + echo -n "" + ;; + esac + + mv $WX_HOME/config.json.tmp $WX_HOME/config.json &> /dev/null +} + +wxi-restricted(){ + if [[ -z $1 || $1 == "--user" ]] + then + if [[ $USER == "root" || $USER == "local" ]] + then + wxi-content status "Command" "Restricted" + wxi-content text "It's not permitted to execute this command with root or local user." + wxi-repeat "\n" 3 + exit 1 + fi + fi + + if [[ $1 == "--org" ]] + then + if [[ ! -z ${args['org']} ]] + then + case ${args['org']} in + warengroup) + ORG=warengroup + ;; + cwchristerw) + ORG=cwchristerw + ;; + *) + wxi-content status "Organization" "Unsupported" + wxi-stop + ;; + esac + elif [[ $(hostname -d) == "devices.waren.io" ]] + then + ORG=warengroup + elif [[ $(hostname -d) == "devices.christerwaren.fi" ]] + then + ORG=cwchristerw + fi + + if [[ ! -z $ORG ]] + then + case $ORG in + warengroup) + DOMAIN=waren.io + VAULT_DOMAIN=vault.cwinfo.net + ORG_HEADER="Warén Group" + ;; + cwchristerw) + DOMAIN=christerwaren.fi + VAULT_DOMAIN=vault.cwinfo.net + ORG_HEADER="Christer Warén" + ;; + *) + wxi-content status "Organization" "Unsupported" + wxi-stop + ;; + esac + else + wxi-content status "Organization" "Required" + wxi-stop + fi + fi + + if [[ -z $1 || $1 == "--vault" ]] + then + if [[ -z $VAULT_DOMAIN ]] + then + wxi-content status "Vault" "Unavailable" + wxi-repeat "\n" 3 + exit 1 + fi + + VAULT_STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://$VAULT_DOMAIN/v1/sys/health) + if [[ ! $VAULT_STATUS -eq 200 ]] + then + wxi-content status "Vault" "Offline" + wxi-repeat "\n" 3 + exit 1 + fi + fi +} + +wxi-start(){ + wxi-header "Warén Group / Infra / CLI" h1 + + mkdir -p $HOME/.warengroup + if [[ ! -f $HOME/.warengroup/config.json || $(jq -e . < $HOME/.warengroup/config.json &>/dev/null; echo $?) -gt 0 ]] + then + echo '{}' | jq > $HOME/.warengroup/config.json + fi + mkdir -p $HOME/.ssh/keys + chmod 700 -R $HOME/.ssh/keys + mkdir -p $HOME/.ssh/multiplex + chmod 700 -R $HOME/.ssh/multiplex + + echo -n "$wxiBold" + echo "Designed by" + echo -n "$wxiNormal" + wxiLink "Warén Group™" "https://waren.io" +} + +wxi-stop(){ + # wxi-repeat "\n" 3 + + # for key in "${!args[@]}" + # do + # echo "$key: ${args[$key]}" + # done + + wxi-repeat "\n" 3 + exit 1 +} + +wxi-content(){ + if [[ $1 == "text" ]] + then + echo "$2" + elif [[ $1 == "text*" ]] + then + echo -n "$2" + elif [[ $1 == "status" ]] + then + echo -n "Status: " + echo "$2 - $3" + elif [[ $1 == "link" ]] + then + echo "$2 - $3" + fi +} + +wxiRed=$(tput setaf 196) +wxiGreen=$(tput setaf 46) +wxiYellow=$(tput setaf 226) +wxiBlue=$(tput setaf 21) +wxiPurple=$(tput setaf 165) +wxiTurquoise=$(tput setaf 14) +wxiPink=$(tput setaf 198) +wxiOrange=$(tput setaf 202) +wxiUnderline=$(tput smul) +wxiBold=$(tput bold) +wxiNormal=$(tput sgr0) + +wxiLink() { + printf '\e]8;;%s\e\\%s\e]8;;\e\\\n' "$2" "$1" + echo "" +} + +wxi-header(){ + if [[ $2 == "h1" ]] + then + #wxi-repeat " " $(($(tput cols)/2-${#1}/2)) + echo -n "$wxiBold" + echo ${1^^} + echo -n "$wxiNormal" + echo "" + fi + + if [[ $2 == "h1*" ]] + then + #wxi-repeat " " $(($(tput cols)/2-${#1}/2)) + echo -n "$wxiBold" + echo ${1^^} + echo -n "$wxiNormal" + fi + + if [[ $2 == "h2" || $2 == "" ]] + then + #wxi-repeat " " $(($(tput cols)/2-${#1}/2)) + echo -n "$wxiBold" + echo ">> $1 <<" + echo -n "$wxiNormal" + fi + + if [[ $2 == "h3" ]] + then + #wxi-repeat " " $(($(tput cols)/2-${#1}/2)) + echo -n "$wxiBold" + echo "$1" + echo -n "$wxiNormal" + fi + + if [[ $2 == "h4" ]] + then + #wxi-repeat " " $(($(tput cols)/2-${#1}/2)) + echo -n "$wxiUnderline" + echo "$1" + echo -n "$wxiNormal" + fi +} + + +wxi-repeat() { + if [[ $1 == " " ]] + then + local str=$1 n=$2 spaces + printf -v spaces "%*s" $n " " + printf "%s" "${spaces// /$str}" + else + for i in $(seq 1 $2); + do + echo -en $1 + done + fi +} + +wx-eas(){ + wxi-header "Emergency Access System" h2 + + LOCATION="$WX_HOME/credentials" + wxi-eas-login + + wxi-repeat "\n" 1 + + case ${args['2']} in + data) + wxi-eas-data + ;; + token) + wxi-eas-token + ;; + *) + wxi-content status "Error" "This feature isn't implemented yet" + ;; + esac +} + +wxi-eas-login(){ + if [[ $(systemd-detect-virt) == "wsl" ]] + then + echo 'KERNEL=="hidraw*", SUBSYSTEM=="hidraw", MODE="0666", TAG+="uaccess", GROUP="plugdev", ATTRS{idVendor}=="1050", ATTRS{idProduct}=="0407"' | sudo tee /etc/udev/rules.d/99-yubikey.rules > /dev/null + sudo udevadm control --reload + fi + + YUBIKEYS=$(ykman list --serials 2>/dev/null | jq -R -s 'split("\n")[:-1]') + + if [[ $(echo $YUBIKEYS | jq 'length') != 1 ]] + then + if [[ ${args['1']} == "eas" ]] + then + wxi-content status "Error" "You need to attach atleast one Yubikey and unplug all other Yubikeys" + wxi-stop + fi + else + SERIAL=$(echo $YUBIKEYS | jq -r '.[0]') + wxi-content text "Emergency Access System - Serial Number: $SERIAL" + export SERIAL + fi + + sudo systemctl enable --now pcscd &> /dev/null +} + +wx-help(){ + +wxi-header "Help" + +wxi-content text " +Usage: $0 COMMAND [OPTIONS] + +Common Commands: + init Init + help Help + ssh SSH + config Config + edit Edit + save Save + sync Sync + clean Clean + keys Keys + generate Generate + sign Sign + retrieve Retrieve + save Save + sync Sync + clean Clean + +Authentication Commands: + login Login + logout Logout + +Management Commands: + auto Auto + clean Clean + settings Settings + +Maintenance Commands: + install Install + update Update + uninstall Uninstall +"; + +} + +wx-infra(){ + wx-login + wx-auto &> /dev/null + + wxi-header "Infra" + wxi-restricted + + case $USERNAME in + cwchristerw) + if [[ -d "$HOME/.warengroup/infra" ]] + then + INFRA_PATH="$HOME/.warengroup/infra" + else + INFRA_PATH="$HOME/.warengroup/infra" + mkdir -p "$INFRA_PATH" &> /dev/null + git clone ssh://git@git.waren.io:2222/warengroup-private/infra.git --config core.sshCommand="ssh -i $HOME/.ssh/keys/warengroup-legacy -o ProxyJump=none" "$INFRA_PATH" &> /dev/null + fi + + if [[ ! -f "$HOME/.warengroup/credentials/ansible-vault/cwchristerw" || ! -f "$HOME/.warengroup/credentials/ansible-vault/warengroup" ]] + then + mkdir -p "$HOME/.warengroup/credentials/ansible-vault" &> /dev/null + + curl \ + -H "X-Vault-Token: $TOKEN" \ + -X GET \ + https://$VAULT_DOMAIN/v1/cli/data/cwchristerw/settings/infra -s | jq -r '.data.data.cwchristerw' > "$HOME/.warengroup/credentials/ansible-vault/cwchristerw" + + curl \ + -H "X-Vault-Token: $TOKEN" \ + -X GET \ + https://$VAULT_DOMAIN/v1/cli/data/cwchristerw/settings/infra -s | jq -r '.data.data.warengroup' > "$HOME/.warengroup/credentials/ansible-vault/warengroup" + fi + + INFRA_VAULT="--vault-id warengroup@$HOME/.warengroup/credentials/ansible-vault/warengroup --vault-id cwchristerw@$HOME/.warengroup/credentials/ansible-vault/cwchristerw" + ;; + *) + wxi-content status "User" "Unsupported" + wxi-stop + INFRA_PATH="$HOME/.warengroup/infra" + INFRA_VAULT="--vault-id warengroup@$HOME/.warengroup/credentials/ansible-vault/warengroup" + ;; + esac + + if [[ -z ${args['2']} ]] + then + echo "Tag Required" + else + cd "$INFRA_PATH/ansible" + #git pull &> /dev/null + #ansible-galaxy collection install -r requirements.yml --upgrade &> /dev/null + + if [[ ${args['2']} == "init" ]] + then + wxi-header "Init" h3 + + host=${args['host']} + + if [[ -z ${args['3']} ]] + then + tags=init + else + tags=${args['3']} + fi + + ansible-playbook $INFRA_VAULT init.yml --tags $tags --limit "$host" + elif [[ ${args['2']} == "manager" ]] + then + if [[ -z ${args['host']} ]] + then + host="*" + else + host=${args['host']} + fi + + operation=${args['operation']} + extra_vars=${args['extra-vars']} + + wxi-header "Manager" h3 + ansible-playbook $INFRA_VAULT manager.yml --tags $operation --extra-vars "host=$host" --extra-vars="$extra_vars" + else + wxi-header "Infra" h3 + host=${args['host']} + tags=${args['2']} + ansible-playbook $INFRA_VAULT infra.yml --tags $tags --limit "$host" + fi + cd "$OLDPWD" + fi +} + +wx-ssh(){ + wx-login &> /dev/null + + case ${args['2']} in + config) + wxi-ssh-config + ;; + keys) + wxi-ssh-keys + ;; + *) + wxi-header "SSH" + wxi-restricted + ;; + esac +} + +wx-welcome(){ + wxi-header "Welcome" + + wxi-header "Help" h3 + wxi-content text "Use \"wx help\" command" + + echo "" + + wxi-header "Useful Links" h3 + wxi-content link "Infra" "https://infra.waren.io" + wxi-content link "Status" "https://status.waren.io" +} + +wx-login(){ + wxi-header "Login" + wxi-restricted --user + wxi-restricted --org + wxi-restricted --vault + + wxi-header "$ORG_HEADER" h3 + + if [[ ! -z ${args['auth-method']} ]] + then + AUTH_METHOD=${args['auth-method']} + elif [[ ! -z ${args['token']} ]] + then + AUTH_METHOD=token + elif [[ $(wxi-config-legacy login read) ]] + then + AUTH_METHOD=token + elif [[ ! -z ${args['username']} ]] + then + AUTH_METHOD=ldap + else + AUTH_METHOD=ldap + fi + + if [[ ! -z $AUTH_METHOD ]] + then + case $AUTH_METHOD in + ldap) + echo -n "Username: " + if [[ ! -z ${args['username']} ]] + then + USERNAME=${args['username']} + wxi-content text "$USERNAME" + else + read USERNAME + fi + + echo -n "Password: " + if [[ ! -z ${args['password']} ]] + then + PASSWORD=${args['password']} + else + read -s PASSWORD + fi + + if [[ ! -z $PASSWORD ]] + then + wxi-content text "****************" + else + wxi-content text "" + fi + + if [[ -z $USERNAME || -z $PASSWORD ]] + then + wxi-content status "Username & Password" "Required" + wxi-stop + fi + + VAULT_LOGIN=$(curl https://$VAULT_DOMAIN/v1/auth/ldap/login/$USERNAME -X POST -d '{ "password": "'$PASSWORD'" }' -s | jq -r '.auth.client_token') + if [[ -z $VAULT_LOGIN || ${#VAULT_LOGIN} -lt 95 || ${#VAULT_LOGIN} -gt 95 ]] + then + wxi-content status "Login" "Failed" + wxi-stop + fi + + TOKEN=$VAULT_LOGIN + wxi-config-legacy login write + ;; + token) + echo -n "Token: " + if [[ ! -z ${args['token']} ]] + then + if [[ ${args['token']} != "true" ]] + then + TOKEN=${args['token']} + fi + elif [[ $(wxi-config-legacy login read) ]] + then + TOKEN=$(wxi-config-legacy login read) + else + read -s TOKEN + fi + + if [[ ! -z $TOKEN ]] + then + wxi-content text "***********************************************************************************************" + fi + + if [[ -z $TOKEN ]] + then + wxi-content status "Token" "Required" + wxi-stop + fi + + if [[ ${#TOKEN} -lt 95 || ${#TOKEN} -gt 95 ]] + then + wxi-content status "Token" "Invalid" + wxi-stop + fi + + VAULT_LOGIN=$(curl https://$VAULT_DOMAIN/v1/auth/token/renew-self -X POST --header "X-Vault-Token: $TOKEN" -d '{ "token": "'$TOKEN'" }' -s | jq -r '.auth.client_token') + if [[ -z $VAULT_LOGIN || ${#VAULT_LOGIN} -lt 95 || ${#VAULT_LOGIN} -gt 95 ]] + then + wxi-content status "Login" "Failed" + + if [[ $(wxi-config-legacy login read) ]] + then + wx-logout &> /dev/null + wx-login + else + wxi-stop + fi + fi + + TOKEN=$VAULT_LOGIN + wxi-config-legacy login write + ;; + *) + wxi-content status "Login Type" "Unsupported" + wxi-stop + ;; + esac + fi + + VAULT_USERNAME=$(curl https://$VAULT_DOMAIN/v1/auth/token/lookup-self -X GET --header "X-Vault-Token: $TOKEN" -s | jq -r '.data.display_name') + if [[ -z $VAULT_USERNAME ]] + then + wxi-content status "Login" "Username Missing" + wxi-stop + elif [[ $VAULT_USERNAME != ldap* && $VAULT_USERNAME != oidc* ]] + then + wxi-content status "Login" "Authentication Method Invalid" + wxi-stop + elif [[ $VAULT_USERNAME == ldap* ]] + then + USERNAME=${VAULT_USERNAME#ldap-} + elif [[ $VAULT_USERNAME == oidc* ]] + then + USERNAME=${VAULT_USERNAME#oidc-} + fi +} + +wx-logout(){ + wxi-header "Logout" + wxi-restricted --user + wxi-restricted --org + wxi-restricted --vault + + if [[ -f "$WX_HOME/config.json" && $(cat $WX_HOME/config.json | jq -r .login.$ORG.token) != 'null' ]] + then + TOKEN=$(cat $WX_HOME/config.json | jq -r .login.$ORG.token) + fi + + VAULT_STATUS=$(curl https://$VAULT_DOMAIN/v1/auth/token/revoke-self -X POST --header "X-Vault-Token: $TOKEN" -s -o /dev/null -w "%{http_code}") + if [[ $VAULT_STATUS -eq 204 || $VAULT_STATUS -eq 403 ]] + then + wxi-header "$ORG_HEADER" h3 + echo "Logging Out..." + wxi-config-legacy login erase + fi + +} + +wxi-eas-data(){ + case ${args['3']} in + encrypt) + wxi-eas-data-encrypt + ;; + decrypt) + wxi-eas-data-decrypt + ;; + retrieve) + wxi-eas-data-retrieve + ;; + deliver) + wxi-eas-data-deliver + ;; + *) + wx-help + ;; + esac +} + +wxi-eas-token(){ + case ${args['3']} in + create) + wxi-eas-token-create + ;; + erase) + wxi-eas-token-erase + ;; + sign) + wxi-eas-token-sign + ;; + esac +} + +wx-install(){ + if [[ "${args['1']}" == "install" ]] + then + wxi-header "Install" h2 + fi + + if [[ ! -L "$HOME/bin/wx" && ! -f "/opt/ansible/bin/ansible-playbook" && ! -f "/bin/jq" && ! -f "/bin/podman" && ! -f "/bin/ykman"|| "${args['1']}" == "upgrade" ]] + then + cd "$WX_HOME/infra" + + wxi-repeat "\n" 1 + + wxi-header "Git" h3 + git stash -- cli.sh + git pull + wxi-repeat "\n" 1 + + wxi-header "Elevated Privileges" h3 + sudo echo 'This command uses sudo!' + + wxi-repeat "\n" 1 + + wxi-header "Ansible" h3 + + wxi-content text* "Dependencies: " + sudo apt-get update &> /dev/null + sudo apt-get install -y python3-pip python3-venv jq git curl lsb-release sudo sshpass rsync &> /dev/null + wxi-content text "Ready ✔" + + wxi-content text* "Python 3 - Virtual Environment: " + sudo python3 -m venv /opt/ansible &> /dev/null + if [[ -d "/opt/ansible" ]] + then + wxi-content text "Ready ✔" + else + wxi-content text "Failed ✖" + fi + + wxi-content text* "Ansible: " + sudo /opt/ansible/bin/pip3 install ansible-core &> /dev/null + if [[ -f "/opt/ansible/bin/ansible-playbook" ]] + then + wxi-content text "Ready ✔" + else + wxi-content text "Failed ✖" + fi + + wxi-content text* "Python3 Libraries - Dependencies: " + sudo /opt/ansible/bin/pip3 install cryptography dnspython hvac jmespath netaddr passlib pexpect xmltodict ansi2html --upgrade &> /dev/null + wxi-content text "Ready ✔" + + wxi-content text* "Collections: " + sudo ln -s /opt/ansible/collections /usr/share/ansible/collections &> /dev/null + sudo /opt/ansible/bin/ansible-galaxy collection install -r ansible/requirements.yml -p /usr/share/ansible/collections --upgrade &> /dev/null + wxi-content text "Ready ✔" + + wxi-repeat "\n" 1 + + wxi-header "CLI Tool" h3 + + if [[ -d "./cli/src" ]] + then + wxi-content text* "Dependencies: " + sudo apt-get update &> /dev/null + sudo apt-get install -y jq yubikey-manager &> /dev/null + + if [[ -f "/bin/jq" && -f "/bin/ykman" ]] + then + wxi-content text "Ready ✔" + else + wxi-content text "Failed ✖" + fi + + wxi-content text* "Generate: " + python3 cli/generator.py + + if [[ -f "./cli/cli.sh" ]] + then + wxi-content text "Ready ✔" + + wxi-content text* "Deploying: " + mv -f ./cli/cli.sh ./cli.sh + chmod +x ./cli.sh + + if [[ ! -L "$HOME/bin/wx" && ! -f "$HOME/bin/wx" ]] + then + mkdir -p "$HOME/bin" + ln -s "$PWD/cli.sh" "$HOME/bin/wx" + fi + + if [[ $(systemd-detect-virt) == "wsl" ]] + then + if [[ ! -L "/bin/wx" ]] + then + sudo ln -s "$PWD/cli.sh" "/bin/wx" + fi + fi + + if [[ -f "./cli.sh" ]] + then + wxi-content text "Ready ✔" + fi + else + wxi-content text "Failed ✖" + fi + fi + cd "$OLDPWD" + else + wxi-content text "You have already installed!" + fi +} + +wx-install2(){ + wxi-header "Install" + wxi-restricted --user + + if [[ -f "./wx" && -f "./maintainer.sh" && -d "./src" ]] + then + ./maintainer.sh + fi + + mkdir -p $HOME/bin + if [[ $(curl -s -o /dev/null -w "%{http_code}" https://git.waren.io/warengroup/wx/raw/branch/master/wx) -eq 200 ]] + then + curl https://git.waren.io/warengroup/wx/raw/branch/master/wx -o $HOME/bin/wx &> /dev/null + chmod +x $HOME/bin/wx &> /dev/null + fi + + CRONJOB_NAME="#Warén CLI: Auto" + CRONJOB_TASK="*/5 * * * * $HOME/bin/wx auto" + if [[ -z $(crontab -l | grep -F "$CRONJOB_NAME") || -z $(crontab -l | grep -F "$CRONJOB_TASK") ]] + then + (crontab -l ; echo "$CRONJOB_NAME" ; echo "$CRONJOB_TASK") | grep -Fv "no crontab" | crontab - + fi +} + +wx-uninstall(){ + wxi-header "Uninstall" + wxi-restricted --user + wx-clean &> /dev/null + + if [[ -d "$HOME/.warengroup" ]] + then + rm "$HOME/.warengroup" -rf + fi + + CRONJOB_NAME="#Warén CLI: Auto" + CRONJOB_TASK="*/5 * * * * $HOME/bin/wx auto" + if [[ $(crontab -l | grep -F "$CRONJOB_NAME") || $(crontab -l | grep -F "$CRONJOB_TASK") ]] + then + crontab -l | grep -Fv "$CRONJOB_NAME" | grep -Fv "$CRONJOB_TASK" | grep -Fv "no crontab" | crontab - + fi + + if [[ -f "$HOME/bin/wx" ]] + then + rm "$HOME/bin/wx" -rf + fi + + wxi-repeat "\n" 3 + exit 1 +} + +wx-update(){ + wxi-header "Update" + wxi-restricted --user + + wx-install &> /dev/null + echo "Updates Completed" + +} + +wx-upgrade(){ + wxi-header "Upgrade" h2 + wx-install +} + +wx-auto(){ + wx-login + + wxi-header "Auto" + wxi-restricted + + wxi-ssh-config-sync + wxi-ssh-keys-sign + wxi-ssh-keys-sync +} + +wx-clean(){ + wxi-header "Clean" + wxi-restricted --user + + wxi-ssh-config-clean + wxi-ssh-keys-clean +} + +wx-settings(){ + wx-login + wx-auto &> /dev/null + + wxi-header "Settings" + wxi-restricted --user +} + +wxi-ssh-config(){ + case ${args['3']} in + edit) + wxi-ssh-config-sync + wxi-ssh-config-edit + wxi-ssh-config-save + ;; + save) + wxi-ssh-config-save + ;; + sync) + wxi-ssh-config-sync + ;; + clean) + wxi-ssh-config-clean + ;; + *) + wxi-ssh-config-sync + ;; + esac +} + +wxi-ssh-keys(){ + case ${args['3']} in + generate) + wxi-ssh-keys-retrieve ${args['4']} + wxi-ssh-keys-generate ${args['4']} + wxi-ssh-keys-save ${args['4']} + ;; + sign) + wxi-ssh-keys-sign + ;; + retrieve) + wxi-ssh-keys-retrieve ${args['4']} + ;; + save) + wxi-ssh-keys-save ${args['4']} + ;; + sync) + wxi-ssh-keys-sync + ;; + clean) + wxi-ssh-keys-clean + ;; + *) + wxi-header "SSH / Keys" + ;; + esac +} + +pxi-eas-data-decrypt(){ + pxi-header "Data - Encrypt" h3 + + if [[ -n ${args['confirm']} ]] + then + if [[ -f $LOCATION/ansible-vault/eas/$SERIAL && $(base64 "$LOCATION/ansible-vault/eas/$SERIAL") != "" ]] + then + i=0 + for file in "$WX_HOME/infra"/eas/data/$SERIAL/*/*/credentials; + do + i=$((i + 1)) + echo "$i)${file#"$WX_HOME/infra/eas/data/$SERIAL/"}" + ansible-vault decrypt --vault-id "Warén Group - Infra - Emergency Access System ($SERIAL)"@"$LOCATION/ansible-vault/eas/$SERIAL" "$file" + done + fi + else + pxi-content text "Confirmation Needed ✖ (--confirm)" + fi +} + +pxi-eas-data-deliver(){ + px-login vault + pxi-repeat "\n" 1 + pxi-header "Data - Deliver" h3 + + if [[ -n ${args['confirm']} ]] + then + cd "$WX_HOME/infra/ansible" + + playbook=cli + tasks=eas-data-deliver + limit="${args['limit']:-all}" + vaulted=(--vault-id "Warén Group - Infra - Emergency Access System ($SERIAL)@$WX_HOME/credentials/ansible-vault/eas/$SERIAL") + + /opt/ansible/bin/ansible-playbook $playbook.yml -t "$tasks" --limit "$limit,localhost" --extra-vars "${args['vars']}" "${vaulted[@]}" + cd "$OLDPWD" + else + pxi-content text "Confirmation Needed ✖ (--confirm)" + fi +} + +pxi-eas-data-encrypt(){ + pxi-header "Data - Encrypt" h3 + + if [[ -f $LOCATION/ansible-vault/eas/$SERIAL && $(base64 "$LOCATION/ansible-vault/eas/$SERIAL") != "" ]] + then + i=0 + for file in "$WX_HOME/infra"/eas/data/$SERIAL/*/*/credentials; + do + i=$((i + 1)) + echo "$i)${file#"$WX_HOME/infra/eas/data/$SERIAL/"}" + ansible-vault encrypt --vault-id "Warén Group - Infra - Emergency Access System ($SERIAL)"@"$LOCATION/ansible-vault/eas/$SERIAL" "$file" + done + fi +} + +pxi-eas-data-retrieve(){ + px-login vault + pxi-repeat "\n" 1 + pxi-header "Data - Retrieve" h3 + + if [[ -n ${args['confirm']} ]] + then + cd "$WX_HOME/infra/ansible" + + playbook=cli + tasks=eas-data-retrieve + limit="${args['limit']:-all}" + vaulted=(--vault-id "Warén Group - Infra - Emergency Access System ($SERIAL)@$WX_HOME/credentials/ansible-vault/eas/$SERIAL") + + /opt/ansible/bin/ansible-playbook $playbook.yml -t "$tasks" --limit "$limit,localhost" --extra-vars "${args['vars']}" "${vaulted[@]}" + cd "$OLDPWD" + else + pxi-content text "Confirmation Needed ✖ (--confirm)" + fi +} + +wxi-eas-token-create(){ + wxi-header "Token - Create" h3 + + wxi-header "Ansible Vault" h4 + if [[ $(ykman piv objects export 0x005FFF16 - 2>/dev/null) == "" ]] + then + wxi-content text* "PIN: " + read -s PIN + wxi-content text "******" + + if [[ -n $PIN ]] + then + wxi-content text* "Generating: " + echo $(LC_ALL=C tr -dc 'A-Z2-7' /dev/null) + if [[ $EAS_AVP != "" ]] + then + echo -e "Warén Group - Infra - Emergency Access System ($SERIAL) / Ansible Vault / $EAS_AVP" > "$LOCATION/ansible-vault/eas/$SERIAL" + if [[ -f "$LOCATION/ansible-vault/eas/$SERIAL" && $(base64 "$LOCATION/ansible-vault/eas/$SERIAL") != "" ]] + then + wxi-content text "Ready ✔" + else + wxi-content text "Failed ✖" + fi + else + wxi-content text "Failed ✖" + fi + + wxi-repeat "\n" 1 + + wxi-header "SSH" h4 + + if [[ ! -f "$LOCATION/ssh/eas/$SERIAL" || ! -f "$WX_HOME/eas/credentials/ssh/$SERIAL.pub" ]] + then + if [[ ! -f "$LOCATION/ssh/eas/$SERIAL" && -f "$WX_HOME/eas/credentials/ssh/$SERIAL.pub" ]] + then + OLDOLDPWD=$OLDPWD + cd "$LOCATION/ssh/eas" + + wxi-content text* "Retrieving: " + wxi-content text "Processing..." + + wxi-repeat "\n" 1 + + ssh-keygen -K + + wxi-repeat "\n" 1 + + if [[ -f "id_ed25519_sk_rk_warengroup-infra-eas-$SERIAL" ]] + then + mv "id_ed25519_sk_rk_warengroup-infra-eas-$SERIAL" "$SERIAL" + mv "id_ed25519_sk_rk_warengroup-infra-eas-$SERIAL.pub" "$SERIAL.pub" + + ssh-keygen -c -f "$LOCATION/ssh/eas/$SERIAL" -P "" -C "Warén Group - Infra - Emergency Access System ($SERIAL)" &> /dev/null + + wxi-content text* "Retrieving: " + wxi-content text "Ready ✔" + else + wxi-content text* "Retrieving: " + wxi-content text "Failed ✖" + fi + + wxi-repeat "\n" 1 + + wxi-content text* "Cleaning: " + rm -f id_ecdsa_sk_rk_* + rm -f id_ed25519_sk_rk_* + rm -f id_rsa_sk_rk_* + wxi-content text "Ready ✔" + + cd "$OLDPWD" + OLDPWD=$OLDOLDPWD + elif [[ ! -f "$LOCATION/ssh/eas/$SERIAL" ]] + then + wxi-content text* "Generating: " + wxi-content text "Processing..." + + wxi-repeat "\n" 1 + + ssh-keygen -t ed25519-sk -f "$LOCATION/ssh/eas/$SERIAL" -O resident -O verify-required -O "application=ssh:warengroup-infra-eas-$SERIAL" -N "" -C "Warén Group - Infra - Emergency Access System ($SERIAL)" + + wxi-repeat "\n" 1 + + if [[ -f "$LOCATION/ssh/eas/$SERIAL" ]] + then + wxi-content text* "Generating: " + wxi-content text "Ready ✔" + else + wxi-content text* "Generating: " + wxi-content text "Failed ✖" + fi + + wxi-repeat "\n" 1 + fi + + if [[ -f "$LOCATION/ssh/eas/$SERIAL.pub" && ! -f "$WX_HOME/infra/eas/credentials/ssh/$SERIAL.pub" ]] + then + wxi-content text* "Delivering: " + cp "$LOCATION/ssh/eas/$SERIAL.pub" "$WX_HOME/infra/eas/credentials/ssh/$SERIAL.pub" + if [[ -f "$WX_HOME/infra/eas/credentials/ssh/$SERIAL.pub" ]] + then + wxi-content text "Ready ✔" + else + wxi-content text "Failed ✖" + fi + fi + else + wxi-content text* "Processing: " + wxi-content text "Done ✔" + fi +} + +wxi-eas-token-erase(){ + wxi-header "Token - Erase" h3 + + wxi-content text* "Processing: " + + if [[ -f "$LOCATION/$serial" || -f "$LOCATION/$serial.pub" || -f "$WX_HOME/infra/eas/credentials/ssh/$serial.pub" ]] + then + if [[ -n ${args['confirm']} ]] + then + rm -f "$LOCATION/ssh/eas/$serial" + rm -f "$LOCATION/ssh/eas/$serial.pub" + rm -f "$WX_HOME/infra/eas/credentials/ssh/$serial.pub" + + if [[ ! -f "$LOCATION/ssh/eas/$serial" && ! -f "$LOCATION/ssh/eas/$serial.pub" && ! -f "$WX_HOME/infra/eas/credentials/ssh/$serial.pub" ]] + then + wxi-content text "Ready ✔" + else + wxi-content text "Failed ✖" + fi + else + wxi-content text "Confirmation Needed ✖ (--confirm)" + fi + else + wxi-content text "Done ✔" + fi +} + +wxi-eas-token-sign(){ + wxi-header "Token - Sign" h3 + + wxi-eas-token-sign-gen +} + +wxi-eas-token-sign-gen(){ + LOCATION="$WX_HOME/credentials" + if [[ -f "$LOCATION/ssh/eas/$SERIAL" ]] + then + if [[ ! -f "$LOCATION/ssh/eas/$SERIAL-" ]] + then + wxi-ssh-keys-generate "eas/$SERIAL-" "Warén Group - Infra - Emergency Access System ($SERIAL)" &> /dev/null + fi + + ssh-keygen -s "$LOCATION/ssh/eas/$SERIAL" -I "Warén Group - Infra - Emergency Access System ($SERIAL)" -n root -V -1m:+30m "$LOCATION/ssh/eas/$SERIAL-.pub" + mv -f "$LOCATION/ssh/eas/$SERIAL--cert.pub" "$LOCATION/ssh/eas/$SERIAL-.sig" + fi +} + +wxi-ssh-config-clean(){ + wxi-header "SSH / Config / Clean" + wxi-restricted + + VAULT_STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://$VAULT_DOMAIN/v1/cli/data/$USERNAME/settings/ssh/config -X GET --header "X-Vault-Token: $TOKEN") + if [[ -f "$HOME/.ssh/config" && $VAULT_STATUS -eq 200 ]] + then + rm "$HOME/.ssh/config" + fi +} + +wxi-ssh-config-edit(){ + wxi-header "SSH / Config / Edit" + wxi-restricted + + nano ~/.ssh/config +} + +wxi-ssh-config-save(){ + wxi-header "SSH / Config / Save" + wxi-restricted + + if [[ -f "$HOME/.ssh/config" ]] + then + curl https://$VAULT_DOMAIN/v1/cli/data/$USERNAME/settings/ssh/config -X POST --header "X-Vault-Token: $TOKEN" -d "{ \"data\": { \"data\": \"$(cat ~/.ssh/config | base64 -w 0)\" } }" -s &> /dev/null + fi +} + +wxi-ssh-config-sync(){ + wxi-header "SSH / Config / Sync" + wxi-restricted + + VAULT_STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://$VAULT_DOMAIN/v1/cli/data/$USERNAME/settings/ssh/config -X GET --header "X-Vault-Token: $TOKEN") + if [[ $VAULT_STATUS -eq 200 ]] + then + touch ~/.ssh/config + SSH1_CONFIG_MD5=$(curl https://$VAULT_DOMAIN/v1/cli/data/$USERNAME/settings/ssh/config -X GET --header "X-Vault-Token: $TOKEN" -s | jq -r '.data.data.data' | base64 -d | md5sum | base64) + SSH2_CONFIG_MD5=$(cat ~/.ssh/config | md5sum | base64) + if [[ $SSH1_CONFIG_MD5 != $SSH2_CONFIG_MD5 ]] + then + echo $(curl https://$VAULT_DOMAIN/v1/cli/data/$USERNAME/settings/ssh/config -X GET --header "X-Vault-Token: $TOKEN" -s | jq -r '.data.data.data') | base64 -d > ~/.ssh/config 2>&1 + chmod 700 ~/.ssh/config + fi + fi +} + +wxi-ssh-keys-clean(){ + wxi-header "SSH / Keys / Clean" + wxi-restricted + + if [[ ! -z $1 ]] + then + if [[ -f "$HOME/.ssh/keys/$1" ]] + then + rm "$HOME/.ssh/keys/$1" &> /dev/null + rm "$HOME/.ssh/keys/$1.pub" &> /dev/null + rm "$HOME/.ssh/keys/$1.sig" &> /dev/null + fi + else + wxi-ssh-keys-clean $ORG + + if [[ $USERNAME == "cwchristerw" ]] + then + wxi-ssh-keys-clean warengroup + fi + + for file in ~/.ssh/keys/* + do + VAULT_STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://$VAULT_DOMAIN/v1/cli/data/$USERNAME/settings/ssh/keys/$(basename "$file" .pub) -X GET --header "X-Vault-Token: $TOKEN") + if [[ $VAULT_STATUS -eq 200 ]] + then + rm "$file" &> /dev/null + fi + done + fi +} + +wxi-ssh-keys-generate(){ + wxi-header "SSH / Keys / Generate" + wxi-restricted + + if [[ ! -z $1 ]] + then + if [[ ! -f "$HOME/.ssh/keys/$1" ]] + then + ssh-keygen -t ed25519 -f $HOME/.ssh/keys/$1 -q -N "" -C "$USERNAME" &> /dev/null + fi + fi +} + +wxi-ssh-keys-retrieve(){ + wxi-header "SSH / Keys / Retrieve" + wxi-restricted + + if [[ ! -z $1 ]] + then + VAULT_STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://$VAULT_DOMAIN/v1/cli/data/$USERNAME/settings/ssh/keys/$1 -X GET --header "X-Vault-Token: $TOKEN") + if [[ $VAULT_STATUS -eq 200 ]] + then + echo $(curl https://$VAULT_DOMAIN/v1/cli/data/$USERNAME/settings/ssh/keys/$1 -X GET --header "X-Vault-Token: $TOKEN" -s | jq -r '.data.data.private') | base64 -d > ~/.ssh/keys/$1 2>&1 + chmod 700 ~/.ssh/keys/$1 + echo $(curl https://$VAULT_DOMAIN/v1/cli/data/$USERNAME/settings/ssh/keys/$1 -X GET --header "X-Vault-Token: $TOKEN" -s | jq -r '.data.data.public') | base64 -d > ~/.ssh/keys/$1.pub 2>&1 + chmod 700 ~/.ssh/keys/$1.pub + fi + fi +} + +wxi-ssh-keys-save(){ + wxi-header "SSH / Keys / Save" + wxi-restricted + + if [[ ! -z $1 ]] + then + if [[ -f "$HOME/.ssh/keys/$1" ]] + then + curl https://$VAULT_DOMAIN/v1/cli/data/$USERNAME/settings/ssh/keys/$1 -X POST --header "X-Vault-Token: $TOKEN" -d "{ \"data\": { \"private\": \"$(cat ~/.ssh/keys/$1 | base64 -w 0)\", \"public\": \"$(cat ~/.ssh/keys/$1.pub | base64 -w 0)\" } }" -s &> /dev/null + fi + fi +} + +wxi-ssh-keys-sign(){ + wxi-header "SSH / Keys / Sign" + wxi-restricted + + if [[ $USERNAME == "cwchristerw" ]] + then + wxi-ssh-keys-sign-create cwchristerw sysadmin 3600 + wxi-ssh-keys-sign-create warengroup sysadmin 3600 + else + wxi-ssh-keys-sign-create $ORG sysadmin 3600 + fi +} + +wxi-ssh-keys-sign-create(){ + NAME=$1 + ROLE=$2 + PRINCIPALS=$2 + TTL=$3 + + wxi-ssh-keys-generate $NAME &> /dev/null + + if [[ -f "$HOME/.ssh/keys/$NAME" ]] + then + wxi-content text "$NAME/$ROLE" + echo $(curl https://$VAULT_DOMAIN/v1/ssh/sign/$ROLE -X POST --header "X-Vault-Token: $TOKEN" -d "{ \"public_key\": \"$(cat $HOME/.ssh/keys/$NAME.pub)\", \"valid_principals\": \"$PRINCIPALS,$USERNAME\", \"ttl\": \"$TTL\" }" -s | jq -r '.data.signed_key') > ~/.ssh/keys/$NAME.sig 2>&1 + fi +} + +wxi-ssh-keys-sync(){ + wxi-header "SSH / Keys / Sync" + wxi-restricted + VAULT_STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://$VAULT_DOMAIN/v1/cli/metadata/$USERNAME/settings/ssh/keys -X LIST --header "X-Vault-Token: $TOKEN") + if [[ $VAULT_STATUS -eq 200 ]] + then + for name in $(curl https://$VAULT_DOMAIN/v1/cli/metadata/$USERNAME/settings/ssh/keys -X LIST --header "X-Vault-Token: $TOKEN" -s | jq -r '.data.keys | @sh' | tr -d \') + do + wxi-content text $name + wxi-ssh-keys-retrieve $name &> /dev/null + done + fi +} + + +wxi-args "$@" +wxi-config + +echo -n $wxiBold$wxiOrange +cat <<'EOF' + + (`\ .-') /` ('-. _ .-') ('-. .-') _ + `.( OO ),' ( OO ).-.( \( -O ) _( OO) ( OO ) ) + ,--./ .--. / . --. / ,------. (,------.,--./ ,--,' + | | | | \-. \ | /`. ' | .---'| \ | |\ + | | | |,.-'-' | | | / | | | | | \| | ) + | |.'.| |_)\| |_.' | | |_.' |(| '--. | . |/ + | | | .-. | | . '.' | .--' | |\ | + | ,'. | | | | | | |\ \ | `---.| | \ | + '--' '--' `--' `--' `--' '--' `------'`--' `--' + +EOF +echo -n $wxiNormal + +wxi-start +if [[ ! -z ${args['1']} ]] && [[ $(type -t wx-${args['1']}) == function ]] +then + wx-${args['1']} +else + wx-welcome +fi +wxi-stop