#!/usr/bin/env bash
#
# Rebuild mcp-operator's trust policy from the permission sets actually provisioned in this account.
#
# RUN THIS after any Identity Center assignment change, and always while you still hold a permission
# set that can edit IAM under /mcp/. Editing this trust policy needs either admin or mcp-operator,
# and a broken trust policy is exactly the case where mcp-operator is unreachable.
#
# ---------------------------------------------------------------------------------------------
# Why the policy has TWO statements that look redundant
#
# They fail in opposite directions, and access here is time-boxed, so both failures are reachable.
#
# WildcardBySsoNamePattern matches any AWSReservedSSO_* role by ARN pattern. It survives the
# quarterly cycle: when an assignment is revoked and later re-approved, Identity Center may
# recreate the role with a DIFFERENT hash suffix, and a pattern does not care. What it cannot do is
# work on its own, because `Principal: "*"` plus a condition is a delegation: IAM also requires the
# CALLER's own policy to allow sts:AssumeRole. The admin permission set happens to grant that. The
# lower ones in the catalogue do not.
#
# NamedSsoPermissionSets lists the role ARNs literally. An explicitly named same-account principal
# needs no permission on the caller's side, so this is what makes the role reachable from a
# low-privilege permission set. What it cannot do is survive a hash change, and after one it is
# dead weight that names a principal which no longer exists.
#
# Together: the named statement carries normal operation, the pattern statement is the escape hatch
# for the first sign-in after a re-approval changed the hash. Re-run this script after each renewal
# to refresh the named list.
#
# The durable fix is smaller than either: ask for `sts:AssumeRole` on
# arn:aws:iam::<account>:role/mcp/* inside whichever permission set you reapply for. One action,
# one resource path, defensible as least privilege. With that, the pattern statement alone is
# enough and this script becomes optional.
# ---------------------------------------------------------------------------------------------

set -euo pipefail

: "${AWS_PROFILE:=mcp}"
export AWS_PROFILE
export AWS_REGION="${AWS_REGION:-us-west-2}"

# The expected account is read, not written down.
#
# `.gitignore` says the account number is not a secret but is not a value that should travel with
# the code, and then this script hardcoded it three times. It comes from terraform.tfvars, which is
# git-ignored and is already the one place that records it, or from the environment for anyone
# running this outside a checkout.
#
# The guard stays. Dropping it would be the wrong way to remove the constant: the operator's default
# AWS profile points at an unrelated production account, and this script rewrites a trust policy.
EXPECTED="${MCP_ACCOUNT_ID:-}"
if [ -z "$EXPECTED" ] && [ -f "$(dirname "$0")/../terraform.tfvars" ]; then
  EXPECTED=$(sed -n 's/^[[:space:]]*account_id[[:space:]]*=[[:space:]]*"\([0-9]\{12\}\)".*/\1/p' \
             "$(dirname "$0")/../terraform.tfvars" | head -1)
fi
if [ -z "$EXPECTED" ]; then
  echo "Cannot tell which account this should be. Set MCP_ACCOUNT_ID, or run from a checkout that" >&2
  echo "has terraform.tfvars. Refusing to rewrite a trust policy without knowing where." >&2
  exit 1
fi

ACCOUNT=$(aws sts get-caller-identity --query Account --output text)
if [ "$ACCOUNT" != "$EXPECTED" ]; then
  echo "Wrong account: credentials resolve to $ACCOUNT, expected $EXPECTED. Refusing." >&2
  exit 1
fi

echo "Permission sets provisioned in $ACCOUNT:"
ARNS=$(aws iam list-roles --path-prefix /aws-reserved/sso.amazonaws.com/ \
        --query 'Roles[].Arn' --output text | tr '\t' '\n' | sed '/^$/d')
if [ -z "$ARNS" ]; then
  echo "None found. Refusing to write a policy that names nothing." >&2
  exit 1
fi
echo "$ARNS" | sed 's/^/  /'

TMP=$(mktemp)
python3 - "$ACCOUNT" "$ARNS" > "$TMP" <<'PY'
import json, sys
account = sys.argv[1]
principals = [l.strip() for l in sys.argv[2].splitlines() if l.strip()]
print(json.dumps({
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "NamedSsoPermissionSets",
            "Effect": "Allow",
            "Principal": {"AWS": principals},
            "Action": "sts:AssumeRole",
        },
        {
            "Sid": "WildcardBySsoNamePattern",
            "Effect": "Allow",
            "Principal": {"AWS": "*"},
            "Action": "sts:AssumeRole",
            "Condition": {
                "StringLike": {
                    "aws:PrincipalArn":
                        f"arn:aws:iam::{account}:role/aws-reserved/sso.amazonaws.com/*/AWSReservedSSO_*"
                }
            },
        },
    ],
}, indent=2))
PY

echo
echo "New trust policy:"
cat "$TMP"

aws iam update-assume-role-policy --role-name mcp-operator --policy-document "file://$TMP"
rm -f "$TMP"

echo
echo "Waiting for propagation, then verifying from THIS session..."
sleep 10
if aws sts assume-role --role-arn "arn:aws:iam::${ACCOUNT}:role/mcp/mcp-operator" \
     --role-session-name relink-verify --duration-seconds 900 \
     --query 'AssumedRoleUser.Arn' --output text >/dev/null 2>&1; then
  echo "OK: assume-role works from the current permission set."
else
  echo "FAILED: assume-role no longer works. Fix this NOW, before this session's access lapses." >&2
  exit 1
fi

cat <<NOTE

This verified the permission set you are signed in with right now.

If you hold more than one, sign in with the LOWEST one and run:

  aws sts assume-role \\
    --role-arn arn:aws:iam::${ACCOUNT}:role/mcp/mcp-operator \\
    --role-session-name check --duration-seconds 900

That is the case that actually matters, because it is the one you will have after
admin lapses.
NOTE
