From ad1c3a8b8b2c7b267cb84f58dd4710fffd5cff5c Mon Sep 17 00:00:00 2001 From: Daniel Gaspar Date: Mon, 10 Nov 2025 11:34:25 +0000 Subject: [PATCH 1/2] chore: add GCP terraform module --- README.md | 67 ++- cloudformation/preset-mpc-iam.yaml | 508 ------------------ examples/.terraform.lock.hcl | 25 - examples/main.tf | 16 - gcp/shell/cleanup-old-mpc-permissions.sh | 139 +++++ gcp/shell/setup-mpc-permissions.sh | 340 ++++++++++++ gcp/terraform/example/.gitignore | 10 + gcp/terraform/example/README.md | 138 +++++ gcp/terraform/example/main.tf | 72 +++ .../example/terraform.tfvars.example | 19 + .../modules/mpc-permissions/README.md | 144 +++++ .../mpc-permissions/default_variables.tf | 23 + .../modules/mpc-permissions/locals.tf | 194 +++++++ gcp/terraform/modules/mpc-permissions/main.tf | 61 +++ .../modules/mpc-permissions/outputs.tf | 29 + .../mpc-permissions/required_variables.tf | 9 + .../modules/mpc-permissions/versions.tf | 10 + generator/cloudformation/cfn.go | 168 ------ .../iam/policies/client-access-management.tpl | 32 -- generator/iam/policies/infra.tpl | 88 --- .../policies/preset-provision-workspaces.tpl | 310 ----------- generator/terraform/tf.go | 168 ------ generator/types.go | 11 - go.mod | 18 - go.sum | 30 -- main.go | 41 -- modules/mpc_iam_init/.terraform.lock.hcl | 25 - .../client-access-management.json.tpl | 32 -- .../mpc_iam_init/iam_policies/infra.json.tpl | 88 --- .../preset-provision-workspaces.json.tpl | 350 ------------ modules/mpc_iam_init/main.tf | 85 --- modules/mpc_iam_init/required-variables.tf | 20 - modules/mpc_iam_init/versions.tf | 9 - 33 files changed, 1239 insertions(+), 2040 deletions(-) delete mode 100644 cloudformation/preset-mpc-iam.yaml delete mode 100644 examples/.terraform.lock.hcl delete mode 100644 examples/main.tf create mode 100755 gcp/shell/cleanup-old-mpc-permissions.sh create mode 100755 gcp/shell/setup-mpc-permissions.sh create mode 100644 gcp/terraform/example/.gitignore create mode 100644 gcp/terraform/example/README.md create mode 100644 gcp/terraform/example/main.tf create mode 100644 gcp/terraform/example/terraform.tfvars.example create mode 100644 gcp/terraform/modules/mpc-permissions/README.md create mode 100644 gcp/terraform/modules/mpc-permissions/default_variables.tf create mode 100644 gcp/terraform/modules/mpc-permissions/locals.tf create mode 100644 gcp/terraform/modules/mpc-permissions/main.tf create mode 100644 gcp/terraform/modules/mpc-permissions/outputs.tf create mode 100644 gcp/terraform/modules/mpc-permissions/required_variables.tf create mode 100644 gcp/terraform/modules/mpc-permissions/versions.tf delete mode 100644 generator/cloudformation/cfn.go delete mode 100644 generator/iam/policies/client-access-management.tpl delete mode 100644 generator/iam/policies/infra.tpl delete mode 100644 generator/iam/policies/preset-provision-workspaces.tpl delete mode 100644 generator/terraform/tf.go delete mode 100644 generator/types.go delete mode 100644 go.mod delete mode 100644 go.sum delete mode 100644 main.go delete mode 100644 modules/mpc_iam_init/.terraform.lock.hcl delete mode 100644 modules/mpc_iam_init/iam_policies/client-access-management.json.tpl delete mode 100644 modules/mpc_iam_init/iam_policies/infra.json.tpl delete mode 100644 modules/mpc_iam_init/iam_policies/preset-provision-workspaces.json.tpl delete mode 100644 modules/mpc_iam_init/main.tf delete mode 100644 modules/mpc_iam_init/required-variables.tf delete mode 100644 modules/mpc_iam_init/versions.tf diff --git a/README.md b/README.md index 94d427d..2f820c1 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,62 @@ -# Preset MPC Bootstrap Scripts +# MPC Setup for GCP -This repository contains a collection of bootstrap scripts for Preset Managed Private Clouds (MPCs). -We currently support Cloudformation as the primary bootstrap method, with Terraform support currently in beta. +This repository provides tools to set up MPC (Managed Private Cloud) permissions in your GCP project. -## Prerequisites +## Setup Options -1. An AWS account with sufficient permissions to create IAM roles and policies, and to create and manage Cloudformation stacks. -2. An external ID for the Preset MPC you wish to bootstrap. This can be obtained from the Preset team. +Choose the method that best fits your workflow: -## Cloudformation +### Option 1: Terraform Module (Recommended) -The Cloudformation bootstrap script is located in the `cloudformation` directory. +For organizations using Terraform for infrastructure management. -### Usage +**See the complete documentation:** [gcp/terraform/modules/mpc-permissions/README.md](gcp/terraform/modules/mpc-permissions/README.md) -1. Simply apply the [preset-mpc-iam.yaml](cloudformation%2Fpreset-mpc-iam.yaml) Cloudformation template to your AWS account. -2. When prompted, enter the external ID for your Preset MPC provided by the Preset team. -3. Choose `production` when prompted for the environment. (The staging environment is used for internal testing only.) +The Terraform module provides: +- Infrastructure as code with version control +- Drift detection and management +- CI/CD integration +- Declarative configuration -### Outputs +### Option 2: Shell Script -* A set of IAM roles and policies for the Preset MPC that will allow the Preset team to provision your cluster. +For quick one-time setup or organizations not using Terraform. -## Terraform +**Script location:** [gcp/shell/setup-mpc-permissions.sh](gcp/shell/setup-mpc-permissions.sh) -Coming soon. +**Quick start:** +```bash +export PROJECT_ID="your-project-id" +export PRESET_SERVICE_ACCOUNT="Service account email provided by Preset" +./gcp/shell/setup-mpc-permissions.sh +``` + +The shell script is: +- Idempotent (safe to run multiple times) +- Self-contained +- Easy to audit + +## What Gets Created + +Both methods create the following resources in your GCP project: + +1. **Custom IAM Role** (`PresetMPCAdminV2`) - Contains all necessary permissions for Preset to manage MPC infrastructure +2. **MPC Service Account** (`preset-mpc-sa`) - Used by Preset to manage your resources +3. **IAM Bindings** - Grants appropriate permissions to Preset's service account + +## Support + +If you encounter issues or need assistance: + +1. Review the detailed documentation for your chosen method (links above) +2. Check the GCP Cloud Console for error messages +3. Contact your Preset support representative with: + - Your project ID + - Error messages or logs + - Which setup method you're using + +## Additional Resources + +- [GCP IAM Custom Roles Documentation](https://cloud.google.com/iam/docs/creating-custom-roles) +- [GCP Service Accounts Documentation](https://cloud.google.com/iam/docs/service-accounts) +- [Terraform Google Provider Documentation](https://registry.terraform.io/providers/hashicorp/google/latest/docs) \ No newline at end of file diff --git a/cloudformation/preset-mpc-iam.yaml b/cloudformation/preset-mpc-iam.yaml deleted file mode 100644 index b22a8cb..0000000 --- a/cloudformation/preset-mpc-iam.yaml +++ /dev/null @@ -1,508 +0,0 @@ -AWSTemplateFormatVersion: 2010-09-09 -Description: >- - This template creates IAM resources required to get MPC services up and running in your AWS account -Parameters: - StsExternalId: - Type: String - Description: An external ID to use when building role assumption policies - NoEcho: true - PresetEnv: - Description: "Preset environment" - Type: String - AllowedValues: - - Production - - Staging - Default: Production - -Mappings: - PresetAccounts: - Staging: - AccountId: "151737340033" - Production: - AccountId: "125098402723" - DevOps: - AccountId: "915571001068" - -Resources: - clientAccessPolicy: - Type: AWS::IAM::ManagedPolicy - Properties: - ManagedPolicyName: client-access-management - Roles: - - !Ref clientAccessRole - PolicyDocument: - { - "Version": "2012-10-17", - "Statement": [ - { - "Action": [ - "eks:AccessKubernetesApi", - "eks:Describe*", - "eks:List*", - "ec2:*", - "ssm:*" - ], - "Effect": "Allow", - "Resource": "*" - }, - { - "Effect": "Deny", - "Action": "ssm:*", - "Resource": "*", - "Condition": { - "NotIpAddress": { - "aws:SourceIp": [ - "35.161.45.11/32", - "52.32.136.34/32", - "54.244.23.85/32", - "52.88.46.148/32", - "35.161.104.245/32", - "52.88.129.18/32" - ] - } - } - } - ] - } - - infraPolicy: - Type: AWS::IAM::ManagedPolicy - Properties: - ManagedPolicyName: preset-provision-infra - Roles: - - !Ref workspaceProvisionerRole - PolicyDocument: - { - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "VPC", - "Effect": "Allow", - "Action": [ - "ec2:ModifyVpc*", - "ec2:Attach*", - "ec2:Detach*" - ], - "Resource": "*" - },{ - "Sid": "cfn", - "Effect": "Allow", - "Action": [ - "cloudformation:Get*", - "cloudformation:List*", - "cloudformation:Describe*", - "cloudformation:ValidateTemplate", - "cloudformation:Create*", - "cloudformation:Update*", - "cloudformation:Tag*", - "cloudformation:Untag*", - ], - "Resource": "*" - }, - { - "Sid": "cfnDelete", - "Effect": "Allow", - "Action": [ - "cloudformation:Delete*" - ], - "Resource": "*" - }, - { - "Sid": "lambda", - "Effect": "Allow", - "Action": [ - "lambda:Add*", - "lambda:Remove*", - "lambda:List*", - "lambda:Get*", - "lambda:Create*", - "lambda:Update*", - "lambda:Put*", - "lambda:Tag*", - "lambda:Untag*", - "lambda:Publish*", - ], - "Resource": "*" - }, - { - "Sid": "LambdaDelete", - "Effect": "Allow", - "Action": [ - "lambda:DeleteFunction" - ], - "Resource": !Sub "arn:aws:lambda:*:${AWS::AccountId}:function:datadog_log_monitoring" - },{ - "Sid": "secretsManager", - "Effect": "Allow", - "Action": [ - "secretsmanager:List*", - "secretsmanager:Describe*", - "secretsmanager:Get*", - "secretsmanager:Tag*", - "secretsmanager:Untag*", - "secretsmanager:Create*", - "secretsmanager:Update*", - "secretsmanager:Put*", - ], - "Resource": "*" - },{ - "Sid": "SecretManagerDelete", - "Effect": "Allow", - "Action": [ - "secretsmanager:Delete*" - ], - "Resource": "*", - "Condition": { - "StringEquals": { - "aws:ResourceTag/Owner": "Preset-io" - } - } - }, - ] - } - provisionerPolicy: - Type: AWS::IAM::ManagedPolicy - Properties: - ManagedPolicyName: preset-provision-workspaces - Roles: - - !Ref workspaceProvisionerRole - PolicyDocument: - { - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "route53", - "Effect": "Allow", - "Action": [ - "route53:Get*", - "route53:List*", - "route53:CreateHostedZone", - "route53:Change*" - ], - "Resource": "*" - }, { - "Sid": "route53Delete", - "Effect": "Allow", - "Action": [ - "route53:DeleteHostedZone" - ], - "Resource": "*" - }, { - "Sid": "acm", - "Effect": "Allow", - "Action": [ - "acm:RequestCertificate", - "acm:AddTagsToCertificate", - "acm:DescribeCertificate", - "acm:ListTagsForCertificate" - ], - "Resource": "*" - }, { - "Sid": "acmDelete", - "Effect": "Allow", - "Action": [ - "acm:DeleteCertificate" - ], - "Resource": "*", - "Condition": { - "StringEquals": { - "aws:ResourceTag/Owner": "Preset-io" - } - } - }, { - "Sid": "logs", - "Effect": "Allow", - "Action": [ - "logs:Untag*", - "logs:Tag*", - "logs:Create*", - "logs:Put*", - "logs:Describe*", - "logs:List*" - ], - "Resource": "*" - }, { - "Sid": "logsDelete", - "Effect": "Allow", - "Action": [ - "logs:DeleteLogGroup", - "logs:DeleteSubscriptionFilter" - ], - "Resource": "*", - "Condition": { - "StringEquals": { - "aws:ResourceTag/Owner": "Preset-io" - } - } - }, { - "Sid": "rds", - "Effect": "Allow", - "Action": [ - "rds:RemoveTagsFromResource", - "rds:AddTagsToResource", - "rds:Create*", - "rds:Describe*", - "rds:Modify*", - "rds:ListTagsForResource" - ], - "Resource": "*" - }, { - "Sid": "rdsDelete", - "Effect": "Allow", - "Action": [ - "rds:Delete*" - ], - "Resource": "*", - "Condition": { - "StringEquals": { - "aws:ResourceTag/Owner": "Preset-io" - } - } - }, { - "Sid": "Elasticache", - "Effect": "Allow", - "Action": [ - "elasticache:UntagResource", - "elasticache:Describe*", - "elasticache:Create*", - "elasticache:AddTagsToResource", - "elasticache:RemoveTagsFromResource", - "elasticache:ModifyCacheParameterGroup", - "elasticache:ModifyReplicationGroup", - "elasticache:ListTagsForResource" - ], - "Resource": "*" - }, { - "Sid": "ElasicLoadBalancing", - "Effect": "Allow", - "Action": [ - "elasticloadbalancing:*" - ], - "Resource": "*" - }, { - "Sid": "elasticacheDelete", - "Effect": "Allow", - "Action": ["elasticache:Delete*"], - "Resource": "*", - "Condition": { - "StringEquals": { "aws:ResourceTag/Owner": "Preset-io"} - } - }, { - "Sid": "ec2", - "Effect": "Allow", - "Action": [ - "ec2:AllocateAddress", - "ec2:AssignPrivateIpAddresses", - "ec2:Associate*", - "ec2:AttachNetworkInterface", - "ec2:AuthorizeSecurityGroupEgress", - "ec2:AuthorizeSecurityGroupIngress", - "ec2:Create*", - "ec2:Describe*", - "ec2:Detach*", - "ec2:Disassociate*", - "ec2:ReleaseAddress", - "ec2:Revoke*", - "ec2:Update*", - "ec2:GetLaunchTemplateData", - "ec2:ModifyLaunchTemplate", - "ec2:TerminateInstances", - "ec2:RunInstances" - ], - "Resource": "*" - }, { - "Sid": "ec2Delete", - "Effect": "Allow", - "Action": [ - "ec2:Delete*" - ], - "Resource": "*", - "Condition": { - "StringEquals": { - "aws:ResourceTag/Owner": "Preset-io" - } - } - },{ - "Sid": "ec2DeleteTags", - "Effect": "Allow", - "Action": [ - "ec2:DeleteTags" - ], - "Resource": "*" - }, { - "Sid": "eks", - "Effect": "Allow", - "Action": [ - "eks:Associate*", - "eks:Create*", - "eks:Describe*", - "eks:List*", - "eks:Update*", - "eks:TagResource", - "eks:UntagResource", - "eks:DeleteAddon" - ], - "Resource": "*" - }, { - "Sid": "eksDelete", - "Effect": "Allow", - "Action": [ - "eks:Delete*" - ], - "Resource": "*", - "Condition": { - "StringEquals": { - "aws:ResourceTag/Owner": "Preset-io" - } - } - }, { - "Sid": "Autoscalng", - "Effect": "Allow", - "Action": [ - "autoscaling:EnableMetricsCollection", - "autoscaling:AttachInstances", - "autoscaling:Create*", - "autoscaling:Delete*", - "autoscaling:Describe*", - "autoscaling:DetachInstances", - "autoscaling:SetDesiredCapacity", - "autoscaling:UpdateAutoScalingGroup", - "autoscaling:SetInstanceProtection", - "autoscaling:SuspendProcesses" - ], - "Resource": "*" - }, { - "Sid": "iam", - "Effect": "Allow", - "Action": [ - "iam:AddRoleToInstanceProfile", - "iam:AttachRolePolicy", - "iam:CreateInstanceProfile", - "iam:CreateOpenIDConnectProvider", - "iam:CreatePolicy", - "iam:CreatePolicyVersion", - "iam:CreateRole", - "iam:CreateServiceLinkedRole", - "iam:DeletePolicyVersion", - "iam:Get*", - "iam:List*", - "iam:PutRolePolicy", - "iam:RemoveRoleFromInstanceProfile", - "iam:TagOpenIDConnectProvider", - "iam:TagRole", - "iam:UntagRole", - "iam:TagPolicy", - "iam:TagInstanceProfile", - "iam:UpdateAssumeRolePolicy", - "iam:DetachRolePolicy", - "iam:DeletePolicy", - "iam:UpdateRoleDescription", - "iam:PassRole" - ], - "Resource": "*" - }, { - "Sid": "iamDelete", - "Effect": "Allow", - "Action": [ - "iam:Delete*" - ], - "Resource": "*", - "Condition": { - "StringEquals": { - "aws:ResourceTag/Owner": "Preset-io" - } - } - }, { - "Sid": "kms", - "Effect": "Allow", - "Action": [ - "kms:*" - ], - "Resource": "*" - }, { - "Sid": "kmsDelete", - "Effect": "Allow", - "Action": [ - "kms:ScheduleKeyDeletion", - "kms:DeleteAlias" - ], - "Resource": !Join [ "", ["arn:aws:kms:*:", !Ref "AWS::AccountId", ":alias/velero-backups-*"] ], - }, { - "Sid": "s3", - "Effect": "Allow", - "Action": [ - "s3:ListBucketVersions", - "s3:CreateBucket", - "s3:Get*", - "s3:Put*", - "s3:List*", - "s3:Replicate*" - ], - "Resource": "*" - }, { - "Sid": "states", - "Effect": "Allow", - "Action": [ - "states:ListStateMachines" - ], - "Resource": "*" - }, { - "Sid": "s3Delete", - "Effect": "Allow", - "Action": [ - "s3:Delete*" - ], - "Resource": "*" - },{ - "Sid": "wafv2", - "Effect": "Allow", - "Action": [ - "wafv2:*" - ], - "Resource": "*" - },{ - "Sid": "firehose", - "Effect": "Allow", - "Action": [ - "firehose:*" - ], - "Resource": "*" - } - ] - } - - # Cross-account access role for Preset created and managed resources - workspaceProvisionerRole: - Type: AWS::IAM::Role - Properties: - AssumeRolePolicyDocument: - Version: '2012-10-17' - Statement: - - Action: "sts:AssumeRole" - Sid: '' - Effect: Allow - Principal: - AWS: - - !Join ['', ['arn:aws:iam::', !FindInMap [PresetAccounts, !Ref "PresetEnv", AccountId], ':root' ]] - - !Join ['', ['arn:aws:iam::', !FindInMap [PresetAccounts, "DevOps", AccountId], ':root' ]] - Condition: - StringEquals: - sts:ExternalId: !Ref StsExternalId - Description: Preset Cluster Provisioner Role - RoleName: preset-admin - - # Access role for cluster management by client - clientAccessRole: - Type: AWS::IAM::Role - Properties: - Description: Preset Cluster Provisioner Role - RoleName: mpc-account-mgmt - AssumeRolePolicyDocument: - Version: '2012-10-17' - Statement: - - Action: "sts:AssumeRole" - Sid: '' - Effect: Allow - Principal: - AWS: - - !Join [ '', [ 'arn:aws:iam::', !Ref "AWS::AccountId", ':root' ] ] diff --git a/examples/.terraform.lock.hcl b/examples/.terraform.lock.hcl deleted file mode 100644 index 935c2ad..0000000 --- a/examples/.terraform.lock.hcl +++ /dev/null @@ -1,25 +0,0 @@ -# This file is maintained automatically by "terraform init". -# Manual edits may be lost in future updates. - -provider "registry.terraform.io/hashicorp/aws" { - version = "4.53.0" - constraints = ">= 3.39.0" - hashes = [ - "h1:P6ZZ716SRIimw0t/SAgYbOMZtO0HDvwVQKxyHEW6aaE=", - "zh:0d44171544a916adf0fa96b7d0851a49d8dec98f71f0229dfd2d178958b3996b", - "zh:16945808ce26b86af7f5a77c4ab1154da786208c793abb95b8f918b4f48daded", - "zh:1a57a5a30cef9a5867579d894b74f60bb99afc7ca0d030d49a80ad776958b428", - "zh:2c718734ae17430d7f598ca0b4e4f86d43d66569c72076a10f4ace3ff8dfc605", - "zh:46fdf6301cb2fa0a4d122d1a8f75f047b6660c24851d6a4537ee38926a86485d", - "zh:53a53920b38a9e1648e85c6ee33bccf95bfcd067bffc4934a2af55621e6a6bd9", - "zh:548d927b234b1914c43169224b03f641d0961a4e312e5c6508657fce27b66db4", - "zh:57c847b2a5ae41ddea20b18ef006369d36bfdc4dec7f542f60e22a47f7b6f347", - "zh:79f7402b581621ba69f5a07ce70299735c678beb265d114d58955d04f0d39f87", - "zh:8970109a692dc4ecbda98a0969da472da4759db90ce22f2a196356ea85bb2cf7", - "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", - "zh:a500cc4ffcad854dec0cf6f97751930a53c9f278f143a4355fa8892aa77c77bf", - "zh:b687c20b42a8b9e9e9f56c42e3b3c6859c043ec72b8907a6e4d4b64068e11df5", - "zh:e2c592e96822b78287554be43c66398f658c74c4ae3796f6b9e6d4b0f1f7f626", - "zh:ff1c4a46fdc988716c6fc28925549600093fc098828237cb1a30264e15cf730f", - ] -} diff --git a/examples/main.tf b/examples/main.tf deleted file mode 100644 index 3f9ef3e..0000000 --- a/examples/main.tf +++ /dev/null @@ -1,16 +0,0 @@ -module "mpc_iam_init" { - source = "../modules/mpc_iam_init" - - aws_account_id = "12345678910" - preset_devops_aws_account_id = "111111111111" - preset_target_env_account_id = "999999999999" -} - -provider "aws" { - region = "us-west-2" - allowed_account_ids = ["12345678910"] - - assume_role { - role_arn = "arn:aws:iam::12345678910:role/some-role" - } -} diff --git a/gcp/shell/cleanup-old-mpc-permissions.sh b/gcp/shell/cleanup-old-mpc-permissions.sh new file mode 100755 index 0000000..ab68b25 --- /dev/null +++ b/gcp/shell/cleanup-old-mpc-permissions.sh @@ -0,0 +1,139 @@ +#!/bin/bash +set -e + +# MPC Permissions Cleanup Script +# This script removes old Deployment Manager resources after migration to Terraform +# WARNING: Only run this AFTER Preset confirms they are using the new service account + +# Color codes for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Old resource names (from Deployment Manager template) +OLD_MPC_SA_ID="mpc-service-account" +OLD_ROLE_ID="PresetMPCAdmin" +OLD_DEPLOYMENT_NAME="preset-mpc-org" +# Check if PRESET_SERVICE_ACCOUNT is set +if [[ -z "${PRESET_SERVICE_ACCOUNT:-}" ]]; then + error 'PRESET_SERVICE_ACCOUNT environment variable is not set. Please run: export PRESET_SERVICE_ACCOUNT=""' +fi + +# Helper functions +error() { + echo -e "${RED}ERROR: $1${NC}" >&2 + exit 1 +} + +info() { + echo -e "${GREEN}INFO: $1${NC}" +} + +warning() { + echo -e "${YELLOW}WARNING: $1${NC}" +} + +# Check if gcloud CLI is installed +if ! command -v gcloud &> /dev/null; then + error "gcloud CLI is not installed or not in PATH. Please install it from: https://cloud.google.com/sdk/docs/install" +fi + +# Check if PROJECT_ID is set +if [[ -z "${PROJECT_ID:-}" ]]; then + error 'PROJECT_ID environment variable is not set. Please run: export PROJECT_ID=""' +fi + +OLD_MPC_SA_EMAIL="${OLD_MPC_SA_ID}@${PROJECT_ID}.iam.gserviceaccount.com" + +warning "==========================================" +warning "MPC OLD RESOURCES CLEANUP" +warning "==========================================" +echo "" +warning "This script will DELETE the following old resources:" +echo "" +echo " Project: $PROJECT_ID" +echo " Service Account: $OLD_MPC_SA_EMAIL" +echo " Custom Role: $OLD_ROLE_ID" +echo " Deployment Manager: $OLD_DEPLOYMENT_NAME" +echo "" +warning "ONLY proceed if:" +warning " 1. You have created NEW resources with Terraform" +warning " 2. You have provided the NEW service account to Preset" +warning " 3. Preset has CONFIRMED they are using the NEW service account" +echo "" + +# Ask for user confirmation +read -p "Are you SURE you want to delete these old resources? (yes/NO): " -r +echo "" +if [[ ! $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then + echo "Cleanup cancelled by user." + exit 0 +fi +echo "" + +# Remove IAM bindings first +info "Step 1/4: Removing IAM policy bindings..." + +info " Removing Preset SA binding from project..." +gcloud projects remove-iam-policy-binding "$PROJECT_ID" \ + --member="serviceAccount:${PRESET_SERVICE_ACCOUNT}" \ + --role="projects/${PROJECT_ID}/roles/${OLD_ROLE_ID}" \ + --quiet 2>/dev/null || warning " Binding may not exist or already removed" + +info " Removing MPC SA binding from project..." +gcloud projects remove-iam-policy-binding "$PROJECT_ID" \ + --member="serviceAccount:${OLD_MPC_SA_EMAIL}" \ + --role="projects/${PROJECT_ID}/roles/${OLD_ROLE_ID}" \ + --quiet 2>/dev/null || warning " Binding may not exist or already removed" + +info " Removing token creator binding..." +gcloud iam service-accounts remove-iam-policy-binding "$OLD_MPC_SA_EMAIL" \ + --member="serviceAccount:${PRESET_SERVICE_ACCOUNT}" \ + --role="roles/iam.serviceAccountTokenCreator" \ + --quiet 2>/dev/null || warning " Binding may not exist or already removed" + +# Delete service account +info "Step 2/4: Deleting old service account..." +if gcloud iam service-accounts describe "$OLD_MPC_SA_EMAIL" --project="$PROJECT_ID" &>/dev/null; then + gcloud iam service-accounts delete "$OLD_MPC_SA_EMAIL" \ + --project="$PROJECT_ID" \ + --quiet + info " Service account deleted: $OLD_MPC_SA_EMAIL" +else + warning " Service account not found or already deleted: $OLD_MPC_SA_EMAIL" +fi + +# Delete custom role +info "Step 3/4: Deleting old custom role..." +if gcloud iam roles describe "$OLD_ROLE_ID" --project="$PROJECT_ID" &>/dev/null; then + gcloud iam roles delete "$OLD_ROLE_ID" \ + --project="$PROJECT_ID" \ + --quiet + info " Custom role deleted: $OLD_ROLE_ID" +else + warning " Custom role not found or already deleted: $OLD_ROLE_ID" +fi + +# Delete Deployment Manager deployment +info "Step 4/4: Deleting Deployment Manager deployment..." +if gcloud deployment-manager deployments describe "$OLD_DEPLOYMENT_NAME" --project="$PROJECT_ID" &>/dev/null; then + gcloud deployment-manager deployments delete "$OLD_DEPLOYMENT_NAME" \ + --project="$PROJECT_ID" \ + --quiet + info " Deployment Manager deployment deleted: $OLD_DEPLOYMENT_NAME" +else + warning " Deployment Manager deployment not found or already deleted: $OLD_DEPLOYMENT_NAME" +fi + +info "" +info "==========================================" +info "Cleanup Complete!" +info "==========================================" +info "" +info "Old MPC resources have been removed from project: $PROJECT_ID" +info "" +info "Your new Terraform-managed resources remain active:" +info " - Service Account: preset-mpc-sa@${PROJECT_ID}.iam.gserviceaccount.com" +info " - Custom Role: PresetMPCAdminV2" +info "" \ No newline at end of file diff --git a/gcp/shell/setup-mpc-permissions.sh b/gcp/shell/setup-mpc-permissions.sh new file mode 100755 index 0000000..63683a2 --- /dev/null +++ b/gcp/shell/setup-mpc-permissions.sh @@ -0,0 +1,340 @@ +#!/bin/bash +set -e + +# MPC Permissions Setup Script +# This script is idempotent and can be run multiple times safely +# It replaces the legacy Deployment Manager template + +# Color codes for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Default values (use new names to avoid conflicts with old Deployment Manager resources) +MPC_SERVICE_ACCOUNT_ID="${MPC_SERVICE_ACCOUNT_ID:-preset-mpc-sa}" +MPC_ADMIN_ROLE_ID="${MPC_ADMIN_ROLE_ID:-PresetMPCAdminV2}" + +# Helper functions +error() { + echo -e "${RED}ERROR: $1${NC}" >&2 + exit 1 +} + +info() { + echo -e "${GREEN}INFO: $1${NC}" +} + +warning() { + echo -e "${YELLOW}WARNING: $1${NC}" +} + +# Check if gcloud CLI is installed +if ! command -v gcloud &> /dev/null; then + error "gcloud CLI is not installed or not in PATH. Please install it from: https://cloud.google.com/sdk/docs/install" +fi + +# Check if PROJECT_ID is set +if [[ -z "${PROJECT_ID:-}" ]]; then + error 'PROJECT_ID environment variable is not set. Please run: export PROJECT_ID=""' +fi + +# Check if PRESET_SERVICE_ACCOUNT is set +if [[ -z "${PRESET_SERVICE_ACCOUNT:-}" ]]; then + error 'PRESET_SERVICE_ACCOUNT environment variable is not set. Please run: export PRESET_SERVICE_ACCOUNT=""' +fi + + +info "Setting up MPC permissions for project: $PROJECT_ID" +info "MPC Service Account ID: $MPC_SERVICE_ACCOUNT_ID" +info "MPC Admin Role ID: $MPC_ADMIN_ROLE_ID" +info "Preset Service Account: $PRESET_SERVICE_ACCOUNT" +echo "" + +# Ask for user confirmation +read -p "Do you want to continue with this configuration? (y/N): " -n 1 -r +echo "" +if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "Setup cancelled by user." + exit 0 +fi +echo "" + +# Verify required APIs are enabled +info "Checking required APIs..." +REQUIRED_APIS=( + "iam.googleapis.com" + "cloudresourcemanager.googleapis.com" + "serviceusage.googleapis.com" +) + +for api in "${REQUIRED_APIS[@]}"; do + if gcloud services list --enabled --project="$PROJECT_ID" --filter="name:$api" --format="value(name)" | grep -q "$api"; then + info "API $api is already enabled" + else + warning "API $api is not enabled. Enabling..." + gcloud services enable "$api" --project="$PROJECT_ID" + fi +done + +# Define all permissions +PERMISSIONS=( + # CloudSQL permissions + "cloudsql.databases.create" + "cloudsql.databases.delete" + "cloudsql.databases.get" + "cloudsql.instances.create" + "cloudsql.instances.delete" + "cloudsql.instances.list" + "cloudsql.instances.get" + "cloudsql.instances.update" + "cloudsql.users.list" + "cloudsql.users.get" + # Compute Engine permissions + "compute.addresses.create" + "compute.addresses.delete" + "compute.addresses.get" + "compute.addresses.setLabels" + "compute.instances.create" + "compute.instances.delete" + "compute.instances.start" + "compute.instances.stop" + "compute.instances.setMetadata" + "compute.instances.setTags" + "compute.instances.update" + "compute.disks.create" + "compute.disks.delete" + "compute.disks.update" + # Storage permissions + "storage.buckets.create" + "storage.buckets.delete" + "storage.buckets.get" + "storage.buckets.list" + "storage.objects.create" + "storage.objects.delete" + # VPC Network permissions + "compute.networks.create" + "compute.networks.delete" + "compute.networks.get" + "compute.networks.list" + "compute.networks.updatePolicy" + "compute.networks.update" + "compute.networks.use" + # Subnet permissions + "compute.subnetworks.create" + "compute.subnetworks.delete" + "compute.subnetworks.expandIpCidrRange" + "compute.subnetworks.get" + "compute.subnetworks.list" + "compute.subnetworks.update" + # Firewall permissions + "compute.firewalls.create" + "compute.firewalls.delete" + "compute.firewalls.get" + "compute.firewalls.list" + "compute.firewalls.update" + "compute.globalOperations.get" + "compute.globalOperations.list" + "compute.globalAddresses.createInternal" + "compute.globalAddresses.deleteInternal" + "compute.globalAddresses.get" + "compute.globalAddresses.create" + "compute.globalAddresses.setLabels" + # Routes permissions + "compute.routes.create" + "compute.routes.delete" + "compute.routes.get" + "compute.routes.list" + # NAT Gateway permissions + "compute.routers.create" + "compute.routers.delete" + "compute.routers.get" + "compute.routers.list" + "compute.routers.update" + "compute.routers.use" + # Additional compute permissions + "compute.regionOperations.get" + "compute.regionOperations.list" + "compute.zoneOperations.get" + "compute.zoneOperations.list" + "compute.zones.list" + "compute.securityPolicies.create" + "compute.securityPolicies.delete" + "compute.securityPolicies.get" + "compute.securityPolicies.getIamPolicy" + "compute.securityPolicies.list" + "compute.securityPolicies.setIamPolicy" + "compute.securityPolicies.update" + "compute.securityPolicies.use" + "compute.instanceGroupManagers.get" + "compute.instanceGroupManagers.list" + # Kubernetes Engine permissions + "container.clusters.create" + "container.clusters.delete" + "container.clusters.get" + "container.clusters.list" + "container.clusters.update" + "container.namespaces.create" + "container.namespaces.delete" + "container.namespaces.get" + "container.operations.get" + "container.operations.list" + "container.secrets.create" + "container.secrets.delete" + "container.secrets.get" + "container.secrets.update" + # DNS permissions + "dns.managedZones.get" + "dns.managedZones.list" + "dns.managedZones.create" + "dns.managedZones.delete" + "dns.managedZones.update" + "dns.changes.create" + "dns.changes.get" + "dns.changes.list" + "dns.resourceRecordSets.create" + "dns.resourceRecordSets.update" + "dns.resourceRecordSets.delete" + "dns.resourceRecordSets.get" + "dns.resourceRecordSets.list" + # Service networking permissions + "servicenetworking.services.addPeering" + # IAM permissions + "iam.serviceAccounts.actAs" + "iam.serviceAccounts.create" + "iam.serviceAccounts.delete" + "iam.serviceAccounts.disable" + "iam.serviceAccounts.enable" + "iam.serviceAccounts.get" + "iam.serviceAccounts.getIamPolicy" + "iam.serviceAccounts.list" + "iam.serviceAccounts.setIamPolicy" + "iam.serviceAccounts.update" + "iam.serviceAccountKeys.create" + "iam.serviceAccountKeys.delete" + "iam.serviceAccountKeys.list" + "iam.serviceAccountKeys.get" + "iam.policybindings.get" + "iam.policybindings.list" + # Workload Identity permissions + "iam.workloadIdentityPools.create" + "iam.workloadIdentityPools.delete" + "iam.workloadIdentityPools.get" + "iam.workloadIdentityPools.list" + "iam.workloadIdentityPools.update" + "iam.workloadIdentityPoolProviders.create" + "iam.workloadIdentityPoolProviders.delete" + "iam.workloadIdentityPoolProviders.get" + "iam.workloadIdentityPoolProviders.list" + "iam.workloadIdentityPoolProviders.update" + # Redis permissions + "redis.instances.get" + "redis.instances.list" + "redis.instances.create" + "redis.instances.update" + "redis.instances.delete" + "redis.operations.get" + "redis.operations.list" + # Resource Manager permissions + "resourcemanager.projects.createPolicyBinding" + "resourcemanager.projects.deletePolicyBinding" + "resourcemanager.projects.get" + "resourcemanager.projects.getIamPolicy" + "resourcemanager.projects.searchPolicyBindings" + "resourcemanager.projects.setIamPolicy" + "resourcemanager.projects.updatePolicyBinding" + # Service Usage permissions + "serviceusage.services.enable" + "serviceusage.services.get" + "serviceusage.services.list" + # Service Networking permissions + "servicenetworking.services.get" + "servicenetworking.services.deleteConnection" +) + +# Create or update custom role +info "Creating or updating custom IAM role: $MPC_ADMIN_ROLE_ID" + +# Create temporary YAML file for role definition +TMP_ROLE_FILE=$(mktemp /tmp/mpc-role-XXXXXX.yaml) +cat > "$TMP_ROLE_FILE" <> "$TMP_ROLE_FILE" +done + +# Check if role exists +if gcloud iam roles describe "$MPC_ADMIN_ROLE_ID" --project="$PROJECT_ID" &>/dev/null; then + info "Role $MPC_ADMIN_ROLE_ID already exists. Updating..." + gcloud iam roles update "$MPC_ADMIN_ROLE_ID" \ + --project="$PROJECT_ID" \ + --file="$TMP_ROLE_FILE" \ + --quiet +else + info "Creating new role: $MPC_ADMIN_ROLE_ID" + gcloud iam roles create "$MPC_ADMIN_ROLE_ID" \ + --project="$PROJECT_ID" \ + --file="$TMP_ROLE_FILE" \ + --quiet +fi + +rm "$TMP_ROLE_FILE" + +# Create MPC service account +MPC_SA_EMAIL="${MPC_SERVICE_ACCOUNT_ID}@${PROJECT_ID}.iam.gserviceaccount.com" + +info "Creating MPC service account: $MPC_SERVICE_ACCOUNT_ID" + +if gcloud iam service-accounts describe "$MPC_SA_EMAIL" --project="$PROJECT_ID" &>/dev/null; then + info "Service account $MPC_SA_EMAIL already exists. Skipping creation." +else + info "Creating new service account: $MPC_SA_EMAIL" + gcloud iam service-accounts create "$MPC_SERVICE_ACCOUNT_ID" \ + --project="$PROJECT_ID" \ + --display-name="Preset MPC Service Account" \ + --description="Service account for Preset to manage MPC infrastructure" +fi + +# Grant Token Creator role to Preset service account on MPC service account +info "Granting Token Creator role to Preset service account on MPC service account" +gcloud iam service-accounts add-iam-policy-binding "$MPC_SA_EMAIL" \ + --project="$PROJECT_ID" \ + --member="serviceAccount:$PRESET_SERVICE_ACCOUNT" \ + --role="roles/iam.serviceAccountTokenCreator" \ + --quiet + +# Grant custom role to Preset service account at project level +info "Granting custom role to Preset service account" +gcloud projects add-iam-policy-binding "$PROJECT_ID" \ + --member="serviceAccount:$PRESET_SERVICE_ACCOUNT" \ + --role="projects/$PROJECT_ID/roles/$MPC_ADMIN_ROLE_ID" \ + --quiet + +# Grant custom role to MPC service account at project level +info "Granting custom role to MPC service account" +gcloud projects add-iam-policy-binding "$PROJECT_ID" \ + --member="serviceAccount:$MPC_SA_EMAIL" \ + --role="projects/$PROJECT_ID/roles/$MPC_ADMIN_ROLE_ID" \ + --quiet + +info "" +info "======================================" +info "MPC Permissions Setup Complete!" +info "======================================" +info "" +info "Summary:" +info " Custom Role ID: $MPC_ADMIN_ROLE_ID" +info " Custom Role: projects/$PROJECT_ID/roles/$MPC_ADMIN_ROLE_ID" +info " MPC Service Account: $MPC_SA_EMAIL" +info " Preset Service Account: $PRESET_SERVICE_ACCOUNT" +info "" +info "Next steps:" +info " 1. Verify the setup with: gcloud iam roles describe $MPC_ADMIN_ROLE_ID --project=$PROJECT_ID" +info " 2. Verify service account: gcloud iam service-accounts describe $MPC_SA_EMAIL --project=$PROJECT_ID" +info "" \ No newline at end of file diff --git a/gcp/terraform/example/.gitignore b/gcp/terraform/example/.gitignore new file mode 100644 index 0000000..3c7425c --- /dev/null +++ b/gcp/terraform/example/.gitignore @@ -0,0 +1,10 @@ +# Terraform files +.terraform/ +.terraform.lock.hcl +terraform.tfstate +terraform.tfstate.backup +*.tfvars +!terraform.tfvars.example + +# OS files +.DS_Store \ No newline at end of file diff --git a/gcp/terraform/example/README.md b/gcp/terraform/example/README.md new file mode 100644 index 0000000..17af952 --- /dev/null +++ b/gcp/terraform/example/README.md @@ -0,0 +1,138 @@ +# MPC Permissions Setup Example + +This example demonstrates how to use the MPC permissions module to set up Preset MPC access in your GCP project. + +## Prerequisites + +Before running this example, ensure you have completed the prerequisites: + +1. **GCP APIs Enabled:** + ```bash + export PROJECT_ID="your-project-id" + + gcloud services enable iam.googleapis.com \ + cloudresourcemanager.googleapis.com \ + serviceusage.googleapis.com \ + --project="$PROJECT_ID" + ``` + +2. **Organization Policy (Optional - Can be managed by Terraform):** + + **Option A: Let Terraform Manage It (Recommended)** + + This example has `manage_org_policy = true` by default, which automatically configures the organization policy. Requires Organization Admin permissions. + + **Option B: Manual Setup** + + If you don't have Organization Admin permissions, set `manage_org_policy = false` in your `terraform.tfvars` and manually apply the policy: + + ```bash + # Create policy file + cat > project-org-policy.yaml <= 1.6.3 + +4. **GCP Authentication:** + ```bash + gcloud auth application-default login + ``` + +## Usage + +### 1. Copy the Example Configuration + +```bash +cp terraform.tfvars.example terraform.tfvars +``` + +### 2. Edit terraform.tfvars + +Edit the `terraform.tfvars` file and set your project ID: + +```hcl +project_id = "your-project-id" +``` + +### 3. Initialize Terraform + +```bash +terraform init +``` + +### 4. Review the Plan + +```bash +terraform plan +``` + +This will show you what resources will be created: +- Custom IAM role with MPC permissions +- MPC service account +- IAM bindings for Preset and MPC service accounts +- Organization policy (if `manage_org_policy = true`) + +### 5. Apply the Configuration + +```bash +terraform apply +``` + +Review the planned changes and type `yes` to proceed. + +### 6. Save the Output + +After successful apply, Terraform will output the MPC service account email: + +``` +Outputs: + +custom_role_id = "projects/your-project-id/roles/PresetMPCAdmin" +custom_role_name = "projects/your-project-id/roles/PresetMPCAdmin" +mpc_service_account_email = "mpc-service-account@your-project-id.iam.gserviceaccount.com" +org_policy_managed = true +``` + +**Important:** Provide the `mpc_service_account_email` to your Preset contact. + +## Troubleshooting + +### Permission Denied + +If you receive permission denied errors, ensure you have: +- Project Owner or Editor role +- `roles/iam.roleAdmin` permission +- `roles/iam.serviceAccountAdmin` permission +- `roles/resourcemanager.projectIamAdmin` permission + +### Organization Policy Violation + +If you see errors about organization policy constraints, ensure you've added Preset's organization to your project's allowed domains (see Prerequisites). + +### API Not Enabled + +If you encounter "API not enabled" errors, run the API enablement commands from the Prerequisites section. + +## Migration from Deployment Manager + +If you have an existing Deployment Manager deployment and want to migrate to Terraform, see the detailed migration guide: [../MIGRATION.md](../MIGRATION.md) + +## Support + +For issues or questions: +1. Check the main README at `../../README_v2.md` +2. Review the module documentation at `../modules/mpc-permissions/README.md` +3. For migration issues, see `../MIGRATION.md` +4. Contact your Preset support representative \ No newline at end of file diff --git a/gcp/terraform/example/main.tf b/gcp/terraform/example/main.tf new file mode 100644 index 0000000..5822a44 --- /dev/null +++ b/gcp/terraform/example/main.tf @@ -0,0 +1,72 @@ +terraform { + required_version = ">= 1.6.3" + + required_providers { + google = { + source = "hashicorp/google" + version = ">= 5.0" + } + } +} + +provider "google" { + project = var.project_id +} + +variable "project_id" { + description = "Your GCP Project ID" + type = string +} + +variable "mpc_service_account_id" { + description = "The ID for the MPC service account (optional)" + type = string + default = "preset-mpc-sa" +} + +variable "mpc_admin_role_id" { + description = "The ID for the MPC admin custom role (optional)" + type = string + default = "PresetMPCAdminV2" +} + +variable "manage_org_policy" { + description = "Whether to manage the organization policy for allowed IAM domains. Requires Organization Admin permissions. Set to false if you don't have org admin access." + type = bool + default = true +} + +locals { + # Preset production service account + preset_service_account = " + gcloud services enable iam.googleapis.com \ + cloudresourcemanager.googleapis.com \ + serviceusage.googleapis.com \ + --project="$PROJECT_ID" + ``` + +2. **Set Organization Policy (Optional with Terraform):** + + **Option A: Manage with Terraform (Recommended)** + + Set `manage_org_policy = true` when calling this module. This will automatically configure the organization policy to allow Preset and Datadog service accounts. Requires Organization Admin permissions. + + **Option B: Manual Setup** + + If you don't have Organization Admin permissions or prefer manual setup, add the following organizations to your project's Organization Policy: + - `C0147pk0i` - Datadog + - `C01i2thyr` - Preset.io + + ```bash + # Create a policy file (project-org-policy.yaml): + constraint: constraints/iam.allowedPolicyMemberDomains + listPolicy: + allowedValues: + - C0147pk0i # Datadog + - C01i2thyr # Preset + inheritFromParent: true + + # Apply the policy (requires Org Admin access): + gcloud resource-manager org-policies set-policy \ + --project $PROJECT_ID project-org-policy.yaml + ``` + +## Usage + +### Basic Example + +```hcl +module "preset_mpc_permissions" { + source = "github.com/preset-io/mpc-init//gcp/terraform/modules/mpc-permissions?ref=1.0.0" + + project_id = "customer-project-id" + preset_service_account = "" +} +``` + +### With Organization Policy Management + +```hcl +module "preset_mpc_permissions" { + source = "github.com/preset-io/mpc-init//gcp/terraform/modules/mpc-permissions?ref=1.0.0" + + project_id = "customer-project-id" + preset_service_account = "" + # Enable organization policy management (requires Org Admin permissions) + manage_org_policy = true +} +``` + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|----------| +| project_id | The GCP project ID where MPC resources will be managed | string | - | yes | +| preset_service_account | The Preset service account email | string | - | yes | +| mpc_service_account_id | The ID for the MPC service account | string | "preset-mpc-sa" | no | +| mpc_admin_role_id | The ID for the MPC admin custom role | string | "PresetMPCAdminV2" | no | +| manage_org_policy | Whether to manage the organization policy for allowed IAM domains (requires Org Admin permissions) | bool | false | no | +| additional_allowed_domains | Additional organization customer IDs to allow in the IAM policy member domains | list(string) | [] | no | + +## Outputs + +| Name | Description | +|------|-------------| +| custom_role_id | The ID of the custom IAM role | +| custom_role_name | The name of the custom IAM role | +| mpc_service_account_email | The email of the MPC service account | +| mpc_service_account_id | The ID of the MPC service account | +| mpc_service_account_unique_id | The unique ID of the MPC service account | +| org_policy_managed | Whether the organization policy is managed by this module | + +## Permissions + +The custom role includes permissions for: +- **CloudSQL**: Database and instance management +- **Compute Engine**: VMs, disks, addresses, networking +- **VPC**: Networks, subnets, firewalls, routes, NAT gateways +- **GKE**: Kubernetes cluster and workload management +- **Cloud DNS**: Managed zones and DNS records +- **Cloud Storage**: Bucket and object management +- **IAM**: Service account and workload identity management +- **Redis**: Memorystore instance management +- **Service Networking**: VPC peering for private services + +## Migration from Deployment Manager + +If you're migrating from the legacy Deployment Manager template, see the detailed migration guide: [MIGRATION.md](./MIGRATION.md) + +This module uses **side-by-side migration** (no complex import commands needed!): +- Creates NEW resources with different default names +- Old resources stay untouched during migration +- Zero risk, easy rollback + +**Default names avoid conflicts with old Deployment Manager resources:** +- New service account: `preset-mpc-sa` (old was `mpc-service-account`) +- New role: `PresetMPCAdminV2` (old was `PresetMPCAdmin`) + +**Quick summary:** +1. Run `terraform apply` to create new resources alongside old ones +2. Provide new service account email to Preset +3. Preset switches their automation to use new service account +4. Optionally clean up old resources after confirmation + +## Requirements + +| Name | Version | +|------|---------| +| terraform | >= 1.6.3 | +| google | >= 5.0 | + +## Providers + +| Name | Version | +|------|---------| +| google | >= 5.0 | \ No newline at end of file diff --git a/gcp/terraform/modules/mpc-permissions/default_variables.tf b/gcp/terraform/modules/mpc-permissions/default_variables.tf new file mode 100644 index 0000000..d4fa3cc --- /dev/null +++ b/gcp/terraform/modules/mpc-permissions/default_variables.tf @@ -0,0 +1,23 @@ +variable "mpc_service_account_id" { + description = "The ID for the MPC service account" + type = string + default = "preset-mpc-sa" +} + +variable "mpc_admin_role_id" { + description = "The ID for the MPC admin custom role" + type = string + default = "PresetMPCAdminV2" +} + +variable "manage_org_policy" { + description = "Whether to manage the organization policy for allowed IAM domains. Requires Organization Admin permissions. Set to false if you don't have org admin access or prefer to manage this manually." + type = bool + default = false +} + +variable "additional_allowed_domains" { + description = "Additional organization customer IDs to allow in the IAM policy member domains constraint (beyond Preset and Datadog)" + type = list(string) + default = [] +} \ No newline at end of file diff --git a/gcp/terraform/modules/mpc-permissions/locals.tf b/gcp/terraform/modules/mpc-permissions/locals.tf new file mode 100644 index 0000000..113036e --- /dev/null +++ b/gcp/terraform/modules/mpc-permissions/locals.tf @@ -0,0 +1,194 @@ +locals { + mpc_permissions = [ + # Permissions for CloudSQL + "cloudsql.databases.create", + "cloudsql.databases.delete", + "cloudsql.databases.get", + "cloudsql.instances.create", + "cloudsql.instances.delete", + "cloudsql.instances.list", + "cloudsql.instances.get", + "cloudsql.instances.update", + "cloudsql.users.list", + "cloudsql.users.get", + + # Permissions for Compute Engine + "compute.addresses.create", + "compute.addresses.delete", + "compute.addresses.get", + "compute.addresses.setLabels", + "compute.instances.create", + "compute.instances.delete", + "compute.instances.start", + "compute.instances.stop", + "compute.instances.setMetadata", + "compute.instances.setTags", + "compute.instances.update", + "compute.disks.create", + "compute.disks.delete", + "compute.disks.update", + + # Permissions for Storage + "storage.buckets.create", + "storage.buckets.delete", + "storage.buckets.get", + "storage.buckets.list", + "storage.objects.create", + "storage.objects.delete", + + # VPC Network Permissions + "compute.networks.create", + "compute.networks.delete", + "compute.networks.get", + "compute.networks.list", + "compute.networks.updatePolicy", + "compute.networks.update", + "compute.networks.use", + + # Subnet Permissions + "compute.subnetworks.create", + "compute.subnetworks.delete", + "compute.subnetworks.expandIpCidrRange", + "compute.subnetworks.get", + "compute.subnetworks.list", + "compute.subnetworks.update", + + # Firewall Rules Permissions + "compute.firewalls.create", + "compute.firewalls.delete", + "compute.firewalls.get", + "compute.firewalls.list", + "compute.firewalls.update", + "compute.globalOperations.get", + "compute.globalOperations.list", + "compute.globalAddresses.createInternal", + "compute.globalAddresses.deleteInternal", + "compute.globalAddresses.get", + "compute.globalAddresses.create", + "compute.globalAddresses.setLabels", + + # Routes Permissions + "compute.routes.create", + "compute.routes.delete", + "compute.routes.get", + "compute.routes.list", + + # NAT Gateway Permissions + "compute.routers.create", + "compute.routers.delete", + "compute.routers.get", + "compute.routers.list", + "compute.routers.update", + "compute.routers.use", + + # Additional compute permissions + "compute.regionOperations.get", + "compute.regionOperations.list", + "compute.zoneOperations.get", + "compute.zoneOperations.list", + "compute.zones.list", + "compute.securityPolicies.create", + "compute.securityPolicies.delete", + "compute.securityPolicies.get", + "compute.securityPolicies.getIamPolicy", + "compute.securityPolicies.list", + "compute.securityPolicies.setIamPolicy", + "compute.securityPolicies.update", + "compute.securityPolicies.use", + "compute.instanceGroupManagers.get", + "compute.instanceGroupManagers.list", + + # Permissions for Kubernetes Engine + "container.clusters.create", + "container.clusters.delete", + "container.clusters.get", + "container.clusters.list", + "container.clusters.update", + "container.namespaces.create", + "container.namespaces.delete", + "container.namespaces.get", + "container.operations.get", + "container.operations.list", + "container.secrets.create", + "container.secrets.delete", + "container.secrets.get", + "container.secrets.update", + + # Permissions for DNS + "dns.managedZones.get", + "dns.managedZones.list", + "dns.managedZones.create", + "dns.managedZones.delete", + "dns.managedZones.update", + "dns.changes.create", + "dns.changes.get", + "dns.changes.list", + "dns.resourceRecordSets.create", + "dns.resourceRecordSets.update", + "dns.resourceRecordSets.delete", + "dns.resourceRecordSets.get", + "dns.resourceRecordSets.list", + + # VPC peering to service networking + "servicenetworking.services.addPeering", + + # IAM Permissions + "iam.serviceAccounts.actAs", + "iam.serviceAccounts.create", + "iam.serviceAccounts.delete", + "iam.serviceAccounts.disable", + "iam.serviceAccounts.enable", + "iam.serviceAccounts.get", + "iam.serviceAccounts.getIamPolicy", + "iam.serviceAccounts.list", + "iam.serviceAccounts.setIamPolicy", + "iam.serviceAccounts.update", + "iam.serviceAccountKeys.create", + "iam.serviceAccountKeys.delete", + "iam.serviceAccountKeys.list", + "iam.serviceAccountKeys.get", + + # Project IAM Admin permissions + "iam.policybindings.get", + "iam.policybindings.list", + + # For Workload Identity Pools + "iam.workloadIdentityPools.create", + "iam.workloadIdentityPools.delete", + "iam.workloadIdentityPools.get", + "iam.workloadIdentityPools.list", + "iam.workloadIdentityPools.update", + "iam.workloadIdentityPoolProviders.create", + "iam.workloadIdentityPoolProviders.delete", + "iam.workloadIdentityPoolProviders.get", + "iam.workloadIdentityPoolProviders.list", + "iam.workloadIdentityPoolProviders.update", + + # Redis permissions + "redis.instances.get", + "redis.instances.list", + "redis.instances.create", + "redis.instances.update", + "redis.instances.delete", + "redis.operations.get", + "redis.operations.list", + + # Resource Manager permissions + "resourcemanager.projects.createPolicyBinding", + "resourcemanager.projects.deletePolicyBinding", + "resourcemanager.projects.get", + "resourcemanager.projects.getIamPolicy", + "resourcemanager.projects.searchPolicyBindings", + "resourcemanager.projects.setIamPolicy", + "resourcemanager.projects.updatePolicyBinding", + + # Service Usage permissions + "serviceusage.services.enable", + "serviceusage.services.get", + "serviceusage.services.list", + + # Service Networking permissions + "servicenetworking.services.get", + "servicenetworking.services.deleteConnection", + ] +} \ No newline at end of file diff --git a/gcp/terraform/modules/mpc-permissions/main.tf b/gcp/terraform/modules/mpc-permissions/main.tf new file mode 100644 index 0000000..cf3ce4f --- /dev/null +++ b/gcp/terraform/modules/mpc-permissions/main.tf @@ -0,0 +1,61 @@ +# Custom IAM Role with full MPC permissions +resource "google_project_iam_custom_role" "preset_mpc_admin" { + project = var.project_id + role_id = var.mpc_admin_role_id + title = "Preset Admin Access Role" + description = "This role provides Preset access to your Project." + stage = "GA" + + permissions = local.mpc_permissions +} + +# MPC Service Account +resource "google_service_account" "mpc_admin" { + project = var.project_id + account_id = var.mpc_service_account_id + display_name = "Preset MPC Service Account" + description = "Service account for Preset to manage MPC infrastructure" +} + +# Allow Preset service account to impersonate the MPC service account +resource "google_service_account_iam_member" "preset_token_creator" { + service_account_id = google_service_account.mpc_admin.name + role = "roles/iam.serviceAccountTokenCreator" + member = "serviceAccount:${var.preset_service_account}" +} + +# Bind custom role to Preset service account +resource "google_project_iam_member" "preset_admin_access" { + project = var.project_id + role = google_project_iam_custom_role.preset_mpc_admin.id + member = "serviceAccount:${var.preset_service_account}" +} + +# Bind custom role to MPC service account +resource "google_project_iam_member" "mpc_admin_access" { + project = var.project_id + role = google_project_iam_custom_role.preset_mpc_admin.id + member = "serviceAccount:${google_service_account.mpc_admin.email}" +} + +# Organization Policy to allow Preset and Datadog service accounts +resource "google_project_organization_policy" "allowed_policy_member_domains" { + count = var.manage_org_policy ? 1 : 0 + project = var.project_id + + constraint = "constraints/iam.allowedPolicyMemberDomains" + + list_policy { + allow { + values = concat( + [ + "C0147pk0i", # Datadog + "C01i2thyr", # Preset.io + ], + var.additional_allowed_domains + ) + } + + inherit_from_parent = true + } +} \ No newline at end of file diff --git a/gcp/terraform/modules/mpc-permissions/outputs.tf b/gcp/terraform/modules/mpc-permissions/outputs.tf new file mode 100644 index 0000000..1036d65 --- /dev/null +++ b/gcp/terraform/modules/mpc-permissions/outputs.tf @@ -0,0 +1,29 @@ +output "custom_role_id" { + description = "The ID of the custom IAM role" + value = google_project_iam_custom_role.preset_mpc_admin.id +} + +output "custom_role_name" { + description = "The name of the custom IAM role" + value = google_project_iam_custom_role.preset_mpc_admin.name +} + +output "mpc_service_account_email" { + description = "The email of the MPC service account" + value = google_service_account.mpc_admin.email +} + +output "mpc_service_account_id" { + description = "The ID of the MPC service account" + value = google_service_account.mpc_admin.id +} + +output "mpc_service_account_unique_id" { + description = "The unique ID of the MPC service account" + value = google_service_account.mpc_admin.unique_id +} + +output "org_policy_managed" { + description = "Whether the organization policy is managed by this module" + value = var.manage_org_policy +} \ No newline at end of file diff --git a/gcp/terraform/modules/mpc-permissions/required_variables.tf b/gcp/terraform/modules/mpc-permissions/required_variables.tf new file mode 100644 index 0000000..72204f0 --- /dev/null +++ b/gcp/terraform/modules/mpc-permissions/required_variables.tf @@ -0,0 +1,9 @@ +variable "project_id" { + description = "The GCP project ID where MPC resources will be managed" + type = string +} + +variable "preset_service_account" { + description = "The Preset service account email that will manage MPC resources" + type = string +} \ No newline at end of file diff --git a/gcp/terraform/modules/mpc-permissions/versions.tf b/gcp/terraform/modules/mpc-permissions/versions.tf new file mode 100644 index 0000000..9e75132 --- /dev/null +++ b/gcp/terraform/modules/mpc-permissions/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.6.3" + + required_providers { + google = { + source = "hashicorp/google" + version = ">= 5.0" + } + } +} \ No newline at end of file diff --git a/generator/cloudformation/cfn.go b/generator/cloudformation/cfn.go deleted file mode 100644 index 955dbb6..0000000 --- a/generator/cloudformation/cfn.go +++ /dev/null @@ -1,168 +0,0 @@ -package cloudformation - -import ( - "fmt" - "github.com/awslabs/goformation/v7/cloudformation" - "github.com/awslabs/goformation/v7/cloudformation/iam" - "github.com/preset-io/mpc-init/generator" - "io/ioutil" - "log" - "os" - "path/filepath" - "strings" -) - -var roles = []map[string]*iam.Role{ - { - "clientAccessRole": { - RoleName: cloudformation.String("mpc-account-mgmt"), - Description: cloudformation.String("Preset Cluster Provisioner Role"), - AssumeRolePolicyDocument: map[string]interface{}{ - "Version": "2012-10-17", - "Statement": []map[string]interface{}{ - { - "Sid": "", - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": map[string]interface{}{ - "AWS": []string{ - cloudformation.Join("", []string{ - "arn:aws:iam::", - cloudformation.Ref("AWS::AccountId"), - ":root", - }), - }, - }, - }, - }, - }, - }, - }, { - "workspaceProvisionerRole": { - RoleName: cloudformation.String("preset-admin"), - Description: cloudformation.String("Preset Cluster Provisioner Role"), - AssumeRolePolicyDocument: map[string]interface{}{ - "Version": "2012-10-17", - "Statement": []map[string]interface{}{ - { - "Sid": "", - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": map[string]interface{}{ - "AWS": []string{ - cloudformation.Join("", []string{ - "arn:aws:iam::", - cloudformation.Ref("PresetEnvAccountId"), - ":root", - }), - cloudformation.Join("", []string{ - "arn:aws:iam::", - cloudformation.Ref("PresetDevOpsAccountId"), - ":root", - }), - }, - }, - }, - }, - }, - }, - }, -} - -func GenCfn(PolicyRoleMap []*generator.PolicyRoleMapping, policiesPath string) { - // Create a new CloudFormation template - template := NewTemplate() - - // Roles - templateRoles(template) - - // Resources - templateResources(template, PolicyRoleMap, policiesPath) - - // Generate a YAML AWS CloudFormation template - yamlTemplate, err := GenerateYaml(template) - if err != nil { - log.Fatalf("error creating yaml: %v", err) - } - - writeFile(yamlTemplate, "cloudformation/preset-mpc-iam.yaml") -} - -func writeFile(content, path string) { - file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) - if err != nil { - fmt.Println(err) - return - } - defer file.Close() - - _, err = file.Write([]byte(content)) - if err != nil { - fmt.Println(err) - return - } -} - -func NewTemplate() *cloudformation.Template { - template := cloudformation.NewTemplate() - template.Description = "This template creates resources for a Preset Superset workspace in your AWS account." - template.Parameters = map[string]cloudformation.Parameter{ - "AccountId": { - Description: "MPC account id", - Type: "String", - }, - "PresetEnvAccountId": { - Description: "Preset environment account id", - Type: "String", - }, - "PresetDevOpsAccountId": { - Description: "Preset devops environment account id", - Type: "String", - }, - } - return template -} - -func GenerateYaml(template *cloudformation.Template) (string, error) { - y, err := template.YAML() - if err != nil { - return "", fmt.Errorf("Failed to generate YAML: %s\n", err) - } else { - return fmt.Sprintf("%s\n", string(y)), nil - } -} - -func templateRoles(template *cloudformation.Template) { - for _, r := range roles { - for name, role := range r { - template.Resources[name] = role - } - } -} - -func templateResources(template *cloudformation.Template, policyRoleMappings []*generator.PolicyRoleMapping, policiesPath string) { - cwd, err := os.Getwd() - if err != nil { - log.Fatalf("Failed to get current working directory: %v", err) - } - - for _, policy := range policyRoleMappings { - // read policy document from file - fileName := filepath.Join(cwd, fmt.Sprintf("%s/%s.tpl", policiesPath, policy.PolicyName)) - J, err := ioutil.ReadFile(fileName) - if err != nil { - log.Fatalf("error reading policy document from file %s: %v", fileName, err) - return - } - - var doc []string - doc = append(doc, strings.Replace(string(J), `""`, `!Sub "arn:aws:lambda:*:${AWS::AccountId}:function:datadog_log_monitoring"`, 1)) - doc = append(doc, strings.Replace(string(J), `""`, `!Join [ "", ["arn:aws:kms:*:", !Ref "AWS::AccountId", ":alias/velero-backups-*"] ]`, 1)) - - template.Resources[policy.ResourceName] = &iam.ManagedPolicy{ - ManagedPolicyName: &policy.PolicyName, - PolicyDocument: strings.Join(doc, "\n"), - Roles: []string{cloudformation.Ref(cloudformation.StringValue(&policy.RoleRef))}, - } - } -} diff --git a/generator/iam/policies/client-access-management.tpl b/generator/iam/policies/client-access-management.tpl deleted file mode 100644 index 052718c..0000000 --- a/generator/iam/policies/client-access-management.tpl +++ /dev/null @@ -1,32 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Action": [ - "eks:AccessKubernetesApi", - "eks:Describe*", - "eks:List*", - "ec2:*", - "ssm:*" - ], - "Effect": "Allow", - "Resource": "*" - },{ - "Effect": "Deny", - "Action": "ssm:*", - "Resource": "*", - "Condition": { - "NotIpAddress": { - "aws:SourceIp": [ - "35.161.45.11/32", - "52.32.136.34/32", - "54.244.23.85/32", - "52.88.46.148/32", - "35.161.104.245/32", - "52.88.129.18/32" - ] - } - } - } - ] -} diff --git a/generator/iam/policies/infra.tpl b/generator/iam/policies/infra.tpl deleted file mode 100644 index 1565429..0000000 --- a/generator/iam/policies/infra.tpl +++ /dev/null @@ -1,88 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "VPC", - "Effect": "Allow", - "Action": [ - "ec2:ModifyVpc*", - "ec2:Attach*", - "ec2:Detach*" - ], - "Resource": "*" - },{ - "Sid": "cfn", - "Effect": "Allow", - "Action": [ - "cloudformation:Get*", - "cloudformation:List*", - "cloudformation:Describe*", - "cloudformation:ValidateTemplate", - "cloudformation:Create*", - "cloudformation:Update*", - "cloudformation:Tag*", - "cloudformation:Untag*" - ], - "Resource": "*" - }, - { - "Sid": "cfnDelete", - "Effect": "Allow", - "Action": [ - "cloudformation:Delete*" - ], - "Resource": "*" - }, - { - "Sid": "lambda", - "Effect": "Allow", - "Action": [ - "lambda:Add*", - "lambda:Remove*", - "lambda:List*", - "lambda:Get*", - "lambda:Create*", - "lambda:Update*", - "lambda:Put*", - "lambda:Tag*", - "lambda:Untag*", - "lambda:Publish*" - ], - "Resource": "*" - }, - { - "Sid": "LambdaDelete", - "Effect": "Allow", - "Action": [ - "lambda:DeleteFunction" - ], - "Resource": "" - },{ - "Sid": "secretsManager", - "Effect": "Allow", - "Action": [ - "secretsmanager:List*", - "secretsmanager:Describe*", - "secretsmanager:Get*", - "secretsmanager:Tag*", - "secretsmanager:Untag*", - "secretsmanager:Create*", - "secretsmanager:Update*", - "secretsmanager:Put*" - ], - "Resource": "*" - },{ - "Sid": "SecretManagerDelete", - "Effect": "Allow", - "Action": [ - "secretsmanager:Delete*" - ], - "Resource": "*", - "Condition": { - "StringEquals": { - "aws:ResourceTag/Owner": "Preset-io" - } - } - } - ] -} diff --git a/generator/iam/policies/preset-provision-workspaces.tpl b/generator/iam/policies/preset-provision-workspaces.tpl deleted file mode 100644 index 6f0bdc6..0000000 --- a/generator/iam/policies/preset-provision-workspaces.tpl +++ /dev/null @@ -1,310 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "route53", - "Effect": "Allow", - "Action": [ - "route53:Get*", - "route53:List*", - "route53:CreateHostedZone", - "route53:Change*" - ], - "Resource": "*" - }, { - "Sid": "route53Delete", - "Effect": "Allow", - "Action": [ - "route53:DeleteHostedZone" - ], - "Resource": "*" - }, { - "Sid": "acm", - "Effect": "Allow", - "Action": [ - "acm:RequestCertificate", - "acm:AddTagsToCertificate", - "acm:DescribeCertificate", - "acm:ListTagsForCertificate" - ], - "Resource": "*" - }, { - "Sid": "acmDelete", - "Effect": "Allow", - "Action": [ - "acm:DeleteCertificate" - ], - "Resource": "*", - "Condition": { - "StringEquals": { - "aws:ResourceTag/Owner": "Preset-io" - } - } - }, { - "Sid": "logs", - "Effect": "Allow", - "Action": [ - "logs:Untag*", - "logs:Tag*", - "logs:Create*", - "logs:Put*", - "logs:Describe*", - "logs:List*" - ], - "Resource": "*" - }, { - "Sid": "logsDelete", - "Effect": "Allow", - "Action": [ - "logs:DeleteLogGroup", - "logs:DeleteSubscriptionFilter" - ], - "Resource": "*", - "Condition": { - "StringEquals": { - "aws:ResourceTag/Owner": "Preset-io" - } - } - }, { - "Sid": "rds", - "Effect": "Allow", - "Action": [ - "rds:RemoveTagsFromResource", - "rds:AddTagsToResource", - "rds:Create*", - "rds:Describe*", - "rds:Modify*", - "rds:ListTagsForResource" - ], - "Resource": "*" - }, { - "Sid": "rdsDelete", - "Effect": "Allow", - "Action": [ - "rds:Delete*" - ], - "Resource": "*", - "Condition": { - "StringEquals": { - "aws:ResourceTag/Owner": "Preset-io" - } - } - }, { - "Sid": "Elasticache", - "Effect": "Allow", - "Action": [ - "elasticache:Describe*", - "elasticache:Create*", - "elasticache:AddTagsToResource", - "elasticache:RemoveTagsFromResource", - "elasticache:ModifyCacheParameterGroup", - "elasticache:ListTagsForResource" - ], - "Resource": "*" - }, { - "Sid": "elasticacheDelete", - "Effect": "Allow", - "Action": ["elasticache:Delete*"], - "Resource": "*", - "Condition": { - "StringEquals": { "aws:ResourceTag/Owner": "Preset-io"} - } - }, { - "Sid": "ec2", - "Effect": "Allow", - "Action": [ - "ec2:AllocateAddress", - "ec2:AssignPrivateIpAddresses", - "ec2:Associate*", - "ec2:AttachNetworkInterface", - "ec2:AuthorizeSecurityGroupEgress", - "ec2:AuthorizeSecurityGroupIngress", - "ec2:Create*", - "ec2:Describe*", - "ec2:Detach*", - "ec2:Disassociate*", - "ec2:ReleaseAddress", - "ec2:Revoke*", - "ec2:Update*", - "ec2:GetLaunchTemplateData", - "ec2:ModifyLaunchTemplate", - "ec2:RunInstances", - "elasticloadbalancing:Describe*" - ], - "Resource": "*" - }, { - "Sid": "ec2Delete", - "Effect": "Allow", - "Action": [ - "ec2:Delete*", - "ec2:TerminateInstances" - ], - "Resource": "*", - "Condition": { - "StringEquals": { - "aws:ResourceTag/Owner": "Preset-io" - } - } - },{ - "Sid": "ec2DeleteTags", - "Effect": "Allow", - "Action": [ - "ec2:DeleteTags" - ], - "Resource": "*" - }, { - "Sid": "eks", - "Effect": "Allow", - "Action": [ - "eks:Associate*", - "eks:Create*", - "eks:Describe*", - "eks:List*", - "eks:Update*", - "eks:TagResource", - "eks:UntagResource", - "eks:DeleteAddon" - ], - "Resource": "*" - }, { - "Sid": "eksDelete", - "Effect": "Allow", - "Action": [ - "eks:Delete*" - ], - "Resource": "*", - "Condition": { - "StringEquals": { - "aws:ResourceTag/Owner": "Preset-io" - } - } - }, { - "Sid": "Autoscalng", - "Effect": "Allow", - "Action": [ - "autoscaling:EnableMetricsCollection", - "autoscaling:AttachInstances", - "autoscaling:Create*", - "autoscaling:Delete*", - "autoscaling:Describe*", - "autoscaling:DetachInstances", - "autoscaling:SetDesiredCapacity", - "autoscaling:UpdateAutoScalingGroup", - "autoscaling:SuspendProcesses" - ], - "Resource": "*" - }, { - "Sid": "iam", - "Effect": "Allow", - "Action": [ - "iam:AddRoleToInstanceProfile", - "iam:AttachRolePolicy", - "iam:CreateInstanceProfile", - "iam:CreateOpenIDConnectProvider", - "iam:CreatePolicy", - "iam:CreatePolicyVersion", - "iam:CreateRole", - "iam:CreateServiceLinkedRole", - "iam:Get*", - "iam:List*", - "iam:PutRolePolicy", - "iam:RemoveRoleFromInstanceProfile", - "iam:TagOpenIDConnectProvider", - "iam:TagRole", - "iam:UntagRole", - "iam:TagPolicy", - "iam:TagInstanceProfile", - "iam:UpdateAssumeRolePolicy", - "iam:DetachRolePolicy", - "iam:DeletePolicy", - "iam:UpdateRoleDescription" - ], - "Resource": "*" - }, { - "Sid": "iamPassRole", - "Effect": "Allow", - "Action": "iam:PassRole", - "Resource": "*", - "Condition": { - "StringEquals": { - "iam:PassedToService": [ - "ec2.amazonaws.com", - "lambda.amazonaws.com", - "vpc-flow-logs.amazonaws.com", - "eks.amazonaws.com", - "rds.amazonaws.com", - "mq.amazonaws.com", - "s3.amazonaws.com" - ] - } - } - },{ - "Sid": "iamDelete", - "Effect": "Allow", - "Action": [ - "iam:Delete*" - ], - "Resource": "*", - "Condition": { - "StringEquals": { - "aws:ResourceTag/Owner": "Preset-io" - } - } - }, { - "Sid": "kms", - "Effect": "Allow", - "Action": [ - "kms:*" - ], - "Resource": "*" - }, { - "Sid": "kmsDelete", - "Effect": "Allow", - "Action": [ - "kms:ScheduleKeyDeletion", - "kms:DeleteAlias" - ], - "Resource": "" - }, { - "Sid": "s3", - "Effect": "Allow", - "Action": [ - "s3:ListBucketVersions", - "s3:CreateBucket", - "s3:Get*", - "s3:Put*", - "s3:List*", - "s3:Replicate*" - ], - "Resource": "*" - }, { - "Sid": "states", - "Effect": "Allow", - "Action": [ - "states:ListStateMachines" - ], - "Resource": "*" - }, { - "Sid": "s3Delete", - "Effect": "Allow", - "Action": [ - "s3:Delete*" - ], - "Resource": "*" - },{ - "Sid": "wafv2", - "Effect": "Allow", - "Action": [ - "wafv2:*" - ], - "Resource": "*" - },{ - "Sid": "firehose", - "Effect": "Allow", - "Action": [ - "firehose:*" - ], - "Resource": "*" - } - ] -} diff --git a/generator/terraform/tf.go b/generator/terraform/tf.go deleted file mode 100644 index 59920ff..0000000 --- a/generator/terraform/tf.go +++ /dev/null @@ -1,168 +0,0 @@ -package terraform - -import ( - "fmt" - "github.com/hashicorp/hcl/v2/hclwrite" - "github.com/preset-io/mpc-init/generator" - "io/ioutil" - "log" - "os" - "path/filepath" - "strings" -) - -const ( - presetAdminRole = "preset-admin" - clientAccessRole = "mpc-account-mgmt" -) - -func GenTf(PolicyRoleMap []*generator.PolicyRoleMapping, policiesPath string) { - cwd, err := os.Getwd() - if err != nil { - log.Fatalf("Failed to get current working directory: %v", err) - } - - modulePath := filepath.Join(cwd, fmt.Sprintf("modules/mpc_iam_init")) - - account := "${var.aws_account_id}" - presetDevOpsAccount := "${var.preset_devops_aws_account_id}" - presetTargetEnvAccount := "${var.preset_target_env_account_id}" - - var body []string - body = append(body, "# The content of this file is autogenerated by the generator.") - body = append(body, createRole(presetAdminRole, []string{presetTargetEnvAccount, presetDevOpsAccount})) - body = append(body, createRole(clientAccessRole, []string{account})) - resources := tfResources(PolicyRoleMap, policiesPath, modulePath) - body = append(body, resources...) - - var vars []string - vars = append(vars, createVar(account)) - vars = append(vars, createVar(presetDevOpsAccount)) - vars = append(vars, createVar(presetTargetEnvAccount)) - writeFile(strings.Join(vars, "\n"), fmt.Sprintf("%s/required-variables.tf", modulePath)) - writeFile(strings.Join(body, "\n"), fmt.Sprintf("%s/main.tf", modulePath)) -} - -func createRole(name string, accountIds []string) string { - var principals []string - for _, act := range accountIds { - principals = append(principals, fmt.Sprintf(`"arn:aws:iam::%s:root"`, act)) - } - - return fmt.Sprintf(` -resource "aws_iam_role" "%s" { - name = "%s" - - assume_role_policy = jsonencode({ - Version = "2012-10-17", - Statement = [ - { - Action = "sts:AssumeRole", - Effect = "Allow", - Principal = { - AWS = [ - %s - ] - } - } - ] - }) -} -`, strings.Replace(name, "-", "_", -1), name, strings.Join(principals, ", ")) -} - -func createVar(name string) string { - startIndex := strings.LastIndex(name, ".") + 1 - endIndex := len(name) - 1 - n := name[startIndex:endIndex] - - return strings.Replace(` -variable "" { - description = " aws account number" - type = string -}`, "", n, -1) -} - -func writeFile(content, path string) { - file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) - if err != nil { - fmt.Println(err) - return - } - defer file.Close() - - formatted := hclwrite.Format([]byte(content)) - _, err = file.Write(formatted) - if err != nil { - fmt.Println(err) - return - } -} - -func tfResources(policyRoleMappings []*generator.PolicyRoleMapping, policiesPath, modulePath string) []string { - var resources []string - cwd, err := os.Getwd() - if err != nil { - log.Fatalf("Failed to get current working directory: %v", err) - } - - for _, policy := range policyRoleMappings { - // read policy document from file - fileName := filepath.Join(cwd, fmt.Sprintf("%s/%s.tpl", policiesPath, policy.PolicyName)) - doc, err := ioutil.ReadFile(fileName) - if err != nil { - log.Fatalf("error reading policy document from file %s: %v", fileName, err) - } - - var roleResourceName string - switch policy.RoleRef { - case "workspaceProvisionerRole": - roleResourceName = presetAdminRole - case "clientAccessRole": - roleResourceName = clientAccessRole - } - - template := ` - -resource "aws_iam_policy" "" { - name = "" - path = "/" - - policy = templatefile("", { aws_account_id = var.aws_account_id }) -} - -resource "aws_iam_role_policy_attachment" "attach_" { - role = aws_iam_role..name - policy_arn = aws_iam_policy..arn -}` - - policyName := strings.Replace(policy.PolicyName, "-", "_", -1) - roleName := strings.Replace(roleResourceName, "-", "_", -1) - - infraPolicyReplacement := "arn:aws:lambda:*:${aws_account_id}:function:datadog_log_monitoring" - workspaceProvisionerPolicyReplacement := "arn:aws:kms:*:${aws_account_id}:alias/velero-backups-*" - - policyRelativeFilePath := fmt.Sprintf("${path.module}/iam_policies/%s.json.tpl", policy.PolicyName) - policyAbsFilePath := fmt.Sprintf("%s/iam_policies/%s.json.tpl", modulePath, policy.PolicyName) - - resource := strings.Replace(template, "", policyName, -1) - resource = strings.Replace(resource, "", roleName, -1) - resource = strings.Replace(resource, "", policyRelativeFilePath, -1) - - var data string - - switch policy.PolicyName { - case "client-access-management": - data = string(doc) - case "infra": - data = strings.Replace(string(doc), "", infraPolicyReplacement, -1) - case "preset-provision-workspaces": - data = strings.Replace(string(doc), "", workspaceProvisionerPolicyReplacement, -1) - } - - writeFile(data, policyAbsFilePath) - - resources = append(resources, resource) - } - return resources -} diff --git a/generator/types.go b/generator/types.go deleted file mode 100644 index b42c804..0000000 --- a/generator/types.go +++ /dev/null @@ -1,11 +0,0 @@ -package generator - -type ManagedPolicies struct { - policies []*PolicyRoleMapping -} - -type PolicyRoleMapping struct { - ResourceName string - PolicyName string - RoleRef string -} diff --git a/go.mod b/go.mod deleted file mode 100644 index 6369443..0000000 --- a/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module github.com/preset-io/mpc-init - -go 1.19 - -require ( - github.com/awslabs/goformation/v7 v7.3.0 - github.com/hashicorp/hcl/v2 v2.16.0 -) - -require ( - github.com/agext/levenshtein v1.2.1 // indirect - github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect - github.com/google/go-cmp v0.5.8 // indirect - github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect - github.com/zclconf/go-cty v1.12.1 // indirect - golang.org/x/text v0.3.7 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) diff --git a/go.sum b/go.sum deleted file mode 100644 index 643fb03..0000000 --- a/go.sum +++ /dev/null @@ -1,30 +0,0 @@ -github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8= -github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= -github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= -github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= -github.com/awslabs/goformation/v7 v7.3.0 h1:Um173eo2D22bGrXfLtZxjWxSqSvEgvq8dX3wHcWQTxk= -github.com/awslabs/goformation/v7 v7.3.0/go.mod h1:PGIiaj6RN0t1QJxeYWhlw/3PS9wIOJfa+TGrZRXu5zo= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/hashicorp/hcl/v2 v2.16.0 h1:MPq1q615H+9wBAdE3EbwEd6imSohElrIguuasbQruB0= -github.com/hashicorp/hcl/v2 v2.16.0/go.mod h1:JRmR89jycNkrrqnMmvPDMd56n1rQJ2Q6KocSLCMCXng= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= -github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4= -github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM= -github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= -github.com/onsi/ginkgo/v2 v2.3.1 h1:8SbseP7qM32WcvE6VaN6vfXxv698izmsJ1UQX9ve7T8= -github.com/onsi/gomega v1.22.1 h1:pY8O4lBfsHKZHM/6nrxkhVPUznOlIu3quZcKP/M20KI= -github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= -github.com/zclconf/go-cty v1.12.1 h1:PcupnljUm9EIvbgSHQnHhUr3fO6oFmkOrvs2BAFNXXY= -github.com/zclconf/go-cty v1.12.1/go.mod h1:s9IfD1LK5ccNMSWCVFCE2rJfHiZgi7JijgeWIMfhLvA= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go deleted file mode 100644 index c0f369b..0000000 --- a/main.go +++ /dev/null @@ -1,41 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "github.com/preset-io/mpc-init/generator" - "github.com/preset-io/mpc-init/generator/cloudformation" - "github.com/preset-io/mpc-init/generator/terraform" -) - -const policiesPath = "./generator/iam/policies" - -func main() { - var PolicyRoleMap = []*generator.PolicyRoleMapping{ - { - ResourceName: "clientAccessPolicy", - PolicyName: "client-access-management", - RoleRef: "clientAccessRole", - }, { - ResourceName: "infraPolicy", - PolicyName: "infra", - RoleRef: "workspaceProvisionerRole", - }, { - ResourceName: "provisionerPolicy", - PolicyName: "preset-provision-workspaces", - RoleRef: "workspaceProvisionerRole", - }, - } - - tf := flag.Bool("tf", false, "Generate Terraform") - cfn := flag.Bool("cfn", false, "Generate CloudFormation") - flag.Parse() - - if *tf { - terraform.GenTf(PolicyRoleMap, policiesPath) - } else if *cfn { - cloudformation.GenCfn(PolicyRoleMap, policiesPath) - } else { - fmt.Println("Please specify either -tf or -cfn") - } -} diff --git a/modules/mpc_iam_init/.terraform.lock.hcl b/modules/mpc_iam_init/.terraform.lock.hcl deleted file mode 100644 index 935c2ad..0000000 --- a/modules/mpc_iam_init/.terraform.lock.hcl +++ /dev/null @@ -1,25 +0,0 @@ -# This file is maintained automatically by "terraform init". -# Manual edits may be lost in future updates. - -provider "registry.terraform.io/hashicorp/aws" { - version = "4.53.0" - constraints = ">= 3.39.0" - hashes = [ - "h1:P6ZZ716SRIimw0t/SAgYbOMZtO0HDvwVQKxyHEW6aaE=", - "zh:0d44171544a916adf0fa96b7d0851a49d8dec98f71f0229dfd2d178958b3996b", - "zh:16945808ce26b86af7f5a77c4ab1154da786208c793abb95b8f918b4f48daded", - "zh:1a57a5a30cef9a5867579d894b74f60bb99afc7ca0d030d49a80ad776958b428", - "zh:2c718734ae17430d7f598ca0b4e4f86d43d66569c72076a10f4ace3ff8dfc605", - "zh:46fdf6301cb2fa0a4d122d1a8f75f047b6660c24851d6a4537ee38926a86485d", - "zh:53a53920b38a9e1648e85c6ee33bccf95bfcd067bffc4934a2af55621e6a6bd9", - "zh:548d927b234b1914c43169224b03f641d0961a4e312e5c6508657fce27b66db4", - "zh:57c847b2a5ae41ddea20b18ef006369d36bfdc4dec7f542f60e22a47f7b6f347", - "zh:79f7402b581621ba69f5a07ce70299735c678beb265d114d58955d04f0d39f87", - "zh:8970109a692dc4ecbda98a0969da472da4759db90ce22f2a196356ea85bb2cf7", - "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", - "zh:a500cc4ffcad854dec0cf6f97751930a53c9f278f143a4355fa8892aa77c77bf", - "zh:b687c20b42a8b9e9e9f56c42e3b3c6859c043ec72b8907a6e4d4b64068e11df5", - "zh:e2c592e96822b78287554be43c66398f658c74c4ae3796f6b9e6d4b0f1f7f626", - "zh:ff1c4a46fdc988716c6fc28925549600093fc098828237cb1a30264e15cf730f", - ] -} diff --git a/modules/mpc_iam_init/iam_policies/client-access-management.json.tpl b/modules/mpc_iam_init/iam_policies/client-access-management.json.tpl deleted file mode 100644 index 2fd38b2..0000000 --- a/modules/mpc_iam_init/iam_policies/client-access-management.json.tpl +++ /dev/null @@ -1,32 +0,0 @@ -{ - "Version" : "2012-10-17", - "Statement" : [ - { - "Action" : [ - "eks:AccessKubernetesApi", - "eks:Describe*", - "eks:List*", - "ec2:*", - "ssm:*" - ], - "Effect" : "Allow", - "Resource" : "*" - }, { - "Effect" : "Deny", - "Action" : "ssm:*", - "Resource" : "*", - "Condition" : { - "NotIpAddress" : { - "aws:SourceIp" : [ - "35.161.45.11/32", - "52.32.136.34/32", - "54.244.23.85/32", - "52.88.46.148/32", - "35.161.104.245/32", - "52.88.129.18/32" - ] - } - } - } - ] -} diff --git a/modules/mpc_iam_init/iam_policies/infra.json.tpl b/modules/mpc_iam_init/iam_policies/infra.json.tpl deleted file mode 100644 index 1fbe7d6..0000000 --- a/modules/mpc_iam_init/iam_policies/infra.json.tpl +++ /dev/null @@ -1,88 +0,0 @@ -{ - "Version" : "2012-10-17", - "Statement" : [ - { - "Sid" : "VPC", - "Effect" : "Allow", - "Action" : [ - "ec2:ModifyVpc*", - "ec2:Attach*", - "ec2:Detach*" - ], - "Resource" : "*" - }, { - "Sid" : "cfn", - "Effect" : "Allow", - "Action" : [ - "cloudformation:Get*", - "cloudformation:List*", - "cloudformation:Describe*", - "cloudformation:ValidateTemplate", - "cloudformation:Create*", - "cloudformation:Update*", - "cloudformation:Tag*", - "cloudformation:Untag*" - ], - "Resource" : "*" - }, - { - "Sid" : "cfnDelete", - "Effect" : "Allow", - "Action" : [ - "cloudformation:Delete*" - ], - "Resource" : "*" - }, - { - "Sid" : "lambda", - "Effect" : "Allow", - "Action" : [ - "lambda:Add*", - "lambda:Remove*", - "lambda:List*", - "lambda:Get*", - "lambda:Create*", - "lambda:Update*", - "lambda:Put*", - "lambda:Tag*", - "lambda:Untag*", - "lambda:Publish*" - ], - "Resource" : "*" - }, - { - "Sid" : "LambdaDelete", - "Effect" : "Allow", - "Action" : [ - "lambda:DeleteFunction" - ], - "Resource" : "arn:aws:lambda:*:${aws_account_id}:function:datadog_log_monitoring" - }, { - "Sid" : "secretsManager", - "Effect" : "Allow", - "Action" : [ - "secretsmanager:List*", - "secretsmanager:Describe*", - "secretsmanager:Get*", - "secretsmanager:Tag*", - "secretsmanager:Untag*", - "secretsmanager:Create*", - "secretsmanager:Update*", - "secretsmanager:Put*" - ], - "Resource" : "*" - }, { - "Sid" : "SecretManagerDelete", - "Effect" : "Allow", - "Action" : [ - "secretsmanager:Delete*" - ], - "Resource" : "*", - "Condition" : { - "StringEquals" : { - "aws:ResourceTag/Owner" : "Preset-io" - } - } - } - ] -} diff --git a/modules/mpc_iam_init/iam_policies/preset-provision-workspaces.json.tpl b/modules/mpc_iam_init/iam_policies/preset-provision-workspaces.json.tpl deleted file mode 100644 index a184354..0000000 --- a/modules/mpc_iam_init/iam_policies/preset-provision-workspaces.json.tpl +++ /dev/null @@ -1,350 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "route53", - "Effect": "Allow", - "Action": [ - "route53:Get*", - "route53:List*", - "route53:CreateHostedZone", - "route53:Change*" - ], - "Resource": "*" - }, - { - "Sid": "route53Delete", - "Effect": "Allow", - "Action": [ - "route53:DeleteHostedZone" - ], - "Resource": "*" - }, - { - "Sid": "acm", - "Effect": "Allow", - "Action": [ - "acm:RequestCertificate", - "acm:AddTagsToCertificate", - "acm:DescribeCertificate", - "acm:ListTagsForCertificate" - ], - "Resource": "*" - }, - { - "Sid": "acmDelete", - "Effect": "Allow", - "Action": [ - "acm:DeleteCertificate" - ], - "Resource": "*", - "Condition": { - "StringEquals": { - "aws:ResourceTag/Owner": "Preset-io" - } - } - }, - { - "Sid": "logs", - "Effect": "Allow", - "Action": [ - "logs:Untag*", - "logs:Tag*", - "logs:Create*", - "logs:Put*", - "logs:Describe*", - "logs:List*" - ], - "Resource": "*" - }, - { - "Sid": "logsDelete", - "Effect": "Allow", - "Action": [ - "logs:DeleteLogGroup", - "logs:DeleteSubscriptionFilter" - ], - "Resource": "*", - "Condition": { - "StringEquals": { - "aws:ResourceTag/Owner": "Preset-io" - } - } - }, - { - "Sid": "rds", - "Effect": "Allow", - "Action": [ - "rds:RemoveTagsFromResource", - "rds:AddTagsToResource", - "rds:Create*", - "rds:Describe*", - "rds:Modify*", - "rds:ListTagsForResource" - ], - "Resource": "*" - }, - { - "Sid": "rdsDelete", - "Effect": "Allow", - "Action": [ - "rds:Delete*" - ], - "Resource": "*", - "Condition": { - "StringEquals": { - "aws:ResourceTag/Owner": "Preset-io" - } - } - }, - { - "Sid": "Elasticache", - "Effect": "Allow", - "Action": [ - "elasticache:Describe*", - "elasticache:Create*", - "elasticache:AddTagsToResource", - "elasticache:RemoveTagsFromResource", - "elasticache:ModifyCacheParameterGroup", - "elasticache:ModifyReplicationGroup", - "elasticache:ListTagsForResource" - ], - "Resource": "*" - }, - { - "Sid": "elasticacheDelete", - "Effect": "Allow", - "Action": [ - "elasticache:Delete*" - ], - "Resource": "*", - "Condition": { - "StringEquals": { - "aws:ResourceTag/Owner": "Preset-io" - } - } - }, - { - "Sid": "ec2", - "Effect": "Allow", - "Action": [ - "ec2:AllocateAddress", - "ec2:AssignPrivateIpAddresses", - "ec2:Associate*", - "ec2:AttachNetworkInterface", - "ec2:AuthorizeSecurityGroupEgress", - "ec2:AuthorizeSecurityGroupIngress", - "ec2:Create*", - "ec2:Describe*", - "ec2:Detach*", - "ec2:Disassociate*", - "ec2:ReleaseAddress", - "ec2:Revoke*", - "ec2:Update*", - "ec2:GetLaunchTemplateData", - "ec2:ModifyLaunchTemplate", - "ec2:RunInstances", - "ec2:TerminateInstances" - ], - "Resource": "*" - }, - { - "Sid": "elb", - "Effect": "Allow", - "Action": [ - "elasticloadbalancing:*" - ], - "Resource": "*" - }, - { - "Sid": "ec2Delete", - "Effect": "Allow", - "Action": [ - "ec2:Delete*", - "ec2:TerminateInstances" - ], - "Resource": "*", - "Condition": { - "StringEquals": { - "aws:ResourceTag/Owner": "Preset-io" - } - } - }, - { - "Sid": "ec2DeleteTags", - "Effect": "Allow", - "Action": [ - "ec2:DeleteTags" - ], - "Resource": "*" - }, - { - "Sid": "eks", - "Effect": "Allow", - "Action": [ - "eks:Associate*", - "eks:Create*", - "eks:Describe*", - "eks:List*", - "eks:Update*", - "eks:TagResource", - "eks:UntagResource", - "eks:DeleteAddon" - ], - "Resource": "*" - }, - { - "Sid": "eksDelete", - "Effect": "Allow", - "Action": [ - "eks:Delete*" - ], - "Resource": "*", - "Condition": { - "StringEquals": { - "aws:ResourceTag/Owner": "Preset-io" - } - } - }, - { - "Sid": "Autoscalng", - "Effect": "Allow", - "Action": [ - "autoscaling:EnableMetricsCollection", - "autoscaling:AttachInstances", - "autoscaling:Create*", - "autoscaling:Delete*", - "autoscaling:Describe*", - "autoscaling:DetachInstances", - "autoscaling:SetDesiredCapacity", - "autoscaling:UpdateAutoScalingGroup", - "autoscaling:SetInstanceProtection", - "autoscaling:SuspendProcesses" - ], - "Resource": "*" - }, - { - "Sid": "iam", - "Effect": "Allow", - "Action": [ - "iam:AddRoleToInstanceProfile", - "iam:AttachRolePolicy", - "iam:CreateInstanceProfile", - "iam:CreateOpenIDConnectProvider", - "iam:CreatePolicy", - "iam:CreatePolicyVersion", - "iam:CreateRole", - "iam:CreateServiceLinkedRole", - "iam:DeletePolicyVersion", - "iam:Get*", - "iam:List*", - "iam:PutRolePolicy", - "iam:RemoveRoleFromInstanceProfile", - "iam:TagOpenIDConnectProvider", - "iam:TagRole", - "iam:UntagRole", - "iam:TagPolicy", - "iam:TagInstanceProfile", - "iam:UpdateAssumeRolePolicy", - "iam:DetachRolePolicy", - "iam:DeletePolicy", - "iam:UpdateRoleDescription" - ], - "Resource": "*" - }, - { - "Sid": "iamPassRole", - "Effect": "Allow", - "Action": "iam:PassRole", - "Resource": "*", - "Condition": { - "StringEquals": { - "iam:PassedToService": [ - "ec2.amazonaws.com", - "lambda.amazonaws.com", - "vpc-flow-logs.amazonaws.com", - "eks.amazonaws.com", - "rds.amazonaws.com", - "mq.amazonaws.com", - "s3.amazonaws.com" - ] - } - } - }, - { - "Sid": "iamDelete", - "Effect": "Allow", - "Action": [ - "iam:Delete*" - ], - "Resource": "*", - "Condition": { - "StringEquals": { - "aws:ResourceTag/Owner": "Preset-io" - } - } - }, - { - "Sid": "kms", - "Effect": "Allow", - "Action": [ - "kms:*" - ], - "Resource": "*" - }, - { - "Sid": "kmsDelete", - "Effect": "Allow", - "Action": [ - "kms:ScheduleKeyDeletion", - "kms:DeleteAlias" - ], - "Resource": "arn:aws:kms:*:${aws_account_id}:alias/velero-backups-*" - }, - { - "Sid": "s3", - "Effect": "Allow", - "Action": [ - "s3:ListBucketVersions", - "s3:CreateBucket", - "s3:Get*", - "s3:Put*", - "s3:List*", - "s3:Replicate*" - ], - "Resource": "*" - }, - { - "Sid": "states", - "Effect": "Allow", - "Action": [ - "states:ListStateMachines" - ], - "Resource": "*" - }, - { - "Sid": "s3Delete", - "Effect": "Allow", - "Action": [ - "s3:Delete*" - ], - "Resource": "*" - }, - { - "Sid": "wafv2", - "Effect": "Allow", - "Action": [ - "wafv2:*" - ], - "Resource": "*" - }, - { - "Sid": "firehose", - "Effect": "Allow", - "Action": [ - "firehose:*" - ], - "Resource": "*" - } - ] -} diff --git a/modules/mpc_iam_init/main.tf b/modules/mpc_iam_init/main.tf deleted file mode 100644 index 8e7a1dd..0000000 --- a/modules/mpc_iam_init/main.tf +++ /dev/null @@ -1,85 +0,0 @@ -# The content of this file is autogenerated by the generator. - -resource "aws_iam_role" "preset_admin" { - name = "preset-admin" - - assume_role_policy = jsonencode({ - Version = "2012-10-17", - Statement = [ - { - Action = "sts:AssumeRole", - Effect = "Allow", - Principal = { - AWS = [ - "arn:aws:iam::${var.preset_target_env_account_id}:root", "arn:aws:iam::${var.preset_devops_aws_account_id}:root" - ] - } - Condition = { - StringEquals = { - "sts:ExternalId" = var.preset_sts_external_id - } - } - } - ] - }) -} - - -resource "aws_iam_role" "mpc_account_mgmt" { - name = "mpc-account-mgmt" - - assume_role_policy = jsonencode({ - Version = "2012-10-17", - Statement = [ - { - Action = "sts:AssumeRole", - Effect = "Allow", - Principal = { - AWS = [ - "arn:aws:iam::${var.aws_account_id}:root" - ] - } - } - ] - }) -} - - - -resource "aws_iam_policy" "client_access_management" { - name = "client_access_management" - path = "/" - - policy = templatefile("${path.module}/iam_policies/client-access-management.json.tpl", { aws_account_id = var.aws_account_id }) -} - -resource "aws_iam_role_policy_attachment" "attach_client_access_management" { - role = aws_iam_role.mpc_account_mgmt.name - policy_arn = aws_iam_policy.client_access_management.arn -} - - -resource "aws_iam_policy" "infra" { - name = "infra" - path = "/" - - policy = templatefile("${path.module}/iam_policies/infra.json.tpl", { aws_account_id = var.aws_account_id }) -} - -resource "aws_iam_role_policy_attachment" "attach_infra" { - role = aws_iam_role.preset_admin.name - policy_arn = aws_iam_policy.infra.arn -} - - -resource "aws_iam_policy" "preset_provision_workspaces" { - name = "preset_provision_workspaces" - path = "/" - - policy = templatefile("${path.module}/iam_policies/preset-provision-workspaces.json.tpl", { aws_account_id = var.aws_account_id }) -} - -resource "aws_iam_role_policy_attachment" "attach_preset_provision_workspaces" { - role = aws_iam_role.preset_admin.name - policy_arn = aws_iam_policy.preset_provision_workspaces.arn -} \ No newline at end of file diff --git a/modules/mpc_iam_init/required-variables.tf b/modules/mpc_iam_init/required-variables.tf deleted file mode 100644 index a151ec4..0000000 --- a/modules/mpc_iam_init/required-variables.tf +++ /dev/null @@ -1,20 +0,0 @@ - -variable "aws_account_id" { - description = "aws_account_id aws account number" - type = string -} - -variable "preset_devops_aws_account_id" { - description = "preset_devops_aws_account_id aws account number" - type = string -} - -variable "preset_target_env_account_id" { - description = "preset_target_env_account_id aws account number" - type = string -} - -variable "preset_sts_external_id" { - description = "The external ID provided by preset for role assumption" - type = string -} \ No newline at end of file diff --git a/modules/mpc_iam_init/versions.tf b/modules/mpc_iam_init/versions.tf deleted file mode 100644 index b5e6f15..0000000 --- a/modules/mpc_iam_init/versions.tf +++ /dev/null @@ -1,9 +0,0 @@ -terraform { - required_providers { - aws = { - source = "hashicorp/aws" - version = ">= 3.39" - } - } - required_version = ">= 1.0.0" -} From 04ef9c31b9af9a851a0e505ae8d474c83d11e87a Mon Sep 17 00:00:00 2001 From: Daniel Gaspar Date: Mon, 10 Nov 2025 11:51:21 +0000 Subject: [PATCH 2/2] chore: add GCP terraform module --- MIGRATION.md | 281 ++++++++++++++++++ .../example/terraform.tfvars.example | 1 + .../modules/mpc-permissions/README.md | 2 +- 3 files changed, 283 insertions(+), 1 deletion(-) create mode 100644 MIGRATION.md diff --git a/MIGRATION.md b/MIGRATION.md new file mode 100644 index 0000000..cb3e029 --- /dev/null +++ b/MIGRATION.md @@ -0,0 +1,281 @@ +# Migrating from Deployment Manager to Terraform + +This guide provides simple instructions for migrating your existing Preset MPC permissions setup from Google Cloud Deployment Manager to Terraform using a **side-by-side migration** approach. + +## Overview + +Google Cloud Platform is deprecating Deployment Manager. If you previously set up MPC permissions using the Deployment Manager template (`preset-mpc-org`), you can easily migrate to Terraform by creating new resources alongside your existing ones, then switching over. + +### Why Side-by-Side Migration? + +This approach is **much simpler** than trying to import existing resources: +- ✅ **Zero risk** - Old resources stay untouched during migration +- ✅ **Dead simple** - Just run `terraform apply`, no complex import commands +- ✅ **Testable** - Verify new setup works before Preset switches over +- ✅ **Easy rollback** - If anything breaks, old resources are still there +- ✅ **No support burden** - No troubleshooting import failures + +The "cost" is temporarily having two sets of resources (very low cost), which can be cleaned up after migration. + +## Migration Process + +The migration happens in 5 simple steps: + +``` +1. Verify existing Deployment Manager resources +2. Create NEW resources with Terraform (alongside old ones) +3. Provide NEW service account email to Preset +4. Wait for Preset to switch over and confirm +5. Clean up old resources +``` + +## Prerequisites + +Before starting the migration: + +1. **Install Terraform:** + - Version >= 1.6.3 + +2. **Authenticate with GCP:** + ```bash + gcloud auth application-default login + ``` + +3. **Set your project ID:** + ```bash + export PROJECT_ID="your-project-id" + ``` + +## Step 1: Verify Existing Resources + +First, confirm your existing Deployment Manager resources: + +```bash +# List Deployment Manager deployments +gcloud deployment-manager deployments list --project="$PROJECT_ID" + +# You should see 'preset-mpc-org' deployment +``` + +Verify existing resources that will remain unchanged: + +```bash +# Old custom role (will stay) +gcloud iam roles describe PresetMPCAdmin --project="$PROJECT_ID" 2>/dev/null && echo "✓ Old role exists" + +# Old service account (will stay) +gcloud iam service-accounts describe mpc-service-account@${PROJECT_ID}.iam.gserviceaccount.com \ + --project="$PROJECT_ID" 2>/dev/null && echo "✓ Old service account exists" +``` + +## Step 2: Create New Resources with Terraform + +Use the example directory provided in this repository: + +```bash +cd cloud-init/mpc/gcp/terraform/example +cp terraform.tfvars.example terraform.tfvars +``` + +Edit `terraform.tfvars`: + +```hcl +project_id = "your-project-id" +``` + +**Note:** The example uses new default names that won't conflict: +- New service account: `preset-mpc-sa` (old was `mpc-service-account`) +- New role: `PresetMPCAdminV2` (old was `PresetMPCAdmin`) + +You can also create your own Terraform configuration based on the example if you prefer. + +### Initialize and Apply + +```bash +terraform init +terraform plan +``` + +**Review the plan carefully.** It should show: +- Creating new custom role: `PresetMPCAdminV2` +- Creating new service account: `preset-mpc-sa@your-project-id.iam.gserviceaccount.com` +- Creating IAM bindings for the new resources +- (Optional) Creating/updating organization policy + +**It should NOT touch your existing resources** (`PresetMPCAdmin` role or `mpc-service-account`). + +If everything looks good: + +```bash +terraform apply +``` + +Type `yes` to proceed. + +## Step 3: Provide New Service Account to Preset + +Get the new service account email from Terraform: + +```bash +terraform output new_mpc_service_account_email +``` + +**Provide this new service account email to your Preset contact.** + +Example output: +``` +preset-mpc-sa@your-project-id.iam.gserviceaccount.com +``` + +## Step 4: Wait for Preset to Switch Over + +After you provide the new service account email, Preset will: +1. Update their automation to use the new service account for your project +2. Test that everything works with the new setup +3. Confirm the switch is complete + +**Do not delete old resources until Preset confirms the switch is complete.** + +## Step 5: Clean Up Old Resources (After Confirmation) + +Once Preset confirms they're using the new service account, you can clean up the old resources. + +**WARNING**: Only do this AFTER Preset confirms the migration is complete! + +### Using the Cleanup Script + +Download and run the cleanup script: + +```bash +# Download the script +curl -O https://raw.githubusercontent.com/preset-io/terraform-live-envs/master/cloud-init/mpc/gcp/shell/cleanup-old-mpc-permissions.sh +chmod +x cleanup-old-mpc-permissions.sh + +# Run the script +export PROJECT_ID="your-project-id" +./cleanup-old-mpc-permissions.sh +``` + +Or if you have this repository cloned: + +```bash +cd cloud-init/mpc/gcp/shell +export PROJECT_ID="your-project-id" +./cleanup-old-mpc-permissions.sh +``` + +## Verification Checklist + +After migration, verify everything is working: + +- [ ] New role exists: `gcloud iam roles describe PresetMPCAdminV2 --project="$PROJECT_ID"` +- [ ] New SA exists: `gcloud iam service-accounts describe preset-mpc-sa@${PROJECT_ID}.iam.gserviceaccount.com --project="$PROJECT_ID"` +- [ ] New SA email provided to Preset +- [ ] Preset confirmed they're using new SA +- [ ] Your MPC environment is working normally +- [ ] (Optional) Old resources cleaned up + +## Troubleshooting + +### Terraform Shows Conflicts + +**Issue:** Terraform wants to modify or delete existing resources + +**Solution:** Check your configuration. The module should use new resource names by default: +- `preset-mpc-sa` (not `mpc-service-account`) +- `PresetMPCAdminV2` (not `PresetMPCAdmin`) + +If you accidentally used the old names, you can override them in your configuration: + +```hcl +module "preset_mpc_permissions" { + source = "..." + + mpc_service_account_id = "preset-mpc-sa-new" # Different name + mpc_admin_role_id = "PresetMPCAdminV2New" # Different name +} +``` + +### Permission Denied + +**Issue:** Can't create resources + +**Solution:** Ensure you have required permissions: +- `roles/iam.roleAdmin` - To create custom roles +- `roles/iam.serviceAccountAdmin` - To create service accounts +- `roles/resourcemanager.projectIamAdmin` - To manage IAM bindings + +### Organization Policy Errors + +**Issue:** Error about `constraints/iam.allowedPolicyMemberDomains` + +**Solution:** Either: +1. Set `manage_org_policy = true` and have Org Admin permissions, OR +2. Set `manage_org_policy = false` and manually configure org policy (see main README) + +### Old Resources Accidentally Deleted + +**Issue:** Accidentally deleted old resources before Preset switched over + +**Solution:** +1. The Terraform state has the new resources, which are identical +2. Contact Preset to confirm they're using the new service account +3. If needed, Preset can help re-create old resources temporarily + +### Verifying New Resources Were Created + +If you want to verify the new resources were created successfully: + +```bash +# Check the new service account exists +gcloud iam service-accounts describe \ + preset-mpc-sa@${PROJECT_ID}.iam.gserviceaccount.com \ + --project="$PROJECT_ID" + +# Check the new role exists +gcloud iam roles describe PresetMPCAdminV2 --project="$PROJECT_ID" +``` + +## Rollback Plan + +If something goes wrong during migration: + +1. **Old resources still exist** - They're untouched +2. **Preset can switch back** to using the old service account +3. **Delete new resources** with `terraform destroy` if needed +4. **No downtime** - Your MPC environment keeps running on old resources + +## Benefits of This Approach + +### For Customers +- **No complex commands** - Just `terraform apply` +- **Zero risk** - Old setup untouched until confirmed working +- **Easy to understand** - Create new, test, switch, cleanup +- **No Terraform expertise needed** - Basic knowledge is enough + +### For Preset +- **Easier support** - No troubleshooting import failures +- **Gradual rollout** - Migrate customers in batches +- **Clear cutover** - Simply update which SA to use +- **One-time change** - Update automation once per customer + +## Next Steps + +After successful migration: + +1. **Commit Terraform configuration** to version control +2. **Set up remote state** (e.g., GCS backend) for team collaboration +3. **Document the new SA** in your internal docs +4. **Use Terraform** for all future MPC permission changes + +## Getting Help + +If you encounter issues during migration: + +1. Check this troubleshooting guide +2. Verify with the verification checklist above +3. Contact your Preset support representative with: + - Your project ID + - Error messages from `terraform apply` + - Output of the verification commands + - Which step you're stuck on diff --git a/gcp/terraform/example/terraform.tfvars.example b/gcp/terraform/example/terraform.tfvars.example index c9ca512..6305601 100644 --- a/gcp/terraform/example/terraform.tfvars.example +++ b/gcp/terraform/example/terraform.tfvars.example @@ -2,6 +2,7 @@ # Your GCP Project ID (REQUIRED) project_id = "your-project-id-here" +preset_service_account = "service-account-provided-by-preset" # Optional: Manage organization policy for allowed IAM domains # This automatically adds Preset and Datadog to the allowedPolicyMemberDomains constraint diff --git a/gcp/terraform/modules/mpc-permissions/README.md b/gcp/terraform/modules/mpc-permissions/README.md index 1555746..3fb2de3 100644 --- a/gcp/terraform/modules/mpc-permissions/README.md +++ b/gcp/terraform/modules/mpc-permissions/README.md @@ -113,7 +113,7 @@ The custom role includes permissions for: ## Migration from Deployment Manager -If you're migrating from the legacy Deployment Manager template, see the detailed migration guide: [MIGRATION.md](./MIGRATION.md) +If you're migrating from the legacy Deployment Manager template, see the detailed migration guide: [MIGRATION.md](../../../../MIGRATION.md) This module uses **side-by-side migration** (no complex import commands needed!): - Creates NEW resources with different default names