From a5f160691c20c425f65af63ca51e277f4f7abfe0 Mon Sep 17 00:00:00 2001 From: Rakhul S Prakash Date: Wed, 21 Jan 2026 09:41:02 +0530 Subject: [PATCH] feat: Update Database User add oidcAuthType --- .github/workflows/contract-testing.yaml | 43 +++++++++ cfn-resources/database-user/Makefile | 20 ++++- .../database-user/cmd/resource/model.go | 1 + .../database-user/cmd/resource/resource.go | 11 ++- cfn-resources/database-user/docs/README.md | 14 +++ .../mongodb-atlas-databaseuser.json | 5 ++ .../test/contract-testing/cfn-test-create.sh | 16 ++++ .../test/contract-testing/cfn-test-delete.sh | 15 ++++ .../test/inputs_3_create.template.json | 28 ++++++ .../test/inputs_3_update.template.json | 31 +++++++ examples/database-user/oidcUser.json | 87 +++++++++++++++++++ 11 files changed, 268 insertions(+), 3 deletions(-) create mode 100755 cfn-resources/database-user/test/contract-testing/cfn-test-create.sh create mode 100755 cfn-resources/database-user/test/contract-testing/cfn-test-delete.sh create mode 100644 cfn-resources/database-user/test/inputs_3_create.template.json create mode 100644 cfn-resources/database-user/test/inputs_3_update.template.json create mode 100644 examples/database-user/oidcUser.json diff --git a/.github/workflows/contract-testing.yaml b/.github/workflows/contract-testing.yaml index 81eedd449..1b8721b32 100644 --- a/.github/workflows/contract-testing.yaml +++ b/.github/workflows/contract-testing.yaml @@ -17,6 +17,7 @@ jobs: auditing: ${{ steps.filter.outputs.auditing }} cloud-backup-restore-jobs: ${{ steps.filter.outputs.cloud-backup-restore-jobs }} cluster-outage-simulation: ${{ steps.filter.outputs.cluster-outage-simulation }} + database-user: ${{ steps.filter.outputs.database-user }} federated-database-instance: ${{ steps.filter.outputs.federated-database-instance }} federated-query-limit: ${{ steps.filter.outputs.federated-query-limit }} flex-cluster: ${{ steps.filter.outputs.flex-cluster }} @@ -51,6 +52,8 @@ jobs: - 'cfn-resources/cloud-backup-restore-jobs/**' cluster-outage-simulation: - 'cfn-resources/cluster-outage-simulation/**' + database-user: + - 'cfn-resources/database-user/**' federated-database-instance: - 'cfn-resources/federated-database-instance/**' federated-query-limit: @@ -323,6 +326,46 @@ jobs: cat inputs/inputs_1_create.json + make run-contract-testing + make delete-test-resources + database-user: + needs: change-detection + if: ${{ needs.change-detection.outputs.database-user == 'true' }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 + with: + go-version-file: 'cfn-resources/go.mod' + - name: setup Atlas CLI + uses: mongodb/atlas-github-action@e3c9e0204659bafbb3b65e1eb1ee745cca0e9f3b + - uses: aws-actions/setup-sam@c2a20b1822cc4a6bc594ff7f1dbb658758e383c3 + with: + use-installer: true + - uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID_TEST_ENV }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY_TEST_ENV }} + aws-region: eu-west-1 + - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 + with: + python-version: '3.9' + cache: 'pip' # caching pip dependencies + - run: pip install cloudformation-cli cloudformation-cli-go-plugin + - name: Run the Contract test + shell: bash + env: + MONGODB_ATLAS_PUBLIC_API_KEY: ${{ secrets.CLOUD_DEV_PUBLIC_KEY }} + MONGODB_ATLAS_PRIVATE_API_KEY: ${{ secrets.CLOUD_DEV_PRIVATE_KEY }} + MONGODB_ATLAS_ORG_ID: ${{ secrets.CLOUD_DEV_ORG_ID }} + MONGODB_ATLAS_OPS_MANAGER_URL: ${{ vars.MONGODB_ATLAS_BASE_URL }} + MONGODB_ATLAS_PROFILE: cfn-cloud-dev-github-action + run: | + pushd cfn-resources/database-user + make create-test-resources + + cat inputs/* + make run-contract-testing make delete-test-resources federated-database-instance: diff --git a/cfn-resources/database-user/Makefile b/cfn-resources/database-user/Makefile index 6df18d210..4f3d93ebd 100644 --- a/cfn-resources/database-user/Makefile +++ b/cfn-resources/database-user/Makefile @@ -13,7 +13,25 @@ build: debug: cfn generate - env GOOS=$(goos) CGO_ENABLED=$(cgo) GOARCH=$(goarch) go build -ldflags="$(ldXflagsD)" -tags="$(tags)" -o bin/bootstrap cmd/main.go + env GOOS=$(goos) CGO_ENABLED=$(cgo) GOARCH=$(goarch) go build -ldflags="$(ldXflagsD)" -tags="$(tags)" -o bin/debug cmd/main.go clean: rm -rf bin + +submit: clean build # submit to private registry must use release build not debug build + @echo "==> Submitting to private registry for testing" + cfn submit --set-default --region us-east-1 + +create-test-resources: + @echo "==> Creating test files and resources for contract testing" + ./test/contract-testing/cfn-test-create.sh + +delete-test-resources: + @echo "==> Delete test resources used for contract testing" + ./test/contract-testing/cfn-test-delete.sh + +run-contract-testing: + @echo "==> Run contract testing" + make build + sam local start-lambda & + cfn test --function-name TestEntrypoint --verbose diff --git a/cfn-resources/database-user/cmd/resource/model.go b/cfn-resources/database-user/cmd/resource/model.go index 4139d9e91..c520840c9 100644 --- a/cfn-resources/database-user/cmd/resource/model.go +++ b/cfn-resources/database-user/cmd/resource/model.go @@ -11,6 +11,7 @@ type Model struct { Labels []LabelDefinition `json:",omitempty"` LdapAuthType *string `json:",omitempty"` X509Type *string `json:",omitempty"` + OIDCAuthType *string `json:",omitempty"` Password *string `json:",omitempty"` ProjectId *string `json:",omitempty"` Roles []RoleDefinition `json:",omitempty"` diff --git a/cfn-resources/database-user/cmd/resource/resource.go b/cfn-resources/database-user/cmd/resource/resource.go index e2e318240..fd11599c7 100644 --- a/cfn-resources/database-user/cmd/resource/resource.go +++ b/cfn-resources/database-user/cmd/resource/resource.go @@ -110,6 +110,7 @@ func Read(req handler.Request, prevModel *Model, currentModel *Model) (handler.P currentModel.LdapAuthType = databaseUser.LdapAuthType currentModel.AWSIAMType = databaseUser.AwsIAMType currentModel.X509Type = databaseUser.X509Type + currentModel.OIDCAuthType = databaseUser.OidcAuthType currentModel.Username = &databaseUser.Username var roles []RoleDefinition @@ -252,7 +253,9 @@ func List(req handler.Request, prevModel *Model, currentModel *Model) (handler.P DatabaseName: &databaseUser.DatabaseName, Description: databaseUser.Description, LdapAuthType: databaseUser.LdapAuthType, + AWSIAMType: databaseUser.AwsIAMType, X509Type: databaseUser.X509Type, + OIDCAuthType: databaseUser.OidcAuthType, Username: &databaseUser.Username, ProjectId: currentModel.ProjectId, } @@ -341,10 +344,13 @@ func setModel(currentModel *Model) (*admin.CloudDatabaseUser, error) { if currentModel.X509Type == nil { currentModel.X509Type = &none } + if currentModel.OIDCAuthType == nil { + currentModel.OIDCAuthType = &none + } if currentModel.Password == nil { - if (*currentModel.LdapAuthType == none) && (*currentModel.AWSIAMType == none) && (*currentModel.X509Type == none) { - err := fmt.Errorf("password cannot be empty if not LDAP or IAM or X509 is not provided") + if (*currentModel.LdapAuthType == none) && (*currentModel.AWSIAMType == none) && (*currentModel.X509Type == none) && (*currentModel.OIDCAuthType == none) { + err := fmt.Errorf("password cannot be empty if not LDAP or IAM or X509 or OIDC is not provided") return nil, err } currentModel.Password = aws.String("") @@ -364,6 +370,7 @@ func setModel(currentModel *Model) (*admin.CloudDatabaseUser, error) { LdapAuthType: currentModel.LdapAuthType, AwsIAMType: currentModel.AWSIAMType, X509Type: currentModel.X509Type, + OidcAuthType: currentModel.OIDCAuthType, DeleteAfterDate: util.StringPtrToTimePtr(currentModel.DeleteAfterDate), Description: currentModel.Description, } diff --git a/cfn-resources/database-user/docs/README.md b/cfn-resources/database-user/docs/README.md index bcea86e6f..d0462667d 100644 --- a/cfn-resources/database-user/docs/README.md +++ b/cfn-resources/database-user/docs/README.md @@ -19,6 +19,7 @@ To declare this entity in your AWS CloudFormation template, use the following sy "Labels" : [ labelDefinition, ... ], "LdapAuthType" : String, "X509Type" : String, + "OIDCAuthType" : String, "Password" : String, "ProjectId" : String, "Roles" : [ roleDefinition, ... ], @@ -42,6 +43,7 @@ Properties: - labelDefinition LdapAuthType: String X509Type: String + OIDCAuthType: String Password: String ProjectId: String Roles: @@ -130,6 +132,18 @@ _Allowed Values_: NONE | MANAGED | CUSTOMERNONE | USER | IDP_GROUP + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + #### Password The user’s password. This field is not included in the entity returned from the server. diff --git a/cfn-resources/database-user/mongodb-atlas-databaseuser.json b/cfn-resources/database-user/mongodb-atlas-databaseuser.json index 5b58a2bff..302d613ec 100644 --- a/cfn-resources/database-user/mongodb-atlas-databaseuser.json +++ b/cfn-resources/database-user/mongodb-atlas-databaseuser.json @@ -120,6 +120,11 @@ ], "type": "string" }, + "OIDCAuthType": { + "description": "Human-readable label that indicates whether the new database user or group authenticates with OIDC federated authentication. To create a federated authentication user, specify the value of USER in this field. To create a federated authentication group, specify the value of IDP_GROUP in this field. Default value is `NONE`.", + "enum": ["NONE", "USER", "IDP_GROUP"], + "type": "string" + }, "Password": { "description": "The user’s password. This field is not included in the entity returned from the server.", "type": "string" diff --git a/cfn-resources/database-user/test/contract-testing/cfn-test-create.sh b/cfn-resources/database-user/test/contract-testing/cfn-test-create.sh new file mode 100755 index 000000000..4b795316e --- /dev/null +++ b/cfn-resources/database-user/test/contract-testing/cfn-test-create.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +# This tool generates the resources and json files in the inputs/ for `cfn test`. +set -o errexit +set -o nounset +set -o pipefail + +projectName="cfn-test-bot-$(date +%s)-$RANDOM" + +# create project +projectId=$(atlas projects create "${projectName}" --output=json | jq -r '.id') + +echo "projectId: $projectId" +echo "projectName: $projectName" + +./test/cfn-test-create-inputs.sh "$projectName" diff --git a/cfn-resources/database-user/test/contract-testing/cfn-test-delete.sh b/cfn-resources/database-user/test/contract-testing/cfn-test-delete.sh new file mode 100755 index 000000000..71286ddfb --- /dev/null +++ b/cfn-resources/database-user/test/contract-testing/cfn-test-delete.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +# This tool deletes the mongodb resources used for `cfn test` as inputs. +set -o errexit +set -o nounset +set -o pipefail + +projectId=$(jq -r '.ProjectId' ./inputs/inputs_1_create.json) + +# delete project +if atlas projects delete "$projectId" --force; then + echo "$projectId project deletion OK" +else + (echo "Failed cleaning project: $projectId" && exit 1) +fi diff --git a/cfn-resources/database-user/test/inputs_3_create.template.json b/cfn-resources/database-user/test/inputs_3_create.template.json new file mode 100644 index 000000000..183c60d98 --- /dev/null +++ b/cfn-resources/database-user/test/inputs_3_create.template.json @@ -0,0 +1,28 @@ +{ + "Username": "DataUser3", + "Password": "MongoDB12345%", + "ProjectId": "${MONGODB_ATLAS_PROJECT_ID}", + "Profile": "${MONGODB_ATLAS_PROFILE}", + "DatabaseName": "admin", + "Description": "User with Labels to test OIDCAuthType field", + "OIDCAuthType": "NONE", + "Roles": [ + { + "RoleName": "readWrite", + "DatabaseName": "testdb", + "CollectionName": "col1" + } + ], + "Scopes": [ + { + "Type": "CLUSTER", + "Name": "testdb" + } + ], + "Labels": [ + { + "Key": "testType", + "Value": "oidc-field-validation" + } + ] +} diff --git a/cfn-resources/database-user/test/inputs_3_update.template.json b/cfn-resources/database-user/test/inputs_3_update.template.json new file mode 100644 index 000000000..a60592af7 --- /dev/null +++ b/cfn-resources/database-user/test/inputs_3_update.template.json @@ -0,0 +1,31 @@ +{ + "Username": "DataUser3", + "Password": "MongoDB12345%", + "ProjectId": "${MONGODB_ATLAS_PROJECT_ID}", + "Profile": "${MONGODB_ATLAS_PROFILE}", + "DatabaseName": "admin", + "Description": "Updated user with modified Labels", + "OIDCAuthType": "NONE", + "Roles": [ + { + "RoleName": "readWriteAnyDatabase", + "DatabaseName": "admin" + } + ], + "Scopes": [ + { + "Type": "CLUSTER", + "Name": "testdb2" + } + ], + "Labels": [ + { + "Key": "testType", + "Value": "oidc-field-validation" + }, + { + "Key": "updated", + "Value": "true" + } + ] +} diff --git a/examples/database-user/oidcUser.json b/examples/database-user/oidcUser.json new file mode 100644 index 000000000..9e0088fe0 --- /dev/null +++ b/examples/database-user/oidcUser.json @@ -0,0 +1,87 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "This template creates a database user with OIDC (OpenID Connect) federated authentication. OIDC authentication requires federation settings to be configured in your MongoDB Atlas organization. OIDCAuthType supports USER (individual users) and IDP_GROUP (identity provider groups).", + "Parameters": { + "ProjectId": { + "Type": "String", + "Description": "Unique 24-hexadecimal digit string that identifies your project" + }, + "Profile": { + "Type": "String", + "Description": "Secret Manager Profile that contains the Atlas Programmatic keys", + "ConstraintDescription": "", + "Default": "default" + }, + "DatabaseName": { + "Type": "String", + "Description": "Database against which the database user authenticates. For OIDC authentication, this must be $external", + "Default": "$external" + }, + "Username": { + "Type": "String", + "Description": "Username for OIDC authentication. Format: / (e.g., 6489e4f0bebcb4b0dbd8e7b3/john.doe). The federation_settings_id can be found in your Atlas organization's federation settings." + }, + "Description": { + "Type": "String", + "Description": "Description of this database user.", + "Default": "OIDC federated authentication user" + } + }, + "Mappings": {}, + "Resources": { + "OidcUser": { + "Type": "MongoDB::Atlas::DatabaseUser", + "Metadata": { + "Comment": "Remember to update the \"Roles\" field with a list of roles that you want to assign to the user. OIDC authentication requires federation settings to be configured in your Atlas organization. The username must follow the format: /. For group-based authentication, use OIDCAuthType: IDP_GROUP and format: /" + }, + "Properties": { + "Username": { + "Ref": "Username" + }, + "OIDCAuthType": "USER", + "ProjectId": { + "Ref": "ProjectId" + }, + "DatabaseName": { + "Ref": "DatabaseName" + }, + "Profile": { + "Ref": "Profile" + }, + "Description": { + "Ref": "Description" + }, + "Roles": [ + { + "RoleName": "readWriteAnyDatabase", + "DatabaseName": "admin" + } + ], + "Labels": [ + { + "Key": "authType", + "Value": "oidc" + }, + { + "Key": "environment", + "Value": "production" + } + ] + } + } + }, + "Outputs": { + "MongoDBOidcUsername": { + "Description": "Unique identifier for the OIDC database user", + "Value": { + "Fn::GetAtt": ["OidcUser", "UserCFNIdentifier"] + } + }, + "Username": { + "Description": "Username of the OIDC database user", + "Value": { + "Ref": "Username" + } + } + } +}