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-228588: Migrate Encryption at Rest #1919

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions .mockery.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ packages:
github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/translation/teams:
github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/translation/privateendpoint:
github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/translation/maintenancewindow:
github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/translation/encryptionatrest:
1 change: 0 additions & 1 deletion internal/mocks/translation/atlas_deployments_service.go

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

145 changes: 145 additions & 0 deletions internal/mocks/translation/encryption_at_rest_service.go

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

175 changes: 175 additions & 0 deletions internal/translation/encryptionatrest/conversion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package encryptionatrest

import (
"reflect"

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

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

type EncryptionAtRest struct {
AWS AwsKms
Azure AzureKeyVault
GCP GoogleCloudKms
}

type AwsKms struct {
akov2.AwsKms
AccessKeyID string
SecretAccessKey string
josvazg marked this conversation as resolved.
Show resolved Hide resolved
CustomerMasterKeyID string
RoleID string
CloudProviderIntegrationRole string
}

func (a *AwsKms) SetSecrets(customerMasterKeyID, roleID string) {
a.CustomerMasterKeyID = customerMasterKeyID
a.RoleID = roleID
}

func (a *AwsKms) ToAtlas() *admin.AWSKMSConfiguration {
if a == nil {
return nil
}

result := &admin.AWSKMSConfiguration{
Enabled: a.AwsKms.Enabled,
Region: &a.AwsKms.Region,
Valid: a.AwsKms.Valid,

RoleId: &a.RoleID,
CustomerMasterKeyID: &a.CustomerMasterKeyID,
}

if result.RoleId == nil && a.CloudProviderIntegrationRole != "" {
result.RoleId = &a.CloudProviderIntegrationRole
}

return result
}

type AzureKeyVault struct {
akov2.AzureKeyVault
SubscriptionID string
KeyVaultName string
KeyIdentifier string
Secret string
}

func (az *AzureKeyVault) SetSecrets(subscriptionID, keyVaultName, keyIdentifier, secret string) {
az.SubscriptionID = subscriptionID
az.KeyVaultName = keyVaultName
az.KeyIdentifier = keyIdentifier
az.Secret = secret
}

func (az *AzureKeyVault) ToAtlas() *admin.AzureKeyVault {
if az == nil {
return nil
}

return &admin.AzureKeyVault{
Enabled: az.AzureKeyVault.Enabled,
ClientID: &az.AzureKeyVault.ClientID,
AzureEnvironment: &az.AzureKeyVault.AzureEnvironment,
ResourceGroupName: &az.AzureKeyVault.ResourceGroupName,
TenantID: &az.AzureKeyVault.TenantID,

SubscriptionID: &az.SubscriptionID,
KeyVaultName: &az.KeyVaultName,
KeyIdentifier: &az.KeyIdentifier,
Secret: &az.Secret,
}
}

type GoogleCloudKms struct {
akov2.GoogleCloudKms
ServiceAccountKey string
KeyVersionResourceID string
}

func (g *GoogleCloudKms) SetSecrets(serviceAccountKey, keyVersionResourceID string) {
g.ServiceAccountKey = serviceAccountKey
g.KeyVersionResourceID = keyVersionResourceID
}

func (g *GoogleCloudKms) ToAtlas() *admin.GoogleCloudKMS {
if g == nil {
return nil
}

return &admin.GoogleCloudKMS{
Enabled: g.GoogleCloudKms.Enabled,

ServiceAccountKey: &g.ServiceAccountKey,
KeyVersionResourceID: &g.KeyVersionResourceID,
}
}

func NewEncryptionAtRest(project *akov2.AtlasProject) *EncryptionAtRest {
spec := project.Spec.EncryptionAtRest
if spec == nil {
return nil
}

ear := &EncryptionAtRest{}
if spec.AwsKms.IsEnabled() {
ear.AWS.AwsKms = spec.AwsKms
for _, role := range project.Status.CloudProviderIntegrations {
if role.ProviderName == "AWS" {
ear.AWS.CloudProviderIntegrationRole = role.RoleID
}
}
Comment on lines +119 to +123
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain this bit? not sure why we need to dig into the Kubernetes status of the project for this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not 100% sure - this is just taken from the current implementation, just this happens in the controller. I think we take the IAM Role from Cloud Provider Integrations if one is not explicitly defined in the encryption at rest CRD. I agree it's not ideal, but I'm trying to be cautious about rewriting or changing stuff like this at risk of changing the behaviour.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where did this came from initially? did you git blame it and check for context with the original author? (if that is possible)

If we cannot get context on why is this done in this way, I would at the very least add a comment, as it is another non obvious part of the conversion.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is a corner case. If customers had Encryption At Rest enabled for their projects created before 2022 (or 2023), they can still use it with AWS configurable. For other projects, this won't work, because Atlas API now requires CPA to be configured before enabling encryption at rest for AWS

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we leave a comment explaining why this is needed? I fear we will forget over time.

}
if spec.AzureKeyVault.IsEnabled() {
ear.Azure.AzureKeyVault = spec.AzureKeyVault
}
if spec.GoogleCloudKms.IsEnabled() {
ear.GCP.GoogleCloudKms = spec.GoogleCloudKms
}
return ear
}

func toAtlas(spec *EncryptionAtRest) *admin.EncryptionAtRest {
if spec == nil {
return nil
}

return &admin.EncryptionAtRest{
AwsKms: spec.AWS.ToAtlas(),
AzureKeyVault: spec.Azure.ToAtlas(),
GoogleCloudKms: spec.GCP.ToAtlas(),
josvazg marked this conversation as resolved.
Show resolved Hide resolved
}
}

func fromAtlas(ear *admin.EncryptionAtRest) *EncryptionAtRest {
out := &EncryptionAtRest{}
if ear.HasAwsKms() {
out.AWS.AwsKms = akov2.AwsKms{
Enabled: ear.AwsKms.Enabled,
Region: ear.AwsKms.GetRegion(),
Valid: ear.AwsKms.Valid,
}
}
if ear.HasAzureKeyVault() {
out.Azure.AzureKeyVault = akov2.AzureKeyVault{
Enabled: ear.AzureKeyVault.Enabled,
AzureEnvironment: ear.AzureKeyVault.GetAzureEnvironment(),
ClientID: ear.AzureKeyVault.GetClientID(),
ResourceGroupName: ear.AzureKeyVault.GetResourceGroupName(),
TenantID: ear.AzureKeyVault.GetTenantID(),
}
}
if ear.HasGoogleCloudKms() {
out.GCP.GoogleCloudKms = akov2.GoogleCloudKms{
Enabled: ear.GoogleCloudKms.Enabled,
}
}
return out
}

func EqualSpecs(spec, atlas *EncryptionAtRest) bool {
// Retracted secrets mean that this will never be equal
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Retracted secrets mean that this will never be equal
// Redacted secrets mean that this will never be equal

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is true, why are we even doing the comparison?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

huh, that's a good question, let me debug

return reflect.DeepEqual(atlas, spec)
}
50 changes: 50 additions & 0 deletions internal/translation/encryptionatrest/conversion_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package encryptionatrest

import (
"testing"

"github.com/google/go-cmp/cmp"
fuzz "github.com/google/gofuzz"
"github.com/stretchr/testify/require"

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

func TestRoundtrip_EncryptionAtRest(t *testing.T) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

f := fuzz.New()

for range 100 {
fuzzed := &EncryptionAtRest{}
f.Fuzz(fuzzed)

//ignore secret fields
fuzzed.AWS.AccessKeyID = ""
fuzzed.AWS.SecretAccessKey = ""
fuzzed.AWS.CustomerMasterKeyID = ""
fuzzed.AWS.RoleID = ""
fuzzed.AWS.CloudProviderIntegrationRole = ""
fuzzed.AWS.SecretRef = common.ResourceRefNamespaced{}

fuzzed.Azure.SubscriptionID = ""
fuzzed.Azure.KeyVaultName = ""
fuzzed.Azure.KeyIdentifier = ""
fuzzed.Azure.Secret = ""
fuzzed.Azure.SecretRef = common.ResourceRefNamespaced{}

fuzzed.GCP.ServiceAccountKey = ""
fuzzed.GCP.KeyVersionResourceID = ""
fuzzed.GCP.SecretRef = common.ResourceRefNamespaced{}

//ignore read-only 'Valid' field
fuzzed.AWS.Valid = nil

toAtlasResult := toAtlas(fuzzed)
fromAtlasResult := fromAtlas(toAtlasResult)

equals := EqualSpecs(fuzzed, fromAtlasResult)
if !equals {
t.Log(cmp.Diff(fuzzed, fromAtlasResult))
}
require.True(t, equals)
}
}
Loading
Loading