From d365afb555f0c525b690cce772b744f7b17e77d5 Mon Sep 17 00:00:00 2001 From: Cuong Tran Date: Wed, 24 Jun 2026 15:41:13 +0200 Subject: [PATCH 1/2] feat: multi-ingress idp provisioning script --- .../multi-ingress-idp-provisioner.sh | 279 ++++++++++++++++++ 1 file changed, 279 insertions(+) create mode 100755 multi-ingress-idp-provisioning/multi-ingress-idp-provisioner.sh diff --git a/multi-ingress-idp-provisioning/multi-ingress-idp-provisioner.sh b/multi-ingress-idp-provisioning/multi-ingress-idp-provisioner.sh new file mode 100755 index 0000000..3da882a --- /dev/null +++ b/multi-ingress-idp-provisioning/multi-ingress-idp-provisioner.sh @@ -0,0 +1,279 @@ +#!/usr/bin/env bash + +set -o pipefail + +# constants +SUCCESS=0 +ERR_GENERAL=1 +ERR_INVALID_ARGS=2 +ERR_AUTH=3 +ERR_DOMAIN_NOT_FOUND=4 +ERR_IDP_NOT_FOUND=5 +ERR_INVALID_IDP_FILE=6 +ERR_API_ERROR=7 + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +LOG_FILE="${LOG_FILE:-${SCRIPT_DIR}/multi-ingress-sso.log}" +WIRE_API_VERSION="${WIRE_API_VERSION:-v16}" +WIRE_VERIFICATION_CODE="${WIRE_VERIFICATION_CODE:-aGVsbG8}" + +NGINZ_HOST="${NGINZ_HOST:-https://nginz-https.sd.pyd.io}" +SPAR_HOST="${SPAR_HOST:-http://spar:8088}" +GALLEY_HOST="${GALLEY_HOST:-http://galley:8088}" +BRIG_HOST="${BRIG_HOST:-http://brig:8080}" + +API_URL="$NGINZ_HOST/$WIRE_API_VERSION" +TMP_DIR="${TMP_DIR:-/tmp}" + +######## +# check dependencies + +check_dependencies() { + local dependencies=("curl" "jq" "base64") + for dep in "${dependencies[@]}"; do + if ! command -v "$dep" >/dev/null 2>&1; then + echo "Error: $dep is not installed or not in PATH." >&2 + exit $ERR_GENERAL + fi + done +} + +# validate idp metadata xml file +validate_idp_metadata() { + local idp_metadata_file="$1" + if [[ ! -f "$idp_metadata_file" ]]; then + echo "Error: IDP metadata file '$idp_metadata_file' does not exist." + return $ERR_INVALID_IDP_FILE + fi + + # for future +# if ! xmllint --noout "$idp_metadata_file" >/dev/null 2>&1; then +# echo "Error: IDP metadata file '$idp_metadata_file' is not a valid XML file." +# exit $ERR_INVALID_IDP_FILE +# fi + +} + +get_auth_token() { + local team_admin="$1" + local team_password="$2" + local file="$TMP_DIR/auth_response.json" + + if [[ -z "$team_admin" || -z "$team_password" ]]; then + echo "Error: Missing required arguments for authentication." >&2 + exit $ERR_INVALID_ARGS + fi + + local auth_response + http_code=$(curl_to_file "$NGINZ_HOST/login" "$file" \ + -H "Content-Type: application/json" \ + -d "{\"email\":\"$team_admin\",\"password\":\"$team_password\"}") + + #echo "HTTP Code: $http_code" >&2 + + if [[ "$http_code" -ne 200 ]]; then + echo "Error: Authentication request failed. HTTP status code: $http_code" >&2 + exit $ERR_AUTH + fi + + local token + token=$(cat "$file" | jq -r '.access_token') + + if [[ "$token" == "null" || -z "$token" ]]; then + echo "Error: Authentication failed. Please check your credentials." >&2 + exit $ERR_AUTH + fi + + echo "$token" +} + +get_idp() { + local team_admin="$1" + local team_password="$2" + + + token=$(get_auth_token "$team_admin" "$team_password") + + #echo "Retrieved token: $token" >&2 + + curl_idp "$token" +} + +curl_idp() { + local token="$1" + local tmp_file="$TMP_DIR/idp.json" + + http_code=$(curl_to_file "$API_URL/identity-providers" "$tmp_file" \ + -H "Authorization: Bearer $token" \ + -H "Accept: application/json" \ + -X GET + ) + + if [[ "$http_code" -ne 200 ]]; then + echo "Error: Failed to retrieve IDP information. HTTP status code: $http_code" >&2 + return $ERR_API_ERROR + fi + + # Check if the file is empty or contains an error message + if [[ ! -s "$tmp_file" ]]; then + echo "Error: IDP information is empty or not found." >&2 + return $ERR_IDP_NOT_FOUND + fi + + # read the JSON content from the file + local idp_json + idp_json=$(cat "$tmp_file") + + print_providers "$tmp_file" +} + +set_idp() { + + # check if the required number of arguments is provided + if (("$# < 1")); then + usage + fi + + local team_admin="$1" + local team_password="$2" + local domain="$3" + local idp_metadata_file="$4" + local idp_json="$TMP_DIR/set_idp_response.json" + + # validate the IDP metadata file + validate_idp_metadata "$idp_metadata_file" + result=$? + if [[ $result -ne 0 ]]; then + echo "Error: Invalid IDP metadata file." >&2 + exit $result + fi + + # get auth token + token=$(get_auth_token "$team_admin" "$team_password") + + # user domain to construct the API URL for setting the IDP + domain="https://nginz-https.$domain/$WIRE_API_VERSION" + http_code=$(curl_to_file "$domain/identity-providers?api_version=v2" "$idp_json" \ + -H "Authorization: Bearer $token" \ + -H "Content-Type: application/xml" \ + -H "Accept: */*" \ + -X POST \ + --data-binary "@$idp_metadata_file") + + if [[ "$http_code" -eq 409 ]]; then + echo "Error: An identity provider for the domain '$domain' already exists." >&2 + return $ERR_IDP_NOT_FOUND + fi + if [[ "$http_code" -ne 200 && "$http_code" -ne 201 ]]; then + echo "Error: Failed to set IDP information. HTTP status code: $http_code" >&2 + return $ERR_API_ERROR + fi + + if [[ ! -s "$idp_json" ]]; then + echo "Error: IDP set response is empty or not found." >&2 + return $ERR_API_ERROR + fi + + # + #if [[ "$http_code" -eq 200 ]]; then + # echo "Successfully updated the identity provider for domain '$domain'." + #fi + + # + if [[ "$http_code" -eq 201 ]]; then + echo "Successfully created a new identity provider for domain '$domain'." + fi + + print_providers "$idp_json" + return $SUCCESS +} + +curl_to_file() { + # echo "Executing curl command: $@" >&2 + + local url="$1" + local file="$2" + shift 2 + + http_code=$(curl \ + --silent \ + --show-error \ + --write-out "%{http_code}" \ + --output "$file" \ + "$url" \ + "$@" + ) + + curl_rc=$? + + if [[ $curl_rc -ne 0 ]]; then + echo "CURL_ERROR:$curl_rc" >&2 + return "$curl_rc" + fi + + echo "$http_code" +} + +print_providers(){ + local providers="${1:-}" + + if [[ -z "$providers" ]]; then + echo "Team has no configured identity providers." + fi + + jq -r ' + ["handle", "domain name", "issuer"], + ( + if has("providers") then .providers[] + else . + end + | [ + .extraInfo.handle, + .extraInfo.domain, + .metadata.issuer + ] + ) + | @tsv +' $providers | column -t -s $'\t' +} + +usage() { + echo "Usage: + $0 get + $0 set + + is the domain name for which the IDP is being set. does not content service name such as 'nginz-https' or 'spar'. It is the domain name that will be used for SSO login. + For example, if your SSO login URL is 'https://nginz-https.example.com/sso', then the domain is 'example.com'. + Examples: + $0 get admin@example.com secret + $0 set admin@example.com secret nginz-https.example.com ./idp.xml + + " >&2 + exit $ERR_INVALID_ARGS +} + +main() { + # check dependencies + + if (("$# < 1")); then + usage + fi + + local command="$1" + shift + + case "$command" in + get) + get_idp "$@" + ;; + set) + set_idp "$@" + ;; + *) + echo "Error: Unknown command '$command'. Use 'get' or 'set'." + exit $ERR_INVALID_ARGS + ;; + esac +} + +main "$@" \ No newline at end of file From 3585994b5c6db760576d5607a5ac75f4d88589eb Mon Sep 17 00:00:00 2001 From: Cuong Tran Date: Wed, 1 Jul 2026 10:26:27 +0200 Subject: [PATCH 2/2] add: WPB-20153 add multi-ingress provisioning --- multi-ingress-idp-provisioning/README.md | 90 +++++ .../domain-wrapper.sh | 80 +++++ .../idp-provisioner.sh | 336 ++++++++++++++++++ .../idps/blueberry.domain.com.xml | 40 +++ .../idps/lemon.domain.com.xml | 37 ++ .../team-wrapper.sh | 89 +++++ .../teams/teams.json | 10 + 7 files changed, 682 insertions(+) create mode 100644 multi-ingress-idp-provisioning/README.md create mode 100755 multi-ingress-idp-provisioning/domain-wrapper.sh create mode 100755 multi-ingress-idp-provisioning/idp-provisioner.sh create mode 100644 multi-ingress-idp-provisioning/idps/blueberry.domain.com.xml create mode 100644 multi-ingress-idp-provisioning/idps/lemon.domain.com.xml create mode 100755 multi-ingress-idp-provisioning/team-wrapper.sh create mode 100644 multi-ingress-idp-provisioning/teams/teams.json diff --git a/multi-ingress-idp-provisioning/README.md b/multi-ingress-idp-provisioning/README.md new file mode 100644 index 0000000..3b246ce --- /dev/null +++ b/multi-ingress-idp-provisioning/README.md @@ -0,0 +1,90 @@ +Provisioning IdP for teams in Mobtown deployment + +# Prerequisites + +## Wire server + +- Multi-ingress SSO configuration +- Teams credentials + +## Dependencies + +- jq +- curl +- domains +- idp metadata files + +# How to run the scripts + +## idp-provisioner.sh + +This script provides two subcommands: + +- get: List the IdPs configured for a team +- set: Add an IdP to a team. + +This script require NGINZ_HOST env. In a multi-ingress deployment, you can use any available domain as the value of NGINZ_HOST to get or configure IdP for a team + +### get + +The subcommand accepts team credentials as parameters and send api request to NGINZ_HOST. + +### set + +The set subcommand accepts the team credentials and two additional parameters: + +- domain: the domain associated with the IdP +- The IdP metadata file + +Example: + +``` +./idp-provisioner get team-a team-a-password +./idp-provisioner set team-a team-a-password blueberry.domain.com blueberry.domain.com.xml + +``` + +## Authentication + +Before making any api call, this script authenticates using provided team credentials. The successful authentication response is sotored in temporary file. The script reuses the access_token for subsequent requests or and automatically obtains a new one when the existing token expires. + +## Tmp folder + +By default, this script stores temporairy files in the system's `/tmp`. You can override this location by setting the TMP_DIR env. + +## domain-wrapper.sh + +This script wraps idp-provisioner.sh and allows you to config multiple IdPs for a single team. + +It requires the team credentials and the directory containing the IdP metadata files. + +The script assumes that each metadata file is named after its corresponding domain. For instance: + +- Domain: blueberry.domain.com +- Idp metadata file: blueberry.domain.com.xml + + +Example: + +``` +./domain-wrapper.sh set team-a team-a-password ./idps + +``` + +## team-wrapper.sh + +This script wraps domain-wrapper.sh and iterates over a list of team to provision all available IdPs for each team. + +This script requires: + +- A teams.json file containing the team credentials +- The directory containing the IdP metadata files. + +Team credentials are stored in a JSON file to avoid issues with special characters in team passwords. + +Example: + +``` +./team-wrapper.sh set ./teams/teams.json ./idps + +``` \ No newline at end of file diff --git a/multi-ingress-idp-provisioning/domain-wrapper.sh b/multi-ingress-idp-provisioning/domain-wrapper.sh new file mode 100755 index 0000000..88a4b38 --- /dev/null +++ b/multi-ingress-idp-provisioning/domain-wrapper.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash + +set -o pipefail + +PROVISIONER_SCRIPT="idp-provisioner.sh" + +check_dependencies() { + if [[ ! -f "$PROVISIONER_SCRIPT" ]]; then + echo "Error: Provisioner script '$PROVISIONER_SCRIPT' not found." + exit 1 + fi + + # check if file is readable + if [[ ! -r "$PROVISIONER_SCRIPT" ]]; then + echo "Error: Provisioner script '$PROVISIONER_SCRIPT' is not readable." + exit 1 + fi +} + +set_idps() { + local team_admin="$1" + local team_password="$2" + local idp_domains_location=$3 + + if [[ -z "$team_admin" || -z "$team_password" || -z "$idp_domains_location" ]]; then + echo "Error: Missing required arguments for 'set' command." + usage + exit $ERR_INVALID_ARGS + fi + + # iterate over all files in the directory and call the provisioner script for each file + for idp_domain_file in "$idp_domains_location"/*.xml; do + if [[ -f "$idp_domain_file" ]]; then + local domain=$(basename "$idp_domain_file" .xml) + echo "Info: Setting IDP file '$idp_domain_file' for domain '$domain'" >&2 + bash "$PROVISIONER_SCRIPT" set "$team_admin" "$team_password" "$domain" "$idp_domain_file" + + echo "" >&2 + # slow down the requests to avoid rate-limiting + sleep 0.5 + else + echo "Warning: 'idp_domain_file' is not a file. Skipping." >&2 + fi + done +} + +usage() { + echo "Usage: + $0 set + + is the directory containing the IDP metadata files for each domain. Each file should be named as '.xml'. + For example, if your SSO login URL is 'https://nginz-https.example.com/sso', then the file should be named 'example.com.xml'. + Examples: + $0 set" +} + +main() { + # check dependencies + check_dependencies + + if (("$# < 1")); then + usage + exit $ERR_INVALID_ARGS + fi + + local command="$1" + shift + + case "$command" in + set) + set_idps "$@" + ;; + *) + echo "Error: Unknown command '$command'. Use 'set'." + exit $ERR_INVALID_ARGS + ;; + esac +} + +main "$@" \ No newline at end of file diff --git a/multi-ingress-idp-provisioning/idp-provisioner.sh b/multi-ingress-idp-provisioning/idp-provisioner.sh new file mode 100755 index 0000000..9dc4bff --- /dev/null +++ b/multi-ingress-idp-provisioning/idp-provisioner.sh @@ -0,0 +1,336 @@ +#!/usr/bin/env bash + +set -o pipefail + +# constants +SUCCESS=0 +ERR_GENERAL=1 +ERR_INVALID_ARGS=2 +ERR_AUTH=3 +ERR_DOMAIN_NOT_FOUND=4 +ERR_IDP_NOT_FOUND=5 +ERR_INVALID_IDP_FILE=6 +ERR_API_ERROR=7 + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +LOG_FILE="${LOG_FILE:-${SCRIPT_DIR}/multi-ingress-sso.log}" +WIRE_API_VERSION="${WIRE_API_VERSION:-v16}" +WIRE_VERIFICATION_CODE="${WIRE_VERIFICATION_CODE:-aGVsbG8}" + +# NGINZ_HOST for the NGINZ service. This can be overridden by setting the NGINZ_HOST environment variable. +# This URL is used to do authentication and to retrieve the list of identity providers (IDPs) for a given team. +# In multi-ingress setups, A team's administrator can use any domain for authentication or listing Idp. +# But for adding an Idp for a team, we need to use specific domain url. +# For example, if the team is using 'https://nginz-https.example.com/sso' for SSO login, +# then the domain is 'example.com' +# and the URL for adding Idp is 'https://nginz-https.example.com/v16/identity-providers?api_version=v2'. + +NGINZ_HOST="${NGINZ_HOST:-https://nginz-https.example.com}" + +# SPAR_HOST="${SPAR_HOST:-http://spar:8088}" +# GALLEY_HOST="${GALLEY_HOST:-http://galley:8088}" +# BRIG_HOST="${BRIG_HOST:-http://brig:8080}" + +API_URL="$NGINZ_HOST/$WIRE_API_VERSION" +TMP_DIR="${TMP_DIR:-/tmp}" + +######## +# check dependencies + +check_dependencies() { + local dependencies=("curl" "jq" "base64") + for dep in "${dependencies[@]}"; do + if ! command -v "$dep" >/dev/null 2>&1; then + echo "Error: $dep is not installed or not in PATH." >&2 + exit $ERR_GENERAL + fi + done +} + +# validate idp metadata xml file +validate_idp_metadata() { + local idp_metadata_file="$1" + if [[ ! -f "$idp_metadata_file" ]]; then + echo "Error: IDP metadata file '$idp_metadata_file' does not exist." + return $ERR_INVALID_IDP_FILE + fi + + # for future +# if ! xmllint --noout "$idp_metadata_file" >/dev/null 2>&1; then +# echo "Error: IDP metadata file '$idp_metadata_file' is not a valid XML file." +# exit $ERR_INVALID_IDP_FILE +# fi + +} + +get_auth_token_from_last_request() { + local file="$1" + + if [[ -f "$file" && -s "$file" ]]; then + local token + token=$(cat "$file" | jq -r '.access_token') + local expires_in + expires_in=$(cat "$file" | jq -r '.expires_in') + + # check if the token is expired + # get mtime of the file in seconds since last auth_request + local mtime + mtime=$(stat -c %Y "$file") + + local expires_at_ts=$((mtime + expires_in - 10)) + + local now + now=$(date +%s) + + #echo "mtime: $mtime, expires_in: $expires_at_ts, now: $now, " >&2 + + if (( now < expires_at_ts )); then + echo "Info: Auth token is still valid. Using cached token." >&2 + echo "$token" + return $SUCCESS + # else + # echo "Info: Auth token has expired." >&2 + fi + fi + + return $ERR_AUTH +} + +get_auth_token() { + local team_admin="$1" + local team_password="$2" + + # response file must be associated to the team_admin + local file="$TMP_DIR/$team_admin-auth_response.json" + + local token + token=$(get_auth_token_from_last_request "$file") + + if [[ $? -eq $SUCCESS ]]; then + echo "$token" + return $SUCCESS + else + echo "Info: Auth token not found or expired. Requesting new token..." >&2 + echo "Info: Using team_admin: $team_admin" >&2 + fi + + if [[ -z "$team_admin" || -z "$team_password" ]]; then + echo "Error: Missing required arguments for authentication." >&2 + exit $ERR_INVALID_ARGS + fi + + local auth_response + http_code=$(curl_to_file "$NGINZ_HOST/login" "$file" \ + -H "Content-Type: application/json" \ + -d "{\"email\":\"$team_admin\",\"password\":\"$team_password\"}") + + #echo "HTTP Code: $http_code" >&2 + + if [[ "$http_code" -ne 200 ]]; then + echo "Error: Authentication request failed. HTTP status code: $http_code" >&2 + exit $ERR_AUTH + fi + + token=$(cat "$file" | jq -r '.access_token') + + if [[ "$token" == "null" || -z "$token" ]]; then + echo "Error: Authentication failed. Please check your credentials." >&2 + exit $ERR_AUTH + fi + + echo "$token" +} + +get_idp() { + local team_admin="$1" + local team_password="$2" + + + token=$(get_auth_token "$team_admin" "$team_password") + + #echo "Retrieved token: $token" >&2 + + curl_idp "$token" +} + +curl_idp() { + local token="$1" + local tmp_file="$TMP_DIR/idp.json" + + http_code=$(curl_to_file "$API_URL/identity-providers" "$tmp_file" \ + -H "Authorization: Bearer $token" \ + -H "Accept: application/json" \ + -X GET + ) + + if [[ "$http_code" -ne 200 ]]; then + echo "Error: Failed to retrieve IDP information. HTTP status code: $http_code" >&2 + return $ERR_API_ERROR + fi + + # Check if the file is empty or contains an error message + if [[ ! -s "$tmp_file" ]]; then + echo "Error: IDP information is empty or not found." >&2 + return $ERR_IDP_NOT_FOUND + fi + + # read the JSON content from the file + local idp_json + idp_json=$(cat "$tmp_file") + + print_providers "$tmp_file" +} + +set_idp() { + + # check if the required number of arguments is provided + if (("$# < 1")); then + usage + fi + + local team_admin="$1" + local team_password="$2" + local domain="$3" + local idp_metadata_file="$4" + local idp_json="$TMP_DIR/set_idp_response.json" + + # validate the IDP metadata file + validate_idp_metadata "$idp_metadata_file" + result=$? + if [[ $result -ne 0 ]]; then + echo "Error: Invalid IDP metadata file." >&2 + exit $result + fi + + # get auth token + token=$(get_auth_token "$team_admin" "$team_password") + + # user domain to construct the API URL for setting the IDP + domain="https://nginz-https.$domain/$WIRE_API_VERSION" + + http_code=$(curl_to_file "$domain/identity-providers?api_version=v2" "$idp_json" \ + -H "Authorization: Bearer $token" \ + -H "Content-Type: application/xml" \ + -H "Accept: */*" \ + -X POST \ + --data-binary "@$idp_metadata_file") + + if [[ "$http_code" -eq 409 ]]; then + echo "Error: An identity provider for the domain '$domain' already exists." >&2 + return $ERR_IDP_NOT_FOUND + fi + # if [[ "$http_code" -ne 200 && "$http_code" -ne 201 ]]; then + if [[ "$http_code" -ne 201 ]]; then + echo "Error: Failed to set IDP information. HTTP status code: $http_code" >&2 + return $ERR_API_ERROR + fi + + if [[ ! -s "$idp_json" ]]; then + echo "Error: IDP set response is empty or not found." >&2 + return $ERR_API_ERROR + fi + + # + #if [[ "$http_code" -eq 200 ]]; then + # echo "Successfully updated the identity provider for domain '$domain'." + #fi + + # + if [[ "$http_code" -eq 201 ]]; then + echo "Successfully created a new identity provider for domain '$domain'." + fi + + print_providers "$idp_json" + return $SUCCESS +} + +curl_to_file() { + # echo "Executing curl command: $@" >&2 + + local url="$1" + local file="$2" + shift 2 + + http_code=$(curl \ + --silent \ + --show-error \ + --write-out "%{http_code}" \ + --output "$file" \ + "$url" \ + "$@" + ) + + curl_rc=$? + + if [[ $curl_rc -ne 0 ]]; then + echo "CURL_ERROR:$curl_rc" >&2 + return "$curl_rc" + fi + + echo "$http_code" +} + +print_providers(){ + local providers="${1:-}" + + if [[ -z "$providers" ]]; then + echo "Team has no configured identity providers." + fi + + jq -r ' + ["handle", "domain name", "issuer"], + ( + if has("providers") then .providers[] + else . + end + | [ + .extraInfo.handle, + .extraInfo.domain, + .metadata.issuer + ] + ) + | @tsv +' $providers | column -t -s $'\t' +} + +usage() { + echo "Usage: + $0 get + $0 set + + is the domain name for which the IDP is being set. does not content service name such as 'nginz-https' or 'spar'. It is the domain name that will be used for SSO login. + For example, if your SSO login URL is 'https://nginz-https.example.com/sso', then the domain is 'example.com'. + Examples: + $0 get admin@example.com secret + $0 set admin@example.com secret nginz-https.example.com ./idp.xml + + " >&2 + exit $ERR_INVALID_ARGS +} + +main() { + # check dependencies + check_dependencies + + if (("$# < 1")); then + usage + fi + + local command="$1" + shift + + case "$command" in + get) + get_idp "$@" + ;; + set) + set_idp "$@" + ;; + *) + echo "Error: Unknown command '$command'. Use 'get' or 'set'." + exit $ERR_INVALID_ARGS + ;; + esac +} + +main "$@" \ No newline at end of file diff --git a/multi-ingress-idp-provisioning/idps/blueberry.domain.com.xml b/multi-ingress-idp-provisioning/idps/blueberry.domain.com.xml new file mode 100644 index 0000000..e68468f --- /dev/null +++ b/multi-ingress-idp-provisioning/idps/blueberry.domain.com.xml @@ -0,0 +1,40 @@ + + + + + xxxxxxxxxx + + + MIICmzCCAYMCBgGKl5/t3DANBgkqhkiG9wxxxxxxxYDVQQDDAZtYXN0ZXIwHhcNMjMwOTE1MDY1NDE1WhcNMzMwOTE1MDY1NTU1WjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDnmO0E0B5A+O0rtcj9CjxtKEIwRPgSoWNqSHpYgFK1uzAmaVcbTX1kKPqlv54+g0JPofQc1sNZM5lKspzcGFtIYG+coFMvBPWXODOsXrvUCLKSK/AirNXvHUnC9Hz63ThO3hPKP/soTySaM1CQrrwdCWo6g8LL8D3rKD6lHU8AkdSpqhIlyjY1p8I75H4efvyQkTym3v2baqxAi6MWPqdw9p270qsglM2BuQUd9eaEqM+i/brdKAJIx5dgsD2CVr9UYpOjtOflhK9xee4JWHti8gc5BQOQblueS4dLk8NpdSAEsoNSNPGGVgXvpmg0uGIcJXYfV0ozpgBQ+8NZKg9pAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAEmbqco/SkMN+wsuYYeh+nRLpzEECOQIe0iGw+4hVmGcBevVKcKtaiR4xy4b/S+qrmY9+xTp7TGTX3QYc+u5hiTV8YaZm5lJDEA9vwA/K+57qAAIEtygzNAenps9cqUY6fUgtgA6YeXrUz8UAofiWJHeggRo3aMuwpwfwgwO3JkMjHXEeyPTPgvi2huvUZhdE42fSEJA6wwAb6yMftMncuQF1iKdl6G6D1e1LWkgsWhFMjiZi7b7CE5kdW5Ng0ga7lAV6FPivf38A3jPwiUyRKEUnSk8uwrpKpKAqaLWb0cHeZfVn5h3IrxFWHN3CZ/zgmu+ofqL7DSgQo/OBm3tkM0= + + + + + + + + + urn:oasis:names:tc:SAML:2.0:nameid-format:persistent + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified + urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress + + + + + + \ No newline at end of file diff --git a/multi-ingress-idp-provisioning/idps/lemon.domain.com.xml b/multi-ingress-idp-provisioning/idps/lemon.domain.com.xml new file mode 100644 index 0000000..fdb3ec7 --- /dev/null +++ b/multi-ingress-idp-provisioning/idps/lemon.domain.com.xml @@ -0,0 +1,37 @@ + + + + + + + + MIIFajCCBFKgAwIBAgISA3kddddddd6ldMA0GCSqGSIb3DQEBCwUAMEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0yMDA3MTAxMjM2MjhaFw0yMDEwMDgxMjM2MjhaMCMxITAfBgNVBAMTGHBocHNhbWwudHJhbnRlc3RzLnB5ZC5pbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMrivtd/gsEhIdDU0yN9MzCscy6SkOsJKaW+YbzlLLqoztVzUEgjMmWtuAxaLSMttR1fSMka2KfLw72NbP1GywrgQJrHH7Hni/Db7u1TMJ97SGITVZhf4i9XxOHljm6Zj+kInbYvaJEQ+fOhti6jBNQlH3V5oNQ2AWS/4oIJubir14nQHmqaxuGGM/XCZfQOZPM8gGr3UAp9xtT/jScU4MAgi+0OSnOq2gjBWwkDtLErNyElhbt+rCz/GVoTmfulAYSjfgnJX29yWVI1uxho02LXKPTgyYPztBY22meLGEcvBDaP38Iv9YHWVdIG8xgGKu6xUWH4ZLUWVcZQvoeQqOkCAwEAAaOCAm8wggJrMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUm46yY/ZCJwE4kY3sSt0saS7/urQwHwYDVR0jBBgwFoAUqEpqYwR93brm0Tm3pkVl7/Oo7KEwbwYIKwYBBQUHAQEEYzBhMC4GCCsGAQUFBzABhiJodHRwOi8vb2NzcC5pbnQteDMubGV0c2VuY3J5cHQub3JnMC8GCCsGAQUFBzAChiNodHRwOi8vY2VydC5pbnQteDMubGV0c2VuY3J5cHQub3JnLzAjBgNVHREEHDAaghhwaHBzYW1sLnRyYW50ZXN0cy5weWQuaW8wTAYDVR0gBEUwQzAIBgZngQwBAgEwNwYLKwYBBAGC3xMBAQEwKDAmBggrBgEFBQcCARYaaHR0cDovL2Nwcy5sZXRzZW5jcnlwdC5vcmcwggEGBgorBgEEAdZ5AgQCBIH3BIH0APIAdwDwlaRZ8gDRgkAQLS+TiI6tS/4dR+OZ4dA0prCoqo6ycwAAAXM48Q47AAAEAwBIMEYCIQDXL7MHD5wBitK2uLX6626eTOuMkV9loS7H6a5Jah7O1wIhAIl+eRWcbOc/et7Cze9VBZD2E1bu42IU4j1DhpvKreD3AHcAB7dcG+V9aP/xsMYdIxXHuuZXfFeUt2ruvGE6GmnTohwAAAFzOPEOcAAABAMASDBGAiEAmuhPt9o+jo3p1asJWoeV4VvKvZmr3n8gsL2HYbw6kyMCIQDeldDQhgHfDkfu8l7Uaf0DG/49kkRE2aYYR7WJWq2fnjANBgkqhkiG9w0BAQsFAAOCAQEAlpAB915o7mJxH5yV/ZWVToZBBGCqbtAT4vDuCXgj9FtVwLZtPJ4gZnVzXB9PzRHhypye2G0pRHSAe8kt5vhiAt1iIyKBXsfWQFWtFUTs5VqTnZB3NMS5FLBEcnYEtZZXjgzcrv8GGMcnmCCoJKGa8/SDKQ4LPhn2I8nc6faTgBL7t+7wy9DAXWQBh2lnFKpKLiZECqE1qSmrolZV1awC83oGsMsKsfH4lQ00mHqaLuSgcHiDZwH284LlVdUUPA84IJVlT7QBtbN2IuFMncl+Cps5J/2R8WJdD9SdXZIq0cu66wZHbPcuTH2o95c+2uwq+5hu5vl+JQyG+/qGvKae0g== + + + + + + + + MIIFajCCBFKgAwIBAgISA3k2guVbdddddddIb3DQEBCwUAMEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0yMDA3MTAxMjM2MjhaFw0yMDEwMDgxMjM2MjhaMCMxITAfBgNVBAMTGHBocHNhbWwudHJhbnRlc3RzLnB5ZC5pbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMrivtd/gsEhIdDU0yN9MzCscy6SkOsJKaW+YbzlLLqoztVzUEgjMmWtuAxaLSMttR1fSMka2KfLw72NbP1GywrgQJrHH7Hni/Db7u1TMJ97SGITVZhf4i9XxOHljm6Zj+kInbYvaJEQ+fOhti6jBNQlH3V5oNQ2AWS/4oIJubir14nQHmqaxuGGM/XCZfQOZPM8gGr3UAp9xtT/jScU4MAgi+0OSnOq2gjBWwkDtLErNyElhbt+rCz/GVoTmfulAYSjfgnJX29yWVI1uxho02LXKPTgyYPztBY22meLGEcvBDaP38Iv9YHWVdIG8xgGKu6xUWH4ZLUWVcZQvoeQqOkCAwEAAaOCAm8wggJrMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUm46yY/ZCJwE4kY3sSt0saS7/urQwHwYDVR0jBBgwFoAUqEpqYwR93brm0Tm3pkVl7/Oo7KEwbwYIKwYBBQUHAQEEYzBhMC4GCCsGAQUFBzABhiJodHRwOi8vb2NzcC5pbnQteDMubGV0c2VuY3J5cHQub3JnMC8GCCsGAQUFBzAChiNodHRwOi8vY2VydC5pbnQteDMubGV0c2VuY3J5cHQub3JnLzAjBgNVHREEHDAaghhwaHBzYW1sLnRyYW50ZXN0cy5weWQuaW8wTAYDVR0gBEUwQzAIBgZngQwBAgEwNwYLKwYBBAGC3xMBAQEwKDAmBggrBgEFBQcCARYaaHR0cDovL2Nwcy5sZXRzZW5jcnlwdC5vcmcwggEGBgorBgEEAdZ5AgQCBIH3BIH0APIAdwDwlaRZ8gDRgkAQLS+TiI6tS/4dR+OZ4dA0prCoqo6ycwAAAXM48Q47AAAEAwBIMEYCIQDXL7MHD5wBitK2uLX6626eTOuMkV9loS7H6a5Jah7O1wIhAIl+eRWcbOc/et7Cze9VBZD2E1bu42IU4j1DhpvKreD3AHcAB7dcG+V9aP/xsMYdIxXHuuZXfFeUt2ruvGE6GmnTohwAAAFzOPEOcAAABAMASDBGAiEAmuhPt9o+jo3p1asJWoeV4VvKvZmr3n8gsL2HYbw6kyMCIQDeldDQhgHfDkfu8l7Uaf0DG/49kkRE2aYYR7WJWq2fnjANBgkqhkiG9w0BAQsFAAOCAQEAlpAB915o7mJxH5yV/ZWVToZBBGCqbtAT4vDuCXgj9FtVwLZtPJ4gZnVzXB9PzRHhypye2G0pRHSAe8kt5vhiAt1iIyKBXsfWQFWtFUTs5VqTnZB3NMS5FLBEcnYEtZZXjgzcrv8GGMcnmCCoJKGa8/SDKQ4LPhn2I8nc6faTgBL7t+7wy9DAXWQBh2lnFKpKLiZECqE1qSmrolZV1awC83oGsMsKsfH4lQ00mHqaLuSgcHiDZwH284LlVdUUPA84IJVlT7QBtbN2IuFMncl+Cps5J/2R8WJdD9SdXZIq0cu66wZHbPcuTH2o95c+2uwq+5hu5vl+JQyG+/qGvKae0g== + + + + + + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + + + + + Lemon + mailto:lemon@example.com + + \ No newline at end of file diff --git a/multi-ingress-idp-provisioning/team-wrapper.sh b/multi-ingress-idp-provisioning/team-wrapper.sh new file mode 100755 index 0000000..54e17dc --- /dev/null +++ b/multi-ingress-idp-provisioning/team-wrapper.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash + +set -o pipefail + +get_idps() { + local file="$1" + + if [[ -z "$file" ]]; then + echo "Error: Missing required argument for 'get' command." >&2 + usage + exit $ERR_INVALID_ARGS + fi + + # echo "File: $file" + + teams=() + + while IFS= read -r team; do + teams+=("$team") + done < <(jq -c '.[]' "$file") + + for team in "${teams[@]}"; do + username=$(jq -r '.username' <<< "$team") + password=$(jq -r '.password' <<< "$team") + + echo "Team admin: $username" >&2 + bash idp-provisioner.sh get "$username" "$password" + echo "" >&2 + # slow down the requests to avoid rate-limiting + sleep 0.5 + done +} + +set_idps() { + local file="$1" + local idp_domains_location="$2" + + if [[ -z "$file" || -z "$idp_domains_location" ]]; then + echo "Error: Missing required arguments for 'set' command." >&2 + usage + exit $ERR_INVALID_ARGS + fi + # echo "File: $file" + + teams=() + + while IFS= read -r team; do + teams+=("$team") + done < <(jq -c '.[]' "$file") + + for team in "${teams[@]}"; do + username=$(jq -r '.username' <<< "$team") + password=$(jq -r '.password' <<< "$team") + + bash domain-wrapper.sh set "$username" "$password" "$idp_domains_location" + echo "" >&2 + # slow down the requests to avoid rate-limiting + sleep 0.5 + done +} + +usage(){ + echo "Usage: $0 set " >&2 + echo " is a JSON file containing an array of team objects with 'username' and 'password' fields." >&2 + echo " is the directory containing the IDP metadata files for each domain. Each file should be named as '.xml'." >&2 + echo " Example: $0 set teams.json ./idp_domains" >&2 + exit $ERR_INVALID_ARGS +} + +main() { + local command="$1" + shift + + case "$command" in + get) + get_idps "$@" + ;; + set) + set_idps "$@" + ;; + *) + echo "Error: Unknown command '$command'." >&2 + usage + exit $ERR_INVALID_ARGS + ;; + esac +} + +main "$@" \ No newline at end of file diff --git a/multi-ingress-idp-provisioning/teams/teams.json b/multi-ingress-idp-provisioning/teams/teams.json new file mode 100644 index 0000000..3689fac --- /dev/null +++ b/multi-ingress-idp-provisioning/teams/teams.json @@ -0,0 +1,10 @@ +[ + { + "username": "mi-sso@dev.local", + "password": "******" + }, + { + "username": "multi-ingress@dev.local", + "password": "******" + } +] \ No newline at end of file