Skip to content

Commit

Permalink
CLOUDP-286477: Workload/Workforce Identity Federation (#1969)
Browse files Browse the repository at this point in the history
  • Loading branch information
roothorp authored Dec 6, 2024
1 parent 4a5bc72 commit 459813e
Show file tree
Hide file tree
Showing 15 changed files with 383 additions and 510 deletions.
17 changes: 10 additions & 7 deletions config/crd/bases/atlas.mongodb.com_atlasdatabaseusers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,11 @@ spec:
type: object
databaseName:
default: admin
description: DatabaseName is a Database against which Atlas authenticates
the user. Default value is 'admin'.
description: |-
DatabaseName is a Database against which Atlas authenticates the user.
If the user authenticates with AWS IAM, x.509, LDAP, or OIDC Workload this value should be '$external'.
If the user authenticates with SCRAM-SHA or OIDC Workforce, this value should be 'admin'.
Default value is 'admin'.
type: string
deleteAfterDate:
description: |-
Expand Down Expand Up @@ -119,13 +122,13 @@ spec:
oidcAuthType:
default: NONE
description: |-
Human-readable label that indicates whether the new database Username
with OIDC federated authentication.
To create a federated authentication user, specify the value
of IDP_GROUP for this field
Human-readable label that indicates whether the new database Username with OIDC federated authentication.
To create a federated authentication group (Workforce), specify the value of IDP_GROUP in this field.
To create a federated authentication user (Workload), specify the value of USER in this field.
enum:
- NONE
- IDP_GROUP
- USER
type: string
passwordSecretRef:
description: PasswordSecret is a reference to the Secret keeping the
Expand Down Expand Up @@ -208,7 +211,7 @@ spec:
Username is a username for authenticating to MongoDB
Human-readable label that represents the user that authenticates to MongoDB. The format of this label depends on the method of authentication:
In case of AWS IAM: the value should be AWS ARN for the IAM User/Role;
In case of OIDC: the value should be the Identity Provider ID;
In case of OIDC Workload or Workforce: the value should be the Atlas OIDC IdP ID, followed by a '/', followed by the IdP group name;
In case of Plain text auth: the value can be anything
maxLength: 1024
type: string
Expand Down
7 changes: 7 additions & 0 deletions config/crd/bases/atlas.mongodb.com_atlasfederatedauths.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@ spec:
required:
- name
type: object
dataAccessIdentityProviders:
description: |-
The collection of unique ids representing the identity providers that can be used for data access in this organization.
Currently connected data access identity providers missing from the this field will be disconnected.
items:
type: string
type: array
domainAllowList:
description: Approved domains that restrict users who can join the
organization based on their email address.
Expand Down
3 changes: 0 additions & 3 deletions internal/featureflags/featureflag.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@ import (
const (
featurePrefix = "FEATURE_"
featureSeparator = "="

//nolint:stylecheck
FeatureOIDC = "FEATURE_PREVIEW_OIDC_DB_ACCESS"
)

type FeatureFlags struct {
Expand Down
300 changes: 150 additions & 150 deletions licenses.csv

Large diffs are not rendered by default.

16 changes: 9 additions & 7 deletions pkg/api/v1/atlasdatabaseuser_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,10 @@ type AtlasDatabaseUserSpec struct {
// ExternalProjectRef holds the Atlas project ID the user belongs to
ExternalProjectRef *ExternalProjectReference `json:"externalProjectRef,omitempty"`

// DatabaseName is a Database against which Atlas authenticates the user. Default value is 'admin'.
// DatabaseName is a Database against which Atlas authenticates the user.
// If the user authenticates with AWS IAM, x.509, LDAP, or OIDC Workload this value should be '$external'.
// If the user authenticates with SCRAM-SHA or OIDC Workforce, this value should be 'admin'.
// Default value is 'admin'.
// +kubebuilder:default=admin
DatabaseName string `json:"databaseName,omitempty"`

Expand All @@ -87,17 +90,16 @@ type AtlasDatabaseUserSpec struct {
// Username is a username for authenticating to MongoDB
// Human-readable label that represents the user that authenticates to MongoDB. The format of this label depends on the method of authentication:
// In case of AWS IAM: the value should be AWS ARN for the IAM User/Role;
// In case of OIDC: the value should be the Identity Provider ID;
// In case of OIDC Workload or Workforce: the value should be the Atlas OIDC IdP ID, followed by a '/', followed by the IdP group name;
// In case of Plain text auth: the value can be anything
// +kubebuilder:validation:MaxLength:=1024
Username string `json:"username"`

// Human-readable label that indicates whether the new database Username
// with OIDC federated authentication.
// To create a federated authentication user, specify the value
// of IDP_GROUP for this field
// Human-readable label that indicates whether the new database Username with OIDC federated authentication.
// To create a federated authentication group (Workforce), specify the value of IDP_GROUP in this field.
// To create a federated authentication user (Workload), specify the value of USER in this field.
// +kubebuilder:default:=NONE
// +kubebuilder:validation:Enum:=NONE;IDP_GROUP
// +kubebuilder:validation:Enum:=NONE;IDP_GROUP;USER
// +optional
OIDCAuthType string `json:"oidcAuthType,omitempty"`

Expand Down
26 changes: 20 additions & 6 deletions pkg/api/v1/atlasfederatedauth_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"errors"
"fmt"

"go.mongodb.org/atlas-sdk/v20231115008/admin"
"go.mongodb.org/atlas-sdk/v20241113001/admin"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"

Expand Down Expand Up @@ -41,6 +41,10 @@ type AtlasFederatedAuthSpec struct {
// Map IDP groups to Atlas roles.
// +optional
RoleMappings []RoleMapping `json:"roleMappings,omitempty"`
// The collection of unique ids representing the identity providers that can be used for data access in this organization.
// Currently connected data access identity providers missing from the this field will be disconnected.
// +optional
DataAccessIdentityProviders *[]string `json:"dataAccessIdentityProviders,omitempty"`
}

func (f *AtlasFederatedAuthSpec) ToAtlas(orgID, idpID string, projectNameToID map[string]string) (*admin.ConnectedOrgConfig, error) {
Expand Down Expand Up @@ -73,11 +77,21 @@ func (f *AtlasFederatedAuthSpec) ToAtlas(orgID, idpID string, projectNameToID ma
}

result := &admin.ConnectedOrgConfig{
DomainAllowList: &f.DomainAllowList,
DomainRestrictionEnabled: *f.DomainRestrictionEnabled,
IdentityProviderId: idpID,
OrgId: orgID,
PostAuthRoleGrants: &f.PostAuthRoleGrants,
DataAccessIdentityProviderIds: f.DataAccessIdentityProviders,
DomainRestrictionEnabled: *f.DomainRestrictionEnabled,
OrgId: orgID,
}

if len(f.DomainAllowList) > 0 {
result.SetDomainAllowList(f.DomainAllowList)
}

if idpID != "" {
result.SetIdentityProviderId(idpID)
}

if len(f.PostAuthRoleGrants) > 0 {
result.SetPostAuthRoleGrants(f.PostAuthRoleGrants)
}

if len(atlasRoleMappings) > 0 {
Expand Down
30 changes: 18 additions & 12 deletions pkg/api/v1/atlasfederatedauth_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ package v1
import (
"testing"

"go.mongodb.org/atlas-sdk/v20231115008/admin"
"go.mongodb.org/atlas-sdk/v20241113001/admin"

"github.com/go-test/deep"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"

"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/pointer"
Expand All @@ -24,12 +25,13 @@ func Test_FederatedAuthSpec_ToAtlas(t *testing.T) {
}

spec := &AtlasFederatedAuthSpec{
Enabled: true,
ConnectionSecretRef: common.ResourceRefNamespaced{},
DomainAllowList: []string{"test.com"},
DomainRestrictionEnabled: pointer.MakePtr(true),
SSODebugEnabled: pointer.MakePtr(true),
PostAuthRoleGrants: []string{"role-3", "role-4"},
Enabled: true,
ConnectionSecretRef: common.ResourceRefNamespaced{},
DomainAllowList: []string{"test.com"},
DomainRestrictionEnabled: pointer.MakePtr(true),
DataAccessIdentityProviders: &[]string{"test-123", "test-456"},
SSODebugEnabled: pointer.MakePtr(true),
PostAuthRoleGrants: []string{"role-3", "role-4"},
RoleMappings: []RoleMapping{
{
ExternalGroupName: "test-group",
Expand All @@ -49,11 +51,12 @@ func Test_FederatedAuthSpec_ToAtlas(t *testing.T) {
assert.NotNil(t, result, "ToAtlas() result is nil")

expected := &admin.ConnectedOrgConfig{
DomainAllowList: &spec.DomainAllowList,
DomainRestrictionEnabled: *spec.DomainRestrictionEnabled,
IdentityProviderId: idpID,
OrgId: orgID,
PostAuthRoleGrants: &spec.PostAuthRoleGrants,
DomainAllowList: &spec.DomainAllowList,
DomainRestrictionEnabled: *spec.DomainRestrictionEnabled,
DataAccessIdentityProviderIds: spec.DataAccessIdentityProviders,
IdentityProviderId: &idpID,
OrgId: orgID,
PostAuthRoleGrants: &spec.PostAuthRoleGrants,
RoleMappings: &[]admin.AuthFederationRoleMapping{
{
ExternalGroupName: spec.RoleMappings[0].ExternalGroupName,
Expand All @@ -69,6 +72,9 @@ func Test_FederatedAuthSpec_ToAtlas(t *testing.T) {
}

diff := deep.Equal(expected, result)
if diff != nil {
t.Log(cmp.Diff(expected, result))
}
assert.Nil(t, diff, diff)
})

Expand Down
9 changes: 9 additions & 0 deletions pkg/api/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 17 additions & 19 deletions pkg/controller/atlasdatabaseuser/atlasdatabaseuser_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,16 +57,15 @@ var ErrOIDCNotEnabled = fmt.Errorf("'OIDCAuthType' field is set but OIDC authent

// AtlasDatabaseUserReconciler reconciles an AtlasDatabaseUser object
type AtlasDatabaseUserReconciler struct {
Client client.Client
Log *zap.SugaredLogger
Scheme *runtime.Scheme
EventRecorder record.EventRecorder
AtlasProvider atlas.Provider
GlobalPredicates []predicate.Predicate
ObjectDeletionProtection bool
SubObjectDeletionProtection bool
FeaturePreviewOIDCAuthEnabled bool
independentSyncPeriod time.Duration
Client client.Client
Log *zap.SugaredLogger
Scheme *runtime.Scheme
EventRecorder record.EventRecorder
AtlasProvider atlas.Provider
GlobalPredicates []predicate.Predicate
ObjectDeletionProtection bool
SubObjectDeletionProtection bool
independentSyncPeriod time.Duration

dbUserService dbuser.AtlasUsersService
deploymentService deployment.AtlasDeploymentsService
Expand Down Expand Up @@ -286,14 +285,13 @@ func NewAtlasDatabaseUserReconciler(
logger *zap.Logger,
) *AtlasDatabaseUserReconciler {
return &AtlasDatabaseUserReconciler{
Scheme: mgr.GetScheme(),
Client: mgr.GetClient(),
EventRecorder: mgr.GetEventRecorderFor("AtlasDatabaseUser"),
GlobalPredicates: predicates,
Log: logger.Named("controllers").Named("AtlasDatabaseUser").Sugar(),
AtlasProvider: atlasProvider,
ObjectDeletionProtection: deletionProtection,
FeaturePreviewOIDCAuthEnabled: featureFlags.IsFeaturePresent(featureflags.FeatureOIDC),
independentSyncPeriod: independentSyncPeriod,
Scheme: mgr.GetScheme(),
Client: mgr.GetClient(),
EventRecorder: mgr.GetEventRecorderFor("AtlasDatabaseUser"),
GlobalPredicates: predicates,
Log: logger.Named("controllers").Named("AtlasDatabaseUser").Sugar(),
AtlasProvider: atlasProvider,
ObjectDeletionProtection: deletionProtection,
independentSyncPeriod: independentSyncPeriod,
}
}
16 changes: 0 additions & 16 deletions pkg/controller/atlasdatabaseuser/databaseuser.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,6 @@ func (r *AtlasDatabaseUserReconciler) dbuLifeCycle(ctx *workflow.Context, atlasD
}

func (r *AtlasDatabaseUserReconciler) create(ctx *workflow.Context, projectID string, atlasDatabaseUser *akov2.AtlasDatabaseUser) ctrl.Result {
if !canManageOIDC(r.FeaturePreviewOIDCAuthEnabled, atlasDatabaseUser.Spec.OIDCAuthType) {
return r.terminate(ctx, atlasDatabaseUser, api.DatabaseUserReadyType, workflow.Internal, false, ErrOIDCNotEnabled)
}

userPassword, passwordVersion, err := r.readPassword(ctx.Context, atlasDatabaseUser)
if err != nil {
return r.terminate(ctx, atlasDatabaseUser, api.DatabaseUserReadyType, workflow.Internal, true, err)
Expand Down Expand Up @@ -136,10 +132,6 @@ func (r *AtlasDatabaseUserReconciler) create(ctx *workflow.Context, projectID st
}

func (r *AtlasDatabaseUserReconciler) update(ctx *workflow.Context, atlasProject *project.Project, atlasDatabaseUser *akov2.AtlasDatabaseUser, databaseUserInAtlas *dbuser.User) ctrl.Result {
if !canManageOIDC(r.FeaturePreviewOIDCAuthEnabled, atlasDatabaseUser.Spec.OIDCAuthType) {
return r.terminate(ctx, atlasDatabaseUser, api.DatabaseUserReadyType, workflow.Internal, false, ErrOIDCNotEnabled)
}

userPassword, passwordVersion, err := r.readPassword(ctx.Context, atlasDatabaseUser)
if err != nil {
return r.terminate(ctx, atlasDatabaseUser, api.DatabaseUserReadyType, workflow.Internal, true, err)
Expand Down Expand Up @@ -331,14 +323,6 @@ func (r *AtlasDatabaseUserReconciler) getProjectFromKube(ctx *workflow.Context,
return project.NewProject(atlasProject, orgID), nil
}

func canManageOIDC(isEnabled bool, oidcType string) bool {
if !isEnabled && (oidcType != "" && oidcType != "NONE") {
return false
}

return true
}

func isExpired(atlasDatabaseUser *akov2.AtlasDatabaseUser) (bool, error) {
if atlasDatabaseUser.Spec.DeleteAfterDate == "" {
return false, nil
Expand Down
Loading

0 comments on commit 459813e

Please sign in to comment.