Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CLOUDP-297401: Handle x509 just like other project resources #2084

Merged
merged 4 commits into from
Feb 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions api/condition.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const (
ProjectTeamsReadyType ConditionType = "ProjectTeamsReady"
SearchIndexesReadyType ConditionType = "AtlasSearchIndexesReady"
BackupComplianceReadyType ConditionType = "BackupCompliancePolicyReady"
X509AuthReadyType ConditionType = "X509AuthReady"
)

// AtlasDeployment condition types
Expand Down
5 changes: 5 additions & 0 deletions internal/controller/atlasproject/atlasproject_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,11 @@ func (r *AtlasProjectReconciler) ensureProjectResources(workflowCtx *workflow.Co
}
results = append(results, result)

if result = r.ensureX509(workflowCtx, project); result.IsOk() {
r.EventRecorder.Event(project, "Normal", string(api.X509AuthReadyType), "")
}
results = append(results, result)

return results
}

Expand Down
5 changes: 0 additions & 5 deletions internal/controller/atlasproject/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,13 @@ func (r *AtlasProjectReconciler) handleProject(ctx *workflow.Context, orgID stri
return r.manage(ctx, atlasProject, projectInAtlas.ID)
}

if err = r.ensureX509(ctx, atlasProject); err != nil {
return r.terminate(ctx, workflow.Internal, err)
}

ctx.SetConditionTrue(api.ProjectReadyType)
r.EventRecorder.Event(atlasProject, "Normal", string(api.ProjectReadyType), "")

results := r.ensureProjectResources(ctx, atlasProject, services)
for i := range results {
if !results[i].IsOk() {
logIfWarning(ctx, results[i])

return results[i].ReconcileResult(), nil
}
}
Expand Down
84 changes: 76 additions & 8 deletions internal/controller/atlasproject/project_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,10 +271,74 @@ func TestHandleProject(t *testing.T) {
},
"should fail to configure authentication modes": {
atlasClientMocker: func() *mongodbatlas.Client {
return nil
integrations := &atlasmocks.ThirdPartyIntegrationsClientMock{
ListFunc: func(projectID string) (*mongodbatlas.ThirdPartyIntegrations, *mongodbatlas.Response, error) {
return &mongodbatlas.ThirdPartyIntegrations{}, nil, nil
},
}
encryptionAtRest := &atlasmocks.EncryptionAtRestClientMock{
GetFunc: func(projectID string) (*mongodbatlas.EncryptionAtRest, *mongodbatlas.Response, error) {
return &mongodbatlas.EncryptionAtRest{}, nil, nil
},
}
projectAPI := &atlasmocks.ProjectsClientMock{}

return &mongodbatlas.Client{
Integrations: integrations,
EncryptionsAtRest: encryptionAtRest,
Projects: projectAPI,
}
},
atlasSDKMocker: func() *admin.APIClient {
return nil
atlasSDKMocker: func() *admin.APIClient { //nolint:dupl
ipAccessList := mockadmin.NewProjectIPAccessListApi(t)
ipAccessList.EXPECT().ListProjectIpAccessLists(context.Background(), "projectID").
Return(admin.ListProjectIpAccessListsApiRequest{ApiService: ipAccessList})
ipAccessList.EXPECT().ListProjectIpAccessListsExecute(mock.AnythingOfType("admin.ListProjectIpAccessListsApiRequest")).
Return(nil, nil, nil)
privateEndpoints := mockadmin.NewPrivateEndpointServicesApi(t)
privateEndpoints.EXPECT().ListPrivateEndpointServices(context.Background(), "projectID", mock.Anything).
Return(admin.ListPrivateEndpointServicesApiRequest{ApiService: privateEndpoints})
privateEndpoints.EXPECT().ListPrivateEndpointServicesExecute(mock.AnythingOfType("admin.ListPrivateEndpointServicesApiRequest")).
Return(nil, nil, nil)
networkPeering := mockadmin.NewNetworkPeeringApi(t)
networkPeering.EXPECT().ListPeeringConnectionsWithParams(context.Background(), mock.AnythingOfType("*admin.ListPeeringConnectionsApiParams")).
Return(admin.ListPeeringConnectionsApiRequest{ApiService: networkPeering})
networkPeering.EXPECT().ListPeeringConnectionsExecute(mock.AnythingOfType("admin.ListPeeringConnectionsApiRequest")).
Return(nil, nil, nil)
networkPeering.EXPECT().ListPeeringContainers(context.Background(), "projectID").
Return(admin.ListPeeringContainersApiRequest{ApiService: networkPeering})
networkPeering.EXPECT().ListPeeringContainersExecute(mock.AnythingOfType("admin.ListPeeringContainersApiRequest")).
Return(nil, nil, nil)
audit := mockadmin.NewAuditingApi(t)
audit.EXPECT().GetAuditingConfiguration(context.Background(), "projectID").
Return(admin.GetAuditingConfigurationApiRequest{ApiService: audit})
audit.EXPECT().GetAuditingConfigurationExecute(mock.AnythingOfType("admin.GetAuditingConfigurationApiRequest")).
Return(nil, nil, nil)
customRoles := mockadmin.NewCustomDatabaseRolesApi(t)
customRoles.EXPECT().ListCustomDatabaseRoles(context.Background(), "projectID").
Return(admin.ListCustomDatabaseRolesApiRequest{ApiService: customRoles})
customRoles.EXPECT().ListCustomDatabaseRolesExecute(mock.AnythingOfType("admin.ListCustomDatabaseRolesApiRequest")).
Return(nil, nil, nil)
projectAPI := mockadmin.NewProjectsApi(t)
projectAPI.EXPECT().GetProjectSettings(context.Background(), "projectID").
Return(admin.GetProjectSettingsApiRequest{ApiService: projectAPI})
projectAPI.EXPECT().GetProjectSettingsExecute(mock.AnythingOfType("admin.GetProjectSettingsApiRequest")).
Return(admin.NewGroupSettings(), nil, nil)
backup := mockadmin.NewCloudBackupsApi(t)
backup.EXPECT().GetDataProtectionSettings(context.Background(), "projectID").
Return(admin.GetDataProtectionSettingsApiRequest{ApiService: backup})
backup.EXPECT().GetDataProtectionSettingsExecute(mock.AnythingOfType("admin.GetDataProtectionSettingsApiRequest")).
Return(nil, nil, nil)

return &admin.APIClient{
ProjectIPAccessListApi: ipAccessList,
PrivateEndpointServicesApi: privateEndpoints,
NetworkPeeringApi: networkPeering,
AuditingApi: audit,
CustomDatabaseRolesApi: customRoles,
ProjectsApi: projectAPI,
CloudBackupsApi: backup,
}
},
projectServiceMocker: func() project.ProjectService {
service := translation.NewProjectServiceMock(t)
Expand All @@ -284,10 +348,14 @@ func TestHandleProject(t *testing.T) {
return service
},
teamServiceMocker: func() teams.TeamsService {
return nil
service := translation.NewTeamsServiceMock(t)
service.EXPECT().ListProjectTeams(context.Background(), mock.Anything).Return([]teams.AssignedTeam{}, nil)
return service
},
encryptionAtRestMocker: func() encryptionatrest.EncryptionAtRestService {
return nil
service := translation.NewEncryptionAtRestServiceMock(t)
service.EXPECT().Get(context.Background(), mock.Anything).Return(nil, nil)
return service
},
project: &akov2.AtlasProject{
ObjectMeta: metav1.ObjectMeta{
Expand All @@ -303,10 +371,10 @@ func TestHandleProject(t *testing.T) {
ID: "projectID",
},
},
result: reconcile.Result{RequeueAfter: workflow.DefaultRetry},
result: reconcile.Result{RequeueAfter: workflow.DefaultRetry, Requeue: false},
conditions: []api.Condition{
api.FalseCondition(api.ProjectReadyType).
WithReason(string(workflow.Internal)).
api.TrueCondition(api.ProjectReadyType),
api.FalseCondition(api.X509AuthReadyType).
WithMessageRegexp("secrets \"invalid-ref\" not found"),
},
finalizers: []string{customresource.FinalizerLabel},
Expand Down
37 changes: 26 additions & 11 deletions internal/controller/atlasproject/x509_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"fmt"
"strings"

"github.com/mongodb/mongodb-atlas-kubernetes/v2/api"

"go.mongodb.org/atlas-sdk/v20231115008/admin"
"go.uber.org/zap"
corev1 "k8s.io/api/core/v1"
Expand All @@ -18,7 +20,21 @@ import (
"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/controller/workflow"
)

func (r *AtlasProjectReconciler) ensureX509(ctx *workflow.Context, atlasProject *akov2.AtlasProject) error {
func terminateX509(workflowCtx *workflow.Context, err error) workflow.Result {
workflowCtx.SetConditionFalseMsg(api.X509AuthReadyType, err.Error())
return workflow.Terminate(workflow.ProjectX509NotConfigured, err)
}

func emptyX509(workflowCtx *workflow.Context) workflow.Result {
workflowCtx.UnsetCondition(api.X509AuthReadyType)
return idleX509()
}

func idleX509() workflow.Result {
return workflow.OK()
}

func (r *AtlasProjectReconciler) ensureX509(ctx *workflow.Context, atlasProject *akov2.AtlasProject) workflow.Result {
atlasProject.Status.AuthModes.AddAuthMode(authmode.Scram)

hasAuthModesX509 := atlasProject.Status.AuthModes.CheckAuthMode(authmode.X509)
Expand All @@ -32,19 +48,18 @@ func (r *AtlasProjectReconciler) ensureX509(ctx *workflow.Context, atlasProject
default:
ctx.EnsureStatusOption(status.AtlasProjectAuthModesOption(atlasProject.Status.AuthModes))
}

return nil
return idleX509()
}

func (r *AtlasProjectReconciler) enableX509Authentication(ctx *workflow.Context, atlasProject *akov2.AtlasProject) error {
func (r *AtlasProjectReconciler) enableX509Authentication(ctx *workflow.Context, atlasProject *akov2.AtlasProject) workflow.Result {
specCert, err := readX509CertFromSecret(ctx.Context, r.Client, *atlasProject.X509SecretObjectKey(), r.Log)
if err != nil {
return err
return terminateX509(ctx, err)
}

ldapConfig, _, err := ctx.SdkClient.LDAPConfigurationApi.GetLDAPConfiguration(ctx.Context, atlasProject.ID()).Execute()
if err != nil {
return err
return terminateX509(ctx, err)
}

customerX509 := ldapConfig.GetCustomerX509()
Expand All @@ -58,27 +73,27 @@ func (r *AtlasProjectReconciler) enableX509Authentication(ctx *workflow.Context,

_, _, err = ctx.SdkClient.LDAPConfigurationApi.SaveLDAPConfiguration(ctx.Context, atlasProject.ID(), &conf).Execute()
if err != nil {
return err
return terminateX509(ctx, err)
}
}

atlasProject.Status.AuthModes.AddAuthMode(authmode.X509)
ctx.EnsureStatusOption(status.AtlasProjectAuthModesOption(atlasProject.Status.AuthModes))

return nil
return idleX509()
}

func (r *AtlasProjectReconciler) disableX509Authentication(ctx *workflow.Context, atlasProject *akov2.AtlasProject) error {
func (r *AtlasProjectReconciler) disableX509Authentication(ctx *workflow.Context, atlasProject *akov2.AtlasProject) workflow.Result {
r.Log.Infow("Disable x509 auth", "projectID", atlasProject.ID())
_, _, err := ctx.SdkClient.X509AuthenticationApi.DisableCustomerManagedX509(ctx.Context, atlasProject.ID()).Execute()
if err != nil {
return err
return terminateX509(ctx, err)
}

atlasProject.Status.AuthModes.RemoveAuthMode(authmode.X509)
ctx.EnsureStatusOption(status.AtlasProjectAuthModesOption(atlasProject.Status.AuthModes))

return nil
return emptyX509(ctx)
}

func readX509CertFromSecret(ctx context.Context, kubeClient client.Client, secretRef client.ObjectKey, log *zap.SugaredLogger) (string, error) {
Expand Down
1 change: 1 addition & 0 deletions internal/controller/workflow/reason.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const (
ProjectAlertConfigurationIsNotReadyInAtlas ConditionReason = "ProjectAlertConfigurationIsNotReadyInAtlas"
ProjectCustomRolesReady ConditionReason = "ProjectCustomRolesReady"
ProjectTeamUnavailable ConditionReason = "ProjectTeamUnavailable"
ProjectX509NotConfigured ConditionReason = "ProjectX509NotConfigured"
)

// Atlas Backup Compliance Policy reasons
Expand Down
Loading