зеркало из https://github.com/Azure/ARO-RP.git
297 строки
8.6 KiB
Go
297 строки
8.6 KiB
Go
package validate
|
|
|
|
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the Apache License 2.0.
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
|
|
"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/azidentity"
|
|
"github.com/Azure/checkaccess-v2-go-sdk/client"
|
|
"github.com/Azure/go-autorest/autorest"
|
|
"github.com/Azure/go-autorest/autorest/to"
|
|
"github.com/form3tech-oss/jwt-go"
|
|
"github.com/sirupsen/logrus"
|
|
|
|
"github.com/Azure/ARO-RP/pkg/api"
|
|
"github.com/Azure/ARO-RP/pkg/env"
|
|
"github.com/Azure/ARO-RP/pkg/util/azureclient"
|
|
"github.com/Azure/ARO-RP/pkg/util/azureclient/azuresdk/armauthorization"
|
|
"github.com/Azure/ARO-RP/pkg/util/azureclient/azuresdk/armmsi"
|
|
"github.com/Azure/ARO-RP/pkg/util/platformworkloadidentity"
|
|
"github.com/Azure/ARO-RP/pkg/validate/dynamic"
|
|
)
|
|
|
|
// OpenShiftClusterDynamicValidator is the dynamic validator interface
|
|
type OpenShiftClusterDynamicValidator interface {
|
|
Dynamic(context.Context) error
|
|
}
|
|
|
|
// NewOpenShiftClusterDynamicValidator creates a new OpenShiftClusterDynamicValidator
|
|
func NewOpenShiftClusterDynamicValidator(
|
|
log *logrus.Entry,
|
|
env env.Interface,
|
|
oc *api.OpenShiftCluster,
|
|
subscriptionDoc *api.SubscriptionDocument,
|
|
fpAuthorizer autorest.Authorizer,
|
|
roleDefinitions armauthorization.RoleDefinitionsClient,
|
|
clusterMsiFederatedIdentityCredentials armmsi.FederatedIdentityCredentialsClient,
|
|
platformWorkloadIdentityRolesByVersion platformworkloadidentity.PlatformWorkloadIdentityRolesByVersion,
|
|
clusterMSICredential azcore.TokenCredential,
|
|
) OpenShiftClusterDynamicValidator {
|
|
return &openShiftClusterDynamicValidator{
|
|
log: log,
|
|
env: env,
|
|
|
|
oc: oc,
|
|
subscriptionDoc: subscriptionDoc,
|
|
fpAuthorizer: fpAuthorizer,
|
|
roleDefinitions: roleDefinitions,
|
|
clusterMsiFederatedIdentityCredentials: clusterMsiFederatedIdentityCredentials,
|
|
platformWorkloadIdentityRolesByVersion: platformWorkloadIdentityRolesByVersion,
|
|
clusterMSICredential: clusterMSICredential,
|
|
}
|
|
}
|
|
|
|
type openShiftClusterDynamicValidator struct {
|
|
log *logrus.Entry
|
|
env env.Interface
|
|
|
|
oc *api.OpenShiftCluster
|
|
subscriptionDoc *api.SubscriptionDocument
|
|
fpAuthorizer autorest.Authorizer
|
|
roleDefinitions armauthorization.RoleDefinitionsClient
|
|
clusterMsiFederatedIdentityCredentials armmsi.FederatedIdentityCredentialsClient
|
|
platformWorkloadIdentityRolesByVersion platformworkloadidentity.PlatformWorkloadIdentityRolesByVersion
|
|
clusterMSICredential azcore.TokenCredential
|
|
}
|
|
|
|
// ensureAccessTokenClaims can detect an error when the service principal (fp, cluster sp) has accidentally deleted from
|
|
// the tenant. Using the token with ARM will result in the error as follows
|
|
//
|
|
// Lack of an altsecid, puid or oid claim in the token. Continuing would
|
|
// subsequently cause the ARM error `Code="InvalidAuthenticationToken"
|
|
// Message="The received access token is not valid: at least one of the
|
|
// claims 'puid' or 'altsecid' or 'oid' should be present. If you are
|
|
// accessing as an application please make sure service principal is
|
|
// properly created in the tenant."`. I think this can be returned when
|
|
// the service principal associated with the application hasn't yet
|
|
// caught up with the application itself.
|
|
func ensureAccessTokenClaims(ctx context.Context, spTokenCredential azcore.TokenCredential, scopes []string) error {
|
|
options := policy.TokenRequestOptions{Scopes: scopes}
|
|
token, err := spTokenCredential.GetToken(ctx, options)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var claims jwt.MapClaims
|
|
parser := &jwt.Parser{UseJSONNumber: true}
|
|
_, _, err = parser.ParseUnverified(token.Token, &claims)
|
|
if err != nil {
|
|
return api.NewCloudError(
|
|
http.StatusBadRequest,
|
|
api.CloudErrorCodeInvalidServicePrincipalToken,
|
|
"properties.servicePrincipalProfile",
|
|
"The provided service principal generated an invalid token.")
|
|
}
|
|
|
|
for _, claim := range []string{"altsecid", "oid", "puid"} {
|
|
if _, found := claims[claim]; found {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return api.NewCloudError(
|
|
http.StatusBadRequest,
|
|
api.CloudErrorCodeInvalidServicePrincipalClaims,
|
|
"properties.servicePrincipalProfile",
|
|
"The Azure Red Hat Openshift resource provider service principal has been removed from your tenant. To restore, please unregister and then re-register the Azure Red Hat OpenShift resource provider.")
|
|
}
|
|
|
|
// Dynamic validates an OpenShift cluster
|
|
func (dv *openShiftClusterDynamicValidator) Dynamic(ctx context.Context) error {
|
|
// Get all subnets
|
|
subnets := []dynamic.Subnet{{
|
|
ID: dv.oc.Properties.MasterProfile.SubnetID,
|
|
Path: "properties.masterProfile.subnetId",
|
|
}}
|
|
|
|
workerProfiles, propertyName := api.GetEnrichedWorkerProfiles(dv.oc.Properties)
|
|
for i, wp := range workerProfiles {
|
|
subnets = append(subnets, dynamic.Subnet{
|
|
ID: wp.SubnetID,
|
|
Path: fmt.Sprintf("properties.%s[%d].subnetId", propertyName, i),
|
|
})
|
|
}
|
|
|
|
tenantID := dv.subscriptionDoc.Subscription.Properties.TenantID
|
|
fpClientCred, err := dv.env.FPNewClientCertificateCredential(tenantID, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
aroEnv := dv.env.Environment()
|
|
clientOptions := &azcore.ClientOptions{
|
|
Transport: &http.Client{
|
|
Transport: azureclient.NewCustomRoundTripper(http.DefaultTransport),
|
|
},
|
|
}
|
|
pdpClient, err := client.NewRemotePDPClient(
|
|
fmt.Sprintf(aroEnv.Endpoint, dv.env.Location()),
|
|
aroEnv.OAuthScope,
|
|
fpClientCred,
|
|
clientOptions,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
scopes := []string{dv.env.Environment().ResourceManagerScope}
|
|
var spDynamic dynamic.Dynamic
|
|
if !dv.oc.UsesWorkloadIdentity() {
|
|
// SP validation
|
|
spp := dv.oc.Properties.ServicePrincipalProfile
|
|
options := dv.env.Environment().ClientSecretCredentialOptions()
|
|
spClientCred, err := azidentity.NewClientSecretCredential(
|
|
tenantID, spp.ClientID, string(spp.ClientSecret), options)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = ensureAccessTokenClaims(ctx, spClientCred, scopes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
spDynamic = dynamic.NewValidator(
|
|
dv.log,
|
|
dv.env,
|
|
dv.env.Environment(),
|
|
dv.subscriptionDoc.ID,
|
|
dv.fpAuthorizer,
|
|
&spp.ClientID,
|
|
dynamic.AuthorizerClusterServicePrincipal,
|
|
spClientCred,
|
|
pdpClient,
|
|
)
|
|
err = spDynamic.ValidateServicePrincipal(ctx, spClientCred)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
//ClusterMSI Validation
|
|
cmsiDynamic := dynamic.NewValidator(
|
|
dv.log,
|
|
dv.env,
|
|
dv.env.Environment(),
|
|
dv.subscriptionDoc.ID,
|
|
dv.fpAuthorizer,
|
|
nil,
|
|
dynamic.AuthorizerClusterUserAssignedIdentity,
|
|
dv.clusterMSICredential,
|
|
pdpClient,
|
|
)
|
|
err = cmsiDynamic.ValidateClusterUserAssignedIdentity(ctx, dv.oc.Properties.PlatformWorkloadIdentityProfile.PlatformWorkloadIdentities, dv.roleDefinitions)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// PlatformWorkloadIdentity Validation
|
|
spDynamic = dynamic.NewValidator(
|
|
dv.log,
|
|
dv.env,
|
|
dv.env.Environment(),
|
|
dv.subscriptionDoc.ID,
|
|
dv.fpAuthorizer,
|
|
nil,
|
|
dynamic.AuthorizerWorkloadIdentity,
|
|
nil,
|
|
pdpClient,
|
|
)
|
|
err = spDynamic.ValidatePlatformWorkloadIdentityProfile(ctx, dv.oc, dv.platformWorkloadIdentityRolesByVersion.GetPlatformWorkloadIdentityRolesByRoleName(), dv.roleDefinitions, dv.clusterMsiFederatedIdentityCredentials)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
err = spDynamic.ValidateVnet(
|
|
ctx,
|
|
dv.oc.Location,
|
|
subnets,
|
|
dv.oc.Properties.NetworkProfile.PodCIDR,
|
|
dv.oc.Properties.NetworkProfile.ServiceCIDR,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = spDynamic.ValidateSubnets(ctx, dv.oc, subnets)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = spDynamic.ValidateDiskEncryptionSets(ctx, dv.oc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = spDynamic.ValidateEncryptionAtHost(ctx, dv.oc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = spDynamic.ValidatePreConfiguredNSGs(ctx, dv.oc, subnets)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = ensureAccessTokenClaims(ctx, fpClientCred, scopes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// FP validation
|
|
fpDynamic := dynamic.NewValidator(
|
|
dv.log,
|
|
dv.env,
|
|
dv.env.Environment(),
|
|
dv.subscriptionDoc.ID,
|
|
dv.fpAuthorizer,
|
|
to.StringPtr(dv.env.FPClientID()),
|
|
dynamic.AuthorizerFirstParty,
|
|
fpClientCred,
|
|
pdpClient,
|
|
)
|
|
|
|
err = fpDynamic.ValidateVnet(
|
|
ctx,
|
|
dv.oc.Location,
|
|
subnets,
|
|
dv.oc.Properties.NetworkProfile.PodCIDR,
|
|
dv.oc.Properties.NetworkProfile.ServiceCIDR,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = fpDynamic.ValidateDiskEncryptionSets(ctx, dv.oc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = fpDynamic.ValidatePreConfiguredNSGs(ctx, dv.oc, subnets)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = fpDynamic.ValidateLoadBalancerProfile(ctx, dv.oc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|