AWSTemplateFormatVersion: "2010-09-09"
Description: >-
  IAM user and policies for connecting Willow to the AWS MCP Server.
  Guide: https://docs.withwillow.ai/docs/admin/build/mcp-servers/connectors/official/aws

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: IAM user
        Parameters:
          - UserName
      - Label:
          default: What agents can read
        Parameters:
          - BaseAccess
          - AdditionalPolicyArns
      - Label:
          default: Guardrails
        Parameters:
          - BlockSecrets
          - DenyDestructiveViaMcp
          - AllowedRegions
    ParameterLabels:
      UserName:
        default: IAM user name
      BaseAccess:
        default: Base access
      AdditionalPolicyArns:
        default: Additional managed policy ARNs
      BlockSecrets:
        default: Block secrets and encryption keys
      DenyDestructiveViaMcp:
        default: Deny destructive actions through the MCP server
      AllowedRegions:
        default: Allowed regions

Parameters:
  UserName:
    Type: String
    Default: willow-aws-mcp
    AllowedPattern: "[\\w+=,.@-]{1,64}"
    Description: Name of the dedicated IAM user Willow signs requests as.

  BaseAccess:
    Type: String
    Default: ViewOnlyAccess
    AllowedValues:
      - ViewOnlyAccess
      - ReadOnlyAccess
      - IncidentInvestigation
      - None
    Description: >-
      ViewOnlyAccess lists and describes resources without reading data inside them.
      ReadOnlyAccess also reads data (S3 objects, DynamoDB items, logs).
      IncidentInvestigation allows only CloudWatch, logs, and compute reads.
      None attaches nothing, so you can add your own policies.

  AdditionalPolicyArns:
    Type: CommaDelimitedList
    Default: ""
    Description: >-
      Comma-separated ARNs of existing managed policies to attach as well, for example
      arn:aws:iam::123456789012:policy/my-policy. Leave empty to attach none.

  BlockSecrets:
    Type: String
    Default: "true"
    AllowedValues: ["true", "false"]
    Description: >-
      Deny KMS, Secrets Manager, Parameter Store values, Lambda environment variables,
      EC2 Windows passwords, creating access keys, and assuming other roles.

  DenyDestructiveViaMcp:
    Type: String
    Default: "true"
    AllowedValues: ["true", "false"]
    Description: >-
      Deny deletes, terminations, and IAM changes for requests made through an
      AWS managed MCP server.

  AllowedRegions:
    Type: CommaDelimitedList
    Default: ""
    Description: >-
      Comma-separated regions agents may call, for example us-east-1,eu-west-1.
      Global services (IAM, STS, Organizations, Route 53, CloudFront, Support) are
      always allowed. Leave empty to allow all regions.

Mappings:
  BaseAccessPolicy:
    ViewOnlyAccess:
      Path: job-function/ViewOnlyAccess
    ReadOnlyAccess:
      Path: ReadOnlyAccess
    IncidentInvestigation:
      Path: none
    None:
      Path: none

Conditions:
  UseAwsManagedBase: !Or
    - !Equals [!Ref BaseAccess, ViewOnlyAccess]
    - !Equals [!Ref BaseAccess, ReadOnlyAccess]
  HasAdditionalPolicies: !Not [!Equals [!Join ["", !Ref AdditionalPolicyArns], ""]]
  UseIncidentInvestigation: !Equals [!Ref BaseAccess, IncidentInvestigation]
  UseBlockSecrets: !Equals [!Ref BlockSecrets, "true"]
  UseDenyDestructiveViaMcp: !Equals [!Ref DenyDestructiveViaMcp, "true"]
  UseRegionLimit: !Not [!Equals [!Join ["", !Ref AllowedRegions], ""]]

Resources:
  WillowUser:
    Type: AWS::IAM::User
    Properties:
      UserName: !Ref UserName
      ManagedPolicyArns: !If
        - UseAwsManagedBase
        - !If
          - HasAdditionalPolicies
          - !Split
            - ","
            - !Join
              - ","
              - - !Sub
                  - "arn:${AWS::Partition}:iam::aws:policy/${Path}"
                  - Path: !FindInMap [BaseAccessPolicy, !Ref BaseAccess, Path]
                - !Join [",", !Ref AdditionalPolicyArns]
          - - !Sub
              - "arn:${AWS::Partition}:iam::aws:policy/${Path}"
              - Path: !FindInMap [BaseAccessPolicy, !Ref BaseAccess, Path]
        - !If
          - HasAdditionalPolicies
          - !Ref AdditionalPolicyArns
          - !Ref AWS::NoValue
      Tags:
        - Key: willow:purpose
          Value: aws-mcp

  IncidentInvestigationPolicy:
    Type: AWS::IAM::Policy
    Condition: UseIncidentInvestigation
    Properties:
      PolicyName: willow-incident-investigation
      Users: [!Ref WillowUser]
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Sid: ReadMonitoring
            Effect: Allow
            Action:
              - cloudwatch:Describe*
              - cloudwatch:Get*
              - cloudwatch:List*
              - logs:Describe*
              - logs:FilterLogEvents
              - logs:GetLogEvents
              - logs:StartQuery
              - logs:GetQueryResults
            Resource: "*"
          - Sid: ReadCompute
            Effect: Allow
            Action:
              - ec2:Describe*
              - ecs:Describe*
              - ecs:List*
              - eks:Describe*
              - eks:List*
              - elasticloadbalancing:Describe*
              - autoscaling:Describe*
            Resource: "*"

  BlockSecretsPolicy:
    Type: AWS::IAM::Policy
    Condition: UseBlockSecrets
    Properties:
      PolicyName: willow-block-secrets
      Users: [!Ref WillowUser]
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Sid: DenyKmsAndSecretsManager
            Effect: Deny
            Action:
              - kms:*
              - secretsmanager:*
            Resource: "*"
          - Sid: DenyOtherPlacesSecretsLive
            Effect: Deny
            Action:
              - ssm:GetParameter
              - ssm:GetParameters
              - ssm:GetParametersByPath
              - ssm:GetParameterHistory
              - lambda:GetFunction
              - lambda:GetFunctionConfiguration
              - ec2:GetPasswordData
              - iam:CreateAccessKey
              - sts:AssumeRole
            Resource: "*"

  DenyDestructiveViaMcpPolicy:
    Type: AWS::IAM::Policy
    Condition: UseDenyDestructiveViaMcp
    Properties:
      PolicyName: willow-deny-destructive-via-mcp
      Users: [!Ref WillowUser]
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Sid: DenyDestructiveActionsViaMcp
            Effect: Deny
            Action:
              - ec2:TerminateInstances
              - ec2:DeleteVolume
              - ec2:DeleteSnapshot
              - s3:DeleteBucket
              - s3:DeleteObject
              - rds:DeleteDBInstance
              - rds:DeleteDBCluster
              - dynamodb:DeleteTable
              - lambda:DeleteFunction
              - cloudformation:DeleteStack
              - iam:Create*
              - iam:Delete*
              - iam:Attach*
              - iam:Put*
            Resource: "*"
            Condition:
              Bool:
                aws:ViaAWSMCPService: "true"

  RegionLimitPolicy:
    Type: AWS::IAM::Policy
    Condition: UseRegionLimit
    Properties:
      PolicyName: willow-allowed-regions
      Users: [!Ref WillowUser]
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Sid: DenyOutsideApprovedRegions
            Effect: Deny
            NotAction:
              - iam:*
              - sts:*
              - organizations:*
              - route53:*
              - cloudfront:*
              - support:*
            Resource: "*"
            Condition:
              StringNotEquals:
                aws:RequestedRegion: !Ref AllowedRegions

Outputs:
  UserName:
    Description: IAM user Willow signs requests as.
    Value: !Ref WillowUser
  UserArn:
    Description: ARN of the IAM user.
    Value: !GetAtt WillowUser.Arn
  CreateAccessKeyUrl:
    Description: Open this page and click Create access key, then store the key in the Willow Vault.
    Value: !Sub "https://console.aws.amazon.com/iam/home#/users/details/${WillowUser}?section=security_credentials"
  CreateAccessKeyCommand:
    Description: Or create the access key with the AWS CLI.
    Value: !Sub "aws iam create-access-key --user-name ${WillowUser}"
