Skip to content

Commit

Permalink
CLOUDP-266544: Support local credentials for Users (#1812)
Browse files Browse the repository at this point in the history
* CLOUDP-265544: Support local credentials for Users

Signed-off-by: jose.vazquez <[email protected]>

* Improve tests

* Do not expose reconcile listing

* Separate the specific indexer parts away

* Remove unused code

---------

Signed-off-by: jose.vazquez <[email protected]>
  • Loading branch information
josvazg authored Sep 30, 2024
1 parent 767cf8b commit 7541102
Show file tree
Hide file tree
Showing 19 changed files with 630 additions and 8 deletions.
12 changes: 12 additions & 0 deletions config/crd/bases/atlas.mongodb.com_atlasdatabaseusers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,18 @@ spec:
- USER
- ROLE
type: string
connectionSecret:
description: LocalObjectReference is a reference to an object in the
same namespace as the referent
properties:
name:
description: |-
Name of the resource being referred to
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
type: string
required:
- name
type: object
databaseName:
default: admin
description: DatabaseName is a Database against which Atlas authenticates
Expand Down
1 change: 1 addition & 0 deletions internal/translation/dbuser/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ func (u *User) clearedSpecClone() *akov2.AtlasDatabaseUserSpec {
clone.Project.Name = ""
clone.Project.Namespace = ""
clone.PasswordSecret = nil
clone.ConnectionSecret = nil
return &clone
}

Expand Down
3 changes: 3 additions & 0 deletions internal/translation/dbuser/conversion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/timeutil"
"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/translation/dbuser"
"github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/api"
akov2 "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/api/v1"
"github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/api/v1/common"
)
Expand Down Expand Up @@ -393,6 +394,7 @@ func TestDiffSpecs(t *testing.T) {
spec.Project.Name = "some-project"
spec.Project.Namespace = "some-namespace"
spec.PasswordSecret = &common.ResourceRef{Name: "some-secret-ref"}
spec.ConnectionSecret = &api.LocalObjectReference{Name: "some-local-secret-ref"}
return spec
}(),
},
Expand All @@ -408,6 +410,7 @@ func TestDiffSpecs(t *testing.T) {
spec.Project.Name = "another-project"
spec.Project.Namespace = "another-namespace"
spec.PasswordSecret = &common.ResourceRef{Name: "another-secret-ref"}
spec.ConnectionSecret = &api.LocalObjectReference{Name: "another-local-secret-ref"}
return spec
}(),
},
Expand Down
28 changes: 28 additions & 0 deletions pkg/api/credentials.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package api

type LocalRef string

// +k8s:deepcopy-gen=false

// CredentialsProvider gives access to custom local credentials
type CredentialsProvider interface {
Credentials() *LocalObjectReference
}

// +k8s:deepcopy-gen=false

// ResourceWithCredentials is to be implemented by all CRDs using custom local credentials
type ResourceWithCredentials interface {
CredentialsProvider
GetName() string
GetNamespace() string
}

// LocalCredentialHolder is to be embedded by Specs of CRDs using custom local credentials
type LocalCredentialHolder struct {
ConnectionSecret *LocalObjectReference `json:"connectionSecret,omitempty"`
}

func (ch *LocalCredentialHolder) Credentials() *LocalObjectReference {
return ch.ConnectionSecret
}
8 changes: 8 additions & 0 deletions pkg/api/localref.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package api

// LocalObjectReference is a reference to an object in the same namespace as the referent
type LocalObjectReference struct {
// Name of the resource being referred to
// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
Name string `json:"name"`
}
6 changes: 6 additions & 0 deletions pkg/api/v1/atlasdatabaseuser_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ const (

// AtlasDatabaseUserSpec defines the desired state of Database User in Atlas
type AtlasDatabaseUserSpec struct {
api.LocalCredentialHolder `json:",inline"`

// Project is a reference to AtlasProject resource the user belongs to
Project common.ResourceRefNamespaced `json:"projectRef"`

Expand Down Expand Up @@ -296,6 +298,10 @@ func (p *AtlasDatabaseUser) WithDeleteAfterDate(date string) *AtlasDatabaseUser
return p
}

func (p AtlasDatabaseUser) Credentials() *api.LocalObjectReference {
return p.Spec.Credentials()
}

func DefaultDBUser(namespace, username, projectName string) *AtlasDatabaseUser {
return NewDBUser(namespace, username, username, projectName).WithRole("clusterMonitor", "admin", "")
}
1 change: 1 addition & 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.

35 changes: 35 additions & 0 deletions pkg/api/zz_generated.deepcopy.go

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

11 changes: 11 additions & 0 deletions pkg/controller/atlasdatabaseuser/atlasdatabaseuser_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,17 @@ func (r *AtlasDatabaseUserReconciler) SetupWithManager(mgr ctrl.Manager, skipNam
handler.EnqueueRequestsFromMapFunc(r.findAtlasDatabaseUserForSecret),
builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}),
).
Watches(
&corev1.Secret{},
handler.EnqueueRequestsFromMapFunc(indexer.CredentialsIndexMapperFunc(
indexer.AtlasDatabaseUserCredentialsIndex,
&akov2.AtlasDatabaseUserList{},
indexer.DatabaseUserRequests,
r.Client,
r.Log,
)),
builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}),
).
WithOptions(controller.TypedOptions[reconcile.Request]{SkipNameValidation: pointer.MakePtr(skipNameValidation)}).
Complete(r)
}
Expand Down
6 changes: 5 additions & 1 deletion pkg/controller/atlasdatabaseuser/databaseuser.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,11 @@ func (r *AtlasDatabaseUserReconciler) handleDatabaseUser(ctx *workflow.Context,
return r.terminate(ctx, atlasDatabaseUser, api.DatabaseUserReadyType, workflow.Internal, true, err)
}

sdkClient, _, err := r.AtlasProvider.SdkClient(ctx.Context, atlasProject.ConnectionSecretObjectKey(), r.Log)
credentialsSecret, err := customresource.ComputeSecret(atlasProject, atlasDatabaseUser)
if err != nil {
return r.terminate(ctx, atlasDatabaseUser, api.DatabaseUserReadyType, workflow.AtlasAPIAccessNotConfigured, true, err)
}
sdkClient, _, err := r.AtlasProvider.SdkClient(ctx.Context, credentialsSecret, r.Log)
if err != nil {
return r.terminate(ctx, atlasDatabaseUser, api.DatabaseUserReadyType, workflow.AtlasAPIAccessNotConfigured, true, err)
}
Expand Down
20 changes: 18 additions & 2 deletions pkg/controller/customresource/customresource.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@ import (
"context"
"fmt"

"github.com/Masterminds/semver"
"go.uber.org/zap"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/reconcile"

"github.com/Masterminds/semver"

"github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/api"
akov2 "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/api/v1"
"github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/controller/workflow"
Expand Down Expand Up @@ -139,3 +138,20 @@ func SetAnnotation(resource api.AtlasCustomResource, key, value string) {
annot[key] = value
resource.SetAnnotations(annot)
}

func ComputeSecret(project *akov2.AtlasProject, resource api.ResourceWithCredentials) (*client.ObjectKey, error) {
if resource == nil {
return nil, fmt.Errorf("resource cannot be nil")
}
creds := resource.Credentials()
if creds != nil && creds.Name != "" {
return &client.ObjectKey{
Namespace: resource.GetNamespace(),
Name: creds.Name,
}, nil
}
if project == nil {
return nil, fmt.Errorf("project cannot be nil")
}
return project.ConnectionSecretObjectKey(), nil
}
102 changes: 102 additions & 0 deletions pkg/controller/customresource/customresource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ import (

"github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/api"
akov2 "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/api/v1"
"github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/api/v1/common"
"github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/api/v1/status"
"github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/version"

"github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
)

func TestResourceShouldBeLeftInAtlas(t *testing.T) {
Expand Down Expand Up @@ -227,3 +230,102 @@ func TestResourceVersionIsValid(t *testing.T) {
})
}
}

func TestComputeSecret(t *testing.T) {
for _, tt := range []struct {
name string
project *akov2.AtlasProject
resource api.ResourceWithCredentials
wantRef *types.NamespacedName
wantErrorMsg string
}{
{
name: "nil inputs fails with resource cannot be nil",
wantErrorMsg: "resource cannot be nil",
},

{
name: "nil project ignored if resource is set",
resource: &akov2.AtlasDatabaseUser{
ObjectMeta: metav1.ObjectMeta{Namespace: "local"},
Spec: akov2.AtlasDatabaseUserSpec{
LocalCredentialHolder: api.LocalCredentialHolder{
ConnectionSecret: &api.LocalObjectReference{Name: "local-secret"},
},
},
},
wantRef: &client.ObjectKey{
Name: "local-secret",
Namespace: "local",
},
},

{
name: "nil resource and empty project fails",
project: &akov2.AtlasProject{},
wantErrorMsg: "resource cannot be nil",
},

{
name: "when both are set empty it renders nil",
project: &akov2.AtlasProject{},
resource: &akov2.AtlasDatabaseUser{},
},

{
name: "empty resource and proper project get creds from project",
project: &akov2.AtlasProject{
Spec: akov2.AtlasProjectSpec{
Name: "",
RegionUsageRestrictions: "",
ConnectionSecret: &common.ResourceRefNamespaced{
Name: "project-secret",
Namespace: "some-namespace",
},
},
},
resource: &akov2.AtlasDatabaseUser{},
wantRef: &client.ObjectKey{
Name: "project-secret",
Namespace: "some-namespace",
},
},

{
name: "when both are properly set the resource wins",
project: &akov2.AtlasProject{
Spec: akov2.AtlasProjectSpec{
Name: "",
RegionUsageRestrictions: "",
ConnectionSecret: &common.ResourceRefNamespaced{
Name: "project-secret",
Namespace: "some-namespace",
},
},
},
resource: &akov2.AtlasDatabaseUser{
ObjectMeta: metav1.ObjectMeta{Namespace: "local"},
Spec: akov2.AtlasDatabaseUserSpec{
LocalCredentialHolder: api.LocalCredentialHolder{
ConnectionSecret: &api.LocalObjectReference{Name: "local-secret"},
},
},
},
wantRef: &client.ObjectKey{
Name: "local-secret",
Namespace: "local",
},
},
} {
t.Run(tt.name, func(t *testing.T) {
result, err := ComputeSecret(tt.project, tt.resource)
if tt.wantErrorMsg != "" {
assert.Nil(t, result, nil)
assert.ErrorContains(t, err, tt.wantErrorMsg)
} else {
assert.Equal(t, result, tt.wantRef)
assert.NoError(t, err)
}
})
}
}
24 changes: 24 additions & 0 deletions pkg/indexer/atlasdatabaseusercredentials.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package indexer

import (
"go.uber.org/zap"
"sigs.k8s.io/controller-runtime/pkg/reconcile"

akov2 "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/api/v1"
)

const (
AtlasDatabaseUserCredentialsIndex = "atlasdatabaseuser.credentials"
)

func NewAtlasDatabaseUserByCredentialIndexer(logger *zap.Logger) *LocalCredentialIndexer {
return NewLocalCredentialsIndexer(AtlasDatabaseUserCredentialsIndex, &akov2.AtlasDatabaseUser{}, logger)
}

func DatabaseUserRequests(list *akov2.AtlasDatabaseUserList) []reconcile.Request {
requests := make([]reconcile.Request, 0, len(list.Items))
for _, item := range list.Items {
requests = append(requests, toRequest(&item))
}
return requests
}
1 change: 1 addition & 0 deletions pkg/indexer/indexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func RegisterAll(ctx context.Context, mgr manager.Manager, logger *zap.Logger) e
NewAtlasProjectByTeamIndexer(logger),
NewAtlasFederatedAuthBySecretsIndexer(logger),
NewAtlasDatabaseUserBySecretsIndexer(logger),
NewAtlasDatabaseUserByCredentialIndexer(logger),
)
}

Expand Down
Loading

0 comments on commit 7541102

Please sign in to comment.