diff --git a/pkg/cluster/cluster.go b/pkg/cluster/cluster.go index b6ac99cd72e..9187ef1acbc 100644 --- a/pkg/cluster/cluster.go +++ b/pkg/cluster/cluster.go @@ -84,6 +84,7 @@ type manager struct { denyAssignments authorization.DenyAssignmentClient fpPrivateEndpoints network.PrivateEndpointsClient rpPrivateLinkServices network.PrivateLinkServicesClient + rpStorageAccounts storage.Manager dns dns.Manager storage storage.Manager @@ -138,6 +139,8 @@ func New(ctx context.Context, log *logrus.Entry, _env env.Interface, db database return nil, err } + // TODO: use msiAuthorizer instead of localFPAuthorizer, figure additional permissions needed + rpStorageAccounts := storage.NewManager(_env, _env.SubscriptionID(), localFPAuthorizer) storage := storage.NewManager(_env, r.SubscriptionID, fpAuthorizer) installViaHive, err := _env.LiveConfig().InstallViaHive(ctx) @@ -179,6 +182,7 @@ func New(ctx context.Context, log *logrus.Entry, _env env.Interface, db database denyAssignments: authorization.NewDenyAssignmentsClient(_env.Environment(), r.SubscriptionID, fpAuthorizer), fpPrivateEndpoints: network.NewPrivateEndpointsClient(_env.Environment(), _env.SubscriptionID(), localFPAuthorizer), rpPrivateLinkServices: network.NewPrivateLinkServicesClient(_env.Environment(), _env.SubscriptionID(), msiAuthorizer), + rpStorageAccounts: rpStorageAccounts, dns: dns.NewManager(_env, localFPAuthorizer), storage: storage, diff --git a/pkg/cluster/delete.go b/pkg/cluster/delete.go index a944bee8fe5..0abec4c6659 100644 --- a/pkg/cluster/delete.go +++ b/pkg/cluster/delete.go @@ -441,6 +441,12 @@ func (m *manager) Delete(ctx context.Context) error { return err } + m.log.Printf("deleting OIDC configuration") + err = m.rpStorageAccounts.DeleteBlobContainer(ctx, m.env.ResourceGroup(), m.env.OIDCStorageAccountName(), env.OIDCBlobContainerPrefix+m.doc.ID) + if err != nil { + return err + } + m.log.Printf("deleting role assignments") err = m.deleteRoleAssignments(ctx) if err != nil { diff --git a/pkg/cluster/deploybaseresources.go b/pkg/cluster/deploybaseresources.go index 4ffc3389d62..682d8ad2f35 100644 --- a/pkg/cluster/deploybaseresources.go +++ b/pkg/cluster/deploybaseresources.go @@ -14,6 +14,7 @@ import ( mgmtnetwork "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2020-08-01/network" mgmtfeatures "github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2019-07-01/features" + mgmtstorage "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2019-06-01/storage" "github.com/Azure/go-autorest/autorest" "github.com/Azure/go-autorest/autorest/azure" "github.com/Azure/go-autorest/autorest/to" @@ -24,6 +25,7 @@ import ( apisubnet "github.com/Azure/ARO-RP/pkg/api/util/subnet" "github.com/Azure/ARO-RP/pkg/env" "github.com/Azure/ARO-RP/pkg/util/arm" + "github.com/Azure/ARO-RP/pkg/util/oidcbuilder" "github.com/Azure/ARO-RP/pkg/util/stringutils" ) @@ -35,6 +37,35 @@ func (m *manager) createDNS(ctx context.Context) error { return m.dns.Create(ctx, m.doc.OpenShiftCluster) } +func (m *manager) createOIDC(ctx context.Context) error { + blobContainerName := env.OIDCBlobContainerPrefix + m.doc.ID + err := m.rpStorageAccounts.CreateBlobContainer(ctx, m.env.ResourceGroup(), m.env.OIDCStorageAccountName(), blobContainerName) + if err != nil { + return err + } + + blobService, err := m.rpStorageAccounts.BlobService(ctx, m.env.ResourceGroup(), m.env.OIDCStorageAccountName(), mgmtstorage.Permissions("cw"), mgmtstorage.SignedResourceTypesO) + if err != nil { + return err + } + + oidcBuilder := oidcbuilder.NewOIDCBuilder() + oidcBuilder.SetOIDCEndpointUrl(m.env.OIDCEndpoint(), blobContainerName) + + err = oidcBuilder.EnsureOIDCDocs(ctx, blobContainerName, blobService) + if err != nil { + return err + } + + m.doc, err = m.db.PatchWithLease(ctx, m.doc.Key, func(doc *api.OpenShiftClusterDocument) error { + doc.OpenShiftCluster.Properties.ClusterProfile.OIDCIssuer = api.OIDCIssuer(oidcBuilder.GetEndpointUrl()) + doc.OpenShiftCluster.Properties.ClusterProfile.BoundServiceAccountSigningKey = api.SecureString(oidcBuilder.GetPrivateKey()) + return nil + }) + + return err +} + func (m *manager) ensureInfraID(ctx context.Context) (err error) { if m.doc.OpenShiftCluster.Properties.InfraID != "" { return err diff --git a/pkg/cluster/install.go b/pkg/cluster/install.go index 660b3f75ecb..adb91d1b028 100644 --- a/pkg/cluster/install.go +++ b/pkg/cluster/install.go @@ -275,6 +275,8 @@ func (m *manager) bootstrap() []steps.Step { steps.Action(m.populateMTUSize), steps.Action(m.createDNS), + // TODO: Add a condition on ServicePrincipalProfile to run createOIDC after testing + steps.Action(m.createOIDC), steps.Action(m.initializeClusterSPClients), // must run before clusterSPObjectID // TODO: this relies on an authorizer that isn't exposed in the manager diff --git a/pkg/env/env.go b/pkg/env/env.go index 20655786dd3..ecf4c0e3647 100644 --- a/pkg/env/env.go +++ b/pkg/env/env.go @@ -58,6 +58,7 @@ const ( ServiceKeyvaultSuffix = "-svc" RPPrivateEndpointPrefix = "rp-pe-" ProxyHostName = "PROXY_HOSTNAME" + OIDCBlobContainerPrefix = "oic-" ) // Interface is clunky and somewhat legacy and only used in the RP codebase (not diff --git a/pkg/util/azureclient/mgmt/storage/blobcontainers.go b/pkg/util/azureclient/mgmt/storage/blobcontainers.go new file mode 100644 index 00000000000..c53b155ab3f --- /dev/null +++ b/pkg/util/azureclient/mgmt/storage/blobcontainers.go @@ -0,0 +1,31 @@ +package storage + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +import ( + "context" + + mgmtstorage "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2019-06-01/storage" + "github.com/Azure/go-autorest/autorest" + + "github.com/Azure/ARO-RP/pkg/util/azureclient" +) + +type BlobContainersClient interface { + Get(ctx context.Context, resourceGroupName string, accountName string, containerName string) (mgmtstorage.BlobContainer, error) + Create(ctx context.Context, resourceGroupName string, accountName string, containerName string, blobContainer mgmtstorage.BlobContainer) (mgmtstorage.BlobContainer, error) + Delete(ctx context.Context, resourceGroupName string, accountName string, containerName string) (result autorest.Response, err error) +} + +type blobContainersClient struct { + mgmtstorage.BlobContainersClient +} + +func NewBlobContainersClient(environment *azureclient.AROEnvironment, subscriptionID string, authorizer autorest.Authorizer) *blobContainersClient { + client := mgmtstorage.NewBlobContainersClientWithBaseURI(environment.ResourceManagerEndpoint, subscriptionID) + client.Authorizer = authorizer + return &blobContainersClient{ + BlobContainersClient: client, + } +} diff --git a/pkg/util/mocks/storage/storage.go b/pkg/util/mocks/storage/storage.go index 3e27ea47344..f33c8c18f2a 100644 --- a/pkg/util/mocks/storage/storage.go +++ b/pkg/util/mocks/storage/storage.go @@ -50,3 +50,31 @@ func (mr *MockManagerMockRecorder) BlobService(arg0, arg1, arg2, arg3, arg4 inte mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BlobService", reflect.TypeOf((*MockManager)(nil).BlobService), arg0, arg1, arg2, arg3, arg4) } + +// CreateBlobContainer mocks base method. +func (m *MockManager) CreateBlobContainer(arg0 context.Context, arg1, arg2, arg3 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateBlobContainer", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateBlobContainer indicates an expected call of CreateBlobContainer. +func (mr *MockManagerMockRecorder) CreateBlobContainer(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateBlobContainer", reflect.TypeOf((*MockManager)(nil).CreateBlobContainer), arg0, arg1, arg2, arg3) +} + +// DeleteBlobContainer mocks base method. +func (m *MockManager) DeleteBlobContainer(arg0 context.Context, arg1, arg2, arg3 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteBlobContainer", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteBlobContainer indicates an expected call of DeleteBlobContainer. +func (mr *MockManagerMockRecorder) DeleteBlobContainer(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteBlobContainer", reflect.TypeOf((*MockManager)(nil).DeleteBlobContainer), arg0, arg1, arg2, arg3) +} diff --git a/pkg/util/storage/manager.go b/pkg/util/storage/manager.go index 03fddd59cc9..917e4578f53 100644 --- a/pkg/util/storage/manager.go +++ b/pkg/util/storage/manager.go @@ -21,17 +21,21 @@ import ( type Manager interface { BlobService(ctx context.Context, resourceGroup, account string, p mgmtstorage.Permissions, r mgmtstorage.SignedResourceTypes) (*azstorage.BlobStorageClient, error) + CreateBlobContainer(ctx context.Context, resourceGroup string, account string, container string) error + DeleteBlobContainer(ctx context.Context, resourceGroupName string, accountName string, containerName string) error } type manager struct { - env env.Core + env env.Interface storageAccounts storage.AccountsClient + blobContainers storage.BlobContainersClient } -func NewManager(env env.Core, subscriptionID string, authorizer autorest.Authorizer) Manager { +func NewManager(env env.Interface, subscriptionID string, authorizer autorest.Authorizer) Manager { return &manager{ env: env, storageAccounts: storage.NewAccountsClient(env.Environment(), subscriptionID, authorizer), + blobContainers: storage.NewBlobContainersClient(env.Environment(), subscriptionID, authorizer), } } @@ -82,3 +86,41 @@ func (m *manager) BlobService(ctx context.Context, resourceGroup, account string return &blobcli, nil } + +func (m *manager) CreateBlobContainer(ctx context.Context, resourceGroup string, account string, container string) error { + needToCreateBlobContainer := false + _, err := m.blobContainers.Get( + ctx, + resourceGroup, + account, + container) + if err != nil { + if detailedErr, ok := err.(autorest.DetailedError); !ok || detailedErr.StatusCode != http.StatusNotFound { + return err + } + needToCreateBlobContainer = true + } + + if !needToCreateBlobContainer { + return nil + } + + _, err = m.blobContainers.Create( + context.Background(), + resourceGroup, + account, + container, + mgmtstorage.BlobContainer{ + ContainerProperties: &mgmtstorage.ContainerProperties{ + PublicAccess: mgmtstorage.PublicAccessNone, + }, + }, + ) + + return err +} + +func (m *manager) DeleteBlobContainer(ctx context.Context, resourceGroupName string, accountName string, containerName string) error { + _, err := m.blobContainers.Delete(ctx, resourceGroupName, accountName, containerName) + return err +}