diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cb4d73..436c6d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Secure Azure Storage Account by making them private and accessible through an Azure Private Endpoint. This also requires the creation of a private DNS zone and A record. - Update Kyverno PolicyException to v2beta1. ### Removed diff --git a/go.mod b/go.mod index 771458e..27388db 100644 --- a/go.mod +++ b/go.mod @@ -2,11 +2,11 @@ module github.com/giantswarm/object-storage-operator go 1.23.0 -toolchain go1.23.4 - require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6 v6.2.0 + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0 github.com/aquilax/truncate v1.0.1 github.com/aws/aws-sdk-go-v2 v1.32.7 @@ -23,6 +23,7 @@ require ( github.com/onsi/ginkgo/v2 v2.22.2 github.com/onsi/gomega v1.36.2 github.com/pkg/errors v0.9.1 + gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.32.0 k8s.io/apimachinery v0.32.0 k8s.io/client-go v0.32.0 @@ -95,7 +96,6 @@ require ( google.golang.org/protobuf v1.36.2 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiextensions-apiserver v0.32.0 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 // indirect diff --git a/go.sum b/go.sum index 8e96514..ada28e7 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,12 @@ github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0 h1:+m0M/LFxN43KvUL github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0/go.mod h1:PwOyop78lveYMRs6oCxjiVyBdyCgIYH6XHIVZO9/SFQ= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v3 v3.0.0 h1:Kb8eVvjdP6kZqYnER5w/PiGCFp91yVgaxve3d7kCEpY= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v3 v3.0.0/go.mod h1:lYq15QkJyEsNegz5EhI/0SXQ6spvGfgwBH/Qyzkoc/s= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v3 v3.1.0 h1:2qsIIvxVT+uE6yrNldntJKlLRgxGbZ85kgtz5SNBhMw= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v3 v3.1.0/go.mod h1:AW8VEadnhw9xox+VaVd9sP7NjzOAnaZBLRH6Tq3cJ38= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6 v6.2.0 h1:HYGD75g0bQ3VO/Omedm54v4LrD3B1cGImuRF3AJ5wLo= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6 v6.2.0/go.mod h1:ulHyBFJOI0ONiRL4vcJTmS7rx18jQQlEPmAgo80cRdM= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 h1:yzrctSl9GMIQ5lHu7jc8olOsGjWDCsBpJhWqfGa/YIM= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0/go.mod h1:GE4m0rnnfwLGX0Y9A9A25Zx5N/90jneT5ABevqzhuFQ= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0 h1:Dd+RhdJn0OTtVGaeDLZpcumkIVCtA/3/Fo42+eoYvVM= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0/go.mod h1:5kakwfW5CjC9KK+Q4wjXAg+ShuIm2mBMua0ZFj2C8PE= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0 h1:PiSrjRPpkQNjrM8H0WwKMnZUdu1RGMtd/LdGKUrOo+c= diff --git a/helm/object-storage-operator/templates/rbac.yaml b/helm/object-storage-operator/templates/rbac.yaml index da9d69e..f557b19 100644 --- a/helm/object-storage-operator/templates/rbac.yaml +++ b/helm/object-storage-operator/templates/rbac.yaml @@ -5,21 +5,7 @@ metadata: {{- include "labels.common" . | nindent 4 }} name: {{ include "resource.default.name" . }} rules: - - apiGroups: - - infrastructure.cluster.x-k8s.io - {{ if eq .Values.managementCluster.provider.kind "capa" -}} - resources: - - awsclusters - - awsclusterroleidentities - {{ else if eq .Values.managementCluster.provider.kind "capz" -}} - resources: - - azureclusters - - azureclusteridentities - {{- end }} - verbs: - - get - - list - - watch + # RBAC needed for all providers - apiGroups: - objectstorage.giantswarm.io resources: @@ -45,48 +31,40 @@ rules: - events verbs: - create - {{ if eq .Values.managementCluster.provider.kind "capz" -}} + + {{ if eq .Values.managementCluster.provider.kind "capa" -}} - apiGroups: - - infrastructure.cluster.x-k8s.io - resources: - - azureclusteridentities - - azureclusteridentities/status - verbs: - - get - - list - - patch - - update - - watch - - apiGroups: - - aadpodidentity.k8s.io + - infrastructure.cluster.x-k8s.io resources: - - azureidentities - - azureidentities/status + - awsclusters + - awsclusterroleidentities verbs: - - get - - list - - watch + - get + - list + - watch + {{- end}} + + {{ if eq .Values.managementCluster.provider.kind "capz" -}} - apiGroups: - - aadpodidentity.k8s.io + - infrastructure.cluster.x-k8s.io resources: - - azureidentities + - azureclusters verbs: - - create + - get + - list + - watch - apiGroups: - - aadpodidentity.k8s.io + - infrastructure.cluster.x-k8s.io resources: - - azureidentitybindings - - azureidentitybindings/status + - azureclusteridentities + - azureclusteridentities/status verbs: - get - list + - patch + - update - watch - - apiGroups: - - aadpodidentity.k8s.io - resources: - - azureidentitybindings - verbs: - - create + # Needed to store Azure storage account credentials - apiGroups: - "" resources: @@ -99,6 +77,15 @@ rules: - get - list - watch + # Needed for Azure to detect private mode + - apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch {{- end }} --- apiVersion: rbac.authorization.k8s.io/v1 diff --git a/internal/pkg/service/objectstorage/cloud/azure/cluster.go b/internal/pkg/service/objectstorage/cloud/azure/cluster.go index 732309f..217629a 100644 --- a/internal/pkg/service/objectstorage/cloud/azure/cluster.go +++ b/internal/pkg/service/objectstorage/cloud/azure/cluster.go @@ -201,3 +201,11 @@ func (c AzureCluster) GetCredentials() cluster.Credentials { func (c AzureCluster) GetResourceGroup() string { return c.Credentials.ResourceGroup } + +func (c AzureCluster) GetSubscriptionID() string { + return c.Credentials.SubscriptionID +} + +func (c AzureCluster) GetVNetName() string { + return c.GetName() + "-vnet" +} diff --git a/internal/pkg/service/objectstorage/cloud/azure/privateendpoint.go b/internal/pkg/service/objectstorage/cloud/azure/privateendpoint.go new file mode 100644 index 0000000..fb03786 --- /dev/null +++ b/internal/pkg/service/objectstorage/cloud/azure/privateendpoint.go @@ -0,0 +1,158 @@ +package azure + +import ( + "context" + "fmt" + "time" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns" + + "github.com/giantswarm/object-storage-operator/api/v1alpha1" +) + +const ( + // This is how the private zone must be named in Azure for the private endpoint to work. + privateZoneID = "privatelink.blob.core.windows.net" + vnetID = "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/virtualNetworks/%s" + subnetID = vnetID + "/subnets/%s" +) + +func (s AzureObjectStorageAdapter) upsertPrivateEndpointARecords(ctx context.Context, bucket *v1alpha1.Bucket, privateEndpoint *armnetwork.PrivateEndpoint, storageAccountName string) (*armprivatedns.RecordSet, error) { + s.logger.Info("Creating A record for private endpoint", "private-endpoint", *privateEndpoint.Name) + + ips := make([]string, 0) + + for _, dnsConfigs := range privateEndpoint.Properties.CustomDNSConfigs { + if dnsConfigs.IPAddresses == nil { + continue + } + for _, ip := range dnsConfigs.IPAddresses { + ips = append(ips, *ip) + } + } + + aRecords := make([]*armprivatedns.ARecord, len(ips)) + for i, ip := range ips { + aRecords[i] = &armprivatedns.ARecord{IPv4Address: &ip} + } + + resp, err := s.recordSetsClient.CreateOrUpdate( + ctx, + s.cluster.GetResourceGroup(), + privateZoneID, + armprivatedns.RecordTypeA, + storageAccountName, + armprivatedns.RecordSet{ + Properties: &armprivatedns.RecordSetProperties{ + ARecords: aRecords, + TTL: to.Ptr(int64(time.Hour.Seconds())), + Metadata: s.getBucketTags(bucket), + }, + }, + nil, + ) + if err != nil { + return nil, err + } + return &resp.RecordSet, nil +} + +func (s AzureObjectStorageAdapter) upsertPrivateEndpoint(ctx context.Context, bucket *v1alpha1.Bucket, storageAccountName string) (*armnetwork.PrivateEndpoint, error) { + // Create or Update Private endpoint + pollersResp, err := s.privateEndpointsClient.BeginCreateOrUpdate( + ctx, + s.cluster.GetResourceGroup(), + bucket.Spec.Name, + armnetwork.PrivateEndpoint{ + Location: to.Ptr(s.cluster.GetRegion()), + Properties: &armnetwork.PrivateEndpointProperties{ + CustomNetworkInterfaceName: to.Ptr(fmt.Sprintf("%s-nodes-nic", bucket.Spec.Name)), + PrivateLinkServiceConnections: []*armnetwork.PrivateLinkServiceConnection{ + { + Name: to.Ptr(bucket.Spec.Name), + Properties: &armnetwork.PrivateLinkServiceConnectionProperties{ + PrivateLinkServiceID: to.Ptr(fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Storage/storageAccounts/%s", s.cluster.GetSubscriptionID(), s.cluster.GetResourceGroup(), storageAccountName)), + GroupIDs: []*string{to.Ptr("blob")}, + }, + }, + }, + Subnet: &armnetwork.Subnet{ + ID: to.Ptr(s.subnetID()), + }, + }, + Tags: s.getBucketTags(bucket), + }, + nil, + ) + + if err != nil { + return nil, err + } + resp, err := pollersResp.PollUntilDone(ctx, nil) + if err != nil { + return nil, err + } + + return &resp.PrivateEndpoint, nil +} + +func (s AzureObjectStorageAdapter) upsertPrivateZone(ctx context.Context, bucket *v1alpha1.Bucket) (*armprivatedns.PrivateZone, error) { + pollersResp, err := s.privateZonesClient.BeginCreateOrUpdate( + ctx, + s.cluster.GetResourceGroup(), + privateZoneID, + armprivatedns.PrivateZone{ + // Private Zone DNS is a global resource + Location: to.Ptr("Global"), + Tags: s.getBucketTags(bucket), + }, + nil, + ) + if err != nil { + return nil, err + } + resp, err := pollersResp.PollUntilDone(ctx, nil) + if err != nil { + return nil, err + } + return &resp.PrivateZone, nil +} + +func (s AzureObjectStorageAdapter) upsertVirtualNetworkLink(ctx context.Context, bucket *v1alpha1.Bucket) (*armprivatedns.VirtualNetworkLink, error) { + pollersResp, err := s.virtualNetworkLinksClient.BeginCreateOrUpdate( + ctx, + s.cluster.GetResourceGroup(), + privateZoneID, + "giantswarm-observability", + armprivatedns.VirtualNetworkLink{ + // Private Zone DNS is a global resource + Location: to.Ptr("Global"), + Properties: &armprivatedns.VirtualNetworkLinkProperties{ + RegistrationEnabled: to.Ptr(false), + VirtualNetwork: &armprivatedns.SubResource{ + ID: to.Ptr(s.vnetID()), + }, + }, + Tags: s.getBucketTags(bucket), + }, + nil, + ) + if err != nil { + return nil, err + } + resp, err := pollersResp.PollUntilDone(ctx, nil) + if err != nil { + return nil, err + } + return &resp.VirtualNetworkLink, nil +} + +func (s AzureObjectStorageAdapter) vnetID() string { + return fmt.Sprintf(vnetID, s.cluster.GetSubscriptionID(), s.cluster.GetResourceGroup(), s.cluster.GetVNetName()) +} + +func (s AzureObjectStorageAdapter) subnetID() string { + return fmt.Sprintf(subnetID, s.cluster.GetSubscriptionID(), s.cluster.GetResourceGroup(), s.cluster.GetVNetName(), "node-subnet") +} diff --git a/internal/pkg/service/objectstorage/cloud/azure/service.go b/internal/pkg/service/objectstorage/cloud/azure/service.go index ffebcfc..81da540 100644 --- a/internal/pkg/service/objectstorage/cloud/azure/service.go +++ b/internal/pkg/service/objectstorage/cloud/azure/service.go @@ -6,6 +6,8 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage" "github.com/go-logr/logr" "github.com/pkg/errors" @@ -57,9 +59,32 @@ func (s AzureObjectStorageService) NewObjectStorageService(ctx context.Context, return nil, errors.WithStack(err) } + var networkClientFactory *armnetwork.ClientFactory + networkClientFactory, err = armnetwork.NewClientFactory(azureCredentials.SubscriptionID, cred, nil) + if err != nil { + return nil, errors.WithStack(err) + } + + var privateZonesClientFactory *armprivatedns.ClientFactory + privateZonesClientFactory, err = armprivatedns.NewClientFactory(azureCredentials.SubscriptionID, cred, nil) + if err != nil { + return nil, errors.WithStack(err) + } + azurecluster, ok := cluster.(AzureCluster) if !ok { return nil, errors.New("Impossible to cast cluster into Azure cluster") } - return NewAzureStorageService(storageClientFactory.NewAccountsClient(), storageClientFactory.NewBlobContainersClient(), storageClientFactory.NewManagementPoliciesClient(), logger, azurecluster, client), nil + return NewAzureStorageService( + storageClientFactory.NewAccountsClient(), + storageClientFactory.NewBlobContainersClient(), + storageClientFactory.NewManagementPoliciesClient(), + networkClientFactory.NewPrivateEndpointsClient(), + privateZonesClientFactory.NewPrivateZonesClient(), + privateZonesClientFactory.NewRecordSetsClient(), + privateZonesClientFactory.NewVirtualNetworkLinksClient(), + logger, + azurecluster, + client, + ), nil } diff --git a/internal/pkg/service/objectstorage/cloud/azure/storage.go b/internal/pkg/service/objectstorage/cloud/azure/storage.go index e9ddf22..aaafdd9 100644 --- a/internal/pkg/service/objectstorage/cloud/azure/storage.go +++ b/internal/pkg/service/objectstorage/cloud/azure/storage.go @@ -4,9 +4,14 @@ import ( "context" "fmt" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage" "github.com/go-logr/logr" + "github.com/pkg/errors" + "gopkg.in/yaml.v3" v1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" @@ -20,13 +25,16 @@ const ( ) type AzureObjectStorageAdapter struct { - storageAccountClient *armstorage.AccountsClient - blobContainerClient *armstorage.BlobContainersClient - managementPoliciesClient *armstorage.ManagementPoliciesClient - logger logr.Logger - cluster AzureCluster - client client.Client - listStorageAccountName []string + storageAccountClient *armstorage.AccountsClient + blobContainerClient *armstorage.BlobContainersClient + managementPoliciesClient *armstorage.ManagementPoliciesClient + privateEndpointsClient *armnetwork.PrivateEndpointsClient + privateZonesClient *armprivatedns.PrivateZonesClient + recordSetsClient *armprivatedns.RecordSetsClient + virtualNetworkLinksClient *armprivatedns.VirtualNetworkLinksClient + logger logr.Logger + cluster AzureCluster + client client.Client } // NewAzureStorageService creates a new instance of AzureObjectStorageAdapter. @@ -35,16 +43,28 @@ type AzureObjectStorageAdapter struct { // The logger is used for logging purposes. // The cluster represents the Azure cluster. // The client is the Kubernetes client used for interacting with the Kubernetes API. -// The listStorageAccountName is a list of storage account names. -func NewAzureStorageService(storageAccountClient *armstorage.AccountsClient, blobContainerClient *armstorage.BlobContainersClient, managementPoliciesClient *armstorage.ManagementPoliciesClient, logger logr.Logger, cluster AzureCluster, client client.Client) AzureObjectStorageAdapter { +func NewAzureStorageService( + storageAccountClient *armstorage.AccountsClient, + blobContainerClient *armstorage.BlobContainersClient, + managementPoliciesClient *armstorage.ManagementPoliciesClient, + privateEndpointsClient *armnetwork.PrivateEndpointsClient, + privateZonesClient *armprivatedns.PrivateZonesClient, + recordSetsClient *armprivatedns.RecordSetsClient, + virtualNetworkLinksClient *armprivatedns.VirtualNetworkLinksClient, + logger logr.Logger, + cluster AzureCluster, + client client.Client) AzureObjectStorageAdapter { return AzureObjectStorageAdapter{ - storageAccountClient: storageAccountClient, - blobContainerClient: blobContainerClient, - managementPoliciesClient: managementPoliciesClient, - logger: logger, - cluster: cluster, - client: client, - listStorageAccountName: []string{}, + storageAccountClient: storageAccountClient, + blobContainerClient: blobContainerClient, + managementPoliciesClient: managementPoliciesClient, + privateEndpointsClient: privateEndpointsClient, + privateZonesClient: privateZonesClient, + recordSetsClient: recordSetsClient, + virtualNetworkLinksClient: virtualNetworkLinksClient, + logger: logger, + cluster: cluster, + client: client, } } @@ -69,8 +89,43 @@ func (s AzureObjectStorageAdapter) ExistsBucket(ctx context.Context, bucket *v1a return s.existsContainer(ctx, bucket, storageAccountName) } +// isManagementClusterPrivate checks if the management cluster is private by reading the cluster user-values CM +func (s AzureObjectStorageAdapter) isManagementClusterPrivate(ctx context.Context) (bool, error) { + key := types.NamespacedName{ + Name: fmt.Sprintf("%s-user-values", s.cluster.GetName()), + Namespace: "org-giantswarm", + } + + configMap := &v1.ConfigMap{} + if err := s.client.Get(ctx, key, configMap); client.IgnoreNotFound(err) != nil { + return false, err + } else if apierrors.IsNotFound(err) { + return false, nil + } + + networkingConfig := struct { + Global *struct { + Connectivity *struct { + Network *struct { + Mode *string `yaml:"mode"` + } `yaml:"network"` + } `yaml:"connectivity"` + } `yaml:"global"` + }{} + + err := yaml.Unmarshal([]byte(configMap.Data["values"]), &networkingConfig) + if err != nil { + return false, errors.WithStack(err) + } + + return networkingConfig.Global != nil && + networkingConfig.Global.Connectivity != nil && + networkingConfig.Global.Connectivity.Network != nil && + networkingConfig.Global.Connectivity.Network.Mode != nil && + *networkingConfig.Global.Connectivity.Network.Mode == "private", nil +} + // CreateBucket creates the Storage Account if it not exists AND the Storage Container -// CreateBucket creates a bucket in Azure Object Storage. // It checks if the storage account exists, and if not, it creates it. // Then, it creates a storage container within the storage account. // Finally, it retrieves the access key for 'key1' and creates a K8S Secret to store the storage account access key. @@ -79,7 +134,12 @@ func (s AzureObjectStorageAdapter) ExistsBucket(ctx context.Context, bucket *v1a func (s AzureObjectStorageAdapter) CreateBucket(ctx context.Context, bucket *v1alpha1.Bucket) error { storageAccountName := sanitizeStorageAccountName(bucket.Spec.Name) - if err := s.upsertStorageAccount(ctx, bucket, storageAccountName); err != nil { + isPrivateManagementCluster, err := s.isManagementClusterPrivate(ctx) + if err != nil { + return err + } + + if err := s.upsertStorageAccount(ctx, bucket, storageAccountName, isPrivateManagementCluster); err != nil { return err } @@ -87,6 +147,25 @@ func (s AzureObjectStorageAdapter) CreateBucket(ctx context.Context, bucket *v1a return err } + if isPrivateManagementCluster { + if _, err := s.upsertPrivateZone(ctx, bucket); err != nil { + return err + } + + if _, err := s.upsertVirtualNetworkLink(ctx, bucket); err != nil { + return err + } + + privateEndpoint, err := s.upsertPrivateEndpoint(ctx, bucket, storageAccountName) + if err != nil { + return err + } + + if _, err = s.upsertPrivateEndpointARecords(ctx, bucket, privateEndpoint, storageAccountName); err != nil { + return err + } + } + // Create a K8S Secret to store Storage Account Access Key // First, we retrieve Storage Account Access Key on Azure listKeys, err := s.storageAccountClient.ListKeys( @@ -98,44 +177,45 @@ func (s AzureObjectStorageAdapter) CreateBucket(ctx context.Context, bucket *v1a if err != nil { return fmt.Errorf("unable to retrieve access keys from storage account %s", storageAccountName) } - // Then, we retrieve the Access Key for 'key1' - foundKey1 := false - for _, k := range listKeys.Keys { - if *k.KeyName == "key1" { - foundKey1 = true - // Finally, we create the Secret into the bucket namespace - secret := &v1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: bucket.Spec.Name, - Namespace: bucket.Namespace, - Labels: map[string]string{ - "giantswarm.io/managed-by": "object-storage-operator", - }, - Finalizers: []string{ - v1alpha1.AzureSecretFinalizer, - }, - }, - Data: map[string][]byte{ + + secret := v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: bucket.Spec.Name, + Namespace: bucket.Namespace, + Labels: map[string]string{ + "giantswarm.io/managed-by": "object-storage-operator", + }, + Finalizers: []string{ + v1alpha1.AzureSecretFinalizer, + }, + }, + } + + _, err = controllerutil.CreateOrUpdate(ctx, s.client, &secret, func() error { + // Then, we retrieve the Access Key for 'key1' + for _, k := range listKeys.Keys { + if *k.KeyName == "key1" { + // Finally, we create the Secret into the bucket namespace + secret.Data = map[string][]byte{ "accountName": []byte(storageAccountName), "accountKey": []byte(*k.Value), - }, + } + return nil } - err := s.client.Create(ctx, secret) - if err != nil { - return err - } - s.logger.Info(fmt.Sprintf("created secret %s", bucket.Spec.Name)) - break } - } - if !foundKey1 { + return fmt.Errorf("unable to retrieve access keys 'key1' from storage account %s", storageAccountName) + }) + + if err != nil { + return err } + s.logger.Info(fmt.Sprintf("upserted secret %s", bucket.Spec.Name)) return nil } -// UpdateBucket creates or updates the Storage Account AND the Storage Container if it does not exists +// UpdateBucket creates or updates the Storage Account AND the Storage Container func (s AzureObjectStorageAdapter) UpdateBucket(ctx context.Context, bucket *v1alpha1.Bucket) error { return s.CreateBucket(ctx, bucket) } diff --git a/internal/pkg/service/objectstorage/cloud/azure/storageaccount.go b/internal/pkg/service/objectstorage/cloud/azure/storageaccount.go index 69f5e20..f1cdbd0 100644 --- a/internal/pkg/service/objectstorage/cloud/azure/storageaccount.go +++ b/internal/pkg/service/objectstorage/cloud/azure/storageaccount.go @@ -15,13 +15,18 @@ import ( "github.com/giantswarm/object-storage-operator/api/v1alpha1" ) -func (s AzureObjectStorageAdapter) upsertStorageAccount(ctx context.Context, bucket *v1alpha1.Bucket, storageAccountName string) error { +func (s AzureObjectStorageAdapter) upsertStorageAccount(ctx context.Context, bucket *v1alpha1.Bucket, storageAccountName string, isPrivateManagementCluster bool) error { // Check if Storage Account exists on Azure existsStorageAccount, err := s.existsStorageAccount(ctx, storageAccountName) if err != nil { return err } + publicNetworkAccess := armstorage.PublicNetworkAccessEnabled + if isPrivateManagementCluster { + publicNetworkAccess = armstorage.PublicNetworkAccessDisabled + } + // Create or Update Storage Account pollerStorageAccount, err := s.storageAccountClient.BeginCreate( ctx, @@ -47,8 +52,7 @@ func (s AzureObjectStorageAdapter) upsertStorageAccount(ctx context.Context, buc }, EnableHTTPSTrafficOnly: to.Ptr(true), MinimumTLSVersion: to.Ptr(armstorage.MinimumTLSVersionTLS12), - // TODO make sure this is not the case on gaggle or public installations - PublicNetworkAccess: to.Ptr(armstorage.PublicNetworkAccessDisabled), + PublicNetworkAccess: to.Ptr(publicNetworkAccess), }, Tags: s.getBucketTags(bucket), }, nil)