Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/data-sources/project_service_account.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion docs/data-sources/project_service_accounts.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion docs/data-sources/service_account.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion docs/data-sources/service_accounts.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
291 changes: 291 additions & 0 deletions docs/guides/migrate-pak-to-service-account.md
Original file line number Diff line number Diff line change
@@ -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).


---

<details>
<summary><span style="font-size:1.4em; font-weight:bold;">Organization-Level Migration</span></summary>

## Organization-Level API Keys to Service Accounts
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] Would it make sense to use L3 headings for everything within this section? (Right sub headings are all L2)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated! thank you!


**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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the statement "allows both authentication methods to work simultaneously" is not true, both SA and PAK exist simultaneously, but are not used simultaneously.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking on the case of using SA/PAKs as the authentication method for the MongoDB Atlas Terraform Provider, but in the cases of other applications/tools, the statement can be true. Feel free to ignore the previous comment


```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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we explain how that would look like in the case of switching the auth method of the TF provider from PAK to SA? something similar to https://registry.terraform.io/providers/mongodb/mongodbatlas/latest/docs/guides/provider-configuration#programmatic-access-key warning block where we say Update your provider attributes or environment variables to use SA credentials instead of PAK credentials, then run terraform plan to verify everything works correctly.

I say this because the guide is generic in terms of the application that uses the PAK/SA, but I think the main use case we want to enable is the TF provider authentication

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A previous iteration mentioned this. I suggested to remove it to avoid confusion and mention only "your applications".
For example: If the user were to change the provider credentials in this same config, they would not have permissions to re-apply as the SA has only ORG_MEMBER role.

I think it makes sense for the credentials used for provider authentication in a particular configuration to be managed outside of that same configuration.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should cover the end to end case of using the new SA resource sand switching from PAK to SA as the auth method, since it's the main problem we are trying to solve by implementing these resources in TF

Copy link
Collaborator

@manupedrozo manupedrozo Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am on board on including a link to the SA authentication registry page to reference how it is used but I would be careful to add it as a step here given the previous comment:

I think it makes sense for the credentials used for provider authentication in a particular configuration to be managed outside of that same configuration.

Do we know of PAK users authenticating with a PAK that is managed within the same configuration? I don't think we should be recommending this approach (can lock yourself out).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not saying to recommend the approach of using PAKs created in a configuration as the auth mechanism within the same configuration, but I would like to have in this guide the case of switching from PAK to SA as the auth mechanism of the TF provider, which is the main use case we want to enable, even if the SA/PAK is created in one configuration and used as auth in another. Hope that makes sense

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that makes sense. @filipcirtog can you mention this in the guide and include a link to the provider configuration guide with SA?
https://registry.terraform.io/providers/mongodb/mongodbatlas/latest/docs/guides/provider-configuration

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the suggestions. I have added some notes on this. Please let me know WDY.

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.`


</details>

---

<details>
<summary><span style="font-size:1.4em; font-weight:bold;">Project-Level Migration</span></summary>

## Project-Level API Keys to Service Accounts
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assume same comments as for Org-level steps

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated! Thank you!


**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.`


</details>

---

## 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.
2 changes: 1 addition & 1 deletion docs/guides/service-account-secret-rotation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion docs/resources/project_service_account.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion docs/resources/service_account.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
Loading