From 37c527d6eb4b5441d6ffe2eae14bc2873e195ae9 Mon Sep 17 00:00:00 2001 From: Rakhul S Prakash Date: Thu, 18 Dec 2025 09:48:26 +0530 Subject: [PATCH 1/5] feat: Add Federated Settings Identity Provider Resource --- .github/workflows/contract-testing.yaml | 48 ++- .../.rpdk-config | 12 + .../Makefile | 37 ++ .../README.md | 31 ++ .../cmd/main.go | 85 +++++ .../cmd/resource/config.go | 19 + .../cmd/resource/handlers.go | 175 +++++++++ .../cmd/resource/mappings.go | 143 ++++++++ .../cmd/resource/mappings_test.go | 114 ++++++ .../cmd/resource/model.go | 28 ++ .../cmd/resource/resource.go | 86 +++++ .../docs/README.md | 281 +++++++++++++++ ...las-federatedsettingsidentityprovider.json | 137 +++++++ .../resource-role.yaml | 38 ++ .../template.yml | 26 ++ .../test/cfn-test-create-inputs.sh | 69 ++++ .../test/cfn-test-delete-inputs.sh | 4 + .../test/contract-testing/cfn-test-create.sh | 8 + .../test/contract-testing/cfn-test-delete.sh | 8 + ...-identity-provider.sample-cfn-request.json | 18 + .../test/inputs_1_create.template.json | 14 + .../test/inputs_1_update.template.json | 16 + cfn-resources/util/constants/constants.go | 2 + .../README.md | 35 ++ ...rated-settings-identity-provider-oidc.json | 334 ++++++++++++++++++ 25 files changed, 1767 insertions(+), 1 deletion(-) create mode 100644 cfn-resources/federated-settings-identity-provider/.rpdk-config create mode 100644 cfn-resources/federated-settings-identity-provider/Makefile create mode 100644 cfn-resources/federated-settings-identity-provider/README.md create mode 100644 cfn-resources/federated-settings-identity-provider/cmd/main.go create mode 100644 cfn-resources/federated-settings-identity-provider/cmd/resource/config.go create mode 100644 cfn-resources/federated-settings-identity-provider/cmd/resource/handlers.go create mode 100644 cfn-resources/federated-settings-identity-provider/cmd/resource/mappings.go create mode 100644 cfn-resources/federated-settings-identity-provider/cmd/resource/mappings_test.go create mode 100644 cfn-resources/federated-settings-identity-provider/cmd/resource/model.go create mode 100644 cfn-resources/federated-settings-identity-provider/cmd/resource/resource.go create mode 100644 cfn-resources/federated-settings-identity-provider/docs/README.md create mode 100644 cfn-resources/federated-settings-identity-provider/mongodb-atlas-federatedsettingsidentityprovider.json create mode 100644 cfn-resources/federated-settings-identity-provider/resource-role.yaml create mode 100644 cfn-resources/federated-settings-identity-provider/template.yml create mode 100755 cfn-resources/federated-settings-identity-provider/test/cfn-test-create-inputs.sh create mode 100755 cfn-resources/federated-settings-identity-provider/test/cfn-test-delete-inputs.sh create mode 100755 cfn-resources/federated-settings-identity-provider/test/contract-testing/cfn-test-create.sh create mode 100644 cfn-resources/federated-settings-identity-provider/test/contract-testing/cfn-test-delete.sh create mode 100644 cfn-resources/federated-settings-identity-provider/test/federated-settings-identity-provider.sample-cfn-request.json create mode 100644 cfn-resources/federated-settings-identity-provider/test/inputs_1_create.template.json create mode 100644 cfn-resources/federated-settings-identity-provider/test/inputs_1_update.template.json create mode 100644 examples/federated-settings-identity-provider/README.md create mode 100644 examples/federated-settings-identity-provider/federated-settings-identity-provider-oidc.json diff --git a/.github/workflows/contract-testing.yaml b/.github/workflows/contract-testing.yaml index ab660c710..553bbcf31 100644 --- a/.github/workflows/contract-testing.yaml +++ b/.github/workflows/contract-testing.yaml @@ -20,6 +20,7 @@ jobs: cluster-outage-simulation: ${{ steps.filter.outputs.cluster-outage-simulation }} 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 }} flex-cluster: ${{ steps.filter.outputs.flex-cluster }} online-archive: ${{ steps.filter.outputs.online-archive }} organization: ${{ steps.filter.outputs.organization }} @@ -59,6 +60,8 @@ jobs: - 'cfn-resources/federated-database-instance/**' federated-query-limit: - 'cfn-resources/federated-query-limit/**' + federated-settings-identity-provider: + - 'cfn-resources/federated-settings-identity-provider/**' flex-cluster: - 'cfn-resources/flex-cluster/**' online-archive: @@ -451,7 +454,50 @@ jobs: cat inputs/inputs_1_create.json cat inputs/inputs_1_update.json - + + make run-contract-testing + make delete-test-resources + + federated-settings-identity-provider: + needs: change-detection + if: ${{ needs.change-detection.outputs.federated-settings-identity-provider == '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 + MONGODB_ATLAS_FEDERATION_SETTINGS_ID: ${{ vars.MONGODB_ATLAS_FEDERATION_SETTINGS_ID }} + run: | + cd cfn-resources/federated-settings-identity-provider + make create-test-resources + + cat inputs/inputs_1_create.json + cat inputs/inputs_1_update.json + make run-contract-testing make delete-test-resources diff --git a/cfn-resources/federated-settings-identity-provider/.rpdk-config b/cfn-resources/federated-settings-identity-provider/.rpdk-config new file mode 100644 index 000000000..7824f8cda --- /dev/null +++ b/cfn-resources/federated-settings-identity-provider/.rpdk-config @@ -0,0 +1,12 @@ +{ + "typeName": "MongoDB::Atlas::FederatedSettingsIdentityProvider", + "language": "go", + "runtime": "provided.al2", + "entrypoint": "bootstrap", + "testEntrypoint": "bootstrap", + "settings": { + "import_path": "github.com/mongodb/mongodbatlas-cloudformation-resources/federated-settings-identity-provider", + "protocolVersion": "2.0.0", + "pluginVersion": "2.0.4" + } +} diff --git a/cfn-resources/federated-settings-identity-provider/Makefile b/cfn-resources/federated-settings-identity-provider/Makefile new file mode 100644 index 000000000..4f3d93ebd --- /dev/null +++ b/cfn-resources/federated-settings-identity-provider/Makefile @@ -0,0 +1,37 @@ +.PHONY: build test clean +tags=logging callback metrics scheduler +cgo=0 +goos=linux +goarch=amd64 +CFNREP_GIT_SHA?=$(shell git rev-parse HEAD) +ldXflags=-s -w -X github.com/mongodb/mongodbatlas-cloudformation-resources/util.defaultLogLevel=info -X github.com/mongodb/mongodbatlas-cloudformation-resources/version.Version=${CFNREP_GIT_SHA} +ldXflagsD=-X github.com/mongodb/mongodbatlas-cloudformation-resources/util.defaultLogLevel=debug -X github.com/mongodb/mongodbatlas-cloudformation-resources/version.Version=${CFNREP_GIT_SHA} + +build: + cfn generate + env GOOS=$(goos) CGO_ENABLED=$(cgo) GOARCH=$(goarch) go build -ldflags="$(ldXflags)" -tags="$(tags)" -o bin/bootstrap cmd/main.go + +debug: + cfn generate + 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/federated-settings-identity-provider/README.md b/cfn-resources/federated-settings-identity-provider/README.md new file mode 100644 index 000000000..81534d636 --- /dev/null +++ b/cfn-resources/federated-settings-identity-provider/README.md @@ -0,0 +1,31 @@ +# MongoDB::Atlas::FederatedSettingsIdentityProvider + +## Description + +The federated settings identity provider resource provides access to your Atlas +federated authentication identity providers (SAML and OIDC). It lets you +create, edit, and delete identity providers within an Atlas federation. + +## Requirements + +To securely give CloudFormation access to your Atlas credentials, you must +set up an [AWS Profile](/README.md#mongodb-atlas-api-keys-credential-management). + +## Attributes and Parameters + +See the [resource docs](docs/README.md). + +## Cloudformation Examples + +Examples for this resource will be added in `/examples/`. + +## Contract Testing + +Contract testing requires a valid Federation Settings ID +export MONGODB_ATLAS_FEDERATION_SETTINGS_ID="your-federation-settings-id" + +# Run contract tests + +make create-test-resources +cfn test -- -k contract_create_delete +make delete-test-resources diff --git a/cfn-resources/federated-settings-identity-provider/cmd/main.go b/cfn-resources/federated-settings-identity-provider/cmd/main.go new file mode 100644 index 000000000..be60b7a80 --- /dev/null +++ b/cfn-resources/federated-settings-identity-provider/cmd/main.go @@ -0,0 +1,85 @@ +// Code generated by 'cfn generate', changes will be undone by the next invocation. DO NOT EDIT. +package main + +import ( + "errors" + "fmt" + "log" + + "github.com/aws-cloudformation/cloudformation-cli-go-plugin/cfn" + "github.com/aws-cloudformation/cloudformation-cli-go-plugin/cfn/handler" + "github.com/mongodb/mongodbatlas-cloudformation-resources/federated-settings-identity-provider/cmd/resource" +) + +// Handler is a container for the CRUDL actions exported by resources +type Handler struct{} + +// Create wraps the related Create function exposed by the resource code +func (r *Handler) Create(req handler.Request) handler.ProgressEvent { + return wrap(req, resource.Create) +} + +// Read wraps the related Read function exposed by the resource code +func (r *Handler) Read(req handler.Request) handler.ProgressEvent { + return wrap(req, resource.Read) +} + +// Update wraps the related Update function exposed by the resource code +func (r *Handler) Update(req handler.Request) handler.ProgressEvent { + return wrap(req, resource.Update) +} + +// Delete wraps the related Delete function exposed by the resource code +func (r *Handler) Delete(req handler.Request) handler.ProgressEvent { + return wrap(req, resource.Delete) +} + +// List wraps the related List function exposed by the resource code +func (r *Handler) List(req handler.Request) handler.ProgressEvent { + return wrap(req, resource.List) +} + +// main is the entry point of the application. +func main() { + cfn.Start(&Handler{}) +} + +type handlerFunc func(handler.Request, *resource.Model, *resource.Model) (handler.ProgressEvent, error) + +func wrap(req handler.Request, f handlerFunc) (response handler.ProgressEvent) { + defer func() { + // Catch any panics and return a failed ProgressEvent + if r := recover(); r != nil { + err, ok := r.(error) + if !ok { + err = errors.New(fmt.Sprint(r)) + } + + log.Printf("Trapped error in handler: %v", err) + + response = handler.NewFailedEvent(err) + } + }() + + // Populate the previous model + prevModel := &resource.Model{} + if err := req.UnmarshalPrevious(prevModel); err != nil { + log.Printf("Error unmarshaling prev model: %v", err) + return handler.NewFailedEvent(err) + } + + // Populate the current model + currentModel := &resource.Model{} + if err := req.Unmarshal(currentModel); err != nil { + log.Printf("Error unmarshaling model: %v", err) + return handler.NewFailedEvent(err) + } + + response, err := f(req, prevModel, currentModel) + if err != nil { + log.Printf("Error returned from handler function: %v", err) + return handler.NewFailedEvent(err) + } + + return response +} diff --git a/cfn-resources/federated-settings-identity-provider/cmd/resource/config.go b/cfn-resources/federated-settings-identity-provider/cmd/resource/config.go new file mode 100644 index 000000000..4d9eb7831 --- /dev/null +++ b/cfn-resources/federated-settings-identity-provider/cmd/resource/config.go @@ -0,0 +1,19 @@ +// Code generated by 'cfn generate', changes will be undone by the next invocation. DO NOT EDIT. +// Updates to this type are made my editing the schema file and executing the 'generate' command. +package resource + +import "github.com/aws-cloudformation/cloudformation-cli-go-plugin/cfn/handler" + +// TypeConfiguration is autogenerated from the json schema +type TypeConfiguration struct { +} + +// Configuration returns a resource's configuration. +func Configuration(req handler.Request) (*TypeConfiguration, error) { + // Populate the type configuration + typeConfig := &TypeConfiguration{} + if err := req.UnmarshalTypeConfig(typeConfig); err != nil { + return typeConfig, err + } + return typeConfig, nil +} diff --git a/cfn-resources/federated-settings-identity-provider/cmd/resource/handlers.go b/cfn-resources/federated-settings-identity-provider/cmd/resource/handlers.go new file mode 100644 index 000000000..218e9c3d6 --- /dev/null +++ b/cfn-resources/federated-settings-identity-provider/cmd/resource/handlers.go @@ -0,0 +1,175 @@ +// Copyright 2026 MongoDB Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package resource + +import ( + "context" + "fmt" + + "github.com/aws-cloudformation/cloudformation-cli-go-plugin/cfn/handler" + "github.com/aws/aws-sdk-go-v2/service/cloudformation/types" + "go.mongodb.org/atlas-sdk/v20250312012/admin" + + "github.com/mongodb/mongodbatlas-cloudformation-resources/util" + "github.com/mongodb/mongodbatlas-cloudformation-resources/util/progressevent" +) + +func HandleCreate(client *util.MongoDBClient, currentModel *Model) handler.ProgressEvent { + if currentModel.Protocol == nil || *currentModel.Protocol != ProtocolOIDC { + return progressevent.GetFailedEventByCode( + fmt.Sprintf("create is only supported by %s, %s must be imported", ProtocolOIDC, ProtocolSAML), + string(types.HandlerErrorCodeInvalidRequest), + ) + } + + federationSettingsID := util.SafeString(currentModel.FederationSettingsId) + + createRequest := ExpandOIDCCreateRequest(currentModel) + created, res, err := client.AtlasSDK.FederatedAuthenticationApi.CreateIdentityProvider(context.Background(), federationSettingsID, createRequest).Execute() + if err != nil { + return progressevent.GetFailedEventByResponse( + fmt.Sprintf("error creating federation settings identity provider (%s): %s", federationSettingsID, err.Error()), + res, + ) + } + + createdID := created.GetId() + currentModel.IdpId = &createdID + return HandleRead(client, currentModel) +} + +func HandleRead(client *util.MongoDBClient, currentModel *Model) handler.ProgressEvent { + federationSettingsID := util.SafeString(currentModel.FederationSettingsId) + idpID := util.SafeString(currentModel.IdpId) + + idp, res, err := client.AtlasSDK.FederatedAuthenticationApi.GetIdentityProvider(context.Background(), federationSettingsID, idpID).Execute() + if err != nil { + if util.StatusNotFound(res) { + return progressevent.GetFailedEventByCode("Resource not found", string(types.HandlerErrorCodeNotFound)) + } + return progressevent.GetFailedEventByResponse( + fmt.Sprintf("error getting federation settings identity provider (%s/%s): %s", federationSettingsID, idpID, err.Error()), + res, + ) + } + + model := GetFederatedSettingsIdentityProviderModel(idp, currentModel) + return handler.ProgressEvent{ + OperationStatus: handler.Success, + Message: "Read Complete", + ResourceModel: model, + } +} + +func HandleUpdate(client *util.MongoDBClient, prevModel *Model, currentModel *Model) handler.ProgressEvent { + federationSettingsID := util.SafeString(currentModel.FederationSettingsId) + idpID := util.SafeString(currentModel.IdpId) + + associatedDomains := currentModel.AssociatedDomains + if associatedDomains == nil { + associatedDomains = []string{} + } + requestedScopes := currentModel.RequestedScopes + if requestedScopes == nil { + requestedScopes = []string{} + } + + updateReq := &admin.FederationIdentityProviderUpdate{ + AssociatedDomains: &associatedDomains, + Audience: currentModel.Audience, + AuthorizationType: currentModel.AuthorizationType, + ClientId: currentModel.ClientId, + Description: currentModel.Description, + DisplayName: currentModel.Name, + GroupsClaim: currentModel.GroupsClaim, + IdpType: currentModel.IdpType, + IssuerUri: currentModel.IssuerUri, + Protocol: currentModel.Protocol, + PemFileInfo: nil, + RequestBinding: currentModel.RequestBinding, + RequestedScopes: &requestedScopes, + ResponseSignatureAlgorithm: currentModel.ResponseSignatureAlgorithm, + SsoDebugEnabled: currentModel.SsoDebugEnabled, + SsoUrl: currentModel.SsoUrl, + Status: currentModel.Status, + UserClaim: currentModel.UserClaim, + } + + updated, updRes, err := client.AtlasSDK.FederatedAuthenticationApi.UpdateIdentityProvider(context.Background(), federationSettingsID, idpID, updateReq).Execute() + if err != nil { + return progressevent.GetFailedEventByResponse( + fmt.Sprintf("error updating federation settings identity provider (%s): %s", federationSettingsID, err.Error()), + updRes, + ) + } + + model := GetFederatedSettingsIdentityProviderModel(updated, currentModel) + return handler.ProgressEvent{ + OperationStatus: handler.Success, + Message: "Update Complete", + ResourceModel: model, + } +} + +func HandleDelete(client *util.MongoDBClient, currentModel *Model) handler.ProgressEvent { + federationSettingsID := util.SafeString(currentModel.FederationSettingsId) + idpID := util.SafeString(currentModel.IdpId) + + res, err := client.AtlasSDK.FederatedAuthenticationApi.DeleteIdentityProvider(context.Background(), federationSettingsID, idpID).Execute() + if err != nil { + return progressevent.GetFailedEventByResponse( + fmt.Sprintf("error deleting federation settings identity provider (%s): %s, error: %s", federationSettingsID, idpID, err.Error()), + res, + ) + } + + return handler.ProgressEvent{ + OperationStatus: handler.Success, + Message: "Delete Complete", + } +} + +func HandleList(client *util.MongoDBClient, currentModel *Model) handler.ProgressEvent { + federationSettingsID := util.SafeString(currentModel.FederationSettingsId) + + params := &admin.ListIdentityProvidersApiParams{ + FederationSettingsId: federationSettingsID, + Protocol: &allProtocols, + IdpType: &allIdpTypes, + } + providers, res, err := client.AtlasSDK.FederatedAuthenticationApi.ListIdentityProvidersWithParams(context.Background(), params).Execute() + if err != nil { + return progressevent.GetFailedEventByResponse( + fmt.Sprintf("error listing federation settings identity providers (%s): %s", federationSettingsID, err.Error()), + res, + ) + } + + results := providers.GetResults() + models := make([]any, 0, len(results)) + for i := range results { + m := &Model{ + Profile: currentModel.Profile, + FederationSettingsId: currentModel.FederationSettingsId, + } + models = append(models, GetFederatedSettingsIdentityProviderModel(&results[i], m)) + } + + return handler.ProgressEvent{ + OperationStatus: handler.Success, + Message: "List Complete", + ResourceModels: models, + } +} diff --git a/cfn-resources/federated-settings-identity-provider/cmd/resource/mappings.go b/cfn-resources/federated-settings-identity-provider/cmd/resource/mappings.go new file mode 100644 index 000000000..bbcc04980 --- /dev/null +++ b/cfn-resources/federated-settings-identity-provider/cmd/resource/mappings.go @@ -0,0 +1,143 @@ +// Copyright 2026 MongoDB Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package resource + +import ( + "go.mongodb.org/atlas-sdk/v20250312012/admin" + + "github.com/mongodb/mongodbatlas-cloudformation-resources/util" +) + +const ( + ProtocolSAML = "SAML" + ProtocolOIDC = "OIDC" + + IdpTypeWorkforce = "WORKFORCE" + IdpTypeWorkload = "WORKLOAD" +) + +var ( + allProtocols = []string{ProtocolSAML, ProtocolOIDC} + allIdpTypes = []string{IdpTypeWorkforce, IdpTypeWorkload} +) + +func GetFederatedSettingsIdentityProviderModel(api *admin.FederationIdentityProvider, currentModel *Model) *Model { + var model *Model + if currentModel != nil { + model = currentModel + } else { + model = &Model{} + } + + if api == nil { + return model + } + + if oktaID, ok := api.GetOktaIdpIdOk(); ok && *oktaID != "" { + model.OktaIdpId = oktaID + } + + idpID := api.GetId() + model.IdpId = &idpID + + displayName := api.GetDisplayName() + model.Name = &displayName + issuerURI := api.GetIssuerUri() + model.IssuerUri = &issuerURI + protocol := api.GetProtocol() + model.Protocol = &protocol + + description := api.GetDescription() + model.Description = &description + authorizationType := api.GetAuthorizationType() + model.AuthorizationType = &authorizationType + idpType := api.GetIdpType() + model.IdpType = &idpType + + switch protocol { + case ProtocolSAML: + requestBinding := api.GetRequestBinding() + model.RequestBinding = &requestBinding + responseSignatureAlgorithm := api.GetResponseSignatureAlgorithm() + model.ResponseSignatureAlgorithm = &responseSignatureAlgorithm + model.SsoDebugEnabled = api.SsoDebugEnabled + ssoURL := api.GetSsoUrl() + model.SsoUrl = &ssoURL + status := api.GetStatus() + model.Status = &status + + associatedDomains := api.GetAssociatedDomains() + if len(associatedDomains) == 0 && currentModel != nil && len(currentModel.AssociatedDomains) > 0 { + associatedDomains = currentModel.AssociatedDomains + } + model.AssociatedDomains = associatedDomains + case ProtocolOIDC: + audience := api.GetAudience() + model.Audience = &audience + clientID := api.GetClientId() + model.ClientId = &clientID + groupsClaim := api.GetGroupsClaim() + model.GroupsClaim = &groupsClaim + + requestedScopes := api.GetRequestedScopes() + if len(requestedScopes) == 0 && currentModel != nil && len(currentModel.RequestedScopes) > 0 { + requestedScopes = currentModel.RequestedScopes + } + model.RequestedScopes = requestedScopes + + userClaim := api.GetUserClaim() + model.UserClaim = &userClaim + + associatedDomains := api.GetAssociatedDomains() + if len(associatedDomains) == 0 && currentModel != nil && len(currentModel.AssociatedDomains) > 0 { + associatedDomains = currentModel.AssociatedDomains + } + model.AssociatedDomains = associatedDomains + default: + return model + } + + return model +} + +func ExpandOIDCCreateRequest(model *Model) *admin.FederationOidcIdentityProviderUpdate { + var associatedDomains []string + if model.AssociatedDomains != nil { + associatedDomains = model.AssociatedDomains + } else { + associatedDomains = []string{} + } + var requestedScopes []string + if model.RequestedScopes != nil { + requestedScopes = model.RequestedScopes + } else { + requestedScopes = []string{} + } + + return &admin.FederationOidcIdentityProviderUpdate{ + Audience: util.Pointer(util.SafeString(model.Audience)), + AssociatedDomains: &associatedDomains, + AuthorizationType: util.Pointer(util.SafeString(model.AuthorizationType)), + ClientId: util.Pointer(util.SafeString(model.ClientId)), + Description: util.Pointer(util.SafeString(model.Description)), + DisplayName: util.Pointer(util.SafeString(model.Name)), + GroupsClaim: util.Pointer(util.SafeString(model.GroupsClaim)), + IdpType: util.Pointer(util.SafeString(model.IdpType)), + IssuerUri: util.Pointer(util.SafeString(model.IssuerUri)), + Protocol: util.Pointer(util.SafeString(model.Protocol)), + RequestedScopes: &requestedScopes, + UserClaim: util.Pointer(util.SafeString(model.UserClaim)), + } +} diff --git a/cfn-resources/federated-settings-identity-provider/cmd/resource/mappings_test.go b/cfn-resources/federated-settings-identity-provider/cmd/resource/mappings_test.go new file mode 100644 index 000000000..405248ce1 --- /dev/null +++ b/cfn-resources/federated-settings-identity-provider/cmd/resource/mappings_test.go @@ -0,0 +1,114 @@ +// Copyright 2025 MongoDB Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package resource_test + +import ( + "testing" + + "github.com/mongodb/mongodbatlas-cloudformation-resources/federated-settings-identity-provider/cmd/resource" + "github.com/stretchr/testify/assert" + "go.mongodb.org/atlas-sdk/v20250312012/admin" +) + +func TestGetFederatedSettingsIdentityProviderModel_SAML(t *testing.T) { + protocol := "SAML" + displayName := "saml-name" + issuerURI := "https://issuer.example.com" + api := &admin.FederationIdentityProvider{ + Id: "idp-1", + OktaIdpId: "okta-1", + Protocol: &protocol, + DisplayName: &displayName, + IssuerUri: &issuerURI, + RequestBinding: func() *string { s := "HTTP-POST"; return &s }(), + ResponseSignatureAlgorithm: func() *string { s := "RSA-SHA256"; return &s }(), + SsoDebugEnabled: func() *bool { b := true; return &b }(), + SsoUrl: func() *string { s := "https://sso.example.com"; return &s }(), + Status: func() *string { s := "ACTIVE"; return &s }(), + AssociatedDomains: func() *[]string { s := []string{"example.com"}; return &s }(), + } + + model := resource.GetFederatedSettingsIdentityProviderModel(api, &resource.Model{}) + + assert.Equal(t, "idp-1", *model.IdpId) + assert.Equal(t, "okta-1", *model.OktaIdpId) + assert.Equal(t, "SAML", *model.Protocol) + assert.Equal(t, "saml-name", *model.Name) + assert.Equal(t, "https://issuer.example.com", *model.IssuerUri) + assert.Equal(t, "HTTP-POST", *model.RequestBinding) + assert.Equal(t, "RSA-SHA256", *model.ResponseSignatureAlgorithm) + assert.True(t, *model.SsoDebugEnabled) + assert.Equal(t, "https://sso.example.com", *model.SsoUrl) + assert.Equal(t, "ACTIVE", *model.Status) + assert.Equal(t, []string{"example.com"}, model.AssociatedDomains) + // OIDC-only fields should not be set by the SAML branch + assert.Nil(t, model.ClientId) + assert.Nil(t, model.UserClaim) +} + +func TestGetFederatedSettingsIdentityProviderModel_OIDC(t *testing.T) { + protocol := "OIDC" + displayName := "oidc-name" + issuerURI := "https://issuer.oidc.example.com" + api := &admin.FederationIdentityProvider{ + Id: "idp-2", + OktaIdpId: "okta-2", + Protocol: &protocol, + DisplayName: &displayName, + IssuerUri: &issuerURI, + Audience: func() *string { s := "aud"; return &s }(), + ClientId: func() *string { s := "client"; return &s }(), + GroupsClaim: func() *string { s := "groups"; return &s }(), + RequestedScopes: func() *[]string { s := []string{"openid", "profile"}; return &s }(), + UserClaim: func() *string { s := "sub"; return &s }(), + AssociatedDomains: func() *[]string { s := []string{"oidc.example.com"}; return &s }(), + } + + model := resource.GetFederatedSettingsIdentityProviderModel(api, &resource.Model{}) + + assert.Equal(t, "idp-2", *model.IdpId) + assert.Equal(t, "okta-2", *model.OktaIdpId) + assert.Equal(t, "OIDC", *model.Protocol) + assert.Equal(t, "oidc-name", *model.Name) + assert.Equal(t, "https://issuer.oidc.example.com", *model.IssuerUri) + assert.Equal(t, "aud", *model.Audience) + assert.Equal(t, "client", *model.ClientId) + assert.Equal(t, "groups", *model.GroupsClaim) + assert.Equal(t, []string{"openid", "profile"}, model.RequestedScopes) + assert.Equal(t, "sub", *model.UserClaim) + assert.Equal(t, []string{"oidc.example.com"}, model.AssociatedDomains) + // SAML-only fields should not be set by the OIDC branch + assert.Nil(t, model.RequestBinding) + assert.Nil(t, model.SsoUrl) +} + +func TestExpandOIDCCreateRequest_DefaultSlices(t *testing.T) { + protocol := resource.ProtocolOIDC + name := "n" + issuer := "i" + m := &resource.Model{ + Protocol: &protocol, + Name: &name, + IssuerUri: &issuer, + // AssociatedDomains and RequestedScopes intentionally nil to exercise defaults + } + + req := resource.ExpandOIDCCreateRequest(m) + assert.NotNil(t, req) + assert.NotNil(t, req.AssociatedDomains) + assert.NotNil(t, req.RequestedScopes) + assert.Empty(t, *req.AssociatedDomains) + assert.Empty(t, *req.RequestedScopes) +} diff --git a/cfn-resources/federated-settings-identity-provider/cmd/resource/model.go b/cfn-resources/federated-settings-identity-provider/cmd/resource/model.go new file mode 100644 index 000000000..055fea9cc --- /dev/null +++ b/cfn-resources/federated-settings-identity-provider/cmd/resource/model.go @@ -0,0 +1,28 @@ +// Code generated by 'cfn generate', changes will be undone by the next invocation. DO NOT EDIT. +// Updates to this type are made my editing the schema file and executing the 'generate' command. +package resource + +// Model is autogenerated from the json schema +type Model struct { + Profile *string `json:",omitempty"` + FederationSettingsId *string `json:",omitempty"` + Name *string `json:",omitempty"` + IssuerUri *string `json:",omitempty"` + RequestBinding *string `json:",omitempty"` + ResponseSignatureAlgorithm *string `json:",omitempty"` + AssociatedDomains []string `json:",omitempty"` + SsoDebugEnabled *bool `json:",omitempty"` + SsoUrl *string `json:",omitempty"` + Status *string `json:",omitempty"` + OktaIdpId *string `json:",omitempty"` + IdpId *string `json:",omitempty"` + Protocol *string `json:",omitempty"` + Audience *string `json:",omitempty"` + ClientId *string `json:",omitempty"` + GroupsClaim *string `json:",omitempty"` + RequestedScopes []string `json:",omitempty"` + UserClaim *string `json:",omitempty"` + Description *string `json:",omitempty"` + AuthorizationType *string `json:",omitempty"` + IdpType *string `json:",omitempty"` +} diff --git a/cfn-resources/federated-settings-identity-provider/cmd/resource/resource.go b/cfn-resources/federated-settings-identity-provider/cmd/resource/resource.go new file mode 100644 index 000000000..816bd6946 --- /dev/null +++ b/cfn-resources/federated-settings-identity-provider/cmd/resource/resource.go @@ -0,0 +1,86 @@ +// Copyright 2026 MongoDB Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package resource + +import ( + "github.com/mongodb/mongodbatlas-cloudformation-resources/util" + "github.com/mongodb/mongodbatlas-cloudformation-resources/util/constants" + "github.com/mongodb/mongodbatlas-cloudformation-resources/util/validator" + + "github.com/aws-cloudformation/cloudformation-cli-go-plugin/cfn/handler" +) + +var ( + createRequiredFields = []string{constants.FederationSettingsID, constants.Name, constants.IssuerURI} + readRequiredFields = []string{constants.FederationSettingsID, constants.IdpID} + updateRequiredFields = []string{constants.FederationSettingsID, constants.IdpID} + deleteRequiredFields = []string{constants.FederationSettingsID, constants.IdpID} + listRequiredFields = []string{constants.FederationSettingsID} +) + +func setupRequest(req handler.Request, currentModel *Model, requiredFields []string) (*util.MongoDBClient, *handler.ProgressEvent) { + util.SetupLogger("mongodb-atlas-federated-settings-identity-provider") + util.SetDefaultProfileIfNotDefined(¤tModel.Profile) + + if errEvent := validator.ValidateModel(requiredFields, currentModel); errEvent != nil { + return nil, errEvent + } + + client, pe := util.NewAtlasClient(&req, currentModel.Profile) + if pe != nil { + return nil, pe + } + return client, nil +} + +func Create(req handler.Request, prevModel *Model, currentModel *Model) (handler.ProgressEvent, error) { + client, peErr := setupRequest(req, currentModel, createRequiredFields) + if peErr != nil { + return *peErr, nil + } + return HandleCreate(client, currentModel), nil +} + +func Read(req handler.Request, prevModel *Model, currentModel *Model) (handler.ProgressEvent, error) { + client, peErr := setupRequest(req, currentModel, readRequiredFields) + if peErr != nil { + return *peErr, nil + } + return HandleRead(client, currentModel), nil +} + +func Update(req handler.Request, prevModel *Model, currentModel *Model) (handler.ProgressEvent, error) { + client, peErr := setupRequest(req, currentModel, updateRequiredFields) + if peErr != nil { + return *peErr, nil + } + return HandleUpdate(client, prevModel, currentModel), nil +} + +func Delete(req handler.Request, prevModel *Model, currentModel *Model) (handler.ProgressEvent, error) { + client, peErr := setupRequest(req, currentModel, deleteRequiredFields) + if peErr != nil { + return *peErr, nil + } + return HandleDelete(client, currentModel), nil +} + +func List(req handler.Request, prevModel *Model, currentModel *Model) (handler.ProgressEvent, error) { + client, peErr := setupRequest(req, currentModel, listRequiredFields) + if peErr != nil { + return *peErr, nil + } + return HandleList(client, currentModel), nil +} diff --git a/cfn-resources/federated-settings-identity-provider/docs/README.md b/cfn-resources/federated-settings-identity-provider/docs/README.md new file mode 100644 index 000000000..ecc553898 --- /dev/null +++ b/cfn-resources/federated-settings-identity-provider/docs/README.md @@ -0,0 +1,281 @@ +# MongoDB::Atlas::FederatedSettingsIdentityProvider + +Resource for managing MongoDB Atlas Federated Settings Identity Providers (SAML and OIDC) within an Atlas federation. + +## Syntax + +To declare this entity in your AWS CloudFormation template, use the following syntax: + +### JSON + +
+{
+    "Type" : "MongoDB::Atlas::FederatedSettingsIdentityProvider",
+    "Properties" : {
+        "Profile" : String,
+        "FederationSettingsId" : String,
+        "Name" : String,
+        "IssuerUri" : String,
+        "RequestBinding" : String,
+        "ResponseSignatureAlgorithm" : String,
+        "AssociatedDomains" : [ String, ... ],
+        "SsoDebugEnabled" : Boolean,
+        "SsoUrl" : String,
+        "Status" : String,
+        "Protocol" : String,
+        "Audience" : String,
+        "ClientId" : String,
+        "GroupsClaim" : String,
+        "RequestedScopes" : [ String, ... ],
+        "UserClaim" : String,
+        "Description" : String,
+        "AuthorizationType" : String,
+        "IdpType" : String
+    }
+}
+
+ +### YAML + +
+Type: MongoDB::Atlas::FederatedSettingsIdentityProvider
+Properties:
+    Profile: String
+    FederationSettingsId: String
+    Name: String
+    IssuerUri: String
+    RequestBinding: String
+    ResponseSignatureAlgorithm: String
+    AssociatedDomains: 
+      - String
+    SsoDebugEnabled: Boolean
+    SsoUrl: String
+    Status: String
+    Protocol: String
+    Audience: String
+    ClientId: String
+    GroupsClaim: String
+    RequestedScopes: 
+      - String
+    UserClaim: String
+    Description: String
+    AuthorizationType: String
+    IdpType: String
+
+ +## Properties + +#### Profile + +The profile is defined in AWS Secrets Manager. See [Secret Manager Profile setup](../../../examples/profile-secret.yaml). + +_Required_: No + +_Type_: String + +_Update requires_: [Replacement](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-replacement) + +#### FederationSettingsId + +Unique 24-hexadecimal digit string that identifies your federation. + +_Required_: Yes + +_Type_: String + +_Minimum Length_: 24 + +_Maximum Length_: 24 + +_Pattern_: ^([a-f0-9]{24})$ + +_Update requires_: [Replacement](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-replacement) + +#### Name + +Human-readable name (display name) of the identity provider. + +_Required_: Yes + +_Type_: String + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### IssuerUri + +Issuer URI of the identity provider. + +_Required_: Yes + +_Type_: String + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### RequestBinding + +SAML request binding. + +_Required_: No + +_Type_: String + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### ResponseSignatureAlgorithm + +SAML response signature algorithm. + +_Required_: No + +_Type_: String + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### AssociatedDomains + +List of associated domains for this identity provider. + +_Required_: No + +_Type_: List of String + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### SsoDebugEnabled + +Flag that indicates whether to enable SSO debug. + +_Required_: No + +_Type_: Boolean + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### SsoUrl + +SSO URL. + +_Required_: No + +_Type_: String + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### Status + +Identity provider status. + +_Required_: No + +_Type_: String + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### Protocol + +Identity provider protocol. + +_Required_: No + +_Type_: String + +_Allowed Values_: SAML | OIDC + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### Audience + +OIDC audience. + +_Required_: No + +_Type_: String + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### ClientId + +OIDC client ID. + +_Required_: No + +_Type_: String + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### GroupsClaim + +OIDC groups claim. + +_Required_: No + +_Type_: String + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### RequestedScopes + +OIDC requested scopes. + +_Required_: No + +_Type_: List of String + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### UserClaim + +OIDC user claim. + +_Required_: No + +_Type_: String + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### Description + +Description of the identity provider. + +_Required_: No + +_Type_: String + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### AuthorizationType + +OIDC authorization type. + +_Required_: No + +_Type_: String + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### IdpType + +Identity provider type (for OIDC). + +_Required_: No + +_Type_: String + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +## Return Values + +### Fn::GetAtt + +The `Fn::GetAtt` intrinsic function returns a value for a specified attribute of this type. The following are the available attributes and sample return values. + +For more information about using the `Fn::GetAtt` intrinsic function, see [Fn::GetAtt](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-getatt.html). + +#### OktaIdpId + +Legacy identity provider identifier (Okta IdP ID). Returned by the API. + +#### IdpId + +Unique identifier of the identity provider. Returned by the API. + diff --git a/cfn-resources/federated-settings-identity-provider/mongodb-atlas-federatedsettingsidentityprovider.json b/cfn-resources/federated-settings-identity-provider/mongodb-atlas-federatedsettingsidentityprovider.json new file mode 100644 index 000000000..31b94196f --- /dev/null +++ b/cfn-resources/federated-settings-identity-provider/mongodb-atlas-federatedsettingsidentityprovider.json @@ -0,0 +1,137 @@ +{ + "typeName": "MongoDB::Atlas::FederatedSettingsIdentityProvider", + "description": "Resource for managing MongoDB Atlas Federated Settings Identity Providers (SAML and OIDC) within an Atlas federation.", + "sourceUrl": "https://github.com/mongodb/mongodbatlas-cloudformation-resources/tree/master/cfn-resources/federated-settings-identity-provider", + "properties": { + "Profile": { + "type": "string", + "description": "The profile is defined in AWS Secrets Manager. See [Secret Manager Profile setup](../../../examples/profile-secret.yaml).", + "default": "default" + }, + "FederationSettingsId": { + "type": "string", + "description": "Unique 24-hexadecimal digit string that identifies your federation.", + "minLength": 24, + "maxLength": 24, + "pattern": "^([a-f0-9]{24})$" + }, + "Name": { + "type": "string", + "description": "Human-readable name (display name) of the identity provider." + }, + "IssuerUri": { + "type": "string", + "description": "Issuer URI of the identity provider." + }, + "RequestBinding": { + "type": "string", + "description": "SAML request binding." + }, + "ResponseSignatureAlgorithm": { + "type": "string", + "description": "SAML response signature algorithm." + }, + "AssociatedDomains": { + "type": "array", + "description": "List of associated domains for this identity provider.", + "insertionOrder": false, + "items": { + "type": "string" + } + }, + "SsoDebugEnabled": { + "type": "boolean", + "description": "Flag that indicates whether to enable SSO debug." + }, + "SsoUrl": { + "type": "string", + "description": "SSO URL." + }, + "Status": { + "type": "string", + "description": "Identity provider status." + }, + "OktaIdpId": { + "type": "string", + "description": "Legacy identity provider identifier (Okta IdP ID). Returned by the API." + }, + "IdpId": { + "type": "string", + "description": "Unique identifier of the identity provider. Returned by the API." + }, + "Protocol": { + "type": "string", + "description": "Identity provider protocol.", + "enum": ["SAML", "OIDC"] + }, + "Audience": { + "type": "string", + "description": "OIDC audience." + }, + "ClientId": { + "type": "string", + "description": "OIDC client ID." + }, + "GroupsClaim": { + "type": "string", + "description": "OIDC groups claim." + }, + "RequestedScopes": { + "type": "array", + "description": "OIDC requested scopes.", + "insertionOrder": false, + "items": { + "type": "string" + } + }, + "UserClaim": { + "type": "string", + "description": "OIDC user claim." + }, + "Description": { + "type": "string", + "description": "Description of the identity provider." + }, + "AuthorizationType": { + "type": "string", + "description": "OIDC authorization type." + }, + "IdpType": { + "type": "string", + "description": "Identity provider type (for OIDC)." + } + }, + "additionalProperties": false, + "required": ["FederationSettingsId", "Name", "IssuerUri"], + "readOnlyProperties": ["/properties/OktaIdpId", "/properties/IdpId"], + "createOnlyProperties": [ + "/properties/FederationSettingsId", + "/properties/Profile" + ], + "primaryIdentifier": [ + "/properties/FederationSettingsId", + "/properties/IdpId", + "/properties/Profile" + ], + "handlers": { + "create": { + "permissions": ["secretsmanager:GetSecretValue"] + }, + "read": { + "permissions": ["secretsmanager:GetSecretValue"] + }, + "update": { + "permissions": ["secretsmanager:GetSecretValue"] + }, + "delete": { + "permissions": ["secretsmanager:GetSecretValue"] + }, + "list": { + "permissions": ["secretsmanager:GetSecretValue"] + } + }, + "documentationUrl": "https://github.com/mongodb/mongodbatlas-cloudformation-resources/blob/master/cfn-resources/federated-settings-identity-provider/README.md", + "tagging": { + "taggable": false + } +} diff --git a/cfn-resources/federated-settings-identity-provider/resource-role.yaml b/cfn-resources/federated-settings-identity-provider/resource-role.yaml new file mode 100644 index 000000000..8cc4ee238 --- /dev/null +++ b/cfn-resources/federated-settings-identity-provider/resource-role.yaml @@ -0,0 +1,38 @@ +AWSTemplateFormatVersion: "2010-09-09" +Description: > + This CloudFormation template creates a role assumed by CloudFormation + during CRUDL operations to mutate resources on behalf of the customer. + +Resources: + ExecutionRole: + Type: AWS::IAM::Role + Properties: + MaxSessionDuration: 8400 + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: resources.cloudformation.amazonaws.com + Action: sts:AssumeRole + Condition: + StringEquals: + aws:SourceAccount: + Ref: AWS::AccountId + StringLike: + aws:SourceArn: + Fn::Sub: arn:${AWS::Partition}:cloudformation:${AWS::Region}:${AWS::AccountId}:type/resource/MongoDB-Atlas-FederatedSettingsIdentityProvider/* + Path: "/" + Policies: + - PolicyName: ResourceTypePolicy + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - "secretsmanager:GetSecretValue" + Resource: "*" +Outputs: + ExecutionRoleArn: + Value: + Fn::GetAtt: ExecutionRole.Arn diff --git a/cfn-resources/federated-settings-identity-provider/template.yml b/cfn-resources/federated-settings-identity-provider/template.yml new file mode 100644 index 000000000..9f4c8dd9a --- /dev/null +++ b/cfn-resources/federated-settings-identity-provider/template.yml @@ -0,0 +1,26 @@ +AWSTemplateFormatVersion: "2010-09-09" +Transform: AWS::Serverless-2016-10-31 +Description: AWS SAM template for the MongoDB::Atlas::FederatedSettingsIdentityProvider resource type + +Globals: + Function: + Timeout: 60 + +Resources: + TypeFunction: + Type: AWS::Serverless::Function + Properties: + Handler: bootstrap + Runtime: provided.al2 + CodeUri: bin/ + + TestEntrypoint: + Type: AWS::Serverless::Function + Properties: + Handler: bootstrap + Runtime: provided.al2 + CodeUri: bin/ + Environment: + Variables: + MODE: Test + LOG_LEVEL: debug diff --git a/cfn-resources/federated-settings-identity-provider/test/cfn-test-create-inputs.sh b/cfn-resources/federated-settings-identity-provider/test/cfn-test-create-inputs.sh new file mode 100755 index 000000000..b90bb0c64 --- /dev/null +++ b/cfn-resources/federated-settings-identity-provider/test/cfn-test-create-inputs.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash +# cfn-test-create-inputs.sh +# +# This tool generates json files in the inputs/ for `cfn test`. +# + +set -o errexit +set -o nounset +set -o pipefail + +function usage { + echo "usage:$0 [federation_settings_id]" + echo "Generates test input files for federated settings identity provider" + exit 0 +} + +if [[ "${1:-}" == "help" ]]; then usage; fi + +rm -rf inputs +mkdir inputs + +profile="default" +if [ ${MONGODB_ATLAS_PROFILE+x} ]; then + echo "profile set to ${MONGODB_ATLAS_PROFILE}" + profile=${MONGODB_ATLAS_PROFILE} +fi + +if [ -n "${MONGODB_ATLAS_FEDERATION_SETTINGS_ID:-}" ]; then + federationSettingsId="${MONGODB_ATLAS_FEDERATION_SETTINGS_ID}" + echo "Using federation settings ID from environment variable: ${federationSettingsId}" +elif [[ "${1:-}" =~ ^[a-f0-9]{24}$ ]]; then + federationSettingsId="${1}" + echo "Using federation settings ID from argument: ${federationSettingsId}" +else + echo "ERROR: MONGODB_ATLAS_FEDERATION_SETTINGS_ID must be set or a valid 24-char hex ID must be provided as argument" + exit 1 +fi +idpName="cfn-test-idp-$(date +%s)-$RANDOM" +updatedName="${idpName}-updated" +uniqueAudience="cfn-test-audience-$(date +%s)-$RANDOM" + +idpId="" +if [ ${MONGODB_ATLAS_IDP_ID+x} ]; then + echo "idp id set to ${MONGODB_ATLAS_IDP_ID}" + idpId=${MONGODB_ATLAS_IDP_ID} +fi + +WORDTOREMOVE="template." +cd "$(dirname "$0")" || exit +for inputFile in inputs_*; do + outputFile=${inputFile//$WORDTOREMOVE/} + nameValue="$idpName" + if [[ "$inputFile" == *"update.template.json" ]]; then + nameValue="$updatedName" + fi + jq --arg profile "$profile" \ + --arg federationSettingsId "$federationSettingsId" \ + --arg name "$nameValue" \ + --arg audience "$uniqueAudience" \ + --arg idpId "$idpId" \ + '.Profile?|=$profile + | .FederationSettingsId?|=$federationSettingsId + | .Name?|=$name + | .Audience?|=$audience + | (if $idpId != "" then .IdpId?=$idpId else . end)' \ + "$inputFile" >"../inputs/$outputFile" +done +cd .. +ls -l inputs diff --git a/cfn-resources/federated-settings-identity-provider/test/cfn-test-delete-inputs.sh b/cfn-resources/federated-settings-identity-provider/test/cfn-test-delete-inputs.sh new file mode 100755 index 000000000..e53aeb41d --- /dev/null +++ b/cfn-resources/federated-settings-identity-provider/test/cfn-test-delete-inputs.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +# cfn-test-delete-inputs.sh +# +# Needs to exist to be called in Publish, but no cleanup is needed. diff --git a/cfn-resources/federated-settings-identity-provider/test/contract-testing/cfn-test-create.sh b/cfn-resources/federated-settings-identity-provider/test/contract-testing/cfn-test-create.sh new file mode 100755 index 000000000..957f8b53d --- /dev/null +++ b/cfn-resources/federated-settings-identity-provider/test/contract-testing/cfn-test-create.sh @@ -0,0 +1,8 @@ +#!/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 + +./test/cfn-test-create-inputs.sh diff --git a/cfn-resources/federated-settings-identity-provider/test/contract-testing/cfn-test-delete.sh b/cfn-resources/federated-settings-identity-provider/test/contract-testing/cfn-test-delete.sh new file mode 100644 index 000000000..411daf374 --- /dev/null +++ b/cfn-resources/federated-settings-identity-provider/test/contract-testing/cfn-test-delete.sh @@ -0,0 +1,8 @@ +#!/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 + +echo "No cleanup required for federated settings identity provider." diff --git a/cfn-resources/federated-settings-identity-provider/test/federated-settings-identity-provider.sample-cfn-request.json b/cfn-resources/federated-settings-identity-provider/test/federated-settings-identity-provider.sample-cfn-request.json new file mode 100644 index 000000000..3a8bcecb2 --- /dev/null +++ b/cfn-resources/federated-settings-identity-provider/test/federated-settings-identity-provider.sample-cfn-request.json @@ -0,0 +1,18 @@ +{ + "desiredResourceState": { + "Profile": "default", + "FederationSettingsId": "", + "Name": "", + "IssuerUri": "https://token.actions.githubusercontent.com", + "Protocol": "OIDC", + "Audience": "", + "AuthorizationType": "GROUP", + "IdpType": "WORKLOAD", + "UserClaim": "sub", + "GroupsClaim": "groups", + "Description": "Example OIDC Identity Provider", + "AssociatedDomains": ["mdb-aws-cfn-publishing.com"] + }, + "providerLogGroupName": "mongodb-atlas-federatedsettingsidentityprovider-logs", + "previousResourceState": {} +} diff --git a/cfn-resources/federated-settings-identity-provider/test/inputs_1_create.template.json b/cfn-resources/federated-settings-identity-provider/test/inputs_1_create.template.json new file mode 100644 index 000000000..3d2a83ea5 --- /dev/null +++ b/cfn-resources/federated-settings-identity-provider/test/inputs_1_create.template.json @@ -0,0 +1,14 @@ +{ + "Profile": "default", + "FederationSettingsId": "", + "Name": "", + "IssuerUri": "https://token.actions.githubusercontent.com", + "Protocol": "OIDC", + "Audience": "", + "AuthorizationType": "GROUP", + "IdpType": "WORKLOAD", + "UserClaim": "sub", + "GroupsClaim": "groups", + "Description": "Test OIDC Identity Provider for CloudFormation", + "AssociatedDomains": ["mdb-aws-cfn-publishing.com"] +} diff --git a/cfn-resources/federated-settings-identity-provider/test/inputs_1_update.template.json b/cfn-resources/federated-settings-identity-provider/test/inputs_1_update.template.json new file mode 100644 index 000000000..3a7946588 --- /dev/null +++ b/cfn-resources/federated-settings-identity-provider/test/inputs_1_update.template.json @@ -0,0 +1,16 @@ +{ + "Profile": "default", + "FederationSettingsId": "", + "IdpId": "", + "Name": "", + "IssuerUri": "https://token.actions.githubusercontent.com", + "Protocol": "OIDC", + "Audience": "", + "AuthorizationType": "GROUP", + "IdpType": "WORKLOAD", + "UserClaim": "sub", + "GroupsClaim": "groups", + "Description": "Updated Test OIDC Identity Provider", + "AssociatedDomains": ["mdb-aws-cfn-publishing.com"], + "Status": "ACTIVE" +} diff --git a/cfn-resources/util/constants/constants.go b/cfn-resources/util/constants/constants.go index f0814ffec..6cbfc8a8c 100644 --- a/cfn-resources/util/constants/constants.go +++ b/cfn-resources/util/constants/constants.go @@ -130,6 +130,8 @@ const ( AppID = "AppId" FederationSettingsID = "FederationSettingsId" + IssuerURI = "IssuerUri" + IdpID = "IdpId" ExportBucketID = "ExportBucketId" ExportID = "ExportId" diff --git a/examples/federated-settings-identity-provider/README.md b/examples/federated-settings-identity-provider/README.md new file mode 100644 index 000000000..a2489bcf2 --- /dev/null +++ b/examples/federated-settings-identity-provider/README.md @@ -0,0 +1,35 @@ +# How to create a MongoDB::Atlas::FederatedSettingsIdentityProvider + +## Step 1: Activate the resource in CloudFormation + +Step a: Create Role using [execution-role.yaml](https://github.com/mongodb/mongodbatlas-cloudformation-resources/blob/master/examples/execution-role.yaml) in CFN resources folder. + +Step b: Search for MongoDB::Atlas::FederatedSettingsIdentityProvider resource. + + (CloudFormation > Public extensions > choose 'Third party' > Search with " Execution name prefix = MongoDB " ) + +Step c: Select and activate +Enter the RoleArn that is created in step 1. + +Your FederatedSettingsIdentityProvider Resource is ready to use. + +## Step 2: Create template using [federated-settings-identity-provider-oidc.json](federated-settings-identity-provider-oidc.json) + + Note: Make sure you are providing appropriate values for: + 1. FederationSettingsId (24-character hexadecimal string) + 2. IdentityProviderName + 3. IssuerUri + 4. Audience + 5. ClientId + 6. GroupsClaim (optional) + 7. UserClaim (optional) + 8. RequestedScopes (optional) + 9. AssociatedDomains (optional) + 10. Description (optional) + 11. AuthorizationType (optional) + 12. IdpType (optional, required for OIDC) + 13. Profile (optional) + +## Important Notes: + +- **CREATE operation only supports OIDC protocol**. SAML identity providers must be imported. diff --git a/examples/federated-settings-identity-provider/federated-settings-identity-provider-oidc.json b/examples/federated-settings-identity-provider/federated-settings-identity-provider-oidc.json new file mode 100644 index 000000000..9592bffca --- /dev/null +++ b/examples/federated-settings-identity-provider/federated-settings-identity-provider-oidc.json @@ -0,0 +1,334 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "This template creates an OIDC identity provider for MongoDB Atlas Federated Settings. This will be billed to your Atlas account.", + "Parameters": { + "FederationSettingsId": { + "Type": "String", + "Description": "Unique 24-hexadecimal digit string that identifies your federation", + "MinLength": 24, + "MaxLength": 24, + "AllowedPattern": "^([a-f0-9]{24})$", + "ConstraintDescription": "Must be a 24-character hexadecimal string" + }, + "IdentityProviderName": { + "Type": "String", + "Description": "Human-readable name (display name) of the identity provider" + }, + "IssuerUri": { + "Type": "String", + "Description": "Issuer URI of the identity provider (e.g., https://your-idp.com)" + }, + "Audience": { + "Type": "String", + "Description": "OIDC audience claim value" + }, + "ClientId": { + "Type": "String", + "Description": "OIDC client ID from your identity provider (optional for WORKLOAD type)", + "Default": "" + }, + "Profile": { + "Type": "String", + "Description": "Secret Manager Profile that contains the Atlas Programmatic keys", + "Default": "default" + }, + "GroupsClaim": { + "Type": "String", + "Description": "OIDC groups claim (optional, e.g., 'groups')", + "Default": "" + }, + "UserClaim": { + "Type": "String", + "Description": "OIDC user claim (optional, e.g., 'email')", + "Default": "" + }, + "Description": { + "Type": "String", + "Description": "Description of the identity provider (optional)", + "Default": "" + }, + "AuthorizationType": { + "Type": "String", + "Description": "OIDC authorization type (optional)", + "Default": "" + }, + "IdpType": { + "Type": "String", + "Description": "Identity provider type", + "Default": "WORKFORCE", + "AllowedValues": ["WORKFORCE", "WORKLOAD"] + }, + "AssociatedDomain1": { + "Type": "String", + "Description": "First associated domain (optional, e.g., 'example.com')", + "Default": "" + }, + "AssociatedDomain2": { + "Type": "String", + "Description": "Second associated domain (optional)", + "Default": "" + }, + "RequestedScope1": { + "Type": "String", + "Description": "First OIDC requested scope (optional, e.g., 'openid'). Leave empty for WORKLOAD providers.", + "Default": "" + }, + "RequestedScope2": { + "Type": "String", + "Description": "Second OIDC requested scope (optional, e.g., 'email'). Leave empty for WORKLOAD providers.", + "Default": "" + }, + "RequestedScope3": { + "Type": "String", + "Description": "Third OIDC requested scope (optional, e.g., 'profile'). Leave empty for WORKLOAD providers.", + "Default": "" + } + }, + "Conditions": { + "HasGroupsClaim": { + "Fn::Not": [ + { + "Fn::Equals": [ + { + "Ref": "GroupsClaim" + }, + "" + ] + } + ] + }, + "HasUserClaim": { + "Fn::Not": [ + { + "Fn::Equals": [ + { + "Ref": "UserClaim" + }, + "" + ] + } + ] + }, + "HasDescription": { + "Fn::Not": [ + { + "Fn::Equals": [ + { + "Ref": "Description" + }, + "" + ] + } + ] + }, + "HasAuthorizationType": { + "Fn::Not": [ + { + "Fn::Equals": [ + { + "Ref": "AuthorizationType" + }, + "" + ] + } + ] + }, + "HasAssociatedDomain1": { + "Fn::Not": [ + { + "Fn::Equals": [ + { + "Ref": "AssociatedDomain1" + }, + "" + ] + } + ] + }, + "HasAssociatedDomain2": { + "Fn::Not": [ + { + "Fn::Equals": [ + { + "Ref": "AssociatedDomain2" + }, + "" + ] + } + ] + }, + "HasClientId": { + "Fn::Not": [ + { + "Fn::Equals": [ + { + "Ref": "ClientId" + }, + "" + ] + } + ] + }, + "HasRequestedScope1": { + "Fn::Not": [ + { + "Fn::Equals": [ + { + "Ref": "RequestedScope1" + }, + "" + ] + } + ] + } + }, + "Resources": { + "AtlasFederatedSettingsIdentityProvider": { + "Type": "MongoDB::Atlas::FederatedSettingsIdentityProvider", + "Properties": { + "FederationSettingsId": { + "Ref": "FederationSettingsId" + }, + "Name": { + "Ref": "IdentityProviderName" + }, + "IssuerUri": { + "Ref": "IssuerUri" + }, + "Protocol": "OIDC", + "Audience": { + "Ref": "Audience" + }, + "ClientId": { + "Fn::If": [ + "HasClientId", + { + "Ref": "ClientId" + }, + { + "Ref": "AWS::NoValue" + } + ] + }, + "Profile": { + "Ref": "Profile" + }, + "IdpType": { + "Ref": "IdpType" + }, + "GroupsClaim": { + "Fn::If": [ + "HasGroupsClaim", + { + "Ref": "GroupsClaim" + }, + { + "Ref": "AWS::NoValue" + } + ] + }, + "UserClaim": { + "Fn::If": [ + "HasUserClaim", + { + "Ref": "UserClaim" + }, + { + "Ref": "AWS::NoValue" + } + ] + }, + "Description": { + "Fn::If": [ + "HasDescription", + { + "Ref": "Description" + }, + { + "Ref": "AWS::NoValue" + } + ] + }, + "AuthorizationType": { + "Fn::If": [ + "HasAuthorizationType", + { + "Ref": "AuthorizationType" + }, + { + "Ref": "AWS::NoValue" + } + ] + }, + "AssociatedDomains": { + "Fn::If": [ + "HasAssociatedDomain1", + [ + { + "Ref": "AssociatedDomain1" + }, + { + "Fn::If": [ + "HasAssociatedDomain2", + { + "Ref": "AssociatedDomain2" + }, + { + "Ref": "AWS::NoValue" + } + ] + } + ], + { + "Ref": "AWS::NoValue" + } + ] + }, + "RequestedScopes": { + "Fn::If": [ + "HasRequestedScope1", + [ + { + "Ref": "RequestedScope1" + }, + { + "Ref": "RequestedScope2" + }, + { + "Ref": "RequestedScope3" + } + ], + { + "Ref": "AWS::NoValue" + } + ] + } + } + } + }, + "Outputs": { + "IdpId": { + "Description": "Unique identifier of the identity provider", + "Export": { + "Name": { + "Fn::Sub": "${AWS::StackName}-IdpId" + } + }, + "Value": { + "Fn::GetAtt": ["AtlasFederatedSettingsIdentityProvider", "IdpId"] + } + }, + "FederationSettingsId": { + "Description": "Federation Settings ID used", + "Value": { + "Ref": "FederationSettingsId" + } + }, + "IdentityProviderName": { + "Description": "Display name of the identity provider", + "Value": { + "Ref": "IdentityProviderName" + } + } + } +} From d17f79b66cf01634ff49705850d63c6e69bd966e Mon Sep 17 00:00:00 2001 From: Oriol Arbusi Abadal Date: Wed, 21 Jan 2026 09:40:36 +0100 Subject: [PATCH 2/5] associated domains change --- ...derated-settings-identity-provider.sample-cfn-request.json | 4 +++- .../test/inputs_1_create.template.json | 4 +++- .../test/inputs_1_update.template.json | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/cfn-resources/federated-settings-identity-provider/test/federated-settings-identity-provider.sample-cfn-request.json b/cfn-resources/federated-settings-identity-provider/test/federated-settings-identity-provider.sample-cfn-request.json index 3a8bcecb2..ff1c33ca3 100644 --- a/cfn-resources/federated-settings-identity-provider/test/federated-settings-identity-provider.sample-cfn-request.json +++ b/cfn-resources/federated-settings-identity-provider/test/federated-settings-identity-provider.sample-cfn-request.json @@ -11,7 +11,9 @@ "UserClaim": "sub", "GroupsClaim": "groups", "Description": "Example OIDC Identity Provider", - "AssociatedDomains": ["mdb-aws-cfn-publishing.com"] + "AssociatedDomains": [ + "cfn-test-domain.com" + ] }, "providerLogGroupName": "mongodb-atlas-federatedsettingsidentityprovider-logs", "previousResourceState": {} diff --git a/cfn-resources/federated-settings-identity-provider/test/inputs_1_create.template.json b/cfn-resources/federated-settings-identity-provider/test/inputs_1_create.template.json index 3d2a83ea5..be5271c40 100644 --- a/cfn-resources/federated-settings-identity-provider/test/inputs_1_create.template.json +++ b/cfn-resources/federated-settings-identity-provider/test/inputs_1_create.template.json @@ -10,5 +10,7 @@ "UserClaim": "sub", "GroupsClaim": "groups", "Description": "Test OIDC Identity Provider for CloudFormation", - "AssociatedDomains": ["mdb-aws-cfn-publishing.com"] + "AssociatedDomains": [ + "cfn-test-domain.com" + ] } diff --git a/cfn-resources/federated-settings-identity-provider/test/inputs_1_update.template.json b/cfn-resources/federated-settings-identity-provider/test/inputs_1_update.template.json index 3a7946588..05e33b77d 100644 --- a/cfn-resources/federated-settings-identity-provider/test/inputs_1_update.template.json +++ b/cfn-resources/federated-settings-identity-provider/test/inputs_1_update.template.json @@ -11,6 +11,8 @@ "UserClaim": "sub", "GroupsClaim": "groups", "Description": "Updated Test OIDC Identity Provider", - "AssociatedDomains": ["mdb-aws-cfn-publishing.com"], + "AssociatedDomains": [ + "cfn-test-domain.com" + ], "Status": "ACTIVE" } From d93fdd99596b5c4066449303250825603870dbd0 Mon Sep 17 00:00:00 2001 From: Oriol Arbusi Abadal Date: Wed, 21 Jan 2026 09:56:57 +0100 Subject: [PATCH 3/5] Revert "associated domains change" This reverts commit 01305d61279660a642edc7f7afa54f3d96e96326. --- ...derated-settings-identity-provider.sample-cfn-request.json | 4 +--- .../test/inputs_1_create.template.json | 4 +--- .../test/inputs_1_update.template.json | 4 +--- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/cfn-resources/federated-settings-identity-provider/test/federated-settings-identity-provider.sample-cfn-request.json b/cfn-resources/federated-settings-identity-provider/test/federated-settings-identity-provider.sample-cfn-request.json index ff1c33ca3..3a8bcecb2 100644 --- a/cfn-resources/federated-settings-identity-provider/test/federated-settings-identity-provider.sample-cfn-request.json +++ b/cfn-resources/federated-settings-identity-provider/test/federated-settings-identity-provider.sample-cfn-request.json @@ -11,9 +11,7 @@ "UserClaim": "sub", "GroupsClaim": "groups", "Description": "Example OIDC Identity Provider", - "AssociatedDomains": [ - "cfn-test-domain.com" - ] + "AssociatedDomains": ["mdb-aws-cfn-publishing.com"] }, "providerLogGroupName": "mongodb-atlas-federatedsettingsidentityprovider-logs", "previousResourceState": {} diff --git a/cfn-resources/federated-settings-identity-provider/test/inputs_1_create.template.json b/cfn-resources/federated-settings-identity-provider/test/inputs_1_create.template.json index be5271c40..3d2a83ea5 100644 --- a/cfn-resources/federated-settings-identity-provider/test/inputs_1_create.template.json +++ b/cfn-resources/federated-settings-identity-provider/test/inputs_1_create.template.json @@ -10,7 +10,5 @@ "UserClaim": "sub", "GroupsClaim": "groups", "Description": "Test OIDC Identity Provider for CloudFormation", - "AssociatedDomains": [ - "cfn-test-domain.com" - ] + "AssociatedDomains": ["mdb-aws-cfn-publishing.com"] } diff --git a/cfn-resources/federated-settings-identity-provider/test/inputs_1_update.template.json b/cfn-resources/federated-settings-identity-provider/test/inputs_1_update.template.json index 05e33b77d..3a7946588 100644 --- a/cfn-resources/federated-settings-identity-provider/test/inputs_1_update.template.json +++ b/cfn-resources/federated-settings-identity-provider/test/inputs_1_update.template.json @@ -11,8 +11,6 @@ "UserClaim": "sub", "GroupsClaim": "groups", "Description": "Updated Test OIDC Identity Provider", - "AssociatedDomains": [ - "cfn-test-domain.com" - ], + "AssociatedDomains": ["mdb-aws-cfn-publishing.com"], "Status": "ACTIVE" } From 02d8887264571709110bf1b09cdad7862125b8ca Mon Sep 17 00:00:00 2001 From: Oriol Arbusi Abadal Date: Wed, 21 Jan 2026 10:02:49 +0100 Subject: [PATCH 4/5] update permissions --- .../test/contract-testing/cfn-test-delete.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 cfn-resources/federated-settings-identity-provider/test/contract-testing/cfn-test-delete.sh diff --git a/cfn-resources/federated-settings-identity-provider/test/contract-testing/cfn-test-delete.sh b/cfn-resources/federated-settings-identity-provider/test/contract-testing/cfn-test-delete.sh old mode 100644 new mode 100755 From 3a077bb7f0b6e5ef0851050b115fccff8d14b239 Mon Sep 17 00:00:00 2001 From: Rakhul S Prakash Date: Thu, 29 Jan 2026 14:52:41 +0530 Subject: [PATCH 5/5] Address review comments --- .../cmd/resource/handlers.go | 14 ++-- .../cmd/resource/mappings.go | 67 +++++++------------ .../cmd/resource/mappings_test.go | 21 +++--- 3 files changed, 41 insertions(+), 61 deletions(-) diff --git a/cfn-resources/federated-settings-identity-provider/cmd/resource/handlers.go b/cfn-resources/federated-settings-identity-provider/cmd/resource/handlers.go index 218e9c3d6..4d34aeef1 100644 --- a/cfn-resources/federated-settings-identity-provider/cmd/resource/handlers.go +++ b/cfn-resources/federated-settings-identity-provider/cmd/resource/handlers.go @@ -45,8 +45,8 @@ func HandleCreate(client *util.MongoDBClient, currentModel *Model) handler.Progr ) } - createdID := created.GetId() - currentModel.IdpId = &createdID + currentModel.IdpId = util.Pointer(created.GetId()) + return HandleRead(client, currentModel) } @@ -77,14 +77,8 @@ func HandleUpdate(client *util.MongoDBClient, prevModel *Model, currentModel *Mo federationSettingsID := util.SafeString(currentModel.FederationSettingsId) idpID := util.SafeString(currentModel.IdpId) - associatedDomains := currentModel.AssociatedDomains - if associatedDomains == nil { - associatedDomains = []string{} - } - requestedScopes := currentModel.RequestedScopes - if requestedScopes == nil { - requestedScopes = []string{} - } + associatedDomains := getStringSliceOrEmpty(currentModel.AssociatedDomains) + requestedScopes := getStringSliceOrEmpty(currentModel.RequestedScopes) updateReq := &admin.FederationIdentityProviderUpdate{ AssociatedDomains: &associatedDomains, diff --git a/cfn-resources/federated-settings-identity-provider/cmd/resource/mappings.go b/cfn-resources/federated-settings-identity-provider/cmd/resource/mappings.go index bbcc04980..05b907439 100644 --- a/cfn-resources/federated-settings-identity-provider/cmd/resource/mappings.go +++ b/cfn-resources/federated-settings-identity-provider/cmd/resource/mappings.go @@ -33,6 +33,13 @@ var ( allIdpTypes = []string{IdpTypeWorkforce, IdpTypeWorkload} ) +func getStringSliceOrEmpty(slice []string) []string { + if slice != nil { + return slice + } + return []string{} +} + func GetFederatedSettingsIdentityProviderModel(api *admin.FederationIdentityProvider, currentModel *Model) *Model { var model *Model if currentModel != nil { @@ -49,34 +56,22 @@ func GetFederatedSettingsIdentityProviderModel(api *admin.FederationIdentityProv model.OktaIdpId = oktaID } - idpID := api.GetId() - model.IdpId = &idpID + model.IdpId = util.Pointer(api.GetId()) + model.Name = util.Pointer(api.GetDisplayName()) + model.IssuerUri = util.Pointer(api.GetIssuerUri()) + model.Protocol = util.Pointer(api.GetProtocol()) + model.Description = util.Pointer(api.GetDescription()) + model.AuthorizationType = util.Pointer(api.GetAuthorizationType()) + model.IdpType = util.Pointer(api.GetIdpType()) - displayName := api.GetDisplayName() - model.Name = &displayName - issuerURI := api.GetIssuerUri() - model.IssuerUri = &issuerURI protocol := api.GetProtocol() - model.Protocol = &protocol - - description := api.GetDescription() - model.Description = &description - authorizationType := api.GetAuthorizationType() - model.AuthorizationType = &authorizationType - idpType := api.GetIdpType() - model.IdpType = &idpType - switch protocol { case ProtocolSAML: - requestBinding := api.GetRequestBinding() - model.RequestBinding = &requestBinding - responseSignatureAlgorithm := api.GetResponseSignatureAlgorithm() - model.ResponseSignatureAlgorithm = &responseSignatureAlgorithm + model.RequestBinding = util.Pointer(api.GetRequestBinding()) + model.ResponseSignatureAlgorithm = util.Pointer(api.GetResponseSignatureAlgorithm()) model.SsoDebugEnabled = api.SsoDebugEnabled - ssoURL := api.GetSsoUrl() - model.SsoUrl = &ssoURL - status := api.GetStatus() - model.Status = &status + model.SsoUrl = util.Pointer(api.GetSsoUrl()) + model.Status = util.Pointer(api.GetStatus()) associatedDomains := api.GetAssociatedDomains() if len(associatedDomains) == 0 && currentModel != nil && len(currentModel.AssociatedDomains) > 0 { @@ -84,12 +79,9 @@ func GetFederatedSettingsIdentityProviderModel(api *admin.FederationIdentityProv } model.AssociatedDomains = associatedDomains case ProtocolOIDC: - audience := api.GetAudience() - model.Audience = &audience - clientID := api.GetClientId() - model.ClientId = &clientID - groupsClaim := api.GetGroupsClaim() - model.GroupsClaim = &groupsClaim + model.Audience = util.Pointer(api.GetAudience()) + model.ClientId = util.Pointer(api.GetClientId()) + model.GroupsClaim = util.Pointer(api.GetGroupsClaim()) requestedScopes := api.GetRequestedScopes() if len(requestedScopes) == 0 && currentModel != nil && len(currentModel.RequestedScopes) > 0 { @@ -97,8 +89,7 @@ func GetFederatedSettingsIdentityProviderModel(api *admin.FederationIdentityProv } model.RequestedScopes = requestedScopes - userClaim := api.GetUserClaim() - model.UserClaim = &userClaim + model.UserClaim = util.Pointer(api.GetUserClaim()) associatedDomains := api.GetAssociatedDomains() if len(associatedDomains) == 0 && currentModel != nil && len(currentModel.AssociatedDomains) > 0 { @@ -113,18 +104,8 @@ func GetFederatedSettingsIdentityProviderModel(api *admin.FederationIdentityProv } func ExpandOIDCCreateRequest(model *Model) *admin.FederationOidcIdentityProviderUpdate { - var associatedDomains []string - if model.AssociatedDomains != nil { - associatedDomains = model.AssociatedDomains - } else { - associatedDomains = []string{} - } - var requestedScopes []string - if model.RequestedScopes != nil { - requestedScopes = model.RequestedScopes - } else { - requestedScopes = []string{} - } + associatedDomains := getStringSliceOrEmpty(model.AssociatedDomains) + requestedScopes := getStringSliceOrEmpty(model.RequestedScopes) return &admin.FederationOidcIdentityProviderUpdate{ Audience: util.Pointer(util.SafeString(model.Audience)), diff --git a/cfn-resources/federated-settings-identity-provider/cmd/resource/mappings_test.go b/cfn-resources/federated-settings-identity-provider/cmd/resource/mappings_test.go index 405248ce1..105ae08d3 100644 --- a/cfn-resources/federated-settings-identity-provider/cmd/resource/mappings_test.go +++ b/cfn-resources/federated-settings-identity-provider/cmd/resource/mappings_test.go @@ -22,13 +22,18 @@ import ( "go.mongodb.org/atlas-sdk/v20250312012/admin" ) +const ( + testIdpID = "test-idp-id" + testOktaID = "test-okta-id" +) + func TestGetFederatedSettingsIdentityProviderModel_SAML(t *testing.T) { protocol := "SAML" displayName := "saml-name" issuerURI := "https://issuer.example.com" api := &admin.FederationIdentityProvider{ - Id: "idp-1", - OktaIdpId: "okta-1", + Id: testIdpID, + OktaIdpId: testOktaID, Protocol: &protocol, DisplayName: &displayName, IssuerUri: &issuerURI, @@ -42,8 +47,8 @@ func TestGetFederatedSettingsIdentityProviderModel_SAML(t *testing.T) { model := resource.GetFederatedSettingsIdentityProviderModel(api, &resource.Model{}) - assert.Equal(t, "idp-1", *model.IdpId) - assert.Equal(t, "okta-1", *model.OktaIdpId) + assert.Equal(t, testIdpID, *model.IdpId) + assert.Equal(t, testOktaID, *model.OktaIdpId) assert.Equal(t, "SAML", *model.Protocol) assert.Equal(t, "saml-name", *model.Name) assert.Equal(t, "https://issuer.example.com", *model.IssuerUri) @@ -63,8 +68,8 @@ func TestGetFederatedSettingsIdentityProviderModel_OIDC(t *testing.T) { displayName := "oidc-name" issuerURI := "https://issuer.oidc.example.com" api := &admin.FederationIdentityProvider{ - Id: "idp-2", - OktaIdpId: "okta-2", + Id: testIdpID, + OktaIdpId: testOktaID, Protocol: &protocol, DisplayName: &displayName, IssuerUri: &issuerURI, @@ -78,8 +83,8 @@ func TestGetFederatedSettingsIdentityProviderModel_OIDC(t *testing.T) { model := resource.GetFederatedSettingsIdentityProviderModel(api, &resource.Model{}) - assert.Equal(t, "idp-2", *model.IdpId) - assert.Equal(t, "okta-2", *model.OktaIdpId) + assert.Equal(t, testIdpID, *model.IdpId) + assert.Equal(t, testOktaID, *model.OktaIdpId) assert.Equal(t, "OIDC", *model.Protocol) assert.Equal(t, "oidc-name", *model.Name) assert.Equal(t, "https://issuer.oidc.example.com", *model.IssuerUri)