Skip to content
Draft
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
357 changes: 357 additions & 0 deletions docs/guides/migrate-pak-to-service-account.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,357 @@
---
page_title: "Migration Guide: Programmatic API Keys (PAKs) to Service Accounts (SAs)"
---

# Migration Guide: Programmatic API Keys (PAKs) to Service Accounts (SAs)

## Overview

Service Accounts are the recommended method to manage authentication to the Atlas Administration API. Service Accounts provide improved security over API keys by using the industry standard OAuth 2.0 protocol with the Client Credentials flow. This guide covers migrating from Programmatic API Keys (PAKs) to Service Accounts (SAs) in MongoDB Atlas.

**Note:** Migration to Service Accounts is **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 are supported.

Comment on lines +7 to +12
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggesting the following. Would like to get docs team input here:

Suggested change
## Overview
Service Accounts are the recommended method to manage authentication to the Atlas Administration API. Service Accounts provide improved security over API keys by using the industry standard OAuth 2.0 protocol with the Client Credentials flow. This guide covers migrating from Programmatic API Keys (PAKs) to Service Accounts (SAs) in MongoDB Atlas.
**Note:** Migration to Service Accounts is **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 are supported.
## Overview
This guide explains how to migrate from Programmatic API Key (PAK) resources to Service Account (SA) resources.
**Note:** Migration to Service Accounts is **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.
- **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).


---

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

## 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 |
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:

|--------------|-------------------------|-------|
| `mongodbatlas_api_key` | `mongodbatlas_service_account` | Organization-level API key / Service Account |
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
| `mongodbatlas_api_key` | `mongodbatlas_service_account` | Organization-level API key / Service Account |
| `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 State - PAK Resources Only
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
### Step 1: Initial State - PAK Resources Only
### Step 1: Initial Configuration - PAK Resources Only


This is your starting configuration with organization-level PAK resources (org PAK + assignment to project + access list entry):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
This is your starting configuration with organization-level PAK resources (org PAK + assignment to project + access list entry):
Original configuration with organization-level PAK resources:


```terraform
# Organization-level Programmatic API Key
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 can avoid repeating "organization-level" so often. Should be clear since we are in the "Organization-Level API Keys to Service Accounts" section.

Changing the api key description to not say "for project access" may help.

resource "mongodbatlas_api_key" "example" {
org_id = var.org_id
description = "Example API Key for project access"
role_names = ["ORG_READ_ONLY"]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe go with ORG_MEMBER here since we are assigning to project. (Also update other configs if agreed)

Suggested change
role_names = ["ORG_READ_ONLY"]
role_names = ["ORG_MEMBER"]

}

# Project assignment for the API Key
resource "mongodbatlas_api_key_project_assignment" "example" {
project_id = var.project_id
api_key_id = mongodbatlas_api_key.example.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" "example" {
org_id = var.org_id
api_key_id = mongodbatlas_api_key.example.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
# Service Account (new)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we use (new) in other guides when adding resources?

resource "mongodbatlas_service_account" "example" {
org_id = var.org_id
name = "example-service-account"
description = "Example Service Account for project access"
roles = ["ORG_READ_ONLY"]
secret_expires_after_hours = 2160 # 90 days
}

# Service Account Project Assignment (new)
resource "mongodbatlas_service_account_project_assignment" "example" {
project_id = var.project_id
client_id = mongodbatlas_service_account.example.client_id
roles = ["GROUP_READ_ONLY", "GROUP_DATA_ACCESS_READ_ONLY"]
}

# Service Account Access List Entry (new)
resource "mongodbatlas_service_account_access_list_entry" "example" {
org_id = var.org_id
client_id = mongodbatlas_service_account.example.client_id
cidr_block = "192.168.1.100/32"
# Alternative: ip_address = "192.168.1.100"
}

# Output to capture the secret (add this before running apply)
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.example.secrets[0].secret, null)
sensitive = true
}
```

```terraform
# Keep existing PAK resources (for now)
resource "mongodbatlas_api_key" "example" {
org_id = var.org_id
description = "Example API Key for project access"
role_names = ["ORG_READ_ONLY"]
}

resource "mongodbatlas_api_key_project_assignment" "example" {
project_id = var.project_id
api_key_id = mongodbatlas_api_key.example.api_key_id
roles = ["GROUP_READ_ONLY", "GROUP_DATA_ACCESS_READ_ONLY"]
}

resource "mongodbatlas_access_list_api_key" "example" {
org_id = var.org_id
api_key_id = mongodbatlas_api_key.example.api_key_id
cidr_block = "192.168.1.100/32"
}
```
Comment on lines +109 to +128
Copy link
Collaborator

Choose a reason for hiding this comment

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

Remove?


**Apply and test:**

1. Run `terraform plan` to review the changes.
2. Run `terraform apply` to create the Service Account resources.
3. **Important**: Save the Service Account secret from the output. The secret value is only returned once at creation time.

You can retrieve it using:

```bash
terraform output -raw service_account_first_secret
```
Comment on lines +134 to +140
Copy link
Collaborator

Choose a reason for hiding this comment

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

Check #4050 to be consistent on the wording for this step.
Example in the rotation guide:

  1. Retrieve and securely store the secret_2 value (warning: this prints the secret to your terminal):
terraform output -raw secret_2


4. Test your Service Account by updating your provider configuration or using it in your applications.
5. Verify that both PAK and SA authentication methods work correctly.
Comment on lines +142 to +143
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should merge these? Would not suggest updating your provider configuration to avoid confusion.

6. Re-run `terraform plan` to ensure you have no unexpected changes: `No changes. Your infrastructure matches the configuration.`
Copy link
Collaborator

Choose a reason for hiding this comment

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

Necessary? Might be a standard step we add in guides. Ok if so.


### Step 3: Final State - Remove PAK Resources, SA Resources Only

Once you've verified that the Service Account works correctly, remove the PAK resources from your Terraform configuration:

Comment on lines +148 to +149
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
Once you've verified that the Service Account works correctly, remove the PAK resources from your Terraform configuration:

Once you've verified that the Service Account works correctly, remove the PAK resources from your Terraform configuration:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
Once you've verified that the Service Account works correctly, remove the PAK resources from your Terraform configuration:
Once you have verified that the Service Account works correctly, remove the PAK resources from your configuration:


```terraform
# Service Account Resources (FINAL STATE)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
# Service Account Resources (FINAL STATE)

resource "mongodbatlas_service_account" "example" {
org_id = var.org_id
name = "example-service-account"
description = "Example Service Account for project access"
roles = ["ORG_READ_ONLY"]
secret_expires_after_hours = 2160 # 90 days
}

resource "mongodbatlas_service_account_project_assignment" "example" {
project_id = var.project_id
client_id = mongodbatlas_service_account.example.client_id
roles = ["GROUP_READ_ONLY", "GROUP_DATA_ACCESS_READ_ONLY"]
}

resource "mongodbatlas_service_account_access_list_entry" "example" {
org_id = var.org_id
client_id = mongodbatlas_service_account.example.client_id
cidr_block = "192.168.1.100/32"
}

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.example.secrets[0].secret, null)
sensitive = true
}
Comment on lines +174 to +178
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should remove this output now?

```

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

---

- **Important:** The Service Account secret is only returned once at creation time. Make sure to save it securely before proceeding.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Bit out of place, I think you can remove


- After successful migration, ensure no references to PAK resources remain in your 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


**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 State - PAK Resources Only

This is your starting configuration with project-level PAK resources (project PAK + access list entry):

```terraform
# Project-level Programmatic API Key
resource "mongodbatlas_project_api_key" "example" {
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" "example" {
org_id = var.org_id
api_key_id = mongodbatlas_project_api_key.example.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
# Project Service Account (new)
resource "mongodbatlas_project_service_account" "example" {
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
}

# Project Service Account Access List Entry (new)
resource "mongodbatlas_project_service_account_access_list_entry" "example" {
project_id = var.project_id
client_id = mongodbatlas_project_service_account.example.client_id
cidr_block = "192.168.1.100/32"
# Alternative: ip_address = "192.168.1.100"
}

# Output to capture the secret (add this before running apply)
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.example.secrets[0].secret, null)
sensitive = true
}
```

```terraform
# Keep existing PAK resources (for now)
resource "mongodbatlas_project_api_key" "example" {
description = "Example Project API Key"
project_assignment {
project_id = var.project_id
role_names = ["GROUP_READ_ONLY", "GROUP_DATA_ACCESS_READ_ONLY"]
}
}

resource "mongodbatlas_access_list_api_key" "example" {
org_id = var.org_id
api_key_id = mongodbatlas_project_api_key.example.api_key_id
cidr_block = "192.168.1.100/32"
}
```

**Apply and test:**

1. Run `terraform plan` to review the changes.
2. Run `terraform apply` to create the Service Account resource.
3. **Important**: Save the Service Account secret from the output. The secret value is only returned once at creation time.

You can retrieve it using:

```bash
terraform output -raw project_service_account_first_secret
```

4. Test your Service Account by updating your provider configuration or using it in your applications.
5. Verify that both PAK and SA authentication methods work correctly.
6. 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've verified that the Service Account works correctly, remove the PAK resources from your Terraform configuration:

```terraform
# Project Service Account Resources (FINAL STATE)
resource "mongodbatlas_project_service_account" "example" {
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" "example" {
project_id = var.project_id
client_id = mongodbatlas_project_service_account.example.client_id
cidr_block = "192.168.1.100/32"
}

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.example.secrets[0].secret, null)
sensitive = true
}
```

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

---

- **Important:** The Service Account secret is only returned once at creation time. Make sure to save it securely before proceeding.

- After successful migration, ensure no references to PAK resources remain in your configuration.
19 changes: 19 additions & 0 deletions examples/migrate_pak_to_service_account/org_level/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Combined Example: Organization-Level PAK → Service Account
Copy link
Collaborator

Choose a reason for hiding this comment

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

Given that we don't have readmes with instructions in the subfolders, I suggest following this style of readme here instead: examples/migrate_atlas_user_and_atlas_users/README.md


This combined example is organized into step subfolders (v1–v3):

- v1/: Initial state with:
- `mongodbatlas_api_key` (organization-level PAK),
- `mongodbatlas_api_key_project_assignment` (project assignment), and
- `mongodbatlas_access_list_api_key` (IP access list entry).
- v2/: Migration step showcasing the intermediate state:
- add `mongodbatlas_service_account` (organization-level SA),
- add `mongodbatlas_service_account_project_assignment` (project assignment),
- add `mongodbatlas_service_account_access_list_entry` (IP access list entry),
- keep existing PAK resources alongside SA resources for testing.
- v3/: Cleaned-up final configuration after v2 is applied:
- remove all PAK resources (`mongodbatlas_api_key`, `mongodbatlas_api_key_project_assignment`, `mongodbatlas_access_list_api_key`),
- keep only SA resources (`mongodbatlas_service_account`, `mongodbatlas_service_account_project_assignment`, `mongodbatlas_service_account_access_list_entry`).

Navigate into each version folder to see the step-specific configuration.

Loading
Loading