Skip to content

Commit 4d7972c

Browse files
EspenAlbertoarbusijwilliams-mongo
authored
doc: Adds example for OIDC Workload Azure (#2334)
* docs: Add example for OIDC Workload Azure * chore: fix shellcheck errors * chore: rename shell script to use .tmpl extension * fix: address lint errors * chore: follow filename format of CI job * chore: fix tf-lint error * Update examples/mongodbatlas_federated_settings_identity_provider/azure/README.md Co-authored-by: Oriol <[email protected]> * Update examples/mongodbatlas_federated_settings_identity_provider/azure/README.md Co-authored-by: Oriol <[email protected]> * address PR comments * remove unused Resource Table header * add warning about unlinking org * Update examples/mongodbatlas_federated_settings_identity_provider/azure/README.md [ci skip] Co-authored-by: John Williams <[email protected]> * Update examples/mongodbatlas_federated_settings_identity_provider/azure/README.md Co-authored-by: John Williams <[email protected]> * Update examples/mongodbatlas_federated_settings_identity_provider/azure/README.md [ci skip] Co-authored-by: John Williams <[email protected]> * Update examples/mongodbatlas_federated_settings_identity_provider/azure/README.md [ci skip] Co-authored-by: John Williams <[email protected]> * Update examples/mongodbatlas_federated_settings_identity_provider/azure/README.md [ci skip] Co-authored-by: John Williams <[email protected]> * Update examples/mongodbatlas_federated_settings_identity_provider/azure/README.md [ci skip] Co-authored-by: John Williams <[email protected]> * Update examples/mongodbatlas_federated_settings_identity_provider/azure/README.md [ci skip] Co-authored-by: John Williams <[email protected]> * link to official docs site --------- Co-authored-by: Oriol <[email protected]> Co-authored-by: John Williams <[email protected]>
1 parent dd63412 commit 4d7972c

File tree

11 files changed

+646
-0
lines changed

11 files changed

+646
-0
lines changed
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
# MongoDB Atlas Provider -- OIDC Workload Azure
2+
3+
## Dependencies
4+
5+
- Terraform [MongoDB Atlas Provider v1.17.0](https://registry.terraform.io/providers/mongodb/mongodbatlas/latest/docs)
6+
- Terraform [Hashicorp Cloud Init Provider v2.3.4](https://registry.terraform.io/providers/hashicorp/cloudinit/latest/docs)
7+
- Terraform [Hashicorp Azurerm Provider v3.106.1](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs)
8+
- Terraform [Hashicorp Random Provider v3.6.2](https://registry.terraform.io/providers/hashicorp/random/latest/docs)
9+
- A MongoDB Atlas account with an organization configured with [Federated Authentication](https://www.mongodb.com/docs/atlas/security/federated-authentication/#federation-management-console)
10+
- Get the `federated_settings_id` from the url, e.g., <https://cloud.mongodb.com/v2#/federation/{federated_settings_id}/overview>
11+
12+
## Diagrams
13+
14+
### Simplified OIDC flow
15+
16+
17+
```mermaid
18+
---
19+
title: "Simplified OIDC flow"
20+
---
21+
sequenceDiagram
22+
autonumber
23+
participant U as User/App
24+
participant A as Atlas
25+
participant Idp as Identity Provider (IdP)
26+
27+
Note right of A: OIDC is configured and set on the organization<br>issuer_uri=https://{some-idp}.com/
28+
A->>Idp: issuer_uri/.well-known/openid-configuration
29+
Idp->>A: {..., "jwks_uri": "jwks_uri"}
30+
A->>Idp: jwks_uri
31+
Idp->>A: {keys: [{"kid": kid}, ...]}
32+
33+
Note right of U: Obtain token
34+
U->>Idp: get_token(authentication-mechanism)
35+
Idp->>U: JWT token {"iss": "issuer_uri", sub: "user-identity", "kid": "key-used, ...}
36+
37+
Note right of A: A database user is registered with idp_id/sub as username
38+
U->>A: connect(JWT token)
39+
A->>A: use kid and keys to validate the token<br>lookup sub with registered users
40+
A->>U: auth ok
41+
```
42+
43+
- Example of `issuer_uri` (1):
44+
- <https://token.actions.githubusercontent.com/>
45+
- <https://gitlab.com/>
46+
- <https://sts.windows.net/{tenant-id}/>
47+
- Example of authentication mechanism (5)
48+
- username+password
49+
- two factor authentication
50+
- certificate
51+
- JWT Token Authentication(8):
52+
- Can use different fields in the `JWT` to authenticate
53+
54+
### OIDC Workload Azure
55+
56+
```mermaid
57+
---
58+
title: "OIDC Workload Azure"
59+
---
60+
sequenceDiagram
61+
autonumber
62+
participant VM as VM<br>(Azure)
63+
participant A as Atlas
64+
participant Idp as IdP<br>(Azure)
65+
66+
Note right of A: OIDC is configured and set on the organization<br>Atlas will keep an internal {idp_id}
67+
A->>Idp: https://sts.windows.net/{tenant-id}/.well-known/openid-configuration
68+
Idp->>A: {..., "jwks_uri": "jwks_uri"}
69+
A->>Idp: jwks_uri
70+
Idp->>A: {keys: [{"kid": kid}, ...]}
71+
72+
Note right of VM: A python script (pymongo.py) runs on boot<br>It reads the MongoURI from an env-var<br>The pymongo driver gets the JWT from the metadata endpoint<br>See description below
73+
VM->>Idp: http://169.254.169.254/metadata/identity/oauth2/token
74+
Idp->>VM: JWT token: {<br>"iss": "https://sts.windows.net/{tenant-id}", <br>"sub": "{vm_configured_identity}"<br>}
75+
76+
Note right of A: A database user exist on the organization<br>username: {idp_id}/{vm_configured_identity}
77+
VM ->> A: connect(JWT token, MongoURI)
78+
activate A
79+
A->>A: validate token and lookup database user
80+
A->>VM: auth ok
81+
VM->>A: insert record into database
82+
A->>VM: insert record response
83+
VM->>A: close connection
84+
deactivate A
85+
```
86+
87+
- (5) We use terraform to configure a [`user_assigned_identity`](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/user_assigned_identity) for the VM and use the [metadata endpoint](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/how-to-use-vm-token#get-a-token-using-http) to obtain the token
88+
- See also [MongoDB pymongo docs.](https://www.mongodb.com/docs/languages/python/pymongo-driver/current/security/enterprise-authentication/#mongodb-oidc)
89+
- The python script is configured by [cloud init](https://cloudinit.readthedocs.io/en/latest/reference/examples.html#writing-out-arbitrary-files) using `custom_data` on the [VM (`linux_virtual_machine`)](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_virtual_machine)
90+
- (10) Can be configured with terraform variables:
91+
- `insert_record_database`
92+
- `insert_record_collection`
93+
- `insert_record_fields`
94+
95+
## Usage
96+
97+
**1\. Ensure your Azure credentials are set up.**
98+
99+
1. Install the Azure CLI by following the steps from the [official Azure documentation](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli).
100+
2. Run the command `az login` and this will open your default browser and perform the authentication.
101+
3. Once authenticated, it will print the user details as below:
102+
103+
```shell
104+
⇒ az login
105+
You have logged in. Now let us find all the subscriptions to which you have access...
106+
The following tenants don't contain accessible subscriptions. Use 'az login --allow-no-subscriptions' to have tenant level access.
107+
XXXXX
108+
[
109+
{
110+
"cloudName": "AzureCloud",
111+
"homeTenantId": "XXXXX",
112+
"id": "XXXXX",
113+
"isDefault": true,
114+
"managedByTenants": [],
115+
"name": "Pay-As-You-Go",
116+
"state": "Enabled",
117+
"tenantId": "XXXXX",
118+
"user": {
119+
"name": "[email protected]",
120+
"type": "user"
121+
}
122+
}
123+
]
124+
```
125+
126+
**2\. Ensure your MongoDB Atlas credentials are set up.**
127+
128+
This can be done using environment variables:
129+
130+
```shell
131+
export MONGODB_ATLAS_PUBLIC_KEY="xxxx"
132+
export MONGODB_ATLAS_PRIVATE_KEY="xxxx"
133+
```
134+
135+
**3\. TFVARS**
136+
137+
Create a file named `vars.auto.tfvars`
138+
139+
```hcl
140+
project_name = "tf-example-oidc"
141+
location = "eastus" # Azure region
142+
owner = "my-user"
143+
org_id = "YOUR_ATLAS_ORG_ID" # e.g 65def6ce0f722a1507105aa5
144+
region = "US_EAST_1" # Atlas region
145+
insert_record_database = "test"
146+
insert_record_collection = "test"
147+
ssh_public_key = "ssh-rsa AAA...." # see below for how you can configure this
148+
```
149+
150+
Configuring the `ssh_public_key` for the Azure VM:
151+
152+
```shell
153+
cd ~/.ssh
154+
ssh-keygen -t rsa -b 4096 -C "<[email protected]>" # to generate a keypair for the VM
155+
export TF_VAR_ssh_public_key=$(cat ~/.ssh/id_rsa.pub) # set the `ssh_public_key` with an env var instead of using the variable
156+
```
157+
158+
**4\. Import the `mongodbatlas_federated_settings_org_config`**
159+
160+
replace `{federated_settings_id}` and `{org_id}` and run:
161+
162+
```shell
163+
terraform init
164+
terraform import mongodbatlas_federated_settings_org_config.this {federated_settings_id}-{org_id}
165+
```
166+
167+
**5\. Review the Terraform plan.**
168+
169+
Execute the below command and ensure you are happy with the plan.
170+
171+
```shell
172+
terraform plan
173+
```
174+
175+
**6\. Execute the Terraform apply.**
176+
177+
Now execute the plan to provision the resources.
178+
179+
```shell
180+
terraform apply
181+
```
182+
183+
**7\. Connect to MongoDB to verify the record has been inserted.**
184+
185+
- Get the connection string by running `terraform output -json | jq -r '.user_test_conn_string.value'`
186+
- Open your preferred tool, e.g., [MongoDB Compass](https://www.mongodb.com/products/tools/compass)
187+
- You should see a new record inserted in `{database}` (default name is `test`), `{collection}` (default name is `test`), e.g.: `{"_id": "6661790007beeb09e3f1b914", "hello": "world", "ts": "2024-06-06T08:53:20.125919"}`
188+
- `{database}` depends on `insert_record_database` Terraform variable
189+
- `{collection}` depends on `insert_record_collection` Terraform variable
190+
- It can be up to 2 minutes after `terraform apply` finishes for the record to be inserted
191+
192+
**8\. Destroy the resources.**
193+
194+
- Once you are finished your testing, ensure you destroy the resources to avoid unnecessary Atlas and Azure charges.
195+
- Note that the organization will be unlinked from the [Federated Authentication](https://www.mongodb.com/docs/atlas/security/federated-authentication/#federation-management-console).
196+
- Use `terraform state rm mongodbatlas_federated_settings_org_config.this` to avoid this
197+
198+
```shell
199+
terraform destroy
200+
```
201+
202+
## Next Steps
203+
204+
- Change `pymongo_oidc.sh` to run your own app, start your `systemd` service, etc.
205+
- Integrate with your identity provider of choice, ideas:
206+
- Github for accessing your Atlas Cluster from a Github Action
207+
- Gitlab for accessing your Atlas Cluster from a Gitlab Job
208+
- For more information, see [MongoDB Atlas Workload OIDC docs](https://www.mongodb.com/docs/atlas/workload-oidc/#prepare-your-external-identity-provider).
209+
210+
## Troubleshooting
211+
212+
### Debugging the VM Instance
213+
214+
1. Get the ssh command for connecting: `terraform output -json | jq -r '.ssh_connection_string.value'` and connect.
215+
2. Verify the python script exist: `ls -la ~` should show `pymongo_oidc.py`.
216+
3. Check the output log from cloud-init:
217+
- `sudo cat /var/log/cloud-init.log`
218+
- expect to find log lines where the script result is logged: `/var/lib/cloud/instance/scripts/part-002` (could have a slightly different name)
219+
- `sudo cat /var/lib/cloud/instance/scripts/part-002`
220+
- `sudo cat /var/log/cloud-init-output.log`
221+
- the log should end with something similar to:
222+
223+
```log
224+
... more log content
225+
Installing collected packages: dnspython, pymongo
226+
Successfully installed dnspython-2.6.1 pymongo-4.7.3
227+
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
228+
creating client with uri=mongodb://looooong-connectionstring.mongodb.net:27017/?ssl=true&authSource=admin&replicaSet=atlas-xgvzij-shard-0&authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:https%3A%2F%2Fmanagement.azure.com%2F # same as output "user_oidc_conn_string"
229+
inserting into test test, record: {'hello': 'world', 'ts': '2024-06-06T08:53:20.125919'}
230+
insert response: InsertOneResult(ObjectId('6661790007beeb09e3f1b914'), acknowledged=True)
231+
script complete
232+
Cloud-init v. 24.1.3-0ubuntu1~22.04.1 finished at Thu, 06 Jun 2024 08:53:21 +0000. Datasource DataSourceAzure [seed=/dev/sr0]. Up 111.45 seconds
233+
```
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
locals {
2+
mongodb_uri = mongodbatlas_cluster.this.connection_strings[0].standard
3+
}
4+
5+
data "mongodbatlas_federated_settings" "this" {
6+
org_id = var.org_id
7+
}
8+
resource "mongodbatlas_project" "this" {
9+
name = var.project_name
10+
org_id = var.org_id
11+
tags = local.tags
12+
}
13+
14+
resource "mongodbatlas_project_ip_access_list" "mongo-access" {
15+
project_id = mongodbatlas_project.this.id
16+
cidr_block = "0.0.0.0/0"
17+
}
18+
19+
resource "mongodbatlas_cluster" "this" {
20+
project_id = mongodbatlas_project.this.id
21+
name = var.project_name
22+
mongo_db_major_version = "7.0"
23+
cluster_type = "REPLICASET"
24+
replication_specs {
25+
num_shards = 1
26+
regions_config {
27+
region_name = var.region
28+
electable_nodes = 3
29+
priority = 7
30+
read_only_nodes = 0
31+
}
32+
}
33+
cloud_backup = false
34+
auto_scaling_disk_gb_enabled = false
35+
provider_name = "AWS"
36+
disk_size_gb = 10
37+
provider_instance_size_name = "M10"
38+
}
39+
40+
resource "mongodbatlas_federated_settings_identity_provider" "oidc" {
41+
federation_settings_id = data.mongodbatlas_federated_settings.this.id
42+
audience = var.token_audience
43+
authorization_type = "USER"
44+
description = "oidc-for-azure"
45+
# e.g. "https://sts.windows.net/91405384-d71e-47f5-92dd-759e272cdc1c/"
46+
issuer_uri = "https://sts.windows.net/${azurerm_user_assigned_identity.this.tenant_id}/"
47+
idp_type = "WORKLOAD"
48+
name = "OIDC-for-azure"
49+
protocol = "OIDC"
50+
# groups_claim = null
51+
user_claim = "sub"
52+
}
53+
54+
resource "mongodbatlas_federated_settings_org_config" "this" {
55+
federation_settings_id = data.mongodbatlas_federated_settings.this.id
56+
org_id = var.org_id
57+
domain_restriction_enabled = false
58+
domain_allow_list = []
59+
data_access_identity_provider_ids = [mongodbatlas_federated_settings_identity_provider.oidc.idp_id]
60+
}
61+
62+
resource "mongodbatlas_database_user" "oidc" {
63+
project_id = mongodbatlas_project.this.id
64+
username = "${mongodbatlas_federated_settings_identity_provider.oidc.idp_id}/${azurerm_user_assigned_identity.this.principal_id}"
65+
oidc_auth_type = "USER"
66+
auth_database_name = "$external" # required when using OIDC USER authentication
67+
68+
roles {
69+
role_name = "atlasAdmin"
70+
database_name = "admin"
71+
}
72+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
locals {
2+
test_user_username = "user_${random_password.username.result}"
3+
test_user_password = random_password.password.result
4+
}
5+
6+
resource "random_password" "password" {
7+
length = 12
8+
special = false
9+
}
10+
11+
resource "random_password" "username" {
12+
length = 12
13+
special = false
14+
}
15+
16+
resource "mongodbatlas_database_user" "test_user" {
17+
username = local.test_user_username
18+
password = local.test_user_password
19+
project_id = mongodbatlas_project.this.id
20+
auth_database_name = "admin"
21+
22+
roles {
23+
role_name = "atlasAdmin"
24+
database_name = "admin"
25+
}
26+
}

0 commit comments

Comments
 (0)