diff --git a/docs/data-sources/project_service_account.md b/docs/data-sources/project_service_account.md index ab68d7559b..8d87333c28 100644 --- a/docs/data-sources/project_service_account.md +++ b/docs/data-sources/project_service_account.md @@ -36,7 +36,7 @@ output "service_account_name" { } output "service_account_first_secret" { - description = "The secret value of the first secret created with the service account. Available only immediately after initial creation." + description = "The secret value of the first secret created with the Project Service Account. Available only immediately after initial creation." value = try(mongodbatlas_project_service_account.this.secrets[0].secret, null) sensitive = true } diff --git a/docs/data-sources/project_service_accounts.md b/docs/data-sources/project_service_accounts.md index 07b5a8f32f..198e635ed2 100644 --- a/docs/data-sources/project_service_accounts.md +++ b/docs/data-sources/project_service_accounts.md @@ -36,7 +36,7 @@ output "service_account_name" { } output "service_account_first_secret" { - description = "The secret value of the first secret created with the service account. Available only immediately after initial creation." + description = "The secret value of the first secret created with the Project Service Account. Available only immediately after initial creation." value = try(mongodbatlas_project_service_account.this.secrets[0].secret, null) sensitive = true } diff --git a/docs/data-sources/service_account.md b/docs/data-sources/service_account.md index d645f5eb32..ca25f9df03 100644 --- a/docs/data-sources/service_account.md +++ b/docs/data-sources/service_account.md @@ -36,7 +36,7 @@ output "service_account_name" { } output "service_account_first_secret" { - description = "The secret value of the first secret created with the service account. Available only immediately after initial creation." + description = "The secret value of the first secret created with the Service Account. Available only immediately after initial creation." value = try(mongodbatlas_service_account.this.secrets[0].secret, null) sensitive = true } diff --git a/docs/data-sources/service_accounts.md b/docs/data-sources/service_accounts.md index faa25de20b..4547f9d900 100644 --- a/docs/data-sources/service_accounts.md +++ b/docs/data-sources/service_accounts.md @@ -36,7 +36,7 @@ output "service_account_name" { } output "service_account_first_secret" { - description = "The secret value of the first secret created with the service account. Available only immediately after initial creation." + description = "The secret value of the first secret created with the Service Account. Available only immediately after initial creation." value = try(mongodbatlas_service_account.this.secrets[0].secret, null) sensitive = true } diff --git a/docs/guides/migrate-pak-to-service-account.md b/docs/guides/migrate-pak-to-service-account.md new file mode 100644 index 0000000000..08b8c2e9b9 --- /dev/null +++ b/docs/guides/migrate-pak-to-service-account.md @@ -0,0 +1,291 @@ +--- +page_title: "Migration Guide: Programmatic API Keys (PAKs) to Service Accounts (SAs)" +--- + +# Migration Guide: Programmatic API Keys (PAKs) to Service Accounts (SAs) + +## Overview + +This guide explains how to migrate from Programmatic API Key (PAK) resources to Service Account (SA) resources managed by Terraform. + +**Important:** The steps in this guide are for migrating Terraform-managed PAK and SA resources (e.g., `mongodbatlas_api_key`, `mongodbatlas_service_account`). If you are looking to change the Terraform provider authentication method from PAK to SA, refer to the [Provider Configuration guide](https://registry.terraform.io/providers/mongodb/mongodbatlas/latest/docs/guides/provider-configuration). + +**Note:** Migration to Service Accounts is recommended but **not required**. If you are currently using API Key resources, you may continue to do so. This guide is for users who wish to adopt Service Accounts for greater security or best practices, but existing PAK configurations will continue to work and be supported. + +## Before You Begin + +- **Backup your Terraform state file** before making any changes. +- **Test the process in a non-production environment** if possible. +- Managing Service Accounts with Terraform **exposes sensitive organizational secrets** in Terraform's state. We suggest following [Terraform's best practices](https://developer.hashicorp.com/terraform/language/state/sensitive-data). + + +--- + +
+ Organization-Level Migration + +## Organization-Level API Keys to Service Accounts + +**Objective**: Migrate from organization-level PAK resources (`mongodbatlas_api_key`, `mongodbatlas_api_key_project_assignment`, `mongodbatlas_access_list_api_key`) to organization-level Service Account resources (`mongodbatlas_service_account`, `mongodbatlas_service_account_project_assignment`, `mongodbatlas_service_account_access_list_entry`). + +### Resource Mapping +The following table shows the mapping between organization-level PAK resources and their Service Account equivalents: + +| PAK Resource | Service Account Resource | Notes | +|--------------|-------------------------|-------| +| `mongodbatlas_api_key` | `mongodbatlas_service_account` | API key / Service Account | +| `mongodbatlas_api_key_project_assignment` | `mongodbatlas_service_account_project_assignment` | Project assignment | +| `mongodbatlas_access_list_api_key` | `mongodbatlas_service_account_access_list_entry` | IP access list entry | + +--- + +### Migration Steps + +For complete working examples, see the [organization-level migration example](https://github.com/mongodb/terraform-provider-mongodbatlas/tree/master/examples/migrate_pak_to_service_account/org_level). + +### Step 1: Initial Configuration - PAK Resources Only + +Original configuration with PAK resources: + +```terraform +resource "mongodbatlas_api_key" "this" { + org_id = var.org_id + description = "Example API Key" + role_names = ["ORG_MEMBER"] +} + +# Project assignment for the API Key +resource "mongodbatlas_api_key_project_assignment" "this" { + project_id = var.project_id + api_key_id = mongodbatlas_api_key.this.api_key_id + roles = ["GROUP_READ_ONLY", "GROUP_DATA_ACCESS_READ_ONLY"] +} + +# IP Access List entry for the API Key +resource "mongodbatlas_access_list_api_key" "this" { + org_id = var.org_id + api_key_id = mongodbatlas_api_key.this.api_key_id + cidr_block = "192.168.1.100/32" + # Alternative: ip_address = "192.168.1.100" +} +``` + +### Step 2: Intermediate State - Add Service Account Resources Alongside Existing PAK Resources + +Add the Service Account resources to your configuration while keeping the existing PAK resources. This allows both authentication methods to work simultaneously, enabling you to test Service Accounts before removing PAKs. + +```terraform +resource "mongodbatlas_service_account" "this" { + org_id = var.org_id + name = "example-service-account" + description = "Example Service Account" + roles = ["ORG_MEMBER"] + secret_expires_after_hours = 2160 # 90 days +} + +resource "mongodbatlas_service_account_project_assignment" "this" { + project_id = var.project_id + client_id = mongodbatlas_service_account.this.client_id + roles = ["GROUP_READ_ONLY", "GROUP_DATA_ACCESS_READ_ONLY"] +} + +resource "mongodbatlas_service_account_access_list_entry" "this" { + org_id = var.org_id + client_id = mongodbatlas_service_account.this.client_id + cidr_block = "192.168.1.100/32" + # Alternative: ip_address = "192.168.1.100" +} + +output "service_account_first_secret" { + description = "The secret value of the first secret created with the Service Account. Available only immediately after initial creation." + value = try(mongodbatlas_service_account.this.secrets[0].secret, null) + sensitive = true +} +``` + +**Apply and test:** + +1. Run `terraform plan` to review the changes. +2. Run `terraform apply` to create the Service Account resources. +3. Retrieve and securely store the `service_account_first_secret` value (**warning**: this prints the secret to your terminal): + + ```bash + terraform output -raw service_account_first_secret + ``` + +4. Test your Service Account in your applications and verify that both PAK and SA authentication methods work correctly. +5. Re-run `terraform plan` to ensure you have no unexpected changes: `No changes. Your infrastructure matches the configuration.` + +### Step 3: Final State - Remove PAK Resources, SA Resources Only + +Once you have verified that the Service Account works correctly, remove the PAK resources from your configuration: + +```terraform +resource "mongodbatlas_service_account" "this" { + org_id = var.org_id + name = "example-service-account" + description = "Example Service Account" + roles = ["ORG_MEMBER"] + secret_expires_after_hours = 2160 # 90 days +} + +resource "mongodbatlas_service_account_project_assignment" "this" { + project_id = var.project_id + client_id = mongodbatlas_service_account.this.client_id + roles = ["GROUP_READ_ONLY", "GROUP_DATA_ACCESS_READ_ONLY"] +} + +resource "mongodbatlas_service_account_access_list_entry" "this" { + org_id = var.org_id + client_id = mongodbatlas_service_account.this.client_id + cidr_block = "192.168.1.100/32" +} +``` + +1. Run `terraform plan` to verify: + - PAK resources are planned for destruction + - Only the Service Account resources remain + - No unexpected changes + +2. Run `terraform apply` to finalize the migration. This will delete the PAK resources from Atlas. + +3. Verify that your applications and infrastructure continue to work with Service Accounts. + +4. Re-run `terraform plan` to ensure you have no planned changes: `No changes. Your infrastructure matches the configuration.` + + +
+ +--- + +
+ Project-Level Migration + +## Project-Level API Keys to Service Accounts + +**Objective**: Migrate from project-level PAK resources (`mongodbatlas_project_api_key`, `mongodbatlas_access_list_api_key`) to project-level Service Account resources (`mongodbatlas_project_service_account`, `mongodbatlas_project_service_account_access_list_entry`). + +**Important:** Organization-level resources (`mongodbatlas_service_account`) are the recommended approach. Project-level resources (`mongodbatlas_project_service_account`) should only be used if you do not have organization-level permissions to manage Service Accounts. Otherwise, use the [Organization-Level Migration](#organization-level-api-keys-to-service-accounts) approach. + +### Resource Mapping + +The following table shows the mapping between project-level PAK resources and their Service Account equivalents: + +| PAK Resource | Service Account Resource | Notes | +|--------------|-------------------------|-------| +| `mongodbatlas_project_api_key` | `mongodbatlas_project_service_account` | Project-level API key / Service Account (use only if you don't have org-level permissions) | +| `mongodbatlas_access_list_api_key` | `mongodbatlas_project_service_account_access_list_entry` | IP access list entry for project-level Service Account | + +**Important:** Organization-level resources (`mongodbatlas_service_account`) are the recommended approach. Project-level resources (`mongodbatlas_project_service_account`) should only be used if you do not have organization-level permissions to manage Service Accounts. + +--- + +### Migration Steps + +For complete working examples, see the [project-level migration example](https://github.com/mongodb/terraform-provider-mongodbatlas/tree/master/examples/migrate_pak_to_service_account/project_level). + +### Step 1: Initial Configuration - PAK Resources Only + +Original configuration with PAK resources: + +```terraform +resource "mongodbatlas_project_api_key" "this" { + description = "Example Project API Key" + project_assignment { + project_id = var.project_id + role_names = ["GROUP_READ_ONLY", "GROUP_DATA_ACCESS_READ_ONLY"] + } +} + +# IP Access List entry for the API Key +resource "mongodbatlas_access_list_api_key" "this" { + org_id = var.org_id + api_key_id = mongodbatlas_project_api_key.this.api_key_id + cidr_block = "192.168.1.100/32" + # Alternative: ip_address = "192.168.1.100" +} +``` + +### Step 2: Intermediate State - Add Service Account Resources Alongside Existing PAK Resources + +Add the Service Account resources to your configuration while keeping the existing PAK resources. This allows both authentication methods to work simultaneously, enabling you to test Service Accounts before removing PAKs. + +```terraform +resource "mongodbatlas_project_service_account" "this" { + project_id = var.project_id + name = "example-project-service-account" + description = "Example Project Service Account" + roles = ["GROUP_READ_ONLY", "GROUP_DATA_ACCESS_READ_ONLY"] + secret_expires_after_hours = 2160 # 90 days +} + +resource "mongodbatlas_project_service_account_access_list_entry" "this" { + project_id = var.project_id + client_id = mongodbatlas_project_service_account.this.client_id + cidr_block = "192.168.1.100/32" + # Alternative: ip_address = "192.168.1.100" +} + +output "project_service_account_first_secret" { + description = "The secret value of the first secret created with the Project Service Account. Available only immediately after initial creation." + value = try(mongodbatlas_project_service_account.this.secrets[0].secret, null) + sensitive = true +} +``` + +**Apply and test:** + +1. Run `terraform plan` to review the changes. +2. Run `terraform apply` to create the Service Account resource. +3. Retrieve and securely store the `project_service_account_first_secret` value (**warning**: this prints the secret to your terminal): + + ```bash + terraform output -raw project_service_account_first_secret + ``` + +4. Test your Service Account in your applications and verify that both PAK and SA authentication methods work correctly. +5. Re-run `terraform plan` to ensure you have no unexpected changes: `No changes. Your infrastructure matches the configuration.` + +### Step 3: Final State - Remove PAK Resources, SA Resources Only + +Once you have verified that the Service Account works correctly, remove the PAK resources from your configuration: + +```terraform +resource "mongodbatlas_project_service_account" "this" { + project_id = var.project_id + name = "example-project-service-account" + description = "Example Project Service Account" + roles = ["GROUP_READ_ONLY", "GROUP_DATA_ACCESS_READ_ONLY"] + secret_expires_after_hours = 2160 # 90 days +} + +resource "mongodbatlas_project_service_account_access_list_entry" "this" { + project_id = var.project_id + client_id = mongodbatlas_project_service_account.this.client_id + cidr_block = "192.168.1.100/32" +} +``` + +1. Run `terraform plan` to verify: + - PAK resources are planned for destruction + - Only the Service Account resources remain + - No unexpected changes + +2. Run `terraform apply` to finalize the migration. This will delete the PAK resource from Atlas. + +3. Verify that your applications and infrastructure continue to work with Service Accounts. + +4. Re-run `terraform plan` to ensure you have no planned changes: `No changes. Your infrastructure matches the configuration.` + + +
+ +--- + +## Switching the Provider Authentication Method + +After migrating your Terraform-managed resources from PAK to SA, you may also want to switch the Terraform provider authentication method. + +For detailed instructions on configuring Service Account authentication for the provider, see the [Provider Configuration guide](https://registry.terraform.io/providers/mongodb/mongodbatlas/latest/docs/guides/provider-configuration). + +**Note:** The Service Account used for provider authentication should be created in a separate Terraform configuration or managed outside of Terraform. It is recommended to manage provider authentication credentials separately from the resources they manage to avoid potential lockout scenarios. diff --git a/docs/guides/service-account-secret-rotation.md b/docs/guides/service-account-secret-rotation.md index 73995d728a..ea6a22d5ee 100644 --- a/docs/guides/service-account-secret-rotation.md +++ b/docs/guides/service-account-secret-rotation.md @@ -22,7 +22,7 @@ This guide applies to both organization-level and project-level service accounts - **Backup your Terraform state file** before making any changes. - **Test the rotation process in a non-production environment** if possible. -- **Secrets handling** - Managing Service Accounts with Terraform will expose sensitive organizational secrets in Terraform's state. We suggest following [Terraform's best practices](https://developer.hashicorp.com/terraform/language/state/sensitive-data). +- Managing Service Accounts with Terraform **exposes sensitive organizational secrets** in Terraform's state. We suggest following [Terraform's best practices](https://developer.hashicorp.com/terraform/language/state/sensitive-data). ## Setup diff --git a/docs/resources/project_service_account.md b/docs/resources/project_service_account.md index 4e1259c38f..b8e9f7c63d 100644 --- a/docs/resources/project_service_account.md +++ b/docs/resources/project_service_account.md @@ -41,7 +41,7 @@ output "service_account_name" { } output "service_account_first_secret" { - description = "The secret value of the first secret created with the service account. Available only immediately after initial creation." + description = "The secret value of the first secret created with the Project Service Account. Available only immediately after initial creation." value = try(mongodbatlas_project_service_account.this.secrets[0].secret, null) sensitive = true } diff --git a/docs/resources/service_account.md b/docs/resources/service_account.md index fa9cbe10c5..ab2b327426 100644 --- a/docs/resources/service_account.md +++ b/docs/resources/service_account.md @@ -37,7 +37,7 @@ output "service_account_name" { } output "service_account_first_secret" { - description = "The secret value of the first secret created with the service account. Available only immediately after initial creation." + description = "The secret value of the first secret created with the Service Account. Available only immediately after initial creation." value = try(mongodbatlas_service_account.this.secrets[0].secret, null) sensitive = true } diff --git a/examples/migrate_pak_to_service_account/org_level/README.md b/examples/migrate_pak_to_service_account/org_level/README.md new file mode 100644 index 0000000000..6cf505ebeb --- /dev/null +++ b/examples/migrate_pak_to_service_account/org_level/README.md @@ -0,0 +1,63 @@ +# Migration Example: Organization-Level API Keys to Service Accounts + +This example demonstrates how to migrate from organization-level Programmatic API Key (PAK) resources to Service Account (SA) resources. + +## Migration Phases + +### v1: Initial State (PAK Resources) +Shows the original configuration using PAK resources: +- `mongodbatlas_api_key` for organization-level API key +- `mongodbatlas_api_key_project_assignment` for project assignment +- `mongodbatlas_access_list_api_key` for IP access list entry + +### v2: Migration Phase (Both PAK and SA Resources) +Demonstrates the migration approach: +- Adds Service Account resources alongside existing PAK resources +- Includes output to capture the Service Account secret +- Allows testing Service Accounts before removing PAKs + +### v3: Final State (SA Resources Only) +Clean final configuration using only: +- `mongodbatlas_service_account` for organization-level service account +- `mongodbatlas_service_account_project_assignment` for project assignment +- `mongodbatlas_service_account_access_list_entry` for IP access list entry + +## Usage + +1. Start with v1 to understand the original setup +2. Apply v2 configuration to add Service Account resources +3. Retrieve and securely store the Service Account secret from the output +4. Verify that both PAK and SA authentication methods work correctly +5. Apply v3 configuration for the final clean state + +## Prerequisites + +- MongoDB Atlas Terraform Provider with Service Account support +- Valid MongoDB Atlas organization and project IDs +- Appropriate permissions to manage API keys and Service Accounts + +## Variables + +Set these variables for all versions: + +```terraform +atlas_client_id = "" # Optional, can use env vars +atlas_client_secret = "" # Optional, can use env vars +org_id = "your-organization-id" +project_id = "your-project-id" +org_roles = ["ORG_MEMBER"] +project_roles = ["GROUP_READ_ONLY", "GROUP_DATA_ACCESS_READ_ONLY"] +cidr_block = "192.168.1.100/32" +``` + +For v2 and v3, also set: +```terraform +service_account_name = "example-service-account" +secret_expires_after_hours = 2160 # 90 days +``` + +Alternatively, set environment variables: +```bash +export MONGODB_ATLAS_CLIENT_ID="" +export MONGODB_ATLAS_CLIENT_SECRET="" +``` diff --git a/examples/migrate_pak_to_service_account/org_level/v1/main.tf b/examples/migrate_pak_to_service_account/org_level/v1/main.tf new file mode 100644 index 0000000000..d56bdef41b --- /dev/null +++ b/examples/migrate_pak_to_service_account/org_level/v1/main.tf @@ -0,0 +1,22 @@ +############################################################ +# v1: Initial State - PAK Resources Only +############################################################ + +resource "mongodbatlas_api_key" "this" { + org_id = var.org_id + description = "Description for the Organization API Key" + role_names = var.org_roles +} + +resource "mongodbatlas_api_key_project_assignment" "this" { + project_id = var.project_id + api_key_id = mongodbatlas_api_key.this.api_key_id + roles = var.project_roles +} + +resource "mongodbatlas_access_list_api_key" "this" { + org_id = var.org_id + api_key_id = mongodbatlas_api_key.this.api_key_id + cidr_block = var.cidr_block + # Alternative: ip_address = "192.168.1.100" +} diff --git a/examples/migrate_pak_to_service_account/org_level/v1/provider.tf b/examples/migrate_pak_to_service_account/org_level/v1/provider.tf new file mode 100644 index 0000000000..edb52cb687 --- /dev/null +++ b/examples/migrate_pak_to_service_account/org_level/v1/provider.tf @@ -0,0 +1,4 @@ +provider "mongodbatlas" { + client_id = var.atlas_client_id + client_secret = var.atlas_client_secret +} diff --git a/examples/migrate_pak_to_service_account/org_level/v1/variables.tf b/examples/migrate_pak_to_service_account/org_level/v1/variables.tf new file mode 100644 index 0000000000..30a492b395 --- /dev/null +++ b/examples/migrate_pak_to_service_account/org_level/v1/variables.tf @@ -0,0 +1,40 @@ +variable "atlas_client_id" { + description = "MongoDB Atlas Service Account Client ID" + type = string + default = "" +} + +variable "atlas_client_secret" { + description = "MongoDB Atlas Service Account Client Secret" + type = string + sensitive = true + default = "" +} + +variable "org_id" { + description = "MongoDB Atlas Organization ID" + type = string +} + +variable "project_id" { + description = "MongoDB Atlas Project ID" + type = string +} + +variable "org_roles" { + description = "Organization roles for the API Key" + type = list(string) + default = ["ORG_MEMBER"] +} + +variable "project_roles" { + description = "Project roles for the API Key assignment" + type = list(string) + default = ["GROUP_READ_ONLY", "GROUP_DATA_ACCESS_READ_ONLY"] +} + +variable "cidr_block" { + description = "CIDR block for IP access list entry" + type = string + default = "192.168.1.100/32" +} diff --git a/examples/migrate_pak_to_service_account/org_level/v1/versions.tf b/examples/migrate_pak_to_service_account/org_level/v1/versions.tf new file mode 100644 index 0000000000..8459278500 --- /dev/null +++ b/examples/migrate_pak_to_service_account/org_level/v1/versions.tf @@ -0,0 +1,8 @@ +terraform { + required_version = ">= 1.0" + required_providers { + mongodbatlas = { + source = "mongodb/mongodbatlas" + } + } +} diff --git a/examples/migrate_pak_to_service_account/org_level/v2/main.tf b/examples/migrate_pak_to_service_account/org_level/v2/main.tf new file mode 100644 index 0000000000..73b8b1df4f --- /dev/null +++ b/examples/migrate_pak_to_service_account/org_level/v2/main.tf @@ -0,0 +1,42 @@ +############################################################ +# v2: Intermediate State - Add Service Account Resources Alongside Existing PAK Resources +############################################################ + +resource "mongodbatlas_service_account" "this" { + org_id = var.org_id + name = var.service_account_name + description = "Description for the Service Account" + roles = var.org_roles + secret_expires_after_hours = var.secret_expires_after_hours +} + +resource "mongodbatlas_service_account_project_assignment" "this" { + project_id = var.project_id + client_id = mongodbatlas_service_account.this.client_id + roles = var.project_roles +} + +resource "mongodbatlas_service_account_access_list_entry" "this" { + org_id = var.org_id + client_id = mongodbatlas_service_account.this.client_id + cidr_block = var.cidr_block + # Alternative: ip_address = "192.168.1.100" +} + +resource "mongodbatlas_api_key" "this" { + org_id = var.org_id + description = "Description for the Organization API Key" + role_names = var.org_roles +} + +resource "mongodbatlas_api_key_project_assignment" "this" { + project_id = var.project_id + api_key_id = mongodbatlas_api_key.this.api_key_id + roles = var.project_roles +} + +resource "mongodbatlas_access_list_api_key" "this" { + org_id = var.org_id + api_key_id = mongodbatlas_api_key.this.api_key_id + cidr_block = var.cidr_block +} diff --git a/examples/migrate_pak_to_service_account/org_level/v2/outputs.tf b/examples/migrate_pak_to_service_account/org_level/v2/outputs.tf new file mode 100644 index 0000000000..21a4d0647e --- /dev/null +++ b/examples/migrate_pak_to_service_account/org_level/v2/outputs.tf @@ -0,0 +1,5 @@ +output "service_account_first_secret" { + description = "The secret value of the first secret created with the Service Account. Only available after initial creation." + value = try(mongodbatlas_service_account.this.secrets[0].secret, null) + sensitive = true +} diff --git a/examples/migrate_pak_to_service_account/org_level/v2/provider.tf b/examples/migrate_pak_to_service_account/org_level/v2/provider.tf new file mode 100644 index 0000000000..edb52cb687 --- /dev/null +++ b/examples/migrate_pak_to_service_account/org_level/v2/provider.tf @@ -0,0 +1,4 @@ +provider "mongodbatlas" { + client_id = var.atlas_client_id + client_secret = var.atlas_client_secret +} diff --git a/examples/migrate_pak_to_service_account/org_level/v2/variables.tf b/examples/migrate_pak_to_service_account/org_level/v2/variables.tf new file mode 100644 index 0000000000..5c4b48757e --- /dev/null +++ b/examples/migrate_pak_to_service_account/org_level/v2/variables.tf @@ -0,0 +1,52 @@ +variable "atlas_client_id" { + description = "MongoDB Atlas Service Account Client ID" + type = string + default = "" +} + +variable "atlas_client_secret" { + description = "MongoDB Atlas Service Account Client Secret" + type = string + sensitive = true + default = "" +} + +variable "org_id" { + description = "MongoDB Atlas Organization ID" + type = string +} + +variable "project_id" { + description = "MongoDB Atlas Project ID" + type = string +} + +variable "service_account_name" { + description = "Name for the Service Account" + type = string + default = "example-service-account" +} + +variable "org_roles" { + description = "Organization roles for the API Key and Service Account" + type = list(string) + default = ["ORG_MEMBER"] +} + +variable "project_roles" { + description = "Project roles for the API Key and Service Account assignment" + type = list(string) + default = ["GROUP_READ_ONLY", "GROUP_DATA_ACCESS_READ_ONLY"] +} + +variable "cidr_block" { + description = "CIDR block for IP access list entry" + type = string + default = "192.168.1.100/32" +} + +variable "secret_expires_after_hours" { + description = "Number of hours after which the Service Account secret expires" + type = number + default = 2160 # 90 days +} diff --git a/examples/migrate_pak_to_service_account/org_level/v2/versions.tf b/examples/migrate_pak_to_service_account/org_level/v2/versions.tf new file mode 100644 index 0000000000..8459278500 --- /dev/null +++ b/examples/migrate_pak_to_service_account/org_level/v2/versions.tf @@ -0,0 +1,8 @@ +terraform { + required_version = ">= 1.0" + required_providers { + mongodbatlas = { + source = "mongodb/mongodbatlas" + } + } +} diff --git a/examples/migrate_pak_to_service_account/org_level/v3/main.tf b/examples/migrate_pak_to_service_account/org_level/v3/main.tf new file mode 100644 index 0000000000..49fed38040 --- /dev/null +++ b/examples/migrate_pak_to_service_account/org_level/v3/main.tf @@ -0,0 +1,23 @@ +############################################################ +# v3: Final State - Remove PAK Resources, SA Resources Only +############################################################ + +resource "mongodbatlas_service_account" "this" { + org_id = var.org_id + name = var.service_account_name + description = "Description for the Service Account" + roles = var.org_roles + secret_expires_after_hours = var.secret_expires_after_hours +} + +resource "mongodbatlas_service_account_project_assignment" "this" { + project_id = var.project_id + client_id = mongodbatlas_service_account.this.client_id + roles = var.project_roles +} + +resource "mongodbatlas_service_account_access_list_entry" "this" { + org_id = var.org_id + client_id = mongodbatlas_service_account.this.client_id + cidr_block = var.cidr_block +} diff --git a/examples/migrate_pak_to_service_account/org_level/v3/provider.tf b/examples/migrate_pak_to_service_account/org_level/v3/provider.tf new file mode 100644 index 0000000000..edb52cb687 --- /dev/null +++ b/examples/migrate_pak_to_service_account/org_level/v3/provider.tf @@ -0,0 +1,4 @@ +provider "mongodbatlas" { + client_id = var.atlas_client_id + client_secret = var.atlas_client_secret +} diff --git a/examples/migrate_pak_to_service_account/org_level/v3/variables.tf b/examples/migrate_pak_to_service_account/org_level/v3/variables.tf new file mode 100644 index 0000000000..913bbb5050 --- /dev/null +++ b/examples/migrate_pak_to_service_account/org_level/v3/variables.tf @@ -0,0 +1,52 @@ +variable "atlas_client_id" { + description = "MongoDB Atlas Service Account Client ID" + type = string + default = "" +} + +variable "atlas_client_secret" { + description = "MongoDB Atlas Service Account Client Secret" + type = string + sensitive = true + default = "" +} + +variable "org_id" { + description = "MongoDB Atlas Organization ID" + type = string +} + +variable "project_id" { + description = "MongoDB Atlas Project ID" + type = string +} + +variable "service_account_name" { + description = "Name for the Service Account" + type = string + default = "example-service-account" +} + +variable "org_roles" { + description = "Organization roles for the Service Account" + type = list(string) + default = ["ORG_MEMBER"] +} + +variable "project_roles" { + description = "Project roles for the Service Account assignment" + type = list(string) + default = ["GROUP_READ_ONLY", "GROUP_DATA_ACCESS_READ_ONLY"] +} + +variable "cidr_block" { + description = "CIDR block for IP access list entry" + type = string + default = "192.168.1.100/32" +} + +variable "secret_expires_after_hours" { + description = "Number of hours after which the Service Account secret expires" + type = number + default = 2160 # 90 days +} diff --git a/examples/migrate_pak_to_service_account/org_level/v3/versions.tf b/examples/migrate_pak_to_service_account/org_level/v3/versions.tf new file mode 100644 index 0000000000..8459278500 --- /dev/null +++ b/examples/migrate_pak_to_service_account/org_level/v3/versions.tf @@ -0,0 +1,8 @@ +terraform { + required_version = ">= 1.0" + required_providers { + mongodbatlas = { + source = "mongodb/mongodbatlas" + } + } +} diff --git a/examples/migrate_pak_to_service_account/project_level/README.md b/examples/migrate_pak_to_service_account/project_level/README.md new file mode 100644 index 0000000000..3b359ed6b0 --- /dev/null +++ b/examples/migrate_pak_to_service_account/project_level/README.md @@ -0,0 +1,62 @@ +# Migration Example: Project-Level API Keys to Service Accounts + +This example demonstrates how to migrate from project-level Programmatic API Key (PAK) resources to Service Account (SA) resources. + +**Important:** Organization-level resources (`mongodbatlas_service_account`) are the recommended approach. Project-level resources (`mongodbatlas_project_service_account`) should only be used if you do not have organization-level permissions to manage service accounts. Otherwise, use the [Organization-Level Migration](../org_level) approach. + +## Migration Phases + +### v1: Initial State (PAK Resources) +Shows the original configuration using PAK resources: +- `mongodbatlas_project_api_key` for project-level API key +- `mongodbatlas_access_list_api_key` for IP access list entry + +### v2: Migration Phase (Both PAK and SA Resources) +Demonstrates the migration approach: +- Adds Service Account resources alongside existing PAK resources +- Includes output to capture the Service Account secret +- Allows testing Service Accounts before removing PAKs + +### v3: Final State (SA Resources Only) +Clean final configuration using only: +- `mongodbatlas_project_service_account` for project-level service account +- `mongodbatlas_project_service_account_access_list_entry` for IP access list entry + +## Usage + +1. Start with v1 to understand the original setup +2. Apply v2 configuration to add Service Account resources +3. Retrieve and securely store the Service Account secret from the output +4. Verify that both PAK and SA authentication methods work correctly +5. Apply v3 configuration for the final clean state + +## Prerequisites + +- MongoDB Atlas Terraform Provider with Service Account support +- Valid MongoDB Atlas organization and project IDs +- Appropriate permissions to manage API keys and Service Accounts at the project level + +## Variables + +Set these variables for all versions: + +```terraform +atlas_client_id = "" # Optional, can use env vars +atlas_client_secret = "" # Optional, can use env vars +org_id = "your-organization-id" # Required for access list entry +project_id = "your-project-id" +project_roles = ["GROUP_READ_ONLY", "GROUP_DATA_ACCESS_READ_ONLY"] +cidr_block = "192.168.1.100/32" +``` + +For v2 and v3, also set: +```terraform +project_service_account_name = "example-project-service-account" +secret_expires_after_hours = 2160 # 90 days +``` + +Alternatively, set environment variables: +```bash +export MONGODB_ATLAS_CLIENT_ID="" +export MONGODB_ATLAS_CLIENT_SECRET="" +``` diff --git a/examples/migrate_pak_to_service_account/project_level/v1/main.tf b/examples/migrate_pak_to_service_account/project_level/v1/main.tf new file mode 100644 index 0000000000..9a07141cd2 --- /dev/null +++ b/examples/migrate_pak_to_service_account/project_level/v1/main.tf @@ -0,0 +1,18 @@ +############################################################ +# v1: Initial State - PAK Resources Only +############################################################ + +resource "mongodbatlas_project_api_key" "this" { + description = "Description for the Project API Key" + project_assignment { + project_id = var.project_id + role_names = var.project_roles + } +} + +resource "mongodbatlas_access_list_api_key" "this" { + org_id = var.org_id + api_key_id = mongodbatlas_project_api_key.this.api_key_id + cidr_block = var.cidr_block + # Alternative: ip_address = "192.168.1.100" +} diff --git a/examples/migrate_pak_to_service_account/project_level/v1/provider.tf b/examples/migrate_pak_to_service_account/project_level/v1/provider.tf new file mode 100644 index 0000000000..edb52cb687 --- /dev/null +++ b/examples/migrate_pak_to_service_account/project_level/v1/provider.tf @@ -0,0 +1,4 @@ +provider "mongodbatlas" { + client_id = var.atlas_client_id + client_secret = var.atlas_client_secret +} diff --git a/examples/migrate_pak_to_service_account/project_level/v1/variables.tf b/examples/migrate_pak_to_service_account/project_level/v1/variables.tf new file mode 100644 index 0000000000..2574960565 --- /dev/null +++ b/examples/migrate_pak_to_service_account/project_level/v1/variables.tf @@ -0,0 +1,34 @@ +variable "atlas_client_id" { + description = "MongoDB Atlas Service Account Client ID" + type = string + default = "" +} + +variable "atlas_client_secret" { + description = "MongoDB Atlas Service Account Client Secret" + type = string + sensitive = true + default = "" +} + +variable "org_id" { + description = "MongoDB Atlas Organization ID (required for access list entry)" + type = string +} + +variable "project_id" { + description = "MongoDB Atlas Project ID" + type = string +} + +variable "project_roles" { + description = "Project roles for the API Key" + type = list(string) + default = ["GROUP_READ_ONLY", "GROUP_DATA_ACCESS_READ_ONLY"] +} + +variable "cidr_block" { + description = "CIDR block for IP access list entry" + type = string + default = "192.168.1.100/32" +} diff --git a/examples/migrate_pak_to_service_account/project_level/v1/versions.tf b/examples/migrate_pak_to_service_account/project_level/v1/versions.tf new file mode 100644 index 0000000000..8459278500 --- /dev/null +++ b/examples/migrate_pak_to_service_account/project_level/v1/versions.tf @@ -0,0 +1,8 @@ +terraform { + required_version = ">= 1.0" + required_providers { + mongodbatlas = { + source = "mongodb/mongodbatlas" + } + } +} diff --git a/examples/migrate_pak_to_service_account/project_level/v2/main.tf b/examples/migrate_pak_to_service_account/project_level/v2/main.tf new file mode 100644 index 0000000000..86e1105a52 --- /dev/null +++ b/examples/migrate_pak_to_service_account/project_level/v2/main.tf @@ -0,0 +1,32 @@ +############################################################ +# v2: Intermediate State - Add Service Account Resources Alongside Existing PAK Resources +############################################################ + +resource "mongodbatlas_project_service_account" "this" { + project_id = var.project_id + name = var.project_service_account_name + description = "Description for the Project Service Account" + roles = var.project_roles + secret_expires_after_hours = var.secret_expires_after_hours +} + +resource "mongodbatlas_project_service_account_access_list_entry" "this" { + project_id = var.project_id + client_id = mongodbatlas_project_service_account.this.client_id + cidr_block = var.cidr_block + # Alternative: ip_address = "192.168.1.100" +} + +resource "mongodbatlas_project_api_key" "this" { + description = "Description for the Project API Key" + project_assignment { + project_id = var.project_id + role_names = var.project_roles + } +} + +resource "mongodbatlas_access_list_api_key" "this" { + org_id = var.org_id + api_key_id = mongodbatlas_project_api_key.this.api_key_id + cidr_block = var.cidr_block +} diff --git a/examples/migrate_pak_to_service_account/project_level/v2/outputs.tf b/examples/migrate_pak_to_service_account/project_level/v2/outputs.tf new file mode 100644 index 0000000000..d41ccbb6e3 --- /dev/null +++ b/examples/migrate_pak_to_service_account/project_level/v2/outputs.tf @@ -0,0 +1,5 @@ +output "project_service_account_first_secret" { + description = "The secret value of the first secret created with the Project Service Account. Only available after initial creation." + value = try(mongodbatlas_project_service_account.this.secrets[0].secret, null) + sensitive = true +} diff --git a/examples/migrate_pak_to_service_account/project_level/v2/provider.tf b/examples/migrate_pak_to_service_account/project_level/v2/provider.tf new file mode 100644 index 0000000000..edb52cb687 --- /dev/null +++ b/examples/migrate_pak_to_service_account/project_level/v2/provider.tf @@ -0,0 +1,4 @@ +provider "mongodbatlas" { + client_id = var.atlas_client_id + client_secret = var.atlas_client_secret +} diff --git a/examples/migrate_pak_to_service_account/project_level/v2/variables.tf b/examples/migrate_pak_to_service_account/project_level/v2/variables.tf new file mode 100644 index 0000000000..bdf79ff83a --- /dev/null +++ b/examples/migrate_pak_to_service_account/project_level/v2/variables.tf @@ -0,0 +1,46 @@ +variable "atlas_client_id" { + description = "MongoDB Atlas Service Account Client ID" + type = string + default = "" +} + +variable "atlas_client_secret" { + description = "MongoDB Atlas Service Account Client Secret" + type = string + sensitive = true + default = "" +} + +variable "org_id" { + description = "MongoDB Atlas Organization ID (required for access list entry)" + type = string +} + +variable "project_id" { + description = "MongoDB Atlas Project ID" + type = string +} + +variable "project_service_account_name" { + description = "Name for the Project Service Account" + type = string + default = "example-project-service-account" +} + +variable "project_roles" { + description = "Project roles for the API Key and Service Account" + type = list(string) + default = ["GROUP_READ_ONLY", "GROUP_DATA_ACCESS_READ_ONLY"] +} + +variable "cidr_block" { + description = "CIDR block for IP access list entry" + type = string + default = "192.168.1.100/32" +} + +variable "secret_expires_after_hours" { + description = "Number of hours after which the Service Account secret expires" + type = number + default = 2160 # 90 days +} diff --git a/examples/migrate_pak_to_service_account/project_level/v2/versions.tf b/examples/migrate_pak_to_service_account/project_level/v2/versions.tf new file mode 100644 index 0000000000..8459278500 --- /dev/null +++ b/examples/migrate_pak_to_service_account/project_level/v2/versions.tf @@ -0,0 +1,8 @@ +terraform { + required_version = ">= 1.0" + required_providers { + mongodbatlas = { + source = "mongodb/mongodbatlas" + } + } +} diff --git a/examples/migrate_pak_to_service_account/project_level/v3/main.tf b/examples/migrate_pak_to_service_account/project_level/v3/main.tf new file mode 100644 index 0000000000..36c5da450b --- /dev/null +++ b/examples/migrate_pak_to_service_account/project_level/v3/main.tf @@ -0,0 +1,17 @@ +############################################################ +# v3: Final State - Remove PAK Resources, SA Resources Only +############################################################ + +resource "mongodbatlas_project_service_account" "this" { + project_id = var.project_id + name = var.project_service_account_name + description = "Description for the Project Service Account" + roles = var.project_roles + secret_expires_after_hours = var.secret_expires_after_hours +} + +resource "mongodbatlas_project_service_account_access_list_entry" "this" { + project_id = var.project_id + client_id = mongodbatlas_project_service_account.this.client_id + cidr_block = var.cidr_block +} diff --git a/examples/migrate_pak_to_service_account/project_level/v3/provider.tf b/examples/migrate_pak_to_service_account/project_level/v3/provider.tf new file mode 100644 index 0000000000..edb52cb687 --- /dev/null +++ b/examples/migrate_pak_to_service_account/project_level/v3/provider.tf @@ -0,0 +1,4 @@ +provider "mongodbatlas" { + client_id = var.atlas_client_id + client_secret = var.atlas_client_secret +} diff --git a/examples/migrate_pak_to_service_account/project_level/v3/variables.tf b/examples/migrate_pak_to_service_account/project_level/v3/variables.tf new file mode 100644 index 0000000000..0c8d14cc94 --- /dev/null +++ b/examples/migrate_pak_to_service_account/project_level/v3/variables.tf @@ -0,0 +1,41 @@ +variable "atlas_client_id" { + description = "MongoDB Atlas Service Account Client ID" + type = string + default = "" +} + +variable "atlas_client_secret" { + description = "MongoDB Atlas Service Account Client Secret" + type = string + sensitive = true + default = "" +} + +variable "project_id" { + description = "MongoDB Atlas Project ID" + type = string +} + +variable "project_service_account_name" { + description = "Name for the Project Service Account" + type = string + default = "example-project-service-account" +} + +variable "project_roles" { + description = "Project roles for the Service Account" + type = list(string) + default = ["GROUP_READ_ONLY", "GROUP_DATA_ACCESS_READ_ONLY"] +} + +variable "cidr_block" { + description = "CIDR block for IP access list entry" + type = string + default = "192.168.1.100/32" +} + +variable "secret_expires_after_hours" { + description = "Number of hours after which the Service Account secret expires" + type = number + default = 2160 # 90 days +} diff --git a/examples/migrate_pak_to_service_account/project_level/v3/versions.tf b/examples/migrate_pak_to_service_account/project_level/v3/versions.tf new file mode 100644 index 0000000000..8459278500 --- /dev/null +++ b/examples/migrate_pak_to_service_account/project_level/v3/versions.tf @@ -0,0 +1,8 @@ +terraform { + required_version = ">= 1.0" + required_providers { + mongodbatlas = { + source = "mongodb/mongodbatlas" + } + } +} diff --git a/examples/mongodbatlas_project_service_account/main.tf b/examples/mongodbatlas_project_service_account/main.tf index 93f6cfcc95..185fd368c5 100644 --- a/examples/mongodbatlas_project_service_account/main.tf +++ b/examples/mongodbatlas_project_service_account/main.tf @@ -24,7 +24,7 @@ output "service_account_name" { } output "service_account_first_secret" { - description = "The secret value of the first secret created with the service account. Available only immediately after initial creation." + description = "The secret value of the first secret created with the Project Service Account. Available only immediately after initial creation." value = try(mongodbatlas_project_service_account.this.secrets[0].secret, null) sensitive = true } diff --git a/examples/mongodbatlas_service_account/main.tf b/examples/mongodbatlas_service_account/main.tf index 0fe6c18c51..7513a51e90 100644 --- a/examples/mongodbatlas_service_account/main.tf +++ b/examples/mongodbatlas_service_account/main.tf @@ -24,7 +24,7 @@ output "service_account_name" { } output "service_account_first_secret" { - description = "The secret value of the first secret created with the service account. Available only immediately after initial creation." + description = "The secret value of the first secret created with the Service Account. Available only immediately after initial creation." value = try(mongodbatlas_service_account.this.secrets[0].secret, null) sensitive = true }