ARO-4373 Enable Web Endpoint for the OIDC Storage Account

This commit is contained in:
Rajdeep Singh Chauhan 2024-06-20 23:49:27 -04:00 коммит произвёл Caden Marchese
Родитель 90fde763c3
Коммит 17805e9120
27 изменённых файлов: 375 добавлений и 489 удалений

Просмотреть файл

@ -45,4 +45,6 @@ feature flags defined in pkg/env/env.go. At the time of writing these include:
* EnableOCMEndpoints: Register the OCM endpoints in the frontend. Otherwise the
endpoints are not available at all.
* EnablePublicOIDCBlobAccess: Allow the Public access to the OIDC blob in case the environment needs a decoupling from an AFD endpoint. Production will always use AFD endpoint so no public access for the production.
* RequireOIDCStorageWebEndpoint: Since Azure Front Door is only present for INT and PROD, there is a need to determine the web endpoint of the OIDC Storage Account after its creation.
Format of web endpoint(It uses Azure DNS Zone endpoint):- **https://[storage-account].z[00-99].web.storage.azure.net** .
Used in development only.

Просмотреть файл

@ -429,7 +429,7 @@ az ad app credential reset \
PARENT_DOMAIN_RESOURCEGROUP='$PARENT_DOMAIN_RESOURCEGROUP'
export DOMAIN_NAME="\$LOCATION.\$PARENT_DOMAIN_NAME"
export AZURE_ENVIRONMENT='AzurePublicCloud'
export STORAGE_ACCOUNT_DOMAIN="${RESOURCEGROUP//-}.blob.core.windows.net"
export OIDC_STORAGE_ACCOUNT_NAME="${RESOURCEGROUP//-}oic"
EOF
```

Просмотреть файл

@ -57,7 +57,9 @@ deploy_oic_dev() {
--template-file pkg/deploy/assets/rp-oic.json \
--parameters \
"rpServicePrincipalId=$(az ad sp list --filter "appId eq '$AZURE_RP_CLIENT_ID'" --query '[].id' -o tsv)" \
"storageAccountDomain=$(echo $STORAGE_ACCOUNT_DOMAIN)" >/dev/null
"oidcStorageAccountName=$(echo $OIDC_STORAGE_ACCOUNT_NAME)" >/dev/null
echo "########## Enabling Static Website for OIDC storage account in RG $RESOURCEGROUP ##########"
az storage blob service-properties update --static-website true --account-name ${OIDC_STORAGE_ACCOUNT_NAME} --auth-mode login >/dev/null
}
deploy_rp_managed_identity() {
@ -98,7 +100,9 @@ deploy_oic_for_dedicated_rp() {
--template-file pkg/deploy/assets/rp-oic.json \
--parameters \
"rpServicePrincipalId=$(az identity show -g $RESOURCEGROUP -n aro-rp-$LOCATION | jq -r '.["principalId"]')" \
"storageAccountDomain=$(yq '.rps[].configuration.storageAccountDomain' dev-config.yaml)"
"oidcStorageAccountName=$(yq '.rps[].configuration.oidcStorageAccountName' dev-config.yaml)" >/dev/null
echo "########## Enabling Static Website for OIDC storage account in RG $RESOURCEGROUP ##########"
az storage blob service-properties update --static-website true --account-name ${yq '.rps[].configuration.oidcStorageAccountName' dev-config.yaml} --auth-mode login >/dev/null
}
deploy_env_dev_override() {

Просмотреть файл

@ -12,6 +12,7 @@ import (
"strings"
"time"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
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"
"github.com/Azure/go-autorest/autorest"
@ -25,6 +26,7 @@ import (
"github.com/Azure/ARO-RP/pkg/util/azureclient"
"github.com/Azure/ARO-RP/pkg/util/azureerrors"
"github.com/Azure/ARO-RP/pkg/util/dns"
"github.com/Azure/ARO-RP/pkg/util/oidcbuilder"
"github.com/Azure/ARO-RP/pkg/util/rbac"
"github.com/Azure/ARO-RP/pkg/util/stringutils"
)
@ -443,7 +445,12 @@ func (m *manager) Delete(ctx context.Context) error {
if m.doc.OpenShiftCluster.Properties.ServicePrincipalProfile == nil && m.doc.OpenShiftCluster.Properties.PlatformWorkloadIdentityProfile != nil {
m.log.Printf("deleting OIDC configuration")
err = m.rpBlob.DeleteBlobContainer(ctx, m.env.ResourceGroup(), m.env.OIDCStorageAccountName(), env.OIDCBlobContainerPrefix+m.doc.ID)
blobContainerURL := oidcbuilder.GenerateBlobContainerURL(m.env)
azBlobClient, err := m.rpBlob.GetAZBlobClient(blobContainerURL, &azblob.ClientOptions{})
if err != nil {
return err
}
err = oidcbuilder.DeleteOidcFolder(ctx, env.OIDCBlobDirectoryPrefix+m.doc.ID, azBlobClient)
if err != nil {
return err
}

Просмотреть файл

@ -12,7 +12,6 @@ import (
"strings"
"time"
azstorage "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
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"
@ -44,19 +43,20 @@ func (m *manager) createOIDC(ctx context.Context) error {
return nil
}
blobContainerName := env.OIDCBlobContainerPrefix + m.doc.ID
publicAccess := azstorage.PublicAccessNone
// Public access on OIDC Container needed for development environments because of no AFD availability
if m.env.FeatureIsSet(env.FeatureEnablePublicOIDCBlobAccess) {
publicAccess = azstorage.PublicAccessBlob
}
err := m.rpBlob.CreateBlobContainer(ctx, m.env.ResourceGroup(), m.env.OIDCStorageAccountName(), blobContainerName, publicAccess)
if err != nil {
return err
// OIDC Storage Web Endpoint need to be determined for Development environments
var oidcEndpoint string
if m.env.FeatureIsSet(env.FeatureRequireOIDCStorageWebEndpoint) {
properties, err := m.rpBlob.GetContainerProperties(ctx, m.env.ResourceGroup(), m.env.OIDCStorageAccountName(), oidcbuilder.WebContainer)
if err != nil {
return err
}
oidcEndpoint = *properties.Properties.PrimaryEndpoints.Web
} else {
// For Production Azure Front Door Endpoint will be the OIDC Endpoint
oidcEndpoint = m.env.OIDCEndpoint()
}
oidcBuilder, err := oidcbuilder.NewOIDCBuilder(m.env.Environment().StorageEndpointSuffix, m.env.OIDCEndpoint(), m.env.OIDCStorageAccountName(), blobContainerName)
oidcBuilder, err := oidcbuilder.NewOIDCBuilder(m.env, oidcEndpoint, env.OIDCBlobDirectoryPrefix+m.doc.ID)
if err != nil {
return err
}
@ -66,7 +66,7 @@ func (m *manager) createOIDC(ctx context.Context) error {
return err
}
err = oidcBuilder.EnsureOIDCDocs(ctx, blobContainerName, azBlobClient)
err = oidcBuilder.EnsureOIDCDocs(ctx, azBlobClient)
if err != nil {
return err
}

Просмотреть файл

@ -1407,10 +1407,20 @@ func TestCreateOIDC(t *testing.T) {
resourceGroupName := "fakeResourceGroup"
oidcStorageAccountName := "eastusoic"
afdEndpoint := "fake.oic.aro.test.net"
storageEndpointForDev := oidcStorageAccountName + ".blob." + azureclient.PublicCloud.StorageEndpointSuffix
storageWebEndpointForDev := oidcStorageAccountName + ".web." + azureclient.PublicCloud.StorageEndpointSuffix
resourceID := "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/resourceGroup/providers/Microsoft.RedHatOpenShift/openShiftClusters/resourceName"
prodOIDCIssuer := fmt.Sprintf("https://%s/%s%s", afdEndpoint, env.OIDCBlobContainerPrefix, clusterID)
devOIDCIssuer := fmt.Sprintf("https://%s/%s%s", storageEndpointForDev, env.OIDCBlobContainerPrefix, clusterID)
blobContainerURL := fmt.Sprintf("https://%s.blob.%s/%s", oidcStorageAccountName, azureclient.PublicCloud.StorageEndpointSuffix, oidcbuilder.WebContainer)
prodOIDCIssuer := fmt.Sprintf("https://%s/%s%s", afdEndpoint, env.OIDCBlobDirectoryPrefix, clusterID)
devOIDCIssuer := fmt.Sprintf("https://%s/%s%s", storageWebEndpointForDev, env.OIDCBlobDirectoryPrefix, clusterID)
containerProperties := azstorage.AccountsClientGetPropertiesResponse{
Account: azstorage.Account{
Properties: &azstorage.AccountProperties{
PrimaryEndpoints: &azstorage.Endpoints{
Web: to.StringPtr(storageWebEndpointForDev),
},
},
},
}
for _, tt := range []struct {
name string
@ -1470,15 +1480,13 @@ func TestCreateOIDC(t *testing.T) {
},
},
mocks: func(blob *mock_azblob.MockManager, menv *mock_env.MockInterface, azblobClient *mock_azblob.MockAZBlobClient) {
menv.EXPECT().OIDCStorageAccountName().AnyTimes().Return(oidcStorageAccountName)
menv.EXPECT().FeatureIsSet(env.FeatureEnablePublicOIDCBlobAccess).Return(false)
menv.EXPECT().ResourceGroup().Return(resourceGroupName)
menv.EXPECT().Environment().Return(&azureclient.PublicCloud)
menv.EXPECT().FeatureIsSet(env.FeatureRequireOIDCStorageWebEndpoint).Return(false)
menv.EXPECT().OIDCEndpoint().Return(afdEndpoint)
blob.EXPECT().CreateBlobContainer(gomock.Any(), resourceGroupName, oidcStorageAccountName, gomock.Any(), azstorage.PublicAccessNone).Return(nil)
azblobClient.EXPECT().UploadBuffer(gomock.Any(), "", oidcbuilder.DiscoveryDocumentKey, gomock.Any()).Return(nil)
azblobClient.EXPECT().UploadBuffer(gomock.Any(), "", oidcbuilder.JWKSKey, gomock.Any()).Return(nil)
blob.EXPECT().GetAZBlobClient(gomock.Any(), &azblob.ClientOptions{}).Return(azblobClient, nil)
menv.EXPECT().OIDCStorageAccountName().Return(oidcStorageAccountName)
menv.EXPECT().Environment().Return(&azureclient.PublicCloud)
blob.EXPECT().GetAZBlobClient(blobContainerURL, &azblob.ClientOptions{}).Return(azblobClient, nil)
azblobClient.EXPECT().UploadBuffer(gomock.Any(), "", oidcbuilder.DocumentKey(env.OIDCBlobDirectoryPrefix+clusterID, oidcbuilder.DiscoveryDocumentKey), gomock.Any()).Return(nil)
azblobClient.EXPECT().UploadBuffer(gomock.Any(), "", oidcbuilder.DocumentKey(env.OIDCBlobDirectoryPrefix+clusterID, oidcbuilder.JWKSKey), gomock.Any()).Return(nil)
},
wantedOIDCIssuer: pointerutils.ToPtr(api.OIDCIssuer(prodOIDCIssuer)),
wantBoundServiceAccountSigningKey: true,
@ -1498,21 +1506,20 @@ func TestCreateOIDC(t *testing.T) {
},
},
mocks: func(blob *mock_azblob.MockManager, menv *mock_env.MockInterface, azblobClient *mock_azblob.MockAZBlobClient) {
menv.EXPECT().OIDCStorageAccountName().AnyTimes().Return(oidcStorageAccountName)
menv.EXPECT().FeatureIsSet(env.FeatureEnablePublicOIDCBlobAccess).Return(true)
menv.EXPECT().FeatureIsSet(env.FeatureRequireOIDCStorageWebEndpoint).Return(true)
menv.EXPECT().ResourceGroup().Return(resourceGroupName)
menv.EXPECT().OIDCStorageAccountName().AnyTimes().Return(oidcStorageAccountName)
blob.EXPECT().GetContainerProperties(gomock.Any(), resourceGroupName, oidcStorageAccountName, oidcbuilder.WebContainer).Return(containerProperties, nil)
menv.EXPECT().Environment().Return(&azureclient.PublicCloud)
menv.EXPECT().OIDCEndpoint().Return(storageEndpointForDev)
blob.EXPECT().CreateBlobContainer(gomock.Any(), resourceGroupName, oidcStorageAccountName, gomock.Any(), azstorage.PublicAccessBlob).Return(nil)
azblobClient.EXPECT().UploadBuffer(gomock.Any(), "", oidcbuilder.DiscoveryDocumentKey, gomock.Any()).Return(nil)
azblobClient.EXPECT().UploadBuffer(gomock.Any(), "", oidcbuilder.JWKSKey, gomock.Any()).Return(nil)
blob.EXPECT().GetAZBlobClient(gomock.Any(), &azblob.ClientOptions{}).Return(azblobClient, nil)
blob.EXPECT().GetAZBlobClient(blobContainerURL, &azblob.ClientOptions{}).Return(azblobClient, nil)
azblobClient.EXPECT().UploadBuffer(gomock.Any(), "", oidcbuilder.DocumentKey(env.OIDCBlobDirectoryPrefix+clusterID, oidcbuilder.DiscoveryDocumentKey), gomock.Any()).Return(nil)
azblobClient.EXPECT().UploadBuffer(gomock.Any(), "", oidcbuilder.DocumentKey(env.OIDCBlobDirectoryPrefix+clusterID, oidcbuilder.JWKSKey), gomock.Any()).Return(nil)
},
wantedOIDCIssuer: pointerutils.ToPtr(api.OIDCIssuer(devOIDCIssuer)),
wantBoundServiceAccountSigningKey: true,
},
{
name: "Fail - Create Blob Container throws error",
name: "Fail - Get Container Properties throws error",
oc: &api.OpenShiftClusterDocument{
Key: strings.ToLower(resourceID),
ID: clusterID,
@ -1526,10 +1533,10 @@ func TestCreateOIDC(t *testing.T) {
},
},
mocks: func(blob *mock_azblob.MockManager, menv *mock_env.MockInterface, azblob *mock_azblob.MockAZBlobClient) {
menv.EXPECT().OIDCStorageAccountName().AnyTimes().Return(oidcStorageAccountName)
menv.EXPECT().FeatureIsSet(env.FeatureEnablePublicOIDCBlobAccess).Return(false)
menv.EXPECT().FeatureIsSet(env.FeatureRequireOIDCStorageWebEndpoint).Return(true)
menv.EXPECT().ResourceGroup().Return(resourceGroupName)
blob.EXPECT().CreateBlobContainer(gomock.Any(), resourceGroupName, oidcStorageAccountName, gomock.Any(), azstorage.PublicAccessNone).Return(errors.New("generic error"))
menv.EXPECT().OIDCStorageAccountName().AnyTimes().Return(oidcStorageAccountName)
blob.EXPECT().GetContainerProperties(gomock.Any(), resourceGroupName, oidcStorageAccountName, oidcbuilder.WebContainer).Return(containerProperties, errors.New("generic error"))
},
wantBoundServiceAccountSigningKey: false,
wantErr: "generic error",
@ -1549,13 +1556,11 @@ func TestCreateOIDC(t *testing.T) {
},
},
mocks: func(blob *mock_azblob.MockManager, menv *mock_env.MockInterface, azblobClient *mock_azblob.MockAZBlobClient) {
menv.EXPECT().OIDCStorageAccountName().AnyTimes().Return(oidcStorageAccountName)
menv.EXPECT().FeatureIsSet(env.FeatureEnablePublicOIDCBlobAccess).Return(false)
menv.EXPECT().ResourceGroup().Return(resourceGroupName)
menv.EXPECT().Environment().Return(&azureclient.PublicCloud)
menv.EXPECT().FeatureIsSet(env.FeatureRequireOIDCStorageWebEndpoint).Return(false)
menv.EXPECT().OIDCEndpoint().Return(afdEndpoint)
blob.EXPECT().CreateBlobContainer(gomock.Any(), resourceGroupName, oidcStorageAccountName, gomock.Any(), azstorage.PublicAccessNone).Return(nil)
blob.EXPECT().GetAZBlobClient(gomock.Any(), &azblob.ClientOptions{}).Return(azblobClient, errors.New("generic error"))
menv.EXPECT().OIDCStorageAccountName().Return(oidcStorageAccountName)
menv.EXPECT().Environment().Return(&azureclient.PublicCloud)
blob.EXPECT().GetAZBlobClient(blobContainerURL, &azblob.ClientOptions{}).Return(azblobClient, errors.New("generic error"))
},
wantBoundServiceAccountSigningKey: false,
wantErr: "generic error",
@ -1575,15 +1580,12 @@ func TestCreateOIDC(t *testing.T) {
},
},
mocks: func(blob *mock_azblob.MockManager, menv *mock_env.MockInterface, azblobClient *mock_azblob.MockAZBlobClient) {
menv.EXPECT().OIDCStorageAccountName().AnyTimes().Return(oidcStorageAccountName)
menv.EXPECT().FeatureIsSet(env.FeatureEnablePublicOIDCBlobAccess).Return(false)
menv.EXPECT().ResourceGroup().Return(resourceGroupName)
menv.EXPECT().Environment().Return(&azureclient.PublicCloud)
menv.EXPECT().FeatureIsSet(env.FeatureRequireOIDCStorageWebEndpoint).Return(false)
menv.EXPECT().OIDCEndpoint().Return(afdEndpoint)
blob.EXPECT().CreateBlobContainer(gomock.Any(), resourceGroupName, oidcStorageAccountName, gomock.Any(), azstorage.PublicAccessNone).Return(nil)
azblobClient.EXPECT().UploadBuffer(gomock.Any(), "", oidcbuilder.DiscoveryDocumentKey, gomock.Any()).Return(nil)
azblobClient.EXPECT().UploadBuffer(gomock.Any(), "", oidcbuilder.JWKSKey, gomock.Any()).Return(errors.New("generic error"))
blob.EXPECT().GetAZBlobClient(gomock.Any(), &azblob.ClientOptions{}).Return(azblobClient, nil)
menv.EXPECT().OIDCStorageAccountName().Return(oidcStorageAccountName)
menv.EXPECT().Environment().Return(&azureclient.PublicCloud)
blob.EXPECT().GetAZBlobClient(blobContainerURL, &azblob.ClientOptions{}).Return(azblobClient, nil)
azblobClient.EXPECT().UploadBuffer(gomock.Any(), "", oidcbuilder.DocumentKey(env.OIDCBlobDirectoryPrefix+clusterID, oidcbuilder.DiscoveryDocumentKey), gomock.Any()).Return(errors.New("generic error"))
},
wantBoundServiceAccountSigningKey: false,
wantErr: "generic error",

Просмотреть файл

@ -2,10 +2,10 @@
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"rpServicePrincipalId": {
"oidcStorageAccountName": {
"type": "string"
},
"storageAccountDomain": {
"rpServicePrincipalId": {
"type": "string"
}
},
@ -26,22 +26,22 @@
"Az.Sec.AnonymousBlobAccessEnforcement::Skip": "PublicRelease"
},
"location": "[resourceGroup().location]",
"name": "[concat(take(substring(parameters('storageAccountDomain'), 0, indexOf(parameters('storageAccountDomain'), '.')), 21), 'oic')]",
"name": "[parameters('oidcStorageAccountName')]",
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2021-09-01"
},
{
"name": "[concat(concat(take(substring(parameters('storageAccountDomain'), 0, indexOf(parameters('storageAccountDomain'), '.')), 21), 'oic'), '/Microsoft.Authorization/', guid(resourceId('Microsoft.Storage/storageAccounts', concat(take(substring(parameters('storageAccountDomain'), 0, indexOf(parameters('storageAccountDomain'), '.')), 21), 'oic'))))]",
"name": "[concat(parameters('oidcStorageAccountName'), '/Microsoft.Authorization/', guid(resourceId('Microsoft.Storage/storageAccounts', parameters('oidcStorageAccountName'))))]",
"type": "Microsoft.Storage/storageAccounts/providers/roleAssignments",
"properties": {
"scope": "[resourceId('Microsoft.Storage/storageAccounts', concat(take(substring(parameters('storageAccountDomain'), 0, indexOf(parameters('storageAccountDomain'), '.')), 21), 'oic'))]",
"scope": "[resourceId('Microsoft.Storage/storageAccounts', parameters('oidcStorageAccountName'))]",
"roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')]",
"principalId": "[parameters('rpServicePrincipalId')]",
"principalType": "ServicePrincipal"
},
"apiVersion": "2018-09-01-preview",
"dependsOn": [
"[resourceId('Microsoft.Storage/storageAccounts', concat(take(substring(parameters('storageAccountDomain'), 0, indexOf(parameters('storageAccountDomain'), '.')), 21), 'oic'))]"
"[resourceId('Microsoft.Storage/storageAccounts', parameters('oidcStorageAccountName'))]"
]
}
]

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -97,6 +97,7 @@ type Configuration struct {
SubscriptionResourceGroupLocation *string `json:"subscriptionResourceGroupLocation,omitempty" value:"required"`
VMSize *string `json:"vmSize,omitempty" value:"required"`
VMSSCleanupEnabled *bool `json:"vmssCleanupEnabled,omitempty"`
OIDCStorageAccountName *string `json:"oidcStorageAccountName,omitempty" value:"required"`
// TODO: Replace with Live Service Configuration in KeyVault
InstallViaHive *string `json:"clustersInstallViaHive,omitempty"`

Просмотреть файл

@ -87,6 +87,12 @@ func DevConfig(_env env.Core) (*Config, error) {
keyvaultPrefix = keyvaultPrefix[:20]
}
oidcStorageAccountName := os.Getenv("USER") + _env.Location()
if len(oidcStorageAccountName) >= 21 {
oidcStorageAccountName = oidcStorageAccountName[:21]
}
oidcStorageAccountName = oidcStorageAccountName + "oic"
return &Config{
RPs: []RPConfig{
{
@ -101,6 +107,7 @@ func DevConfig(_env env.Core) (*Config, error) {
KeyvaultDNSSuffix: &_env.Environment().KeyVaultDNSSuffix,
KeyvaultPrefix: &keyvaultPrefix,
StorageAccountDomain: to.StringPtr(os.Getenv("USER") + "aro" + _env.Location() + ".blob." + _env.Environment().StorageEndpointSuffix),
OIDCStorageAccountName: to.StringPtr(oidcStorageAccountName),
},
},
},
@ -175,7 +182,7 @@ func DevConfig(_env env.Core) (*Config, error) {
"RequireD2sV3Workers",
"DisableReadinessDelay",
"EnableOCMEndpoints",
"EnablePublicOIDCBlobAccess",
"RequireOIDCStorageWebEndpoint",
},
// TODO update this to support FF
RPImagePrefix: to.StringPtr(os.Getenv("USER") + "aro.azurecr.io/aro"),

Просмотреть файл

@ -17,7 +17,7 @@ import (
var (
// Storage accounts must not contain dashes or be more than 24 characters
// Append "oic" to the pre-existing storage account prefix.
storageAccountName string = "concat(take(substring(parameters('storageAccountDomain'), 0, indexOf(parameters('storageAccountDomain'), '.')), 21), 'oic')"
storageAccountName string = "parameters('oidcStorageAccountName')"
resourceTypeStorageAccount string = "Microsoft.Storage/storageAccounts"
)

Просмотреть файл

@ -394,7 +394,7 @@ func (g *generator) rpVMSS() *arm.Resource {
"rpMdsdConfigVersion",
"rpMdsdNamespace",
"rpParentDomainName",
"storageAccountDomain",
"oidcStorageAccountName",
// TODO: Replace with Live Service Configuration in KeyVault
"clustersInstallViaHive",

Просмотреть файл

@ -290,8 +290,8 @@ ARO_INSTALL_VIA_HIVE='$CLUSTERSINSTALLVIAHIVE'
ARO_HIVE_DEFAULT_INSTALLER_PULLSPEC='$CLUSTERDEFAULTINSTALLERPULLSPEC'
ARO_ADOPT_BY_HIVE='$CLUSTERSADOPTBYHIVE'
USE_CHECKACCESS='$USECHECKACCESS'
STORAGE_ACCOUNT_DOMAIN='$STORAGEACCOUNTDOMAIN'
OIDC_AFD_ENDPOINT='$LOCATION.oic.$RPPARENTDOMAINNAME'
OIDC_STORAGE_ACCOUNT_NAME='$OIDCSTORAGEACCOUNTNAME'
EOF
cat >/etc/systemd/system/aro-rp.service <<'EOF'
@ -330,8 +330,8 @@ ExecStart=/usr/bin/docker run \
-e ARO_HIVE_DEFAULT_INSTALLER_PULLSPEC \
-e ARO_ADOPT_BY_HIVE \
-e USE_CHECKACCESS \
-e STORAGE_ACCOUNT_DOMAIN \
-e OIDC_AFD_ENDPOINT \
-e OIDC_STORAGE_ACCOUNT_NAME \
-m 2g \
-p 443:8443 \
-v /etc/aro-rp:/etc/aro-rp \

Просмотреть файл

@ -18,7 +18,7 @@ func (g *generator) oicTemplate() *arm.Template {
"rpServicePrincipalId": {
Type: "string",
},
"storageAccountDomain": {
"oidcStorageAccountName": {
Type: "string",
},
}

6
pkg/env/dev.go поставляемый
Просмотреть файл

@ -36,7 +36,7 @@ func newDev(ctx context.Context, log *logrus.Entry, component ServiceComponent)
FeatureDisableSignedCertificates,
FeatureRequireD2sV3Workers,
FeatureDisableReadinessDelay,
FeatureEnablePublicOIDCBlobAccess,
FeatureRequireOIDCStorageWebEndpoint,
} {
d.features[feature] = true
}
@ -98,7 +98,3 @@ func (d *dev) FPNewClientCertificateCredential(tenantID string) (*azidentity.Cli
return credential, nil
}
func (d *dev) OIDCEndpoint() string {
return fmt.Sprintf("%s.blob.%s", d.OIDCStorageAccountName(), d.Environment().StorageEndpointSuffix)
}

4
pkg/env/env.go поставляемый
Просмотреть файл

@ -35,7 +35,7 @@ const (
FeatureRequireD2sV3Workers
FeatureDisableReadinessDelay
FeatureEnableOCMEndpoints
FeatureEnablePublicOIDCBlobAccess
FeatureRequireOIDCStorageWebEndpoint
)
const (
@ -57,7 +57,7 @@ const (
ServiceKeyvaultSuffix = "-svc"
RPPrivateEndpointPrefix = "rp-pe-"
ProxyHostName = "PROXY_HOSTNAME"
OIDCBlobContainerPrefix = "oic-"
OIDCBlobDirectoryPrefix = "oic-"
)
// Interface is clunky and somewhat legacy and only used in the RP codebase (not

19
pkg/env/prod.go поставляемый
Просмотреть файл

@ -31,9 +31,9 @@ import (
)
const (
KeyvaultPrefix = "KEYVAULT_PREFIX"
StorageAccountDomain = "STORAGE_ACCOUNT_DOMAIN"
OIDCAFDEndpoint = "OIDC_AFD_ENDPOINT"
KeyvaultPrefix = "KEYVAULT_PREFIX"
OIDCAFDEndpoint = "OIDC_AFD_ENDPOINT"
OIDCStorageAccountName = "OIDC_STORAGE_ACCOUNT_NAME"
)
type prod struct {
@ -205,11 +205,11 @@ func newProd(ctx context.Context, log *logrus.Entry, component ServiceComponent)
p.gatewayDomains = append(p.gatewayDomains, p.acrDomain, acrDataDomain)
}
if err := ValidateVars(StorageAccountDomain); err != nil {
if err := ValidateVars(OIDCStorageAccountName); err != nil {
return nil, err
}
if !p.IsLocalDevelopmentMode() {
if !p.FeatureIsSet(FeatureRequireOIDCStorageWebEndpoint) {
if err := ValidateVars(OIDCAFDEndpoint); err != nil {
return nil, err
}
@ -274,16 +274,11 @@ func (p *prod) ACRDomain() string {
}
func (p *prod) OIDCStorageAccountName() string {
storageAccountDomain := os.Getenv("STORAGE_ACCOUNT_DOMAIN")
idx := strings.Index(storageAccountDomain, ".")
if idx >= 21 {
return storageAccountDomain[:21] + "oic"
}
return storageAccountDomain[:idx] + "oic"
return os.Getenv(OIDCStorageAccountName)
}
func (p *prod) OIDCEndpoint() string {
return os.Getenv("OIDC_AFD_ENDPOINT")
return fmt.Sprintf("https://%s/", os.Getenv("OIDC_AFD_ENDPOINT"))
}
func (p *prod) AROOperatorImage() string {

6
pkg/env/zz_generated_feature_enumer.go поставляемый
Просмотреть файл

@ -6,9 +6,9 @@ import (
"fmt"
)
const _FeatureName = "FeatureDisableDenyAssignmentsFeatureDisableSignedCertificatesFeatureEnableDevelopmentAuthorizerFeatureRequireD2sV3WorkersFeatureDisableReadinessDelayFeatureEnableOCMEndpointsFeatureEnablePublicOIDCBlobAccess"
const _FeatureName = "FeatureDisableDenyAssignmentsFeatureDisableSignedCertificatesFeatureEnableDevelopmentAuthorizerFeatureRequireD2sV3WorkersFeatureDisableReadinessDelayFeatureEnableOCMEndpointsFeatureRequireOIDCStorageWebEndpoint"
var _FeatureIndex = [...]uint8{0, 29, 61, 95, 121, 149, 174, 207}
var _FeatureIndex = [...]uint8{0, 29, 61, 95, 121, 149, 174, 210}
func (i Feature) String() string {
if i < 0 || i >= Feature(len(_FeatureIndex)-1) {
@ -26,7 +26,7 @@ var _FeatureNameToValueMap = map[string]Feature{
_FeatureName[95:121]: 3,
_FeatureName[121:149]: 4,
_FeatureName[149:174]: 5,
_FeatureName[174:207]: 6,
_FeatureName[174:210]: 6,
}
// FeatureString retrieves an enum value from the enum constants string name.

Просмотреть файл

@ -7,89 +7,36 @@ import (
"context"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
azstorage "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/bloberror"
"github.com/Azure/ARO-RP/pkg/util/azureclient"
"github.com/Azure/ARO-RP/pkg/util/azureclient/azuresdk/armstorage"
)
type Manager interface {
CreateBlobContainer(ctx context.Context, resourceGroup string, account string, container string, publicAccess azstorage.PublicAccess) error
DeleteBlobContainer(ctx context.Context, resourceGroupName string, accountName string, containerName string) error
GetContainerProperties(ctx context.Context, resourceGroupName string, accountName string, containerName string) (azstorage.AccountsClientGetPropertiesResponse, error)
GetAZBlobClient(blobContainerURL string, options *azblob.ClientOptions) (AZBlobClient, error)
}
type manager struct {
cred azcore.TokenCredential
blobContainer armstorage.BlobContainersClient
cred azcore.TokenCredential
account armstorage.AccountsClient
}
func NewManager(environment *azureclient.AROEnvironment, subscriptionID string, credential azcore.TokenCredential) (Manager, error) {
client, err := armstorage.NewBlobContainersClient(environment, subscriptionID, credential)
accountsClient, err := armstorage.NewAccountsClient(environment, subscriptionID, credential)
if err != nil {
return nil, err
}
return &manager{
cred: credential,
blobContainer: client,
cred: credential,
account: accountsClient,
}, nil
}
func (m *manager) CreateBlobContainer(ctx context.Context, resourceGroup string, accountName string, containerName string, publicAccess azstorage.PublicAccess) error {
_, err := m.blobContainer.Get(
ctx,
resourceGroup,
accountName,
containerName,
&azstorage.BlobContainersClientGetOptions{},
)
if err != nil && !bloberror.HasCode(err, bloberror.ContainerNotFound) {
return err
} else if err == nil {
return nil
}
_, err = m.blobContainer.Create(
ctx,
resourceGroup,
accountName,
containerName,
azstorage.BlobContainer{
ContainerProperties: &azstorage.ContainerProperties{
PublicAccess: to.Ptr(publicAccess),
},
},
&azstorage.BlobContainersClientCreateOptions{},
)
return err
}
func (m *manager) DeleteBlobContainer(ctx context.Context, resourceGroupName string, accountName string, containerName string) error {
_, err := m.blobContainer.Get(
ctx,
resourceGroupName,
accountName,
containerName,
&azstorage.BlobContainersClientGetOptions{},
)
if err != nil {
if bloberror.HasCode(err, bloberror.ContainerNotFound) {
return nil
}
}
_, err = m.blobContainer.Delete(
ctx,
resourceGroupName,
accountName,
containerName,
&azstorage.BlobContainersClientDeleteOptions{},
)
return err
func (m *manager) GetContainerProperties(ctx context.Context, resourceGroupName string, accountName string, containerName string) (azstorage.AccountsClientGetPropertiesResponse, error) {
return m.account.GetProperties(ctx, resourceGroupName, accountName, &azstorage.AccountsClientGetPropertiesOptions{})
}
func (m *manager) GetAZBlobClient(blobContainerURL string, options *azblob.ClientOptions) (AZBlobClient, error) {
@ -98,6 +45,7 @@ func (m *manager) GetAZBlobClient(blobContainerURL string, options *azblob.Clien
type AZBlobClient interface {
UploadBuffer(ctx context.Context, containerName string, blobName string, buffer []byte) error
DeleteBlob(ctx context.Context, containerName string, directoryName string) error
}
type azBlobClient struct {
@ -116,3 +64,8 @@ func (azBlobClient *azBlobClient) UploadBuffer(ctx context.Context, containerNam
_, err := azBlobClient.client.UploadBuffer(ctx, containerName, blobName, buffer, &azblob.UploadBufferOptions{})
return err
}
func (azBlobClient *azBlobClient) DeleteBlob(ctx context.Context, containerName string, directoryName string) error {
_, err := azBlobClient.client.DeleteBlob(ctx, containerName, directoryName, &azblob.DeleteBlobOptions{})
return err
}

Просмотреть файл

@ -1,205 +0,0 @@
package azblob
// Copyright (c) Microsoft Corporation.
// Licensed under the Apache License 2.0.
import (
"bytes"
"context"
"io"
"net/http"
"net/url"
"testing"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
azstorage "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/bloberror"
"github.com/golang/mock/gomock"
mock_armstorage "github.com/Azure/ARO-RP/pkg/util/mocks/azureclient/azuresdk/armstorage"
utilerror "github.com/Azure/ARO-RP/test/util/error"
)
type fakeTokenCredential struct{}
func (c fakeTokenCredential) GetToken(ctx context.Context, options policy.TokenRequestOptions) (azcore.AccessToken, error) {
return azcore.AccessToken{}, nil
}
type fakeReadCloser struct {
io.Reader
}
func (fakeReadCloser) Close() error { return nil }
func TestCreateBlobContainer(t *testing.T) {
ctx := context.Background()
resourceGroupName := "fakeResourceGroup"
containerName := "fakeContainer"
container := azstorage.BlobContainer{
ContainerProperties: &azstorage.ContainerProperties{
PublicAccess: to.Ptr(azstorage.PublicAccessNone),
},
}
respErrContainerNotFound := &azcore.ResponseError{
ErrorCode: string(bloberror.ContainerNotFound),
}
respErrGeneric := &azcore.ResponseError{
ErrorCode: string("Generic Error"),
RawResponse: &http.Response{
Request: &http.Request{
Method: "FAKE",
URL: &url.URL{},
},
Body: fakeReadCloser{bytes.NewBufferString("Generic Error")},
},
StatusCode: 400,
}
genericErrorMessage := `FAKE ://
--------------------------------------------------------------------------------
RESPONSE 0:
ERROR CODE: Generic Error
--------------------------------------------------------------------------------
Generic Error
--------------------------------------------------------------------------------
`
for _, tt := range []struct {
name string
mocks func(*mock_armstorage.MockBlobContainersClient)
wantErr string
}{
{
name: "Success - Create the container",
mocks: func(blobContainer *mock_armstorage.MockBlobContainersClient) {
blobContainer.EXPECT().Get(ctx, resourceGroupName, "", containerName, &azstorage.BlobContainersClientGetOptions{}).Return(azstorage.BlobContainersClientGetResponse{}, respErrContainerNotFound)
blobContainer.EXPECT().Create(ctx, resourceGroupName, "", containerName, container, &azstorage.BlobContainersClientCreateOptions{}).Return(azstorage.BlobContainersClientCreateResponse{}, nil)
},
},
{
name: "Success - Container already exists, so not creating",
mocks: func(blobContainer *mock_armstorage.MockBlobContainersClient) {
blobContainer.EXPECT().Get(ctx, resourceGroupName, "", containerName, &azstorage.BlobContainersClientGetOptions{}).Return(azstorage.BlobContainersClientGetResponse{}, nil)
},
},
{
name: "Fail - Get Container fails with generic error",
mocks: func(blobContainer *mock_armstorage.MockBlobContainersClient) {
blobContainer.EXPECT().Get(ctx, resourceGroupName, "", containerName, &azstorage.BlobContainersClientGetOptions{}).Return(azstorage.BlobContainersClientGetResponse{}, respErrGeneric)
},
wantErr: genericErrorMessage,
},
{
name: "Fail - Create Container fails with generic error",
mocks: func(blobContainer *mock_armstorage.MockBlobContainersClient) {
blobContainer.EXPECT().Get(ctx, resourceGroupName, "", containerName, &azstorage.BlobContainersClientGetOptions{}).Return(azstorage.BlobContainersClientGetResponse{}, respErrContainerNotFound)
blobContainer.EXPECT().Create(ctx, resourceGroupName, "", containerName, container, &azstorage.BlobContainersClientCreateOptions{}).Return(azstorage.BlobContainersClientCreateResponse{}, respErrGeneric)
},
wantErr: genericErrorMessage,
},
} {
t.Run(tt.name, func(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
blobContainer := mock_armstorage.NewMockBlobContainersClient(controller)
if tt.mocks != nil {
tt.mocks(blobContainer)
}
m := &manager{
cred: fakeTokenCredential{},
blobContainer: blobContainer,
}
err := m.CreateBlobContainer(ctx, resourceGroupName, "", containerName, azstorage.PublicAccessNone)
utilerror.AssertErrorMessage(t, err, tt.wantErr)
})
}
}
func TestDeleteBlobContainer(t *testing.T) {
ctx := context.Background()
resourceGroupName := "fakeResourceGroup"
containerName := "fakeContainer"
respErrContainerNotFound := &azcore.ResponseError{
ErrorCode: string(bloberror.ContainerNotFound),
}
respErrGeneric := &azcore.ResponseError{
ErrorCode: string("Generic Error"),
RawResponse: &http.Response{
Request: &http.Request{
Method: "FAKE",
URL: &url.URL{},
},
Body: fakeReadCloser{bytes.NewBufferString("Generic Error")},
},
StatusCode: 400,
}
genericErrorMessage := `FAKE ://
--------------------------------------------------------------------------------
RESPONSE 0:
ERROR CODE: Generic Error
--------------------------------------------------------------------------------
Generic Error
--------------------------------------------------------------------------------
`
for _, tt := range []struct {
name string
mocks func(*mock_armstorage.MockBlobContainersClient)
wantErr string
}{
{
name: "Success - Delete the container",
mocks: func(blobContainer *mock_armstorage.MockBlobContainersClient) {
blobContainer.EXPECT().Get(ctx, resourceGroupName, "", containerName, &azstorage.BlobContainersClientGetOptions{}).Return(azstorage.BlobContainersClientGetResponse{}, nil)
blobContainer.EXPECT().Delete(ctx, resourceGroupName, "", containerName, &azstorage.BlobContainersClientDeleteOptions{}).Return(azstorage.BlobContainersClientDeleteResponse{}, nil)
},
},
{
name: "Success - Container does not exist, so not deleting",
mocks: func(blobContainer *mock_armstorage.MockBlobContainersClient) {
blobContainer.EXPECT().Get(ctx, resourceGroupName, "", containerName, &azstorage.BlobContainersClientGetOptions{}).Return(azstorage.BlobContainersClientGetResponse{}, respErrContainerNotFound)
},
},
{
name: "Success - Get Container fails with generic error, still attempt container deletion",
mocks: func(blobContainer *mock_armstorage.MockBlobContainersClient) {
blobContainer.EXPECT().Get(ctx, resourceGroupName, "", containerName, &azstorage.BlobContainersClientGetOptions{}).Return(azstorage.BlobContainersClientGetResponse{}, respErrGeneric)
blobContainer.EXPECT().Delete(ctx, resourceGroupName, "", containerName, &azstorage.BlobContainersClientDeleteOptions{}).Return(azstorage.BlobContainersClientDeleteResponse{}, nil)
},
},
{
name: "Fail - Delete Container fails with generic error",
mocks: func(blobContainer *mock_armstorage.MockBlobContainersClient) {
blobContainer.EXPECT().Get(ctx, resourceGroupName, "", containerName, &azstorage.BlobContainersClientGetOptions{}).Return(azstorage.BlobContainersClientGetResponse{}, nil)
blobContainer.EXPECT().Delete(ctx, resourceGroupName, "", containerName, &azstorage.BlobContainersClientDeleteOptions{}).Return(azstorage.BlobContainersClientDeleteResponse{}, respErrGeneric)
},
wantErr: genericErrorMessage,
},
} {
t.Run(tt.name, func(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
blobContainer := mock_armstorage.NewMockBlobContainersClient(controller)
if tt.mocks != nil {
tt.mocks(blobContainer)
}
m := &manager{
cred: fakeTokenCredential{},
blobContainer: blobContainer,
}
err := m.DeleteBlobContainer(ctx, resourceGroupName, "", containerName)
utilerror.AssertErrorMessage(t, err, tt.wantErr)
})
}
}

Просмотреть файл

@ -0,0 +1,39 @@
package armstorage
// Copyright (c) Microsoft Corporation.
// Licensed under the Apache License 2.0.
import (
"context"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage"
"github.com/Azure/ARO-RP/pkg/util/azureclient"
)
// AccountsClient is a minimal interface for Azure AccountsClient
type AccountsClient interface {
GetProperties(ctx context.Context, resourceGroupName string, accountName string, options *armstorage.AccountsClientGetPropertiesOptions) (armstorage.AccountsClientGetPropertiesResponse, error)
}
type accountsClient struct {
*armstorage.AccountsClient
}
var _ AccountsClient = &accountsClient{}
// NewAccountsClient creates a new AccountsClient
func NewAccountsClient(environment *azureclient.AROEnvironment, subscriptionID string, credential azcore.TokenCredential) (AccountsClient, error) {
options := arm.ClientOptions{
ClientOptions: azcore.ClientOptions{
Cloud: environment.Cloud,
},
}
clientFactory, err := armstorage.NewClientFactory(subscriptionID, credential, &options)
if err != nil {
return nil, err
}
return &accountsClient{AccountsClient: clientFactory.NewAccountsClient()}, nil
}

Просмотреть файл

@ -1,41 +0,0 @@
package armstorage
// Copyright (c) Microsoft Corporation.
// Licensed under the Apache License 2.0.
import (
"context"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage"
"github.com/Azure/ARO-RP/pkg/util/azureclient"
)
// BlobContainersClient is a minimal interface for Azure BlobContainersClient
type BlobContainersClient interface {
Create(ctx context.Context, resourceGroupName string, accountName string, containerName string, blobContainer armstorage.BlobContainer, options *armstorage.BlobContainersClientCreateOptions) (armstorage.BlobContainersClientCreateResponse, error)
Get(ctx context.Context, resourceGroupName string, accountName string, containerName string, options *armstorage.BlobContainersClientGetOptions) (armstorage.BlobContainersClientGetResponse, error)
Delete(ctx context.Context, resourceGroupName string, accountName string, containerName string, options *armstorage.BlobContainersClientDeleteOptions) (armstorage.BlobContainersClientDeleteResponse, error)
}
type blobContainersClient struct {
*armstorage.BlobContainersClient
}
var _ BlobContainersClient = &blobContainersClient{}
// NewBlobContainersClient creates a new BlobContainersClient
func NewBlobContainersClient(environment *azureclient.AROEnvironment, subscriptionID string, credential azcore.TokenCredential) (BlobContainersClient, error) {
options := arm.ClientOptions{
ClientOptions: azcore.ClientOptions{
Cloud: environment.Cloud,
},
}
clientFactory, err := armstorage.NewClientFactory(subscriptionID, credential, &options)
if err != nil {
return nil, err
}
return &blobContainersClient{BlobContainersClient: clientFactory.NewBlobContainersClient()}, nil
}

Просмотреть файл

@ -4,5 +4,5 @@ package armstorage
// Licensed under the Apache License 2.0.
//go:generate rm -rf ../../../../util/mocks/$GOPACKAGE
//go:generate go run ../../../../../vendor/github.com/golang/mock/mockgen -destination=../../../../util/mocks/azureclient/azuresdk/$GOPACKAGE/$GOPACKAGE.go github.com/Azure/ARO-RP/pkg/util/azureclient/azuresdk/$GOPACKAGE BlobContainersClient
//go:generate go run ../../../../../vendor/github.com/golang/mock/mockgen -destination=../../../../util/mocks/azureclient/azuresdk/$GOPACKAGE/$GOPACKAGE.go github.com/Azure/ARO-RP/pkg/util/azureclient/azuresdk/$GOPACKAGE AccountsClient
//go:generate go run ../../../../../vendor/golang.org/x/tools/cmd/goimports -local=github.com/Azure/ARO-RP -e -w ../../../../util/mocks/azureclient/azuresdk/$GOPACKAGE/$GOPACKAGE.go

Просмотреть файл

@ -38,34 +38,6 @@ func (m *MockManager) EXPECT() *MockManagerMockRecorder {
return m.recorder
}
// CreateBlobContainer mocks base method.
func (m *MockManager) CreateBlobContainer(arg0 context.Context, arg1, arg2, arg3 string, arg4 armstorage.PublicAccess) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateBlobContainer", arg0, arg1, arg2, arg3, arg4)
ret0, _ := ret[0].(error)
return ret0
}
// CreateBlobContainer indicates an expected call of CreateBlobContainer.
func (mr *MockManagerMockRecorder) CreateBlobContainer(arg0, arg1, arg2, arg3, arg4 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, arg4)
}
// 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)
}
// GetAZBlobClient mocks base method.
func (m *MockManager) GetAZBlobClient(arg0 string, arg1 *azblob0.ClientOptions) (azblob.AZBlobClient, error) {
m.ctrl.T.Helper()
@ -81,6 +53,21 @@ func (mr *MockManagerMockRecorder) GetAZBlobClient(arg0, arg1 interface{}) *gomo
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAZBlobClient", reflect.TypeOf((*MockManager)(nil).GetAZBlobClient), arg0, arg1)
}
// GetContainerProperties mocks base method.
func (m *MockManager) GetContainerProperties(arg0 context.Context, arg1, arg2, arg3 string) (armstorage.AccountsClientGetPropertiesResponse, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetContainerProperties", arg0, arg1, arg2, arg3)
ret0, _ := ret[0].(armstorage.AccountsClientGetPropertiesResponse)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetContainerProperties indicates an expected call of GetContainerProperties.
func (mr *MockManagerMockRecorder) GetContainerProperties(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetContainerProperties", reflect.TypeOf((*MockManager)(nil).GetContainerProperties), arg0, arg1, arg2, arg3)
}
// MockAZBlobClient is a mock of AZBlobClient interface.
type MockAZBlobClient struct {
ctrl *gomock.Controller
@ -104,6 +91,20 @@ func (m *MockAZBlobClient) EXPECT() *MockAZBlobClientMockRecorder {
return m.recorder
}
// DeleteBlob mocks base method.
func (m *MockAZBlobClient) DeleteBlob(arg0 context.Context, arg1, arg2 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteBlob", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteBlob indicates an expected call of DeleteBlob.
func (mr *MockAZBlobClientMockRecorder) DeleteBlob(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteBlob", reflect.TypeOf((*MockAZBlobClient)(nil).DeleteBlob), arg0, arg1, arg2)
}
// UploadBuffer mocks base method.
func (m *MockAZBlobClient) UploadBuffer(arg0 context.Context, arg1, arg2 string, arg3 []byte) error {
m.ctrl.T.Helper()

Просмотреть файл

@ -1,5 +1,5 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/Azure/ARO-RP/pkg/util/azureclient/azuresdk/armstorage (interfaces: BlobContainersClient)
// Source: github.com/Azure/ARO-RP/pkg/util/azureclient/azuresdk/armstorage (interfaces: AccountsClient)
// Package mock_armstorage is a generated GoMock package.
package mock_armstorage
@ -12,70 +12,40 @@ import (
gomock "github.com/golang/mock/gomock"
)
// MockBlobContainersClient is a mock of BlobContainersClient interface.
type MockBlobContainersClient struct {
// MockAccountsClient is a mock of AccountsClient interface.
type MockAccountsClient struct {
ctrl *gomock.Controller
recorder *MockBlobContainersClientMockRecorder
recorder *MockAccountsClientMockRecorder
}
// MockBlobContainersClientMockRecorder is the mock recorder for MockBlobContainersClient.
type MockBlobContainersClientMockRecorder struct {
mock *MockBlobContainersClient
// MockAccountsClientMockRecorder is the mock recorder for MockAccountsClient.
type MockAccountsClientMockRecorder struct {
mock *MockAccountsClient
}
// NewMockBlobContainersClient creates a new mock instance.
func NewMockBlobContainersClient(ctrl *gomock.Controller) *MockBlobContainersClient {
mock := &MockBlobContainersClient{ctrl: ctrl}
mock.recorder = &MockBlobContainersClientMockRecorder{mock}
// NewMockAccountsClient creates a new mock instance.
func NewMockAccountsClient(ctrl *gomock.Controller) *MockAccountsClient {
mock := &MockAccountsClient{ctrl: ctrl}
mock.recorder = &MockAccountsClientMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockBlobContainersClient) EXPECT() *MockBlobContainersClientMockRecorder {
func (m *MockAccountsClient) EXPECT() *MockAccountsClientMockRecorder {
return m.recorder
}
// Create mocks base method.
func (m *MockBlobContainersClient) Create(arg0 context.Context, arg1, arg2, arg3 string, arg4 armstorage.BlobContainer, arg5 *armstorage.BlobContainersClientCreateOptions) (armstorage.BlobContainersClientCreateResponse, error) {
// GetProperties mocks base method.
func (m *MockAccountsClient) GetProperties(arg0 context.Context, arg1, arg2 string, arg3 *armstorage.AccountsClientGetPropertiesOptions) (armstorage.AccountsClientGetPropertiesResponse, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Create", arg0, arg1, arg2, arg3, arg4, arg5)
ret0, _ := ret[0].(armstorage.BlobContainersClientCreateResponse)
ret := m.ctrl.Call(m, "GetProperties", arg0, arg1, arg2, arg3)
ret0, _ := ret[0].(armstorage.AccountsClientGetPropertiesResponse)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Create indicates an expected call of Create.
func (mr *MockBlobContainersClientMockRecorder) Create(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call {
// GetProperties indicates an expected call of GetProperties.
func (mr *MockAccountsClientMockRecorder) GetProperties(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockBlobContainersClient)(nil).Create), arg0, arg1, arg2, arg3, arg4, arg5)
}
// Delete mocks base method.
func (m *MockBlobContainersClient) Delete(arg0 context.Context, arg1, arg2, arg3 string, arg4 *armstorage.BlobContainersClientDeleteOptions) (armstorage.BlobContainersClientDeleteResponse, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Delete", arg0, arg1, arg2, arg3, arg4)
ret0, _ := ret[0].(armstorage.BlobContainersClientDeleteResponse)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Delete indicates an expected call of Delete.
func (mr *MockBlobContainersClientMockRecorder) Delete(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockBlobContainersClient)(nil).Delete), arg0, arg1, arg2, arg3, arg4)
}
// Get mocks base method.
func (m *MockBlobContainersClient) Get(arg0 context.Context, arg1, arg2, arg3 string, arg4 *armstorage.BlobContainersClientGetOptions) (armstorage.BlobContainersClientGetResponse, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Get", arg0, arg1, arg2, arg3, arg4)
ret0, _ := ret[0].(armstorage.BlobContainersClientGetResponse)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Get indicates an expected call of Get.
func (mr *MockBlobContainersClientMockRecorder) Get(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockBlobContainersClient)(nil).Get), arg0, arg1, arg2, arg3, arg4)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProperties", reflect.TypeOf((*MockAccountsClient)(nil).GetProperties), arg0, arg1, arg2, arg3)
}

Просмотреть файл

@ -7,12 +7,16 @@ import (
"context"
"fmt"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/bloberror"
"github.com/Azure/ARO-RP/pkg/env"
utilazblob "github.com/Azure/ARO-RP/pkg/util/azblob"
)
const (
DiscoveryDocumentKey = ".well-known/openid-configuration"
JWKSKey = "openid/v1/jwks"
WebContainer = "$web"
)
type OIDCBuilder struct {
@ -20,9 +24,10 @@ type OIDCBuilder struct {
publicKey []byte
blobContainerURL string
endpointURL string
directory string
}
func NewOIDCBuilder(storageEndpointSuffix string, storageEndpoint string, accountName, containerName string) (*OIDCBuilder, error) {
func NewOIDCBuilder(env env.Interface, oidcEndpoint string, directoryName string) (*OIDCBuilder, error) {
privateKey, publicKey, err := CreateKeyPair()
if err != nil {
return nil, err
@ -31,12 +36,17 @@ func NewOIDCBuilder(storageEndpointSuffix string, storageEndpoint string, accoun
return &OIDCBuilder{
privateKey: privateKey,
publicKey: publicKey,
blobContainerURL: fmt.Sprintf("https://%s.blob.%s/%s", accountName, storageEndpointSuffix, containerName),
endpointURL: fmt.Sprintf("https://%s/%s", storageEndpoint, containerName),
blobContainerURL: GenerateBlobContainerURL(env),
endpointURL: fmt.Sprintf("%s%s", oidcEndpoint, directoryName),
directory: directoryName,
}, nil
}
func (b *OIDCBuilder) EnsureOIDCDocs(ctx context.Context, oidcContainerName string, azBlobClient utilazblob.AZBlobClient) error {
func GenerateBlobContainerURL(env env.Interface) string {
return fmt.Sprintf("https://%s.blob.%s/%s", env.OIDCStorageAccountName(), env.Environment().StorageEndpointSuffix, WebContainer)
}
func (b *OIDCBuilder) EnsureOIDCDocs(ctx context.Context, azBlobClient utilazblob.AZBlobClient) error {
// Create the OIDC configuration
discoveryDocument := GenerateDiscoveryDocument(b.endpointURL)
@ -46,7 +56,7 @@ func (b *OIDCBuilder) EnsureOIDCDocs(ctx context.Context, oidcContainerName stri
return err
}
return populateOidcFolder(ctx, discoveryDocument, jwks, azBlobClient)
return populateOidcFolder(ctx, b.directory, discoveryDocument, jwks, azBlobClient)
}
func (b *OIDCBuilder) GetEndpointUrl() string {
@ -61,11 +71,11 @@ func (b *OIDCBuilder) GetBlobContainerURL() string {
return b.blobContainerURL
}
func populateOidcFolder(ctx context.Context, discoveryDocument string, jwks []byte, azBlobClient utilazblob.AZBlobClient) error {
func populateOidcFolder(ctx context.Context, directory string, discoveryDocument string, jwks []byte, azBlobClient utilazblob.AZBlobClient) error {
err := azBlobClient.UploadBuffer(
ctx,
"",
DiscoveryDocumentKey,
DocumentKey(directory, DiscoveryDocumentKey),
[]byte(discoveryDocument),
)
if err != nil {
@ -75,7 +85,21 @@ func populateOidcFolder(ctx context.Context, discoveryDocument string, jwks []by
return azBlobClient.UploadBuffer(
ctx,
"",
JWKSKey,
DocumentKey(directory, JWKSKey),
jwks,
)
}
func DeleteOidcFolder(ctx context.Context, directory string, azBlobClient utilazblob.AZBlobClient) error {
for _, key := range []string{DiscoveryDocumentKey, JWKSKey} {
err := azBlobClient.DeleteBlob(ctx, "", DocumentKey(directory, key))
if err != nil && !bloberror.HasCode(err, bloberror.BlobNotFound) {
return err
}
}
return nil
}
func DocumentKey(directory string, blobKey string) string {
return fmt.Sprintf("%s/%s", directory, blobKey)
}

Просмотреть файл

@ -1,6 +1,7 @@
package oidcbuilder
import (
"bytes"
"context"
"crypto/ecdsa"
"crypto/elliptic"
@ -9,12 +10,20 @@ import (
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"reflect"
"testing"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/bloberror"
"github.com/golang/mock/gomock"
"github.com/Azure/ARO-RP/pkg/util/azureclient"
mock_azblob "github.com/Azure/ARO-RP/pkg/util/mocks/azblob"
mock_env "github.com/Azure/ARO-RP/pkg/util/mocks/env"
utilerror "github.com/Azure/ARO-RP/test/util/error"
)
@ -23,7 +32,7 @@ import (
func TestEnsureOIDCDocs(t *testing.T) {
ctx := context.Background()
fakeContainerName := "fakeContainer"
directoryName := "fakeDirectory"
blobContainerURL := "fakeBlobContainerURL"
endpointURL := "fakeEndPointURL"
@ -64,14 +73,15 @@ func TestEnsureOIDCDocs(t *testing.T) {
privateKey: priKey,
publicKey: pubKey,
blobContainerURL: blobContainerURL,
directory: directoryName,
endpointURL: endpointURL,
},
mocks: func(azblobClient *mock_azblob.MockAZBlobClient) {
azblobClient.EXPECT().
UploadBuffer(gomock.Any(), "", DiscoveryDocumentKey, gomock.Any()).
UploadBuffer(gomock.Any(), "", DocumentKey(directoryName, DiscoveryDocumentKey), gomock.Any()).
Return(nil)
azblobClient.EXPECT().
UploadBuffer(gomock.Any(), "", JWKSKey, gomock.Any()).
UploadBuffer(gomock.Any(), "", DocumentKey(directoryName, JWKSKey), gomock.Any()).
Return(nil)
},
},
@ -82,6 +92,7 @@ func TestEnsureOIDCDocs(t *testing.T) {
publicKey: invalidKey,
blobContainerURL: blobContainerURL,
endpointURL: endpointURL,
directory: directoryName,
},
wantErr: "Failed to decode PEM file",
},
@ -92,6 +103,7 @@ func TestEnsureOIDCDocs(t *testing.T) {
publicKey: incorrectlyEncodedPublicKey,
blobContainerURL: blobContainerURL,
endpointURL: endpointURL,
directory: directoryName,
},
wantErr: "Failed to parse key content: x509: failed to parse public key (use ParsePKCS1PublicKey instead for this key format)",
},
@ -102,10 +114,11 @@ func TestEnsureOIDCDocs(t *testing.T) {
publicKey: pubKey,
blobContainerURL: blobContainerURL,
endpointURL: endpointURL,
directory: directoryName,
},
mocks: func(azblobClient *mock_azblob.MockAZBlobClient) {
azblobClient.EXPECT().
UploadBuffer(gomock.Any(), "", DiscoveryDocumentKey, gomock.Any()).
UploadBuffer(gomock.Any(), "", DocumentKey(directoryName, DiscoveryDocumentKey), gomock.Any()).
Return(errors.New("generic error"))
},
wantErr: "generic error",
@ -117,13 +130,14 @@ func TestEnsureOIDCDocs(t *testing.T) {
publicKey: pubKey,
blobContainerURL: blobContainerURL,
endpointURL: endpointURL,
directory: directoryName,
},
mocks: func(azblobClient *mock_azblob.MockAZBlobClient) {
azblobClient.EXPECT().
UploadBuffer(gomock.Any(), "", DiscoveryDocumentKey, gomock.Any()).
UploadBuffer(gomock.Any(), "", DocumentKey(directoryName, DiscoveryDocumentKey), gomock.Any()).
Return(nil)
azblobClient.EXPECT().
UploadBuffer(gomock.Any(), "", JWKSKey, gomock.Any()).
UploadBuffer(gomock.Any(), "", DocumentKey(directoryName, JWKSKey), gomock.Any()).
Return(errors.New("generic error"))
},
wantErr: "generic error",
@ -135,6 +149,7 @@ func TestEnsureOIDCDocs(t *testing.T) {
publicKey: nonRSAEncodedPublicKey,
blobContainerURL: blobContainerURL,
endpointURL: endpointURL,
directory: directoryName,
},
wantErr: "Public key is not of type RSA",
},
@ -149,7 +164,7 @@ func TestEnsureOIDCDocs(t *testing.T) {
tt.mocks(azBlobClient)
}
err = tt.oidcbuilder.EnsureOIDCDocs(ctx, fakeContainerName, azBlobClient)
err = tt.oidcbuilder.EnsureOIDCDocs(ctx, azBlobClient)
utilerror.AssertErrorMessage(t, err, tt.wantErr)
if tt.oidcbuilder.GetEndpointUrl() != tt.oidcbuilder.endpointURL {
@ -166,3 +181,119 @@ func TestEnsureOIDCDocs(t *testing.T) {
})
}
}
type fakeReadCloser struct {
io.Reader
}
func (fakeReadCloser) Close() error { return nil }
func TestDeleteOidcFolder(t *testing.T) {
ctx := context.Background()
directoryName := "fakeDirectory"
respErrBlobNotFound := &azcore.ResponseError{
ErrorCode: string(bloberror.BlobNotFound),
}
respErrGeneric := &azcore.ResponseError{
ErrorCode: string("Generic Error"),
RawResponse: &http.Response{
Request: &http.Request{
Method: "FAKE",
URL: &url.URL{},
},
Body: fakeReadCloser{bytes.NewBufferString("Generic Error")},
},
StatusCode: 400,
}
genericErrorMessage := `FAKE ://
--------------------------------------------------------------------------------
RESPONSE 0:
ERROR CODE: Generic Error
--------------------------------------------------------------------------------
Generic Error
--------------------------------------------------------------------------------
`
for _, tt := range []struct {
name string
mocks func(*mock_azblob.MockAZBlobClient)
wantErr string
}{
{
name: "Success",
mocks: func(azblobClient *mock_azblob.MockAZBlobClient) {
azblobClient.EXPECT().DeleteBlob(ctx, "", DocumentKey(directoryName, DiscoveryDocumentKey)).Return(nil)
azblobClient.EXPECT().DeleteBlob(ctx, "", DocumentKey(directoryName, JWKSKey)).Return(nil)
},
},
{
name: "Fail - Generic Error when deleting DiscoveryDocument",
mocks: func(azblobClient *mock_azblob.MockAZBlobClient) {
azblobClient.EXPECT().DeleteBlob(ctx, "", DocumentKey(directoryName, DiscoveryDocumentKey)).Return(respErrGeneric)
},
wantErr: genericErrorMessage,
},
{
name: "Fail - Generic Error when deleting JWKS",
mocks: func(azblobClient *mock_azblob.MockAZBlobClient) {
azblobClient.EXPECT().DeleteBlob(ctx, "", DocumentKey(directoryName, DiscoveryDocumentKey)).Return(respErrBlobNotFound)
azblobClient.EXPECT().DeleteBlob(ctx, "", DocumentKey(directoryName, JWKSKey)).Return(respErrGeneric)
},
wantErr: genericErrorMessage,
},
{
name: "Success - One Blob exists and other doesn't",
mocks: func(azblobClient *mock_azblob.MockAZBlobClient) {
azblobClient.EXPECT().DeleteBlob(ctx, "", DocumentKey(directoryName, DiscoveryDocumentKey)).Return(respErrBlobNotFound)
azblobClient.EXPECT().DeleteBlob(ctx, "", DocumentKey(directoryName, JWKSKey)).Return(nil)
},
},
} {
t.Run(tt.name, func(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
azBlobClient := mock_azblob.NewMockAZBlobClient(controller)
if tt.mocks != nil {
tt.mocks(azBlobClient)
}
err := DeleteOidcFolder(ctx, directoryName, azBlobClient)
utilerror.AssertErrorMessage(t, err, tt.wantErr)
})
}
}
func TestGenerateBlobContainerURL(t *testing.T) {
oidcStorageAccountName := "eastusoic"
for _, tt := range []struct {
name string
mocks func(*mock_env.MockInterface)
expected string
}{
{
name: "Success: Working as Expected",
mocks: func(menv *mock_env.MockInterface) {
menv.EXPECT().OIDCStorageAccountName().Return(oidcStorageAccountName)
menv.EXPECT().Environment().Return(&azureclient.PublicCloud)
},
expected: fmt.Sprintf("https://%s.blob.%s/%s", oidcStorageAccountName, azureclient.PublicCloud.StorageEndpointSuffix, WebContainer),
},
} {
t.Run(tt.name, func(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
env := mock_env.NewMockInterface(controller)
if tt.mocks != nil {
tt.mocks(env)
}
result := GenerateBlobContainerURL(env)
if result != tt.expected {
t.Fatalf("Expected %s, but received %s", tt.expected, result)
}
})
}
}