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

Network Storage Account Lockdown #1865

Merged
merged 15 commits into from
Feb 24, 2022
11 changes: 11 additions & 0 deletions cmd/aro/operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

configclient "github.com/openshift/client-go/config/clientset/versioned"
consoleclient "github.com/openshift/client-go/console/clientset/versioned"
imageregistryclient "github.com/openshift/client-go/imageregistry/clientset/versioned"
securityclient "github.com/openshift/client-go/security/clientset/versioned"
maoclient "github.com/openshift/machine-api-operator/pkg/generated/clientset/versioned"
mcoclient "github.com/openshift/machine-config-operator/pkg/generated/clientset/versioned"
Expand All @@ -36,6 +37,7 @@ import (
"github.com/Azure/ARO-RP/pkg/operator/controllers/pullsecret"
"github.com/Azure/ARO-RP/pkg/operator/controllers/rbac"
"github.com/Azure/ARO-RP/pkg/operator/controllers/routefix"
"github.com/Azure/ARO-RP/pkg/operator/controllers/storageaccounts"
"github.com/Azure/ARO-RP/pkg/operator/controllers/subnets"
"github.com/Azure/ARO-RP/pkg/operator/controllers/workaround"
"github.com/Azure/ARO-RP/pkg/util/dynamichelper"
Expand Down Expand Up @@ -99,6 +101,10 @@ func operator(ctx context.Context, log *logrus.Entry) error {
if err != nil {
return err
}
imageregistrycli, err := imageregistryclient.NewForConfig(restConfig)
if err != nil {
return err
}
// TODO (NE): dh is sometimes passed, sometimes created later. Can we standardize?
dh, err := dynamichelper.New(log, restConfig)
if err != nil {
Expand Down Expand Up @@ -191,6 +197,11 @@ func operator(ctx context.Context, log *logrus.Entry) error {
arocli, configcli)).SetupWithManager(mgr); err != nil {
return fmt.Errorf("unable to create controller ImageConfig: %v", err)
}
if err = (storageaccounts.NewReconciler(
log.WithField("controller", controllers.StorageAccountsControllerName),
arocli, maocli, kubernetescli, imageregistrycli)).SetupWithManager(mgr); err != nil {
return fmt.Errorf("unable to create controller %s: %v", controllers.StorageAccountsControllerName, err)
}
}

if err = (checker.NewReconciler(
Expand Down
8 changes: 8 additions & 0 deletions deploy/rp-development-predeploy.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,14 @@
"id": "[resourceId('Microsoft.Network/networkSecurityGroups', 'rp-pe-nsg')]",
"tags": null
},
"serviceEndpoints": [
{
"service": "Microsoft.Storage",
"locations": [
"*"
]
}
],
"privateEndpointNetworkPolicies": "Disabled"
},
"name": "rp-pe-subnet"
Expand Down
14 changes: 14 additions & 0 deletions deploy/rp-production-predeploy.json
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,12 @@
"locations": [
"*"
]
},
{
"service": "Microsoft.Storage",
"locations": [
"*"
]
}
]
},
Expand Down Expand Up @@ -235,6 +241,14 @@
"id": "[resourceId('Microsoft.Network/networkSecurityGroups', 'rp-pe-nsg')]",
"tags": null
},
"serviceEndpoints": [
{
"service": "Microsoft.Storage",
"locations": [
"*"
]
}
],
"privateEndpointNetworkPolicies": "Disabled"
},
"name": "rp-pe-subnet"
Expand Down
49 changes: 19 additions & 30 deletions pkg/api/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,36 +49,25 @@ const FLAG_FALSE string = "false"

// Default flags for new clusters & ones that have not been AdminUpdated
var DefaultOperatorFlags OperatorFlags = OperatorFlags{
"aro.alertwebhook.enabled": FLAG_TRUE,

"aro.azuresubnets.enabled": FLAG_TRUE,

"aro.banner.enabled": FLAG_FALSE,

"aro.checker.enabled": FLAG_TRUE,

"aro.dnsmasq.enabled": FLAG_TRUE,

"aro.genevalogging.enabled": FLAG_TRUE,

"aro.imageconfig.enabled": FLAG_TRUE,

"aro.machine.enabled": FLAG_TRUE,

"aro.machineset.enabled": FLAG_TRUE,

"aro.monitoring.enabled": FLAG_TRUE,

"aro.nodedrainer.enabled": FLAG_TRUE,

"aro.pullsecret.enabled": FLAG_TRUE,
"aro.pullsecret.managed": FLAG_TRUE,

"aro.rbac.enabled": FLAG_TRUE,

"aro.routefix.enabled": FLAG_TRUE,

"aro.workaround.enabled": FLAG_TRUE,
"aro.alertwebhook.enabled": FLAG_TRUE,
"aro.azuresubnets.enabled": FLAG_TRUE,
25region marked this conversation as resolved.
Show resolved Hide resolved
"aro.azuresubnets.nsg.managed": FLAG_TRUE,
25region marked this conversation as resolved.
Show resolved Hide resolved
"aro.azuresubnets.serviceendpoint.managed": FLAG_TRUE,
"aro.banner.enabled": FLAG_FALSE,
"aro.checker.enabled": FLAG_TRUE,
"aro.dnsmasq.enabled": FLAG_TRUE,
"aro.genevalogging.enabled": FLAG_TRUE,
"aro.imageconfig.enabled": FLAG_TRUE,
"aro.machine.enabled": FLAG_TRUE,
"aro.machineset.enabled": FLAG_TRUE,
"aro.monitoring.enabled": FLAG_TRUE,
"aro.nodedrainer.enabled": FLAG_TRUE,
"aro.pullsecret.enabled": FLAG_TRUE,
"aro.pullsecret.managed": FLAG_TRUE,
"aro.rbac.enabled": FLAG_TRUE,
"aro.routefix.enabled": FLAG_TRUE,
"aro.storageaccounts.enabled": FLAG_TRUE,
"aro.workaround.enabled": FLAG_TRUE,
}

func (d OperatorFlags) Copy() OperatorFlags {
Expand Down
11 changes: 11 additions & 0 deletions pkg/api/subnets.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package api

// Copyright (c) Microsoft Corporation.
// Licensed under the Apache License 2.0.

var (
SubnetsEndpoints = []string{
"Microsoft.ContainerRegistry",
"Microsoft.Storage",
}
)
6 changes: 6 additions & 0 deletions pkg/cluster/adminupdate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ func TestAdminUpdateSteps(t *testing.T) {
"[Action fixInfraID-fm]",
"[AuthorizationRefreshingAction [Action ensureResourceGroup-fm]]",
"[Action createOrUpdateDenyAssignment-fm]",
"[AuthorizationRefreshingAction [Action enableServiceEndpoints-fm]]",
"[Action populateRegistryStorageAccountName-fm]",
"[Action migrateStorageAccounts-fm]",
"[Action fixSSH-fm]",
"[Action populateDatabaseIntIP-fm]",
"[Action startVMs-fm]",
Expand Down Expand Up @@ -102,6 +105,9 @@ func TestAdminUpdateSteps(t *testing.T) {
"[Action fixInfraID-fm]",
"[AuthorizationRefreshingAction [Action ensureResourceGroup-fm]]",
"[Action createOrUpdateDenyAssignment-fm]",
"[AuthorizationRefreshingAction [Action enableServiceEndpoints-fm]]",
"[Action populateRegistryStorageAccountName-fm]",
"[Action migrateStorageAccounts-fm]",
"[Action fixSSH-fm]",
"[Action populateDatabaseIntIP-fm]",
"[Action startVMs-fm]",
Expand Down
20 changes: 10 additions & 10 deletions pkg/cluster/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,16 +80,16 @@ type manager struct {
subnet subnet.Manager
graph graph.Manager

kubernetescli kubernetes.Interface
extensionscli extensionsclient.Interface
maocli maoclient.Interface
mcocli mcoclient.Interface
operatorcli operatorclient.Interface
configcli configclient.Interface
samplescli samplesclient.Interface
securitycli securityclient.Interface
arocli aroclient.Interface
registryclient imageregistryclient.Interface
kubernetescli kubernetes.Interface
extensionscli extensionsclient.Interface
maocli maoclient.Interface
mcocli mcoclient.Interface
operatorcli operatorclient.Interface
configcli configclient.Interface
samplescli samplesclient.Interface
securitycli securityclient.Interface
arocli aroclient.Interface
imageregistrycli imageregistryclient.Interface
}

// New returns a cluster manager
Expand Down
4 changes: 2 additions & 2 deletions pkg/cluster/deploystorage.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,10 @@ func (m *manager) deployStorageTemplate(ctx context.Context, installConfig *inst
clusterStorageAccountName := "cluster" + m.doc.OpenShiftCluster.Properties.StorageSuffix

resources := []*arm.Resource{
m.storageAccount(clusterStorageAccountName, installConfig.Config.Azure.Region),
m.storageAccount(clusterStorageAccountName, installConfig.Config.Azure.Region, true),
m.storageAccountBlobContainer(clusterStorageAccountName, "ignition"),
m.storageAccountBlobContainer(clusterStorageAccountName, "aro"),
m.storageAccount(m.doc.OpenShiftCluster.Properties.ImageRegistryStorageAccountName, installConfig.Config.Azure.Region),
m.storageAccount(m.doc.OpenShiftCluster.Properties.ImageRegistryStorageAccountName, installConfig.Config.Azure.Region, true),
m.storageAccountBlobContainer(m.doc.OpenShiftCluster.Properties.ImageRegistryStorageAccountName, "image-registry"),
m.clusterNSG(infraID, installConfig.Config.Azure.Region),
m.clusterServicePrincipalRBAC(),
Expand Down
96 changes: 85 additions & 11 deletions pkg/cluster/deploystorage_resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,19 +74,93 @@ func (m *manager) clusterServicePrincipalRBAC() *arm.Resource {
)
}

func (m *manager) storageAccount(name, region string) *arm.Resource {
return &arm.Resource{
Resource: &mgmtstorage.Account{
Sku: &mgmtstorage.Sku{
Name: "Standard_LRS",
},
AccountProperties: &mgmtstorage.AccountProperties{
MinimumTLSVersion: mgmtstorage.TLS12,
// storageAccount will return storage account resource.
// Legacy storage accounts (public) are not encrypted and cannot be retrofitted.
// The flag controls this behavior in update/create.
25region marked this conversation as resolved.
Show resolved Hide resolved
func (m *manager) storageAccount(name, region string, encrypted bool) *arm.Resource {

virtualNetworkRules := []mgmtstorage.VirtualNetworkRule{
{
VirtualNetworkResourceID: to.StringPtr(m.doc.OpenShiftCluster.Properties.MasterProfile.SubnetID),
Action: mgmtstorage.Allow,
},
{
VirtualNetworkResourceID: to.StringPtr(m.doc.OpenShiftCluster.Properties.WorkerProfiles[0].SubnetID),
Action: mgmtstorage.Allow,
},
{
VirtualNetworkResourceID: to.StringPtr("/subscriptions/" + m.env.SubscriptionID() + "/resourceGroups/" + m.env.ResourceGroup() + "/providers/Microsoft.Network/virtualNetworks/rp-pe-vnet-001/subnets/rp-pe-subnet"),
Action: mgmtstorage.Allow,
},
{
VirtualNetworkResourceID: to.StringPtr("/subscriptions/" + m.env.SubscriptionID() + "/resourceGroups/" + m.env.ResourceGroup() + "/providers/Microsoft.Network/virtualNetworks/rp-vnet/subnets/rp-subnet"),
Action: mgmtstorage.Allow,
25region marked this conversation as resolved.
Show resolved Hide resolved
},
}

// Prod includes a gateway rule as well
// Once we reach a PLS limit (1000) within a vnet , we may need some refactoring here
// https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/azure-subscription-service-limits#private-link-limits
if !m.env.IsLocalDevelopmentMode() {
virtualNetworkRules = append(virtualNetworkRules, mgmtstorage.VirtualNetworkRule{
VirtualNetworkResourceID: to.StringPtr("/subscriptions/" + m.env.SubscriptionID() + "/resourceGroups/" + m.env.GatewayResourceGroup() + "/providers/Microsoft.Network/virtualNetworks/gateway-vnet/subnets/gateway-subnet"),
Action: mgmtstorage.Allow,
25region marked this conversation as resolved.
Show resolved Hide resolved
})
}

sa := &mgmtstorage.Account{
Kind: mgmtstorage.StorageV2,
Sku: &mgmtstorage.Sku{
Name: "Standard_LRS",
},
AccountProperties: &mgmtstorage.AccountProperties{
AllowBlobPublicAccess: to.BoolPtr(false),
EnableHTTPSTrafficOnly: to.BoolPtr(true),
MinimumTLSVersion: mgmtstorage.TLS12,
NetworkRuleSet: &mgmtstorage.NetworkRuleSet{
Bypass: mgmtstorage.AzureServices,
VirtualNetworkRules: &virtualNetworkRules,
DefaultAction: "Deny",
},
Name: &name,
Location: &region,
Type: to.StringPtr("Microsoft.Storage/storageAccounts"),
},
Name: &name,
Location: &region,
Type: to.StringPtr("Microsoft.Storage/storageAccounts"),
}

// In development API calls originates from user laptop so we allow all.
// TODO: Move to development on VPN so we can make this IPRule. Will be done as part of Simply secure v2 work
if m.env.IsLocalDevelopmentMode() {
sa.NetworkRuleSet.DefaultAction = mgmtstorage.DefaultActionAllow
}
// When migrating storage accounts for old clusters we are not able to change
// encryption which is why we have this encryption flag. We will not add this
// retrospectively to old clusters
// If a storage account already has encryption enabled and the encrypted
// bool is set to false, it will still maintain the encryption on the storage account.
if encrypted {
bennerv marked this conversation as resolved.
Show resolved Hide resolved
25region marked this conversation as resolved.
Show resolved Hide resolved
sa.AccountProperties.Encryption = &mgmtstorage.Encryption{
RequireInfrastructureEncryption: to.BoolPtr(true),
Services: &mgmtstorage.EncryptionServices{
Blob: &mgmtstorage.EncryptionService{
KeyType: mgmtstorage.KeyTypeAccount,
},
File: &mgmtstorage.EncryptionService{
KeyType: mgmtstorage.KeyTypeAccount,
},
Table: &mgmtstorage.EncryptionService{
KeyType: mgmtstorage.KeyTypeAccount,
},
Queue: &mgmtstorage.EncryptionService{
KeyType: mgmtstorage.KeyTypeAccount,
},
},
KeySource: mgmtstorage.KeySourceMicrosoftStorage,
}
}

return &arm.Resource{
Resource: sa,
APIVersion: azureclient.APIVersion("Microsoft.Storage"),
}
}
Expand Down
6 changes: 5 additions & 1 deletion pkg/cluster/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ func (m *manager) adminUpdate() []steps.Step {
toRun = append(toRun,
steps.AuthorizationRefreshingAction(m.fpAuthorizer, steps.Action(m.ensureResourceGroup)), // re-create RP RBAC if needed after tenant migration
steps.Action(m.createOrUpdateDenyAssignment),
steps.AuthorizationRefreshingAction(m.fpAuthorizer, steps.Action(m.enableServiceEndpoints)),
Copy link
Collaborator

Choose a reason for hiding this comment

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

If the Operator does it, do we strictly need to do this in the AdminUpdate?

Copy link
Collaborator

@bennerv bennerv Jan 26, 2022

Choose a reason for hiding this comment

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

This isn't necessarily needed, but it may eliminate some errors upon enabling the storage lockdown on the controller for existing clusters.

Over time, desired state would look good, but if we removed this step during the first controller run and the controller which sets storage account virtual network rules attempts to run first, the storage account will fail to update because storage endpoints must first be set on the subnet.

This will stop an SRE from wondering if this is a normal error with the controller, or if it's a first time reconciliation issue (noise)

steps.Action(m.populateRegistryStorageAccountName), // must go before migrateStorageAccounts
steps.Action(m.migrateStorageAccounts),
steps.Action(m.fixSSH),
steps.Action(m.populateDatabaseIntIP),
//steps.Action(m.removePrivateDNSZone), // TODO(mj): re-enable once we communicate this out
Expand Down Expand Up @@ -142,6 +145,7 @@ func (m *manager) Install(ctx context.Context) error {
return m.ensureInfraID(ctx, installConfig)
}),
steps.AuthorizationRefreshingAction(m.fpAuthorizer, steps.Action(m.ensureResourceGroup)),
steps.AuthorizationRefreshingAction(m.fpAuthorizer, steps.Action(m.enableServiceEndpoints)),
steps.AuthorizationRefreshingAction(m.fpAuthorizer, steps.Action(m.setMasterSubnetPolicies)),
steps.AuthorizationRefreshingAction(m.fpAuthorizer, steps.Action(func(ctx context.Context) error {
return m.deployStorageTemplate(ctx, installConfig)
Expand Down Expand Up @@ -288,7 +292,7 @@ func (m *manager) initializeKubernetesClients(ctx context.Context) error {
return err
}

m.registryclient, err = imageregistryclient.NewForConfig(restConfig)
m.imageregistrycli, err = imageregistryclient.NewForConfig(restConfig)
return err
}

Expand Down
Loading