Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MIWI cluster Dynamic Validation update for strict 1:1 matching for provided Platform Workload Identity to expected OCP Operators #3966

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -520,9 +520,13 @@ func (sv openShiftClusterStaticValidator) validatePlatformIdentities(oc *OpenShi
return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, "identity", "Cluster identity and platform workload identities require each other.")
}

if operatorRolePresent && len(oc.Identity.UserAssignedIdentities) != 1 {
if clusterIdentityPresent && len(oc.Identity.UserAssignedIdentities) != 1 {
return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, "identity", "The provided cluster identity is invalid; there should be exactly one.")
}

if operatorRolePresent && len(pwip.PlatformWorkloadIdentities) == 0 {
return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, "properties.platformWorkloadIdentityProfile", "Platform workload identity profile cannot be empty.")
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, "properties.platformWorkloadIdentityProfile", "Platform workload identity profile cannot be empty.")
return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, "properties.platformWorkloadIdentityProfile.platformWorkloadIdentities", "The set of platform workload identities cannot be empty.")

Suggesting this change because the error message should point to the exact property that we're validating to make the message easily actionable.

}

return nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -1427,6 +1427,9 @@ func TestOpenShiftClusterStaticValidatePlatformWorkloadIdentityProfile(t *testin
}
oc.Properties.ServicePrincipalProfile = nil
oc.Properties.PlatformWorkloadIdentityProfile = &PlatformWorkloadIdentityProfile{
PlatformWorkloadIdentities: map[string]PlatformWorkloadIdentity{
"FAKE-OPERATOR": platformIdentity1,
},
UpgradeableTo: &validUpgradeableToValue,
}
},
Expand All @@ -1441,11 +1444,45 @@ func TestOpenShiftClusterStaticValidatePlatformWorkloadIdentityProfile(t *testin
}
oc.Properties.ServicePrincipalProfile = nil
oc.Properties.PlatformWorkloadIdentityProfile = &PlatformWorkloadIdentityProfile{
PlatformWorkloadIdentities: map[string]PlatformWorkloadIdentity{
"FAKE-OPERATOR": platformIdentity1,
},
UpgradeableTo: &invalidUpgradeableToValue,
}
},
wantErr: `400: InvalidParameter: properties.platformWorkloadIdentityProfile.UpgradeableTo[16.107.invalid]: UpgradeableTo must be a valid OpenShift version in the format 'x.y.z'.`,
},
{
name: "No platform identities provided in PlatformWorkloadIdentityProfile - nil",
modify: func(oc *OpenShiftCluster) {
oc.Identity = &ManagedServiceIdentity{
UserAssignedIdentities: map[string]UserAssignedIdentity{
"Dummy": {},
},
}
oc.Properties.ServicePrincipalProfile = nil
oc.Properties.PlatformWorkloadIdentityProfile = &PlatformWorkloadIdentityProfile{
UpgradeableTo: &invalidUpgradeableToValue,
}
},
wantErr: "400: InvalidParameter: properties.platformWorkloadIdentityProfile: Platform workload identity profile cannot be empty.",
},
{
name: "No platform identities provided in PlatformWorkloadIdentityProfile - empty map",
modify: func(oc *OpenShiftCluster) {
oc.Identity = &ManagedServiceIdentity{
UserAssignedIdentities: map[string]UserAssignedIdentity{
"Dummy": {},
},
}
oc.Properties.ServicePrincipalProfile = nil
oc.Properties.PlatformWorkloadIdentityProfile = &PlatformWorkloadIdentityProfile{
PlatformWorkloadIdentities: map[string]PlatformWorkloadIdentity{},
UpgradeableTo: &invalidUpgradeableToValue,
}
},
wantErr: "400: InvalidParameter: properties.platformWorkloadIdentityProfile: Platform workload identity profile cannot be empty.",
},
}

updateTests := []*validateTest{
Expand Down Expand Up @@ -1580,6 +1617,46 @@ func TestOpenShiftClusterStaticValidatePlatformWorkloadIdentityProfile(t *testin
},
wantErr: "400: PropertyChangeNotAllowed: properties.platformWorkloadIdentityProfile.platformWorkloadIdentities: Operator identity cannot be removed or have its name changed.",
},
{
name: "No platform identities provided in PlatformWorkloadIdentityProfile - empty map",
current: func(oc *OpenShiftCluster) {
oc.Properties.PlatformWorkloadIdentityProfile = &PlatformWorkloadIdentityProfile{
PlatformWorkloadIdentities: map[string]PlatformWorkloadIdentity{
"operator1": platformIdentity1,
},
}
oc.Identity = &ManagedServiceIdentity{
UserAssignedIdentities: map[string]UserAssignedIdentity{
"first": clusterIdentity1,
},
}
oc.Properties.ServicePrincipalProfile = nil
},
modify: func(oc *OpenShiftCluster) {
oc.Properties.PlatformWorkloadIdentityProfile.PlatformWorkloadIdentities = map[string]PlatformWorkloadIdentity{}
},
wantErr: "400: InvalidParameter: properties.platformWorkloadIdentityProfile: Platform workload identity profile cannot be empty.",
},
{
name: "No platform identities provided in PlatformWorkloadIdentityProfile - nil",
current: func(oc *OpenShiftCluster) {
oc.Properties.PlatformWorkloadIdentityProfile = &PlatformWorkloadIdentityProfile{
PlatformWorkloadIdentities: map[string]PlatformWorkloadIdentity{
"operator1": platformIdentity1,
},
}
oc.Identity = &ManagedServiceIdentity{
UserAssignedIdentities: map[string]UserAssignedIdentity{
"first": clusterIdentity1,
},
}
oc.Properties.ServicePrincipalProfile = nil
},
modify: func(oc *OpenShiftCluster) {
oc.Properties.PlatformWorkloadIdentityProfile.PlatformWorkloadIdentities = nil
},
wantErr: "400: InvalidParameter: properties.platformWorkloadIdentityProfile: Platform workload identity profile cannot be empty.",
},
}

runTests(t, testModeCreate, createTests)
Expand Down
8 changes: 5 additions & 3 deletions pkg/cluster/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,9 +305,11 @@ func New(ctx context.Context, log *logrus.Entry, _env env.Interface, db database
}

if doc.OpenShiftCluster.UsesWorkloadIdentity() {
err = m.platformWorkloadIdentityRolesByVersion.PopulatePlatformWorkloadIdentityRolesByVersion(ctx, doc.OpenShiftCluster, dbPlatformWorkloadIdentityRoleSets)
if err != nil {
return nil, err
if m.doc.OpenShiftCluster.Properties.ProvisioningState != api.ProvisioningStateDeleting {
err = m.platformWorkloadIdentityRolesByVersion.PopulatePlatformWorkloadIdentityRolesByVersion(ctx, doc.OpenShiftCluster, dbPlatformWorkloadIdentityRoleSets)
if err != nil {
return nil, err
}
}

msiResourceId, err := m.doc.OpenShiftCluster.ClusterMsiResourceId()
Expand Down
10 changes: 1 addition & 9 deletions pkg/cluster/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -391,15 +391,7 @@ func (m *manager) deleteFederatedCredentials(ctx context.Context) error {
}
}

platformWIRolesByRoleName := m.platformWorkloadIdentityRolesByVersion.GetPlatformWorkloadIdentityRolesByRoleName()
platformWorkloadIdentities := m.doc.OpenShiftCluster.Properties.PlatformWorkloadIdentityProfile.PlatformWorkloadIdentities

for name, identity := range platformWorkloadIdentities {
_, exists := platformWIRolesByRoleName[name]
if !exists {
continue
}

for _, identity := range m.doc.OpenShiftCluster.Properties.PlatformWorkloadIdentityProfile.PlatformWorkloadIdentities {
identityResourceId, err := azure.ParseResourceID(identity.ResourceID)
if err != nil {
return err
Expand Down
54 changes: 0 additions & 54 deletions pkg/cluster/delete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@ import (
mock_subnet "github.com/Azure/ARO-RP/pkg/util/mocks/subnet"
"github.com/Azure/ARO-RP/pkg/util/platformworkloadidentity"
"github.com/Azure/ARO-RP/pkg/util/pointerutils"
testdatabase "github.com/Azure/ARO-RP/test/database"
"github.com/Azure/ARO-RP/test/util/deterministicuuid"
utilerror "github.com/Azure/ARO-RP/test/util/error"
)

Expand Down Expand Up @@ -813,57 +811,6 @@ func TestDeleteFederatedCredentials(t *testing.T) {
}

for _, tt := range tests {
uuidGen := deterministicuuid.NewTestUUIDGenerator(deterministicuuid.OPENSHIFT_VERSIONS)
dbPlatformWorkloadIdentityRoleSets, _ := testdatabase.NewFakePlatformWorkloadIdentityRoleSets(uuidGen)
f := testdatabase.NewFixture().WithPlatformWorkloadIdentityRoleSets(dbPlatformWorkloadIdentityRoleSets, uuidGen)
pir := platformworkloadidentity.NewPlatformWorkloadIdentityRolesByVersionService()
f.AddPlatformWorkloadIdentityRoleSetDocuments(&api.PlatformWorkloadIdentityRoleSetDocument{
PlatformWorkloadIdentityRoleSet: &api.PlatformWorkloadIdentityRoleSet{
Name: "testRoleSet",
Properties: api.PlatformWorkloadIdentityRoleSetProperties{
OpenShiftVersion: "4.14",
PlatformWorkloadIdentityRoles: []api.PlatformWorkloadIdentityRole{
{
OperatorName: "CloudControllerManager",
ServiceAccounts: []string{ccmServiceAccountName},
},
{
OperatorName: "ClusterIngressOperator",
ServiceAccounts: []string{ingressServiceAccountName},
},
},
},
},
},
&api.PlatformWorkloadIdentityRoleSetDocument{
PlatformWorkloadIdentityRoleSet: &api.PlatformWorkloadIdentityRoleSet{
Name: "testRoleSet",
Properties: api.PlatformWorkloadIdentityRoleSetProperties{
OpenShiftVersion: "4.15",
PlatformWorkloadIdentityRoles: []api.PlatformWorkloadIdentityRole{
{
OperatorName: "CloudControllerManager",
ServiceAccounts: []string{"openshift-cloud-controller-manager:cloud-controller-manager"},
},
{
OperatorName: "ClusterIngressOperator",
ServiceAccounts: []string{"openshift-ingress-operator:ingress-operator"},
},
},
},
},
},
)
err := f.Create()
if err != nil {
t.Fatal(err)
}

err = pir.PopulatePlatformWorkloadIdentityRolesByVersion(ctx, tt.doc.OpenShiftCluster, dbPlatformWorkloadIdentityRoleSets)
if err != nil {
t.Fatal(err)
}

t.Run(tt.name, func(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
Expand All @@ -876,7 +823,6 @@ func TestDeleteFederatedCredentials(t *testing.T) {
m := manager{
log: logrus.NewEntry(logrus.StandardLogger()),
doc: tt.doc,
platformWorkloadIdentityRolesByVersion: pir,
clusterMsiFederatedIdentityCredentials: federatedIdentityCredentials,
}

Expand Down
3 changes: 2 additions & 1 deletion pkg/cluster/deploybaseresources.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"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/platformworkloadidentity"
"github.com/Azure/ARO-RP/pkg/util/pointerutils"
"github.com/Azure/ARO-RP/pkg/util/stringutils"
)
Expand Down Expand Up @@ -485,7 +486,7 @@ func (m *manager) federateIdentityCredentials(ctx context.Context) error {

platformWIRole, exists := platformWIRolesByRoleName[name]
if !exists {
continue
return platformworkloadidentity.GetPlatformWorkloadIdentityMismatchError(m.doc.OpenShiftCluster, platformWIRolesByRoleName)
}

for _, sa := range platformWIRole.ServiceAccounts {
Expand Down
3 changes: 2 additions & 1 deletion pkg/cluster/deploybaseresources_additional.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/Azure/ARO-RP/pkg/api"
"github.com/Azure/ARO-RP/pkg/util/arm"
"github.com/Azure/ARO-RP/pkg/util/azureclient"
"github.com/Azure/ARO-RP/pkg/util/platformworkloadidentity"
"github.com/Azure/ARO-RP/pkg/util/rbac"
"github.com/Azure/ARO-RP/pkg/util/stringutils"
)
Expand Down Expand Up @@ -106,7 +107,7 @@ func (m *manager) platformWorkloadIdentityRBAC() ([]*arm.Resource, error) {
for name, identity := range platformWorkloadIdentities {
role, exists := platformWIRolesByRoleName[name]
if !exists {
continue
return nil, platformworkloadidentity.GetPlatformWorkloadIdentityMismatchError(m.doc.OpenShiftCluster, platformWIRolesByRoleName)
}

if strings.TrimSpace(identity.ObjectID) == "" {
Expand Down
4 changes: 2 additions & 2 deletions pkg/cluster/deploybaseresources_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1811,7 +1811,7 @@ func TestGenerateFederatedIdentityCredentials(t *testing.T) {
wantErr: "OIDCIssuer is nil",
},
{
name: "Success - Operator name do not exists in PlatformWorkloadIdentityProfile",
name: "Fail - Operator name do not exists in PlatformWorkloadIdentityProfile",
oc: &api.OpenShiftClusterDocument{
ID: docID,
OpenShiftCluster: &api.OpenShiftCluster{
Expand Down Expand Up @@ -1857,7 +1857,7 @@ func TestGenerateFederatedIdentityCredentials(t *testing.T) {
},
)
},
wantErr: "",
wantErr: fmt.Sprintf("400: %s: properties.PlatformWorkloadIdentityProfile.PlatformWorkloadIdentities: There's a mismatch between the required and expected set of platform workload identities for the requested OpenShift minor version '%s or %s'. The required platform workload identities are '[CloudControllerManager]'", api.CloudErrorCodePlatformWorkloadIdentityMismatch, "4.14", "4.15"),
},
} {
uuidGen := deterministicuuid.NewTestUUIDGenerator(deterministicuuid.OPENSHIFT_VERSIONS)
Expand Down
54 changes: 28 additions & 26 deletions pkg/cluster/workloadidentityresources.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,33 +81,35 @@ func (m *manager) generatePlatformWorkloadIdentitySecrets() ([]*corev1.Secret, e

secrets := []*corev1.Secret{}
for name, identity := range m.doc.OpenShiftCluster.Properties.PlatformWorkloadIdentityProfile.PlatformWorkloadIdentities {
if role, ok := roles[name]; ok {
// Skip creating a secret for the ARO Operator. This will be
// generated by the ARO Operator deployer instead
// (see pkg/operator/deploy/deploy.go#generateOperatorIdentitySecret())
if role.OperatorName == pkgoperator.OperatorIdentityName {
continue
}

secrets = append(secrets, &corev1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.Identifier(),
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Namespace: role.SecretLocation.Namespace,
Name: role.SecretLocation.Name,
},
Type: corev1.SecretTypeOpaque,
StringData: map[string]string{
"azure_client_id": identity.ClientID,
"azure_subscription_id": subscriptionId,
"azure_tenant_id": tenantId,
"azure_region": region,
"azure_federated_token_file": azureFederatedTokenFileLocation,
},
})
role, exists := roles[name]
if !exists {
return nil, platformworkloadidentity.GetPlatformWorkloadIdentityMismatchError(m.doc.OpenShiftCluster, roles)
}
// Skip creating a secret for the ARO Operator. This will be
// generated by the ARO Operator deployer instead
// (see pkg/operator/deploy/deploy.go#generateOperatorIdentitySecret())
if role.OperatorName == pkgoperator.OperatorIdentityName {
continue
}

secrets = append(secrets, &corev1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.Identifier(),
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Namespace: role.SecretLocation.Namespace,
Name: role.SecretLocation.Name,
},
Type: corev1.SecretTypeOpaque,
StringData: map[string]string{
"azure_client_id": identity.ClientID,
"azure_subscription_id": subscriptionId,
"azure_tenant_id": tenantId,
"azure_region": region,
"azure_federated_token_file": azureFederatedTokenFileLocation,
},
})
}

return secrets, nil
Expand Down
16 changes: 9 additions & 7 deletions pkg/cluster/workloadidentityresources_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ func TestGeneratePlatformWorkloadIdentitySecrets(t *testing.T) {
identities map[string]api.PlatformWorkloadIdentity
roles []api.PlatformWorkloadIdentityRole
want []*corev1.Secret
wantErr string
}{
{
name: "no identities, no secrets",
Expand Down Expand Up @@ -394,17 +395,15 @@ func TestGeneratePlatformWorkloadIdentitySecrets(t *testing.T) {
},
},
{
name: "ignores identities with no role present",
name: "error - identities with unexpected operator name present",
identities: map[string]api.PlatformWorkloadIdentity{
"foo": {
ClientID: "00f00f00-0f00-0f00-0f00-f00f00f00f00",
},
"bar": {
ClientID: "00ba4ba4-0ba4-0ba4-0ba4-ba4ba4ba4ba4",
},
},
roles: []api.PlatformWorkloadIdentityRole{},
want: []*corev1.Secret{},
roles: []api.PlatformWorkloadIdentityRole{},
want: []*corev1.Secret{},
wantErr: fmt.Sprintf("400: %s: properties.PlatformWorkloadIdentityProfile.PlatformWorkloadIdentities: There's a mismatch between the required and expected set of platform workload identities for the requested OpenShift minor version '%s'. The required platform workload identities are '[]'", api.CloudErrorCodePlatformWorkloadIdentityMismatch, "4.14"),
},
{
name: "skips ARO operator identity",
Expand Down Expand Up @@ -466,6 +465,9 @@ func TestGeneratePlatformWorkloadIdentitySecrets(t *testing.T) {
PlatformWorkloadIdentityProfile: &api.PlatformWorkloadIdentityProfile{
PlatformWorkloadIdentities: tt.identities,
},
ClusterProfile: api.ClusterProfile{
Version: "4.14.40",
},
},
},
},
Expand All @@ -484,7 +486,7 @@ func TestGeneratePlatformWorkloadIdentitySecrets(t *testing.T) {
}
got, err := m.generatePlatformWorkloadIdentitySecrets()

utilerror.AssertErrorMessage(t, err, "")
utilerror.AssertErrorMessage(t, err, tt.wantErr)
assert.ElementsMatch(t, got, tt.want)
})
}
Expand Down
Loading
Loading