зеркало из https://github.com/Azure/ARO-RP.git
ARO-6425 v20240812preview validation (#3563)
This commit is contained in:
Родитель
cceb396a67
Коммит
e71343ad58
|
@ -339,8 +339,10 @@ func (c openShiftClusterConverter) ExternalNoReadOnly(_oc interface{}) {
|
|||
for i := range oc.Properties.IngressProfiles {
|
||||
oc.Properties.IngressProfiles[i].IP = ""
|
||||
}
|
||||
for i := range oc.Properties.PlatformWorkloadIdentityProfile.PlatformWorkloadIdentities {
|
||||
oc.Properties.PlatformWorkloadIdentityProfile.PlatformWorkloadIdentities[i].ClientID = ""
|
||||
oc.Properties.PlatformWorkloadIdentityProfile.PlatformWorkloadIdentities[i].ObjectID = ""
|
||||
if oc.Properties.PlatformWorkloadIdentityProfile != nil {
|
||||
for i := range oc.Properties.PlatformWorkloadIdentityProfile.PlatformWorkloadIdentities {
|
||||
oc.Properties.PlatformWorkloadIdentityProfile.PlatformWorkloadIdentities[i].ClientID = ""
|
||||
oc.Properties.PlatformWorkloadIdentityProfile.PlatformWorkloadIdentities[i].ObjectID = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"net/url"
|
||||
"strings"
|
||||
|
||||
azcorearm "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
|
||||
"github.com/Azure/ARO-RP/pkg/api"
|
||||
|
@ -78,6 +79,10 @@ func (sv openShiftClusterStaticValidator) validate(oc *OpenShiftCluster, isCreat
|
|||
return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, "location", "The provided location '%s' is invalid.", oc.Location)
|
||||
}
|
||||
|
||||
if err := sv.validatePlatformIdentities(oc); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return sv.validateProperties("properties", &oc.Properties, isCreate, architectureVersion)
|
||||
}
|
||||
|
||||
|
@ -110,6 +115,9 @@ func (sv openShiftClusterStaticValidator) validateProperties(path string, p *Ope
|
|||
if err := sv.validateAPIServerProfile(path+".apiserverProfile", &p.APIServerProfile); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := sv.validatePlatformWorkloadIdentityProfile(path+".platformWorkloadIdentityProfile", p.PlatformWorkloadIdentityProfile); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if isCreate {
|
||||
if len(p.WorkerProfilesStatus) != 0 {
|
||||
|
@ -412,3 +420,48 @@ func (sv openShiftClusterStaticValidator) validateDelta(oc, current *OpenShiftCl
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sv openShiftClusterStaticValidator) validatePlatformWorkloadIdentityProfile(path string, pwip *PlatformWorkloadIdentityProfile) error {
|
||||
// PlatformWorkloadIdentityProfile being empty is acceptable
|
||||
if pwip == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate the PlatformWorkloadIdentities
|
||||
for n, p := range pwip.PlatformWorkloadIdentities {
|
||||
resource, err := azcorearm.ParseResourceID(p.ResourceID)
|
||||
if err != nil {
|
||||
return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, fmt.Sprintf("%s.PlatformWorkloadIdentities[%d].resourceID", path, n), "ResourceID %s formatted incorrectly.", p.ResourceID)
|
||||
}
|
||||
|
||||
if p.OperatorName == "" {
|
||||
return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, fmt.Sprintf("%s.PlatformWorkloadIdentities[%d].resourceID", path, n), "Operator name is empty.")
|
||||
}
|
||||
|
||||
if resource.ResourceType.Type != "userAssignedIdentities" {
|
||||
return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, fmt.Sprintf("%s.PlatformWorkloadIdentities[%d].resourceID", path, n), "Resource must be a user assigned identity.")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sv openShiftClusterStaticValidator) validatePlatformIdentities(oc *OpenShiftCluster) error {
|
||||
pwip := oc.Properties.PlatformWorkloadIdentityProfile
|
||||
spp := oc.Properties.ServicePrincipalProfile
|
||||
|
||||
if pwip == nil && spp == nil {
|
||||
return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, "properties.servicePrincipalProfile", "Must provide either an identity or service principal credentials.")
|
||||
}
|
||||
|
||||
if pwip != nil && spp != nil && (spp.ClientID != "" || spp.ClientSecret != "") {
|
||||
return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, "properties.servicePrincipalProfile", "Cannot use identities and service principal credentials at the same time.")
|
||||
}
|
||||
|
||||
clusterIdentityPresent := oc.Identity != nil
|
||||
operatorRolePresent := pwip != nil
|
||||
|
||||
if clusterIdentityPresent != operatorRolePresent {
|
||||
return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, "identity", "Cluster identity and platform workload identities require each other.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1161,3 +1161,169 @@ func TestOpenShiftClusterStaticValidateDelta(t *testing.T) {
|
|||
|
||||
runTests(t, testModeUpdate, tests)
|
||||
}
|
||||
|
||||
func TestOpenShiftClusterStaticValidatePlatformWorkloadIdentityProfile(t *testing.T) {
|
||||
createTests := []*validateTest{
|
||||
{
|
||||
name: "valid empty workloadIdentityProfile",
|
||||
modify: func(oc *OpenShiftCluster) {
|
||||
oc.Properties.PlatformWorkloadIdentityProfile = nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid workloadIdentityProfile",
|
||||
modify: func(oc *OpenShiftCluster) {
|
||||
oc.Properties.PlatformWorkloadIdentityProfile = &PlatformWorkloadIdentityProfile{
|
||||
PlatformWorkloadIdentities: []PlatformWorkloadIdentity{
|
||||
{
|
||||
OperatorName: "FAKE-OPERATOR",
|
||||
ResourceID: "/subscriptions/12345678-1234-1234-1234-123456789012/resourceGroups/a-fake-group/providers/Microsoft.RedHatOpenShift/userAssignedIdentities/fake-cluster-name",
|
||||
},
|
||||
},
|
||||
}
|
||||
oc.Identity = &Identity{
|
||||
UserAssignedIdentities: UserAssignedIdentities{
|
||||
"first": {
|
||||
ClientID: "11111111-1111-1111-1111-111111111111",
|
||||
PrincipalID: "SOMETHING",
|
||||
},
|
||||
},
|
||||
}
|
||||
oc.Properties.ServicePrincipalProfile = nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid resourceID",
|
||||
modify: func(oc *OpenShiftCluster) {
|
||||
oc.Identity = &Identity{
|
||||
UserAssignedIdentities: UserAssignedIdentities{
|
||||
"first": {
|
||||
ClientID: "11111111-1111-1111-1111-111111111111",
|
||||
PrincipalID: "SOMETHING",
|
||||
},
|
||||
},
|
||||
}
|
||||
oc.Properties.PlatformWorkloadIdentityProfile = &PlatformWorkloadIdentityProfile{
|
||||
PlatformWorkloadIdentities: []PlatformWorkloadIdentity{
|
||||
{
|
||||
OperatorName: "FAKE-OPERATOR",
|
||||
ResourceID: "BAD",
|
||||
},
|
||||
},
|
||||
}
|
||||
oc.Properties.ServicePrincipalProfile = nil
|
||||
},
|
||||
wantErr: "400: InvalidParameter: properties.platformWorkloadIdentityProfile.PlatformWorkloadIdentities[0].resourceID: ResourceID BAD formatted incorrectly.",
|
||||
},
|
||||
{
|
||||
name: "wrong resource type",
|
||||
modify: func(oc *OpenShiftCluster) {
|
||||
oc.Properties.PlatformWorkloadIdentityProfile = &PlatformWorkloadIdentityProfile{
|
||||
PlatformWorkloadIdentities: []PlatformWorkloadIdentity{
|
||||
{
|
||||
OperatorName: "FAKE-OPERATOR",
|
||||
ResourceID: "/subscriptions/12345678-1234-1234-1234-123456789012/resourceGroups/a-fake-group/providers/Microsoft.RedHatOpenShift/otherThing/fake-cluster-name",
|
||||
},
|
||||
},
|
||||
}
|
||||
oc.Properties.ServicePrincipalProfile = nil
|
||||
oc.Identity = &Identity{
|
||||
UserAssignedIdentities: UserAssignedIdentities{
|
||||
"first": {
|
||||
ClientID: "11111111-1111-1111-1111-111111111111",
|
||||
PrincipalID: "SOMETHING",
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
wantErr: "400: InvalidParameter: properties.platformWorkloadIdentityProfile.PlatformWorkloadIdentities[0].resourceID: Resource must be a user assigned identity.",
|
||||
},
|
||||
{
|
||||
name: "no credentials with identities",
|
||||
modify: func(oc *OpenShiftCluster) {
|
||||
oc.Properties.PlatformWorkloadIdentityProfile = &PlatformWorkloadIdentityProfile{
|
||||
PlatformWorkloadIdentities: []PlatformWorkloadIdentity{
|
||||
{
|
||||
OperatorName: "FAKE-OPERATOR",
|
||||
ResourceID: "/subscriptions/12345678-1234-1234-1234-123456789012/resourceGroups/a-fake-group/providers/Microsoft.RedHatOpenShift/userAssignedIdentities/fake-cluster-name",
|
||||
},
|
||||
},
|
||||
}
|
||||
oc.Identity = &Identity{
|
||||
UserAssignedIdentities: UserAssignedIdentities{
|
||||
"first": {
|
||||
ClientID: "11111111-1111-1111-1111-111111111111",
|
||||
PrincipalID: "SOMETHING",
|
||||
},
|
||||
},
|
||||
}
|
||||
oc.Properties.ServicePrincipalProfile = &ServicePrincipalProfile{
|
||||
ClientID: "11111111-1111-1111-1111-111111111111",
|
||||
ClientSecret: "BAD",
|
||||
}
|
||||
},
|
||||
wantErr: "400: InvalidParameter: properties.servicePrincipalProfile: Cannot use identities and service principal credentials at the same time.",
|
||||
},
|
||||
{
|
||||
name: "cluster identity missing platform workload identity",
|
||||
modify: func(oc *OpenShiftCluster) {
|
||||
oc.Identity = &Identity{
|
||||
UserAssignedIdentities: UserAssignedIdentities{
|
||||
"first": {
|
||||
ClientID: "11111111-1111-1111-1111-111111111111",
|
||||
PrincipalID: "SOMETHING",
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
wantErr: "400: InvalidParameter: identity: Cluster identity and platform workload identities require each other.",
|
||||
},
|
||||
{
|
||||
name: "platform workload identity missing cluster identity",
|
||||
modify: func(oc *OpenShiftCluster) {
|
||||
oc.Properties.PlatformWorkloadIdentityProfile = &PlatformWorkloadIdentityProfile{
|
||||
PlatformWorkloadIdentities: []PlatformWorkloadIdentity{
|
||||
{
|
||||
OperatorName: "operator_name",
|
||||
},
|
||||
},
|
||||
}
|
||||
oc.Properties.ServicePrincipalProfile = nil
|
||||
},
|
||||
wantErr: "400: InvalidParameter: identity: Cluster identity and platform workload identities require each other.",
|
||||
},
|
||||
{
|
||||
name: "operator name missing",
|
||||
modify: func(oc *OpenShiftCluster) {
|
||||
oc.Identity = &Identity{
|
||||
UserAssignedIdentities: UserAssignedIdentities{
|
||||
"first": {
|
||||
ClientID: "11111111-1111-1111-1111-111111111111",
|
||||
PrincipalID: "SOMETHING",
|
||||
},
|
||||
},
|
||||
}
|
||||
oc.Properties.PlatformWorkloadIdentityProfile = &PlatformWorkloadIdentityProfile{
|
||||
PlatformWorkloadIdentities: []PlatformWorkloadIdentity{
|
||||
{
|
||||
ResourceID: "/subscriptions/12345678-1234-1234-1234-123456789012/resourceGroups/a-fake-group/providers/Microsoft.RedHatOpenShift/userAssignedIdentities/fake-cluster-name",
|
||||
OperatorName: "",
|
||||
},
|
||||
},
|
||||
}
|
||||
oc.Properties.ServicePrincipalProfile = nil
|
||||
},
|
||||
wantErr: "400: InvalidParameter: properties.platformWorkloadIdentityProfile.PlatformWorkloadIdentities[0].resourceID: Operator name is empty.",
|
||||
},
|
||||
{
|
||||
name: "identity and service principal missing",
|
||||
modify: func(oc *OpenShiftCluster) {
|
||||
oc.Properties.PlatformWorkloadIdentityProfile = nil
|
||||
oc.Properties.ServicePrincipalProfile = nil
|
||||
},
|
||||
wantErr: "400: InvalidParameter: properties.servicePrincipalProfile: Must provide either an identity or service principal credentials.",
|
||||
},
|
||||
}
|
||||
|
||||
runTests(t, testModeCreate, createTests)
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package arm
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
@ -40,27 +39,31 @@ func (r ArmResource) String() string {
|
|||
return fmt.Sprintf("/subscriptions/%s/resourcegroups/%s/providers/%s/%s/%s/%s/%s", r.SubscriptionID, r.ResourceGroup, r.Provider, r.ResourceType, r.ResourceName, r.SubResource.ResourceType, r.SubResource.ResourceName)
|
||||
}
|
||||
|
||||
// ParseArmResourceId take the resourceID of a child resource to an OpenShiftCluster
|
||||
// TODO refactor this function to support an additional layer of child resources if we ever get to that point, right now only supports 1 child resource
|
||||
// ParseArmResourceId takes in an ARM resource ID and returns an ArmResource object representing that resource. It supports up to two levels of subresource nesting.
|
||||
func ParseArmResourceId(resourceId string) (*ArmResource, error) {
|
||||
const resourceIDPatternText = `(?i)subscriptions/(.+)/resourcegroups/(.+)/providers/(.+?)/(.+?)/(.+?)/(.+?)/(.+)`
|
||||
resourceIDPattern := regexp.MustCompile(resourceIDPatternText)
|
||||
match := resourceIDPattern.FindStringSubmatch(resourceId)
|
||||
|
||||
if len(match) != 8 || strings.Contains(match[7], "/") {
|
||||
resourceComponents := strings.Split(strings.TrimPrefix(resourceId, "/"), "/")
|
||||
if len(resourceComponents) < 8 || !strings.EqualFold(resourceComponents[0], "subscriptions") || !strings.EqualFold(resourceComponents[2], "resourceGroups") || !strings.EqualFold(resourceComponents[4], "providers") {
|
||||
return nil, fmt.Errorf("parsing failed for %s. Invalid resource Id format", resourceId)
|
||||
}
|
||||
|
||||
result := &ArmResource{
|
||||
SubscriptionID: match[1],
|
||||
ResourceGroup: match[2],
|
||||
Provider: match[3],
|
||||
ResourceType: match[4],
|
||||
ResourceName: match[5],
|
||||
SubResource: SubResource{
|
||||
ResourceType: match[6],
|
||||
ResourceName: match[7],
|
||||
},
|
||||
SubscriptionID: resourceComponents[1],
|
||||
ResourceGroup: resourceComponents[3],
|
||||
Provider: resourceComponents[5],
|
||||
ResourceType: resourceComponents[6],
|
||||
ResourceName: resourceComponents[7],
|
||||
}
|
||||
if len(resourceComponents) > 8 {
|
||||
result.SubResource = SubResource{
|
||||
ResourceType: resourceComponents[8],
|
||||
ResourceName: resourceComponents[9],
|
||||
}
|
||||
if len(resourceComponents) > 10 {
|
||||
result.SubResource.SubResource = &SubResource{
|
||||
ResourceType: resourceComponents[8], // same subresource type as the first subresource
|
||||
ResourceName: resourceComponents[10],
|
||||
}
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
|
|
@ -31,9 +31,15 @@ func TestArmResources(t *testing.T) {
|
|||
},
|
||||
},
|
||||
{
|
||||
name: "sad path - bad input - missing subresources",
|
||||
name: "happy path - missing subresources",
|
||||
input: "/subscriptions/abc/resourcegroups/v4-eastus/providers/Microsoft.RedHatOpenShift/openshiftclusters/cluster1",
|
||||
err: "parsing failed for /subscriptions/abc/resourcegroups/v4-eastus/providers/Microsoft.RedHatOpenShift/openshiftclusters/cluster1. Invalid resource Id format",
|
||||
want: &ArmResource{
|
||||
SubscriptionID: "abc",
|
||||
ResourceGroup: "v4-eastus",
|
||||
Provider: "Microsoft.RedHatOpenShift",
|
||||
ResourceName: "cluster1",
|
||||
ResourceType: "openshiftclusters",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sad path - bad input - missing cluster resource",
|
||||
|
@ -41,21 +47,37 @@ func TestArmResources(t *testing.T) {
|
|||
err: "parsing failed for /subscriptions/abc/resourcegroups/v4-eastus/providers. Invalid resource Id format",
|
||||
},
|
||||
{
|
||||
name: "sad path - bad input - too many nested resource",
|
||||
name: "happy path - two subresources",
|
||||
input: "/subscriptions/abc/resourcegroups/v4-eastus/providers/Microsoft.RedHatOpenShift/openshiftclusters/cluster1/syncSets/syncset1/nextResource",
|
||||
err: "parsing failed for /subscriptions/abc/resourcegroups/v4-eastus/providers/Microsoft.RedHatOpenShift/openshiftclusters/cluster1/syncSets/syncset1/nextResource. Invalid resource Id format",
|
||||
want: &ArmResource{
|
||||
SubscriptionID: "abc",
|
||||
ResourceGroup: "v4-eastus",
|
||||
Provider: "Microsoft.RedHatOpenShift",
|
||||
ResourceName: "cluster1",
|
||||
ResourceType: "openshiftclusters",
|
||||
SubResource: SubResource{
|
||||
ResourceName: "syncset1",
|
||||
ResourceType: "syncSets",
|
||||
SubResource: &SubResource{
|
||||
ResourceName: "nextResource",
|
||||
ResourceType: "syncSets",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
actual, err := ParseArmResourceId(test.input)
|
||||
if err != nil {
|
||||
if test.err != err.Error() {
|
||||
t.Fatalf("want %v, got %v", test.err, err)
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
actual, err := ParseArmResourceId(test.input)
|
||||
if err != nil {
|
||||
if test.err != err.Error() {
|
||||
t.Errorf("%s: want %v, got %v", test.name, test.err, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !reflect.DeepEqual(actual, test.want) {
|
||||
t.Fatalf("want %v, got %v", test.want, actual)
|
||||
}
|
||||
if !reflect.DeepEqual(actual, test.want) {
|
||||
t.Errorf("%s: want %v, got %v", test.name, test.want, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,9 @@ package stringutils
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the Apache License 2.0.
|
||||
|
||||
import "strings"
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// LastTokenByte splits s on sep and returns the last token
|
||||
func LastTokenByte(s string, sep byte) string {
|
||||
|
|
Загрузка…
Ссылка в новой задаче