forked from baron/baron-sso
368 lines
15 KiB
Bash
368 lines
15 KiB
Bash
#!/usr/bin/env bash
|
|
|
|
personnel_dataset_dir() {
|
|
local backup_dir="$1"
|
|
printf '%s\n' "$backup_dir/datasets/personnel"
|
|
}
|
|
|
|
write_personnel_dataset_manifest() {
|
|
local backup_dir="$1"
|
|
local services="$2"
|
|
local dataset_dir
|
|
local reports_dir
|
|
local include_kratos
|
|
local reset_credentials
|
|
local include_worksmobile_mapping
|
|
local include_outbox
|
|
local tenant_roots
|
|
|
|
dataset_dir="$(personnel_dataset_dir "$backup_dir")"
|
|
reports_dir="$dataset_dir/reports"
|
|
include_kratos="${PERSONNEL_INCLUDE_KRATOS_IDENTITIES:-true}"
|
|
reset_credentials="${PERSONNEL_RESET_CREDENTIALS:-true}"
|
|
include_worksmobile_mapping="${PERSONNEL_INCLUDE_WORKSMOBILE_MAPPING:-true}"
|
|
include_outbox="${PERSONNEL_INCLUDE_OUTBOX:-false}"
|
|
tenant_roots="${PERSONNEL_TENANT_ROOT_SLUGS:-}"
|
|
|
|
mkdir -p "$reports_dir"
|
|
jq -n \
|
|
--arg dataset "personnel" \
|
|
--arg services "$services" \
|
|
--arg tenant_roots "$tenant_roots" \
|
|
--arg include_kratos "$include_kratos" \
|
|
--arg reset_credentials "$reset_credentials" \
|
|
--arg include_worksmobile_mapping "$include_worksmobile_mapping" \
|
|
--arg include_outbox "$include_outbox" \
|
|
'{
|
|
dataset: $dataset,
|
|
format_version: "1",
|
|
services: ($services | split(" ") | map(select(length > 0))),
|
|
scope: {
|
|
tenant_root_slugs: (if $tenant_roots == "" then [] else ($tenant_roots | split(",") | map(gsub("^\\s+|\\s+$"; ""))) end)
|
|
},
|
|
included: {
|
|
baron_postgres_tables: [
|
|
"public.tenants",
|
|
"public.tenant_domains",
|
|
"public.user_groups",
|
|
"public.users",
|
|
"public.user_login_ids",
|
|
"public.worksmobile_resource_mappings"
|
|
],
|
|
ory_kratos_tables: (if $include_kratos == "true" then ["public.identities"] else [] end),
|
|
ory_keto_tables: ["public.keto_relation_tuples"]
|
|
},
|
|
excluded: {
|
|
databases: ["ory_hydra"],
|
|
tables: [
|
|
"public.relying_parties",
|
|
"public.rp_user_metadata",
|
|
"public.client_consents",
|
|
"public.client_secrets",
|
|
"public.api_keys",
|
|
"public.worksmobile_outboxes"
|
|
],
|
|
volatile: ["redis", "sessions", "oauth2_tokens", "audit_logs"]
|
|
},
|
|
restore_policy: {
|
|
reset_credentials: ($reset_credentials == "true"),
|
|
include_worksmobile_mapping: ($include_worksmobile_mapping == "true"),
|
|
include_outbox: ($include_outbox == "true"),
|
|
hydra_restore: false,
|
|
rp_metadata_restore: false,
|
|
default_mode: "replace-dataset-rows"
|
|
}
|
|
}' >"$dataset_dir/dataset-manifest.json"
|
|
}
|
|
|
|
personnel_psql_jsonl() {
|
|
local container="$1"
|
|
local user="$2"
|
|
local password="$3"
|
|
local database="$4"
|
|
local sql="$5"
|
|
local output_file="$6"
|
|
|
|
docker exec -e "PGPASSWORD=$password" "$container" \
|
|
psql -U "$user" -d "$database" -At -v ON_ERROR_STOP=1 -c "$sql" \
|
|
>"$output_file"
|
|
}
|
|
|
|
personnel_write_count() {
|
|
local report_file="$1"
|
|
local key="$2"
|
|
local file_path="$3"
|
|
local count
|
|
|
|
if [[ -f "$file_path" ]]; then
|
|
count="$(wc -l <"$file_path" | tr -d '[:space:]')"
|
|
else
|
|
count="0"
|
|
fi
|
|
printf '%s:%s\n' "$key" "$count" >>"$report_file"
|
|
}
|
|
|
|
dump_personnel_baron_postgres() {
|
|
local backup_dir="$1"
|
|
local db_user="${DB_USER:-baron}"
|
|
local db_password="${DB_PASSWORD:-password}"
|
|
local db_name="${DB_NAME:-baron_sso}"
|
|
local dataset_dir
|
|
local output_dir
|
|
local report_file
|
|
|
|
backup_require_command docker
|
|
backup_require_container baron_postgres
|
|
|
|
dataset_dir="$(personnel_dataset_dir "$backup_dir")"
|
|
output_dir="$dataset_dir/postgres"
|
|
report_file="$dataset_dir/reports/row-counts.txt"
|
|
mkdir -p "$output_dir" "$dataset_dir/reports"
|
|
: >"$report_file"
|
|
|
|
backup_log "Dumping personnel Baron Postgres dataset: $db_name"
|
|
personnel_psql_jsonl baron_postgres "$db_user" "$db_password" "$db_name" \
|
|
"select to_jsonb(t)::text from public.tenants t order by t.id" \
|
|
"$output_dir/tenants.jsonl"
|
|
personnel_psql_jsonl baron_postgres "$db_user" "$db_password" "$db_name" \
|
|
"select to_jsonb(t)::text from public.tenant_domains t order by t.id" \
|
|
"$output_dir/tenant_domains.jsonl"
|
|
personnel_psql_jsonl baron_postgres "$db_user" "$db_password" "$db_name" \
|
|
"select to_jsonb(t)::text from public.user_groups t order by t.id" \
|
|
"$output_dir/user_groups.jsonl"
|
|
personnel_psql_jsonl baron_postgres "$db_user" "$db_password" "$db_name" \
|
|
"select (to_jsonb(t) - 'password_hash' - 'relying_party_id' || jsonb_build_object('relying_party_id', null))::text from public.users t order by t.id" \
|
|
"$output_dir/users.jsonl"
|
|
personnel_psql_jsonl baron_postgres "$db_user" "$db_password" "$db_name" \
|
|
"select to_jsonb(t)::text from public.user_login_ids t order by t.id" \
|
|
"$output_dir/user_login_ids.jsonl"
|
|
|
|
if [[ "${PERSONNEL_INCLUDE_WORKSMOBILE_MAPPING:-true}" == "true" ]]; then
|
|
personnel_psql_jsonl baron_postgres "$db_user" "$db_password" "$db_name" \
|
|
"select to_jsonb(t)::text from public.worksmobile_resource_mappings t where t.baron_resource_type in ('USER', 'ORGUNIT') order by t.id" \
|
|
"$output_dir/worksmobile_resource_mappings.jsonl"
|
|
else
|
|
: >"$output_dir/worksmobile_resource_mappings.jsonl"
|
|
fi
|
|
|
|
personnel_write_count "$report_file" "public.tenants" "$output_dir/tenants.jsonl"
|
|
personnel_write_count "$report_file" "public.tenant_domains" "$output_dir/tenant_domains.jsonl"
|
|
personnel_write_count "$report_file" "public.user_groups" "$output_dir/user_groups.jsonl"
|
|
personnel_write_count "$report_file" "public.users" "$output_dir/users.jsonl"
|
|
personnel_write_count "$report_file" "public.user_login_ids" "$output_dir/user_login_ids.jsonl"
|
|
personnel_write_count "$report_file" "public.worksmobile_resource_mappings" "$output_dir/worksmobile_resource_mappings.jsonl"
|
|
}
|
|
|
|
dump_personnel_ory_postgres() {
|
|
local backup_dir="$1"
|
|
local db_user="${ORY_POSTGRES_USER:-ory}"
|
|
local db_password="${ORY_POSTGRES_PASSWORD:-secret}"
|
|
local kratos_db="${KRATOS_DB:-ory_kratos}"
|
|
local keto_db="${KETO_DB:-ory_keto}"
|
|
local dataset_dir
|
|
local report_file
|
|
|
|
backup_require_command docker
|
|
backup_require_container ory_postgres
|
|
|
|
dataset_dir="$(personnel_dataset_dir "$backup_dir")"
|
|
report_file="$dataset_dir/reports/row-counts.txt"
|
|
mkdir -p "$dataset_dir/ory_kratos" "$dataset_dir/ory_keto" "$dataset_dir/reports"
|
|
|
|
if [[ "${PERSONNEL_INCLUDE_KRATOS_IDENTITIES:-true}" == "true" ]]; then
|
|
backup_log "Dumping personnel Kratos identity subset: $kratos_db"
|
|
personnel_psql_jsonl ory_postgres "$db_user" "$db_password" "$kratos_db" \
|
|
"select (to_jsonb(i) - 'metadata_admin')::text from public.identities i order by i.id" \
|
|
"$dataset_dir/ory_kratos/identities.jsonl"
|
|
else
|
|
: >"$dataset_dir/ory_kratos/identities.jsonl"
|
|
fi
|
|
jq -n '{policy:"reset_credentials", credentials:[]}' >"$dataset_dir/ory_kratos/identity_credentials.reset-plan.jsonl"
|
|
|
|
backup_log "Dumping personnel Keto relation tuple subset: $keto_db"
|
|
personnel_psql_jsonl ory_postgres "$db_user" "$db_password" "$keto_db" \
|
|
"select to_jsonb(t)::text from public.keto_relation_tuples t where t.namespace <> 'RelyingParty' and coalesce(t.subject_set_namespace, '') <> 'RelyingParty' order by t.namespace, t.object, t.relation, t.subject_id" \
|
|
"$dataset_dir/ory_keto/relation_tuples.jsonl"
|
|
|
|
personnel_write_count "$report_file" "public.identities" "$dataset_dir/ory_kratos/identities.jsonl"
|
|
personnel_write_count "$report_file" "public.keto_relation_tuples" "$dataset_dir/ory_keto/relation_tuples.jsonl"
|
|
}
|
|
|
|
dump_personnel_dataset() {
|
|
local backup_dir="$1"
|
|
local services="$2"
|
|
|
|
write_personnel_dataset_manifest "$backup_dir" "$services"
|
|
if service_enabled postgres "$services"; then
|
|
dump_personnel_baron_postgres "$backup_dir"
|
|
fi
|
|
if service_enabled ory-postgres "$services"; then
|
|
dump_personnel_ory_postgres "$backup_dir"
|
|
fi
|
|
if service_enabled config "$services"; then
|
|
dump_config_snapshot "$backup_dir"
|
|
fi
|
|
}
|
|
|
|
personnel_dataset_manifest_path() {
|
|
local backup_dir="$1"
|
|
printf '%s\n' "$backup_dir/datasets/personnel/dataset-manifest.json"
|
|
}
|
|
|
|
restore_personnel_plan_policy_json() {
|
|
local backup_dir="$1"
|
|
local manifest_path
|
|
|
|
manifest_path="$(personnel_dataset_manifest_path "$backup_dir")"
|
|
if [[ -f "$manifest_path" ]]; then
|
|
jq -c '{dataset, included, excluded, restore_policy, scope}' "$manifest_path"
|
|
else
|
|
printf '{}\n'
|
|
fi
|
|
}
|
|
|
|
personnel_restore_jsonl_id_table() {
|
|
local container="$1"
|
|
local user="$2"
|
|
local password="$3"
|
|
local database="$4"
|
|
local table="$5"
|
|
local input_file="$6"
|
|
local scratch
|
|
local columns
|
|
local updates
|
|
|
|
backup_require_path "$input_file"
|
|
if [[ ! -s "$input_file" ]]; then
|
|
backup_log "Skipping empty personnel dataset table: public.$table"
|
|
return 0
|
|
fi
|
|
|
|
scratch="_personnel_restore_${table}_json"
|
|
columns="$(docker exec -e "PGPASSWORD=$password" "$container" \
|
|
psql -U "$user" -d "$database" -At -v ON_ERROR_STOP=1 \
|
|
-c "select string_agg(quote_ident(column_name), ', ' order by ordinal_position) from information_schema.columns where table_schema = 'public' and table_name = '$table'")"
|
|
updates="$(docker exec -e "PGPASSWORD=$password" "$container" \
|
|
psql -U "$user" -d "$database" -At -v ON_ERROR_STOP=1 \
|
|
-c "select string_agg(format('%I = excluded.%I', column_name, column_name), ', ' order by ordinal_position) from information_schema.columns where table_schema = 'public' and table_name = '$table' and column_name <> 'id'")"
|
|
|
|
[[ -n "$columns" ]] || backup_die "cannot resolve columns for personnel restore table: public.$table"
|
|
[[ -n "$updates" ]] || backup_die "cannot resolve update columns for personnel restore table: public.$table"
|
|
|
|
backup_log "Restoring personnel dataset table: public.$table"
|
|
docker exec -e "PGPASSWORD=$password" "$container" \
|
|
psql -U "$user" -d "$database" -v ON_ERROR_STOP=1 \
|
|
-c "drop table if exists public.${scratch}" \
|
|
-c "create table public.${scratch} (line jsonb not null)"
|
|
docker exec -i -e "PGPASSWORD=$password" "$container" \
|
|
psql -U "$user" -d "$database" -v ON_ERROR_STOP=1 \
|
|
-c "\\copy public.${scratch}(line) from stdin" <"$input_file"
|
|
docker exec -e "PGPASSWORD=$password" "$container" \
|
|
psql -U "$user" -d "$database" -v ON_ERROR_STOP=1 \
|
|
-c "insert into public.${table} (${columns}) select ${columns} from (select (jsonb_populate_record(null::public.${table}, line)).* from public.${scratch}) r on conflict (id) do update set ${updates}" \
|
|
-c "drop table public.${scratch}"
|
|
}
|
|
|
|
restore_personnel_baron_postgres() {
|
|
local backup_dir="$1"
|
|
local db_user="${DB_USER:-baron}"
|
|
local db_password="${DB_PASSWORD:-password}"
|
|
local db_name="${DB_NAME:-baron_sso}"
|
|
local input_dir
|
|
|
|
input_dir="$(personnel_dataset_dir "$backup_dir")/postgres"
|
|
backup_require_command docker
|
|
backup_require_container baron_postgres
|
|
|
|
personnel_restore_jsonl_id_table baron_postgres "$db_user" "$db_password" "$db_name" tenants "$input_dir/tenants.jsonl"
|
|
personnel_restore_jsonl_id_table baron_postgres "$db_user" "$db_password" "$db_name" tenant_domains "$input_dir/tenant_domains.jsonl"
|
|
personnel_restore_jsonl_id_table baron_postgres "$db_user" "$db_password" "$db_name" user_groups "$input_dir/user_groups.jsonl"
|
|
personnel_restore_jsonl_id_table baron_postgres "$db_user" "$db_password" "$db_name" users "$input_dir/users.jsonl"
|
|
personnel_restore_jsonl_id_table baron_postgres "$db_user" "$db_password" "$db_name" user_login_ids "$input_dir/user_login_ids.jsonl"
|
|
personnel_restore_jsonl_id_table baron_postgres "$db_user" "$db_password" "$db_name" worksmobile_resource_mappings "$input_dir/worksmobile_resource_mappings.jsonl"
|
|
}
|
|
|
|
restore_personnel_keto_relation_tuples() {
|
|
local backup_dir="$1"
|
|
local db_user="${ORY_POSTGRES_USER:-ory}"
|
|
local db_password="${ORY_POSTGRES_PASSWORD:-secret}"
|
|
local keto_db="${KETO_DB:-ory_keto}"
|
|
local input_file
|
|
local scratch="_personnel_restore_keto_relation_tuples_json"
|
|
|
|
input_file="$(personnel_dataset_dir "$backup_dir")/ory_keto/relation_tuples.jsonl"
|
|
backup_require_path "$input_file"
|
|
if [[ ! -s "$input_file" ]]; then
|
|
backup_log "Skipping empty personnel Keto relation tuple dataset"
|
|
return 0
|
|
fi
|
|
|
|
backup_log "Restoring personnel Keto relation tuple subset: $keto_db"
|
|
docker exec -e "PGPASSWORD=$db_password" ory_postgres \
|
|
psql -U "$db_user" -d "$keto_db" -v ON_ERROR_STOP=1 \
|
|
-c "drop table if exists public.${scratch}" \
|
|
-c "create table public.${scratch} (line jsonb not null)"
|
|
docker exec -i -e "PGPASSWORD=$db_password" ory_postgres \
|
|
psql -U "$db_user" -d "$keto_db" -v ON_ERROR_STOP=1 \
|
|
-c "\\copy public.${scratch}(line) from stdin" <"$input_file"
|
|
docker exec -e "PGPASSWORD=$db_password" ory_postgres \
|
|
psql -U "$db_user" -d "$keto_db" -v ON_ERROR_STOP=1 \
|
|
-c "delete from public.keto_relation_tuples where namespace <> 'RelyingParty' and coalesce(subject_set_namespace, '') <> 'RelyingParty'" \
|
|
-c "insert into public.keto_relation_tuples select (jsonb_populate_record(null::public.keto_relation_tuples, line)).* from public.${scratch}" \
|
|
-c "drop table public.${scratch}"
|
|
}
|
|
|
|
restore_personnel_kratos_identities() {
|
|
local backup_dir="$1"
|
|
local db_user="${ORY_POSTGRES_USER:-ory}"
|
|
local db_password="${ORY_POSTGRES_PASSWORD:-secret}"
|
|
local kratos_db="${KRATOS_DB:-ory_kratos}"
|
|
local input_file
|
|
|
|
input_file="$(personnel_dataset_dir "$backup_dir")/ory_kratos/identities.jsonl"
|
|
backup_require_command docker
|
|
backup_require_container ory_postgres
|
|
personnel_restore_jsonl_id_table ory_postgres "$db_user" "$db_password" "$kratos_db" identities "$input_file"
|
|
}
|
|
|
|
restore_personnel_ory_postgres() {
|
|
local backup_dir="$1"
|
|
|
|
restore_personnel_kratos_identities "$backup_dir"
|
|
restore_personnel_keto_relation_tuples "$backup_dir"
|
|
}
|
|
|
|
restore_personnel_dataset() {
|
|
local backup_dir="$1"
|
|
local services="$2"
|
|
local report_items=()
|
|
local item
|
|
|
|
backup_require_path "$(personnel_dataset_manifest_path "$backup_dir")"
|
|
|
|
if service_enabled postgres "$services"; then
|
|
restore_personnel_baron_postgres "$backup_dir"
|
|
item="$(jq -n --arg label "personnel/postgres" --arg status "passed" '{label:$label, status:$status}')"
|
|
report_items+=("$item")
|
|
fi
|
|
|
|
if service_enabled ory-postgres "$services"; then
|
|
restore_personnel_ory_postgres "$backup_dir"
|
|
item="$(jq -n --arg label "personnel/ory-postgres" --arg status "passed" '{label:$label, status:$status}')"
|
|
report_items+=("$item")
|
|
fi
|
|
|
|
if service_enabled config "$services"; then
|
|
restore_config_snapshot "$backup_dir"
|
|
item="$(jq -n --arg label "personnel/config" --arg status "passed" '{label:$label, status:$status}')"
|
|
report_items+=("$item")
|
|
fi
|
|
|
|
if [[ "${#report_items[@]}" -eq 0 ]]; then
|
|
target_verification_reports="[]"
|
|
else
|
|
target_verification_reports="$(printf '%s\n' "${report_items[@]}" | jq -s '.')"
|
|
fi
|
|
target_verification_status="passed"
|
|
}
|