diff --git a/.github/workflows/contract-testing.yaml b/.github/workflows/contract-testing.yaml
index c44710081..c9024af6c 100644
--- a/.github/workflows/contract-testing.yaml
+++ b/.github/workflows/contract-testing.yaml
@@ -18,6 +18,7 @@ jobs:
backup-compliance-policy: ${{ steps.filter.outputs.backup-compliance-policy }}
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 }}
federated-settings-identity-provider: ${{ steps.filter.outputs.federated-settings-identity-provider }}
@@ -56,6 +57,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:
@@ -373,6 +376,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 e525854c8..32af935ce 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"
+ }
+ }
+ }
+}