Skip to content

Commit 1768b4b

Browse files
CLOUDP-186806: Move encryption at rest credentials to secrets (#1045)
Move encryption at rest credentials to secrets
1 parent a345693 commit 1768b4b

File tree

6 files changed

+581
-13
lines changed

6 files changed

+581
-13
lines changed

pkg/api/v1/encryption_at_rest.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"github.com/mongodb/mongodb-atlas-kubernetes/pkg/util/compat"
55

66
"go.mongodb.org/atlas/mongodbatlas"
7+
8+
"github.com/mongodb/mongodb-atlas-kubernetes/pkg/api/v1/common"
79
)
810

911
// EncryptionAtRest allows to specify the Encryption at Rest for AWS, Azure and GCP providers
@@ -22,6 +24,9 @@ type AwsKms struct {
2224
Region string `json:"region,omitempty"` // The AWS region in which the AWS customer master key exists: CA_CENTRAL_1, US_EAST_1, US_EAST_2, US_WEST_1, US_WEST_2, SA_EAST_1
2325
RoleID string `json:"roleId,omitempty"` // ID of an AWS IAM role authorized to manage an AWS customer master key.
2426
Valid *bool `json:"valid,omitempty"` // Specifies whether the encryption key set for the provider is valid and may be used to encrypt and decrypt data.
27+
// A reference to as Secret containing the AccessKeyID, SecretAccessKey, CustomerMasterKey and RoleID fields
28+
// +optional
29+
SecretRef common.ResourceRefNamespaced `json:"secretRef,omitempty"`
2530
}
2631

2732
// AzureKeyVault specifies Azure Key Vault configuration details and whether Encryption at Rest is enabled for an Atlas project.
@@ -35,19 +40,59 @@ type AzureKeyVault struct {
3540
KeyIdentifier string `json:"keyIdentifier,omitempty"` // The unique identifier of a key in an Azure Key Vault.
3641
Secret string `json:"secret,omitempty"` // The secret associated with the Azure Key Vault specified by azureKeyVault.tenantID.
3742
TenantID string `json:"tenantID,omitempty"` // The unique identifier for an Azure AD tenant within an Azure subscription.
43+
// A reference to as Secret containing the SubscriptionID, KeyVaultName, KeyIdentifier, Secret fields
44+
// +optional
45+
SecretRef common.ResourceRefNamespaced `json:"secretRef,omitempty"`
3846
}
3947

4048
// GoogleCloudKms specifies GCP KMS configuration details and whether Encryption at Rest is enabled for an Atlas project.
4149
type GoogleCloudKms struct {
4250
Enabled *bool `json:"enabled,omitempty"` // Specifies whether Encryption at Rest is enabled for an Atlas project. To disable Encryption at Rest, pass only this parameter with a value of false. When you disable Encryption at Rest, Atlas also removes the configuration details.
4351
ServiceAccountKey string `json:"serviceAccountKey,omitempty"` // String-formatted JSON object containing GCP KMS credentials from your GCP account.
4452
KeyVersionResourceID string `json:"keyVersionResourceID,omitempty"` // The Key Version Resource ID from your GCP account.
53+
// A reference to as Secret containing the ServiceAccountKey, KeyVersionResourceID fields
54+
// +optional
55+
SecretRef common.ResourceRefNamespaced `json:"secretRef,omitempty"`
4556
}
4657

4758
func (e EncryptionAtRest) ToAtlas(projectID string) (*mongodbatlas.EncryptionAtRest, error) {
4859
result := &mongodbatlas.EncryptionAtRest{
4960
GroupID: projectID,
5061
}
62+
5163
err := compat.JSONCopy(result, e)
5264
return result, err
5365
}
66+
67+
func (a AwsKms) ToAtlas() mongodbatlas.AwsKms {
68+
return mongodbatlas.AwsKms{
69+
Enabled: a.Enabled,
70+
AccessKeyID: a.AccessKeyID,
71+
SecretAccessKey: a.SecretAccessKey,
72+
RoleID: a.RoleID,
73+
CustomerMasterKeyID: a.CustomerMasterKeyID,
74+
Region: a.Region,
75+
Valid: a.Valid,
76+
}
77+
}
78+
79+
func (g GoogleCloudKms) ToAtlas() mongodbatlas.GoogleCloudKms {
80+
return mongodbatlas.GoogleCloudKms{
81+
Enabled: g.Enabled,
82+
ServiceAccountKey: g.ServiceAccountKey,
83+
KeyVersionResourceID: g.KeyVersionResourceID,
84+
}
85+
}
86+
87+
func (az AzureKeyVault) ToAtlas() mongodbatlas.AzureKeyVault {
88+
return mongodbatlas.AzureKeyVault{
89+
Enabled: az.Enabled,
90+
ClientID: az.ClientID,
91+
AzureEnvironment: az.AzureEnvironment,
92+
SubscriptionID: az.SubscriptionID,
93+
ResourceGroupName: az.ResourceGroupName,
94+
KeyVaultName: az.KeyVaultName,
95+
KeyIdentifier: az.KeyIdentifier,
96+
TenantID: az.TenantID,
97+
}
98+
}

pkg/api/v1/zz_generated.deepcopy.go

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/controller/atlasproject/atlasproject_controller.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ func (r *AtlasProjectReconciler) ensureProjectResources(ctx *workflow.Context, p
264264
}
265265
results = append(results, result)
266266

267-
if result = ensureEncryptionAtRest(ctx, projectID, project); result.IsOk() {
267+
if result = r.ensureEncryptionAtRest(ctx, projectID, project); result.IsOk() {
268268
r.EventRecorder.Event(project, "Normal", string(status.EncryptionAtRestReadyType), "")
269269
}
270270
results = append(results, result)

pkg/controller/atlasproject/encryption_at_rest.go

Lines changed: 123 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,15 @@ import (
55
"fmt"
66
"reflect"
77
"regexp"
8+
"strings"
9+
10+
v1 "k8s.io/api/core/v1"
11+
"sigs.k8s.io/controller-runtime/pkg/client"
812

913
mdbv1 "github.com/mongodb/mongodb-atlas-kubernetes/pkg/api/v1"
14+
"github.com/mongodb/mongodb-atlas-kubernetes/pkg/api/v1/common"
1015
"github.com/mongodb/mongodb-atlas-kubernetes/pkg/api/v1/status"
16+
"github.com/mongodb/mongodb-atlas-kubernetes/pkg/controller/watch"
1117
"github.com/mongodb/mongodb-atlas-kubernetes/pkg/controller/workflow"
1218
"github.com/mongodb/mongodb-atlas-kubernetes/pkg/util/toptr"
1319

@@ -18,7 +24,12 @@ const (
1824
ObjectIDRegex = "^([a-f0-9]{24})$"
1925
)
2026

21-
func ensureEncryptionAtRest(ctx *workflow.Context, projectID string, project *mdbv1.AtlasProject) workflow.Result {
27+
func (r *AtlasProjectReconciler) ensureEncryptionAtRest(ctx *workflow.Context, projectID string, project *mdbv1.AtlasProject) workflow.Result {
28+
if err := readEncryptionAtRestSecrets(r.Client, ctx, project.Spec.EncryptionAtRest, project.Namespace); err != nil {
29+
ctx.UnsetCondition(status.EncryptionAtRestReadyType)
30+
return workflow.Terminate(workflow.ProjectEncryptionAtRestReady, err.Error())
31+
}
32+
2233
result := createOrDeleteEncryptionAtRests(ctx, projectID, project)
2334
if !result.IsOk() {
2435
ctx.SetConditionFromResult(status.EncryptionAtRestReadyType, result)
@@ -34,6 +45,114 @@ func ensureEncryptionAtRest(ctx *workflow.Context, projectID string, project *md
3445
return workflow.OK()
3546
}
3647

48+
func readEncryptionAtRestSecrets(kubeClient client.Client, service *workflow.Context, encRest *mdbv1.EncryptionAtRest, parentNs string) error {
49+
if encRest == nil {
50+
return nil
51+
}
52+
53+
if encRest.AwsKms.Enabled != nil && *encRest.AwsKms.Enabled && encRest.AwsKms.SecretRef.Name != "" {
54+
watchObj, err := readAndFillAWSSecret(kubeClient, parentNs, &encRest.AwsKms)
55+
service.AddResourcesToWatch(*watchObj)
56+
if err != nil {
57+
return err
58+
}
59+
}
60+
61+
if encRest.GoogleCloudKms.Enabled != nil && *encRest.GoogleCloudKms.Enabled && encRest.GoogleCloudKms.SecretRef.Name != "" {
62+
watchObj, err := readAndFillGoogleSecret(kubeClient, parentNs, &encRest.GoogleCloudKms)
63+
service.AddResourcesToWatch(*watchObj)
64+
if err != nil {
65+
return err
66+
}
67+
}
68+
69+
if encRest.AzureKeyVault.Enabled != nil && *encRest.AzureKeyVault.Enabled && encRest.AzureKeyVault.SecretRef.Name != "" {
70+
watchObj, err := readAndFillAzureSecret(kubeClient, parentNs, &encRest.AzureKeyVault)
71+
service.AddResourcesToWatch(*watchObj)
72+
if err != nil {
73+
return err
74+
}
75+
}
76+
77+
return nil
78+
}
79+
80+
func readAndFillAWSSecret(kubeClient client.Client, parentNs string, awsKms *mdbv1.AwsKms) (*watch.WatchedObject, error) {
81+
fieldData, watchObj, err := readSecretData(kubeClient, awsKms.SecretRef, parentNs, "CustomerMasterKeyID", "Region", "RoleID")
82+
if err != nil {
83+
return watchObj, err
84+
}
85+
86+
awsKms.CustomerMasterKeyID = fieldData["CustomerMasterKeyID"]
87+
awsKms.Region = fieldData["Region"]
88+
awsKms.RoleID = fieldData["RoleID"]
89+
90+
return watchObj, nil
91+
}
92+
93+
func readAndFillGoogleSecret(kubeClient client.Client, parentNs string, gkms *mdbv1.GoogleCloudKms) (*watch.WatchedObject, error) {
94+
fieldData, watchObj, err := readSecretData(kubeClient, gkms.SecretRef, parentNs, "ServiceAccountKey", "KeyVersionResourceID")
95+
if err != nil {
96+
return watchObj, err
97+
}
98+
99+
gkms.ServiceAccountKey = fieldData["ServiceAccountKey"]
100+
gkms.KeyVersionResourceID = fieldData["KeyVersionResourceID"]
101+
102+
return watchObj, nil
103+
}
104+
105+
func readAndFillAzureSecret(kubeClient client.Client, parentNs string, azureVault *mdbv1.AzureKeyVault) (*watch.WatchedObject, error) {
106+
fieldData, watchObj, err := readSecretData(kubeClient, azureVault.SecretRef, parentNs, "ClientID", "AzureEnvironment", "SubscriptionID", "ResourceGroupName", "KeyVaultName", "KeyIdentifier")
107+
if err != nil {
108+
return watchObj, err
109+
}
110+
111+
azureVault.ClientID = fieldData["ClientID"]
112+
azureVault.AzureEnvironment = fieldData["AzureEnvironment"]
113+
azureVault.SubscriptionID = fieldData["SubscriptionID"]
114+
azureVault.ResourceGroupName = fieldData["ResourceGroupName"]
115+
azureVault.KeyVaultName = fieldData["KeyVaultName"]
116+
azureVault.KeyIdentifier = fieldData["KeyIdentifier"]
117+
118+
return watchObj, nil
119+
}
120+
121+
// Return all requested field from a secret
122+
func readSecretData(kubeClient client.Client, res common.ResourceRefNamespaced, parentNamespace string, fieldNames ...string) (map[string]string, *watch.WatchedObject, error) {
123+
secret := &v1.Secret{}
124+
var ns string
125+
if res.Namespace == "" {
126+
ns = parentNamespace
127+
} else {
128+
ns = res.Namespace
129+
}
130+
131+
result := map[string]string{}
132+
133+
secretObj := client.ObjectKey{Name: res.Name, Namespace: ns}
134+
obj := &watch.WatchedObject{ResourceKind: "Secret", Resource: secretObj}
135+
136+
if err := kubeClient.Get(context.Background(), secretObj, secret); err != nil {
137+
return result, obj, err
138+
}
139+
140+
missingFields := []string{}
141+
for i := range fieldNames {
142+
val, exists := secret.Data[fieldNames[i]]
143+
if !exists || len(val) == 0 {
144+
missingFields = append(missingFields, fieldNames[i])
145+
}
146+
result[fieldNames[i]] = string(val)
147+
}
148+
149+
if len(missingFields) != 0 {
150+
return result, obj, fmt.Errorf("the following fields are either missing or their values are empty: %s", strings.Join(missingFields, ", "))
151+
}
152+
153+
return result, obj, nil
154+
}
155+
37156
func createOrDeleteEncryptionAtRests(ctx *workflow.Context, projectID string, project *mdbv1.AtlasProject) workflow.Result {
38157
encryptionAtRestsInAtlas, err := fetchEncryptionAtRests(ctx, projectID)
39158
if err != nil {
@@ -202,7 +321,7 @@ func getAwsKMS(project *mdbv1.AtlasProject) (result mongodbatlas.AwsKms) {
202321
return
203322
}
204323

205-
result = mongodbatlas.AwsKms(project.Spec.EncryptionAtRest.AwsKms)
324+
result = project.Spec.EncryptionAtRest.AwsKms.ToAtlas()
206325

207326
if (result == mongodbatlas.AwsKms{}) {
208327
result.Enabled = toptr.MakePtr(false)
@@ -223,7 +342,7 @@ func getAzureKeyVault(project *mdbv1.AtlasProject) (result mongodbatlas.AzureKey
223342
return
224343
}
225344

226-
result = mongodbatlas.AzureKeyVault(project.Spec.EncryptionAtRest.AzureKeyVault)
345+
result = project.Spec.EncryptionAtRest.AzureKeyVault.ToAtlas()
227346

228347
if (result == mongodbatlas.AzureKeyVault{}) {
229348
result.Enabled = toptr.MakePtr(false)
@@ -237,7 +356,7 @@ func getGoogleCloudKms(project *mdbv1.AtlasProject) (result mongodbatlas.GoogleC
237356
return
238357
}
239358

240-
result = mongodbatlas.GoogleCloudKms(project.Spec.EncryptionAtRest.GoogleCloudKms)
359+
result = project.Spec.EncryptionAtRest.GoogleCloudKms.ToAtlas()
241360

242361
if (result == mongodbatlas.GoogleCloudKms{}) {
243362
result.Enabled = toptr.MakePtr(false)

0 commit comments

Comments
 (0)