Status condition messages for IDP transforms show index of invalid IDP

This commit is contained in:
Ryan Richard 2023-07-18 15:00:58 -07:00
parent b89e6d9d93
commit e42e3ca421
2 changed files with 453 additions and 156 deletions

View File

@ -328,7 +328,7 @@ func (c *federationDomainWatcherController) makeLegacyFederationDomainIssuer(
conditions = appendIdentityProviderDuplicateDisplayNamesCondition(sets.Set[string]{}, conditions)
conditions = appendIdentityProviderObjectRefAPIGroupSuffixCondition(c.apiGroup, []string{}, conditions)
conditions = appendIdentityProviderObjectRefKindCondition(c.sortedAllowedKinds(), []string{}, conditions)
conditions = appendTransformsConstantsNamesUniqueCondition(sets.Set[string]{}, conditions)
conditions = appendTransformsConstantsNamesUniqueCondition([]string{}, conditions)
conditions = appendTransformsExpressionsValidCondition([]string{}, conditions)
conditions = appendTransformsExamplesPassedCondition([]string{}, conditions)
@ -347,6 +347,7 @@ func (c *federationDomainWatcherController) makeFederationDomainIssuerWithExplic
duplicateDisplayNames := sets.Set[string]{}
badAPIGroupNames := []string{}
badKinds := []string{}
validationErrorMessages := &transformsValidationErrorMessages{}
for index, idp := range federationDomain.Spec.IdentityProviders {
idpIsValid := true
@ -397,7 +398,8 @@ func (c *federationDomainWatcherController) makeFederationDomainIssuerWithExplic
var err error
var pipeline *idtransform.TransformationPipeline
var allExamplesPassed bool
pipeline, allExamplesPassed, conditions, err = c.makeTransformationPipelineAndEvaluateExamplesForIdentityProvider(ctx, idp, index, conditions)
pipeline, allExamplesPassed, err = c.makeTransformationPipelineAndEvaluateExamplesForIdentityProvider(
ctx, idp, index, validationErrorMessages)
if err != nil {
return nil, nil, err
}
@ -448,6 +450,10 @@ func (c *federationDomainWatcherController) makeFederationDomainIssuerWithExplic
conditions = appendIdentityProviderObjectRefAPIGroupSuffixCondition(c.apiGroup, badAPIGroupNames, conditions)
conditions = appendIdentityProviderObjectRefKindCondition(c.sortedAllowedKinds(), badKinds, conditions)
conditions = appendTransformsConstantsNamesUniqueCondition(validationErrorMessages.errorsForConstants, conditions)
conditions = appendTransformsExpressionsValidCondition(validationErrorMessages.errorsForExpressions, conditions)
conditions = appendTransformsExamplesPassedCondition(validationErrorMessages.errorsForExamples, conditions)
return federationDomainIssuer, conditions, nil
}
func (c *federationDomainWatcherController) findIDPsUIDByObjectRef(objectRef corev1.TypedLocalObjectReference, namespace string) (types.UID, bool, error) {
@ -482,27 +488,36 @@ func (c *federationDomainWatcherController) makeTransformationPipelineAndEvaluat
ctx context.Context,
idp configv1alpha1.FederationDomainIdentityProvider,
idpIndex int,
conditions []*configv1alpha1.Condition,
) (*idtransform.TransformationPipeline, bool, []*configv1alpha1.Condition, error) {
consts, conditions, err := c.makeTransformsConstants(idp, conditions)
validationErrorMessages *transformsValidationErrorMessages,
) (*idtransform.TransformationPipeline, bool, error) {
consts, errorsForConstants, err := c.makeTransformsConstantsForIdentityProvider(idp, idpIndex)
if err != nil {
return nil, false, nil, err
return nil, false, err
}
if len(errorsForConstants) > 0 {
validationErrorMessages.errorsForConstants = append(validationErrorMessages.errorsForConstants, errorsForConstants)
}
pipeline, conditions, err := c.makeTransformationPipeline(idp, idpIndex, consts, conditions)
pipeline, errorsForExpressions, err := c.makeTransformationPipelineForIdentityProvider(idp, idpIndex, consts)
if err != nil {
return nil, false, nil, err
return nil, false, err
}
if len(errorsForExpressions) > 0 {
validationErrorMessages.errorsForExpressions = append(validationErrorMessages.errorsForExpressions, errorsForExpressions)
}
allExamplesPassed, conditions := c.evaluateExamples(ctx, idp, idpIndex, pipeline, conditions)
allExamplesPassed, errorsForExamples := c.evaluateExamplesForIdentityProvider(ctx, idp, idpIndex, pipeline)
if len(errorsForExamples) > 0 {
validationErrorMessages.errorsForExamples = append(validationErrorMessages.errorsForExamples, errorsForExamples)
}
return pipeline, allExamplesPassed, conditions, nil
return pipeline, allExamplesPassed, nil
}
func (c *federationDomainWatcherController) makeTransformsConstants(
func (c *federationDomainWatcherController) makeTransformsConstantsForIdentityProvider(
idp configv1alpha1.FederationDomainIdentityProvider,
conditions []*configv1alpha1.Condition,
) (*celtransformer.TransformationConstants, []*configv1alpha1.Condition, error) {
idpIndex int,
) (*celtransformer.TransformationConstants, string, error) {
consts := &celtransformer.TransformationConstants{
StringConstants: map[string]string{},
StringListConstants: map[string][]string{},
@ -525,21 +540,24 @@ func (c *federationDomainWatcherController) makeTransformsConstants(
consts.StringListConstants[constant.Name] = constant.StringListValue
default:
// This shouldn't really happen since the CRD validates it, but handle it as an error.
return nil, nil, fmt.Errorf("one of spec.identityProvider[].transforms.constants[].type is invalid: %q", constant.Type)
return nil, "", fmt.Errorf("one of spec.identityProvider[].transforms.constants[].type is invalid: %q", constant.Type)
}
}
conditions = appendTransformsConstantsNamesUniqueCondition(duplicateConstNames, conditions)
if duplicateConstNames.Len() > 0 {
return consts, fmt.Sprintf(
"the names specified by .spec.identityProviders[%d].transforms.constants[].name contain duplicates: %s",
idpIndex, strings.Join(sortAndQuote(duplicateConstNames.UnsortedList()), ", ")), nil
}
return consts, conditions, nil
return consts, "", nil
}
func (c *federationDomainWatcherController) makeTransformationPipeline(
func (c *federationDomainWatcherController) makeTransformationPipelineForIdentityProvider(
idp configv1alpha1.FederationDomainIdentityProvider,
idpIndex int,
consts *celtransformer.TransformationConstants,
conditions []*configv1alpha1.Condition,
) (*idtransform.TransformationPipeline, []*configv1alpha1.Condition, error) {
) (*idtransform.TransformationPipeline, string, error) {
pipeline := idtransform.NewTransformationPipeline()
expressionsCompileErrors := []string{}
@ -558,7 +576,7 @@ func (c *federationDomainWatcherController) makeTransformationPipeline(
}
default:
// This shouldn't really happen since the CRD validates it, but handle it as an error.
return nil, nil, fmt.Errorf("one of spec.identityProvider[].transforms.expressions[].type is invalid: %q", expr.Type)
return nil, "", fmt.Errorf("one of spec.identityProvider[].transforms.expressions[].type is invalid: %q", expr.Type)
}
compiledTransform, err := c.celTransformer.CompileTransformation(rawTransform, consts)
@ -571,30 +589,29 @@ func (c *federationDomainWatcherController) makeTransformationPipeline(
pipeline.AppendTransformation(compiledTransform)
}
conditions = appendTransformsExpressionsValidCondition(expressionsCompileErrors, conditions)
if len(expressionsCompileErrors) > 0 {
// One or more of the expressions did not compile, so we don't have a useful pipeline to return.
return nil, conditions, nil
// Return the validation messages.
return nil, strings.Join(expressionsCompileErrors, "\n\n"), nil
}
return pipeline, conditions, nil
return pipeline, "", nil
}
func (c *federationDomainWatcherController) evaluateExamples(
func (c *federationDomainWatcherController) evaluateExamplesForIdentityProvider(
ctx context.Context,
idp configv1alpha1.FederationDomainIdentityProvider,
idpIndex int,
pipeline *idtransform.TransformationPipeline,
conditions []*configv1alpha1.Condition,
) (bool, []*configv1alpha1.Condition) {
) (bool, string) {
const errorFmt = ".spec.identityProviders[%d].transforms.examples[%d] example failed:\nexpected: %s\nactual: %s"
examplesErrors := []string{}
if pipeline == nil {
// Unable to evaluate the conditions where the pipeline of expressions was invalid.
conditions = appendTransformsExamplesPassedCondition(nil, conditions)
return false, conditions
return false, fmt.Sprintf(
"unable to check if the examples specified by .spec.identityProviders[%d].transforms.examples[] had errors because an expression was invalid",
idpIndex)
}
// Run all the provided transform examples. If any fail, put errors on the FederationDomain status.
@ -652,13 +669,11 @@ func (c *federationDomainWatcherController) evaluateExamples(
}
}
conditions = appendTransformsExamplesPassedCondition(examplesErrors, conditions)
if len(examplesErrors) > 0 {
return false, strings.Join(examplesErrors, "\n\n")
}
return len(examplesErrors) == 0, conditions
}
func (c *federationDomainWatcherController) sortedAllowedKinds() []string {
return sortAndQuote(c.allowedKinds.UnsortedList())
return true, ""
}
func appendIdentityProviderObjectRefKindCondition(expectedKinds []string, badSuffixNames []string, conditions []*configv1alpha1.Condition) []*configv1alpha1.Condition {
@ -707,8 +722,7 @@ func appendTransformsExpressionsValidCondition(errors []string, conditions []*co
Type: typeTransformsExpressionsValid,
Status: configv1alpha1.ConditionFalse,
Reason: reasonInvalidTransformsExpressions,
Message: fmt.Sprintf("the expressions specified by .spec.identityProviders[].transforms.expressions[] were invalid:\n\n%s",
strings.Join(errors, "\n\n")),
Message: strings.Join(errors, "\n\n"),
})
} else {
conditions = append(conditions, &configv1alpha1.Condition{
@ -722,23 +736,14 @@ func appendTransformsExpressionsValidCondition(errors []string, conditions []*co
}
func appendTransformsExamplesPassedCondition(errors []string, conditions []*configv1alpha1.Condition) []*configv1alpha1.Condition {
switch {
case errors == nil:
conditions = append(conditions, &configv1alpha1.Condition{
Type: typeTransformsExamplesPassed,
Status: configv1alpha1.ConditionUnknown,
Reason: reasonUnableToValidate,
Message: "unable to check if the examples specified by .spec.identityProviders[].transforms.examples[] had errors because an expression was invalid",
})
case len(errors) > 0:
if len(errors) > 0 {
conditions = append(conditions, &configv1alpha1.Condition{
Type: typeTransformsExamplesPassed,
Status: configv1alpha1.ConditionFalse,
Reason: reasonTransformsExamplesFailed,
Message: fmt.Sprintf("the examples specified by .spec.identityProviders[].transforms.examples[] had errors:\n\n%s",
strings.Join(errors, "\n\n")),
Message: strings.Join(errors, "\n\n"),
})
default:
} else {
conditions = append(conditions, &configv1alpha1.Condition{
Type: typeTransformsExamplesPassed,
Status: configv1alpha1.ConditionTrue,
@ -749,6 +754,25 @@ func appendTransformsExamplesPassedCondition(errors []string, conditions []*conf
return conditions
}
func appendTransformsConstantsNamesUniqueCondition(errors []string, conditions []*configv1alpha1.Condition) []*configv1alpha1.Condition {
if len(errors) > 0 {
conditions = append(conditions, &configv1alpha1.Condition{
Type: typeTransformsConstantsNamesUnique,
Status: configv1alpha1.ConditionFalse,
Reason: reasonDuplicateConstantsNames,
Message: strings.Join(errors, "\n\n"),
})
} else {
conditions = append(conditions, &configv1alpha1.Condition{
Type: typeTransformsConstantsNamesUnique,
Status: configv1alpha1.ConditionTrue,
Reason: reasonSuccess,
Message: "the names specified by .spec.identityProviders[].transforms.constants[].name are unique",
})
}
return conditions
}
func appendIdentityProviderDuplicateDisplayNamesCondition(duplicateDisplayNames sets.Set[string], conditions []*configv1alpha1.Condition) []*configv1alpha1.Condition {
if duplicateDisplayNames.Len() > 0 {
conditions = append(conditions, &configv1alpha1.Condition{
@ -769,26 +793,6 @@ func appendIdentityProviderDuplicateDisplayNamesCondition(duplicateDisplayNames
return conditions
}
func appendTransformsConstantsNamesUniqueCondition(duplicateConstNames sets.Set[string], conditions []*configv1alpha1.Condition) []*configv1alpha1.Condition {
if duplicateConstNames.Len() > 0 {
conditions = append(conditions, &configv1alpha1.Condition{
Type: typeTransformsConstantsNamesUnique,
Status: configv1alpha1.ConditionFalse,
Reason: reasonDuplicateConstantsNames,
Message: fmt.Sprintf("the names specified by .spec.identityProviders[].transforms.constants[].name contain duplicates: %s",
strings.Join(sortAndQuote(duplicateConstNames.UnsortedList()), ", ")),
})
} else {
conditions = append(conditions, &configv1alpha1.Condition{
Type: typeTransformsConstantsNamesUnique,
Status: configv1alpha1.ConditionTrue,
Reason: reasonSuccess,
Message: "the names specified by .spec.identityProviders[].transforms.constants[].name are unique",
})
}
return conditions
}
func appendIssuerURLValidCondition(err error, conditions []*configv1alpha1.Condition) []*configv1alpha1.Condition {
if err != nil {
// Note that the FederationDomainIssuer constructors only validate the Issuer URL,
@ -810,15 +814,6 @@ func appendIssuerURLValidCondition(err error, conditions []*configv1alpha1.Condi
return conditions
}
func sortAndQuote(strs []string) []string {
quoted := make([]string, 0, len(strs))
for _, s := range strs {
quoted = append(quoted, fmt.Sprintf("%q", s))
}
sort.Strings(quoted)
return quoted
}
func (c *federationDomainWatcherController) updateStatus(
ctx context.Context,
federationDomain *configv1alpha1.FederationDomain,
@ -859,6 +854,25 @@ func (c *federationDomainWatcherController) updateStatus(
return err
}
func sortAndQuote(strs []string) []string {
quoted := make([]string, 0, len(strs))
for _, s := range strs {
quoted = append(quoted, fmt.Sprintf("%q", s))
}
sort.Strings(quoted)
return quoted
}
func (c *federationDomainWatcherController) sortedAllowedKinds() []string {
return sortAndQuote(c.allowedKinds.UnsortedList())
}
type transformsValidationErrorMessages struct {
errorsForConstants []string
errorsForExpressions []string
errorsForExamples []string
}
type crossFederationDomainConfigValidator struct {
issuerCounts map[string]int
uniqueSecretNamesPerIssuerAddress map[string]map[string]bool

View File

@ -411,14 +411,14 @@ func TestTestFederationDomainWatcherControllerSync(t *testing.T) {
}
}
sadConstNamesUniqueCondition := func(duplicateNames string, time metav1.Time, observedGeneration int64) configv1alpha1.Condition {
sadConstNamesUniqueCondition := func(errorMessages string, time metav1.Time, observedGeneration int64) configv1alpha1.Condition {
return configv1alpha1.Condition{
Type: "TransformsConstantsNamesUnique",
Status: "False",
ObservedGeneration: observedGeneration,
LastTransitionTime: time,
Reason: "DuplicateConstantsNames",
Message: fmt.Sprintf("the names specified by .spec.identityProviders[].transforms.constants[].name contain duplicates: %s", duplicateNames),
Message: errorMessages,
}
}
@ -440,7 +440,7 @@ func TestTestFederationDomainWatcherControllerSync(t *testing.T) {
ObservedGeneration: observedGeneration,
LastTransitionTime: time,
Reason: "InvalidTransformsExpressions",
Message: fmt.Sprintf("the expressions specified by .spec.identityProviders[].transforms.expressions[] were invalid:\n\n%s", errorMessages),
Message: errorMessages,
}
}
@ -462,18 +462,7 @@ func TestTestFederationDomainWatcherControllerSync(t *testing.T) {
ObservedGeneration: observedGeneration,
LastTransitionTime: time,
Reason: "TransformsExamplesFailed",
Message: fmt.Sprintf("the examples specified by .spec.identityProviders[].transforms.examples[] had errors:\n\n%s", errorMessages),
}
}
unknownTransformationExamplesCondition := func(time metav1.Time, observedGeneration int64) configv1alpha1.Condition {
return configv1alpha1.Condition{
Type: "TransformsExamplesPassed",
Status: "Unknown",
ObservedGeneration: observedGeneration,
LastTransitionTime: time,
Reason: "UnableToValidate",
Message: "unable to check if the examples specified by .spec.identityProviders[].transforms.examples[] had errors because an expression was invalid",
Message: errorMessages,
}
}
@ -587,7 +576,7 @@ func TestTestFederationDomainWatcherControllerSync(t *testing.T) {
wantFDIssuers: []*federationdomainproviders.FederationDomainIssuer{},
},
{
name: "legacy config: when no identity provider is specified on federation domains, but exactly one identity " +
name: "legacy config: when no identity provider is specified on federation domains, but exactly one OIDC identity " +
"provider resource exists on cluster, the controller will set a default IDP on each federation domain " +
"matching the only identity provider found",
inputObjects: []runtime.Object{
@ -610,6 +599,54 @@ func TestTestFederationDomainWatcherControllerSync(t *testing.T) {
),
},
},
{
name: "legacy config: when no identity provider is specified on federation domains, but exactly one LDAP identity " +
"provider resource exists on cluster, the controller will set a default IDP on each federation domain " +
"matching the only identity provider found",
inputObjects: []runtime.Object{
federationDomain1,
federationDomain2,
ldapIdentityProvider,
},
wantFDIssuers: []*federationdomainproviders.FederationDomainIssuer{
federationDomainIssuerWithDefaultIDP(t, federationDomain1.Spec.Issuer, ldapIdentityProvider.ObjectMeta),
federationDomainIssuerWithDefaultIDP(t, federationDomain2.Spec.Issuer, ldapIdentityProvider.ObjectMeta),
},
wantStatusUpdates: []*configv1alpha1.FederationDomain{
expectedFederationDomainStatusUpdate(federationDomain1,
configv1alpha1.FederationDomainPhaseReady,
allHappyConditionsLegacyConfigurationSuccess(federationDomain1.Spec.Issuer, ldapIdentityProvider.Name, frozenMetav1Now, 123),
),
expectedFederationDomainStatusUpdate(federationDomain2,
configv1alpha1.FederationDomainPhaseReady,
allHappyConditionsLegacyConfigurationSuccess(federationDomain2.Spec.Issuer, ldapIdentityProvider.Name, frozenMetav1Now, 123),
),
},
},
{
name: "legacy config: when no identity provider is specified on federation domains, but exactly one AD identity " +
"provider resource exists on cluster, the controller will set a default IDP on each federation domain " +
"matching the only identity provider found",
inputObjects: []runtime.Object{
federationDomain1,
federationDomain2,
adIdentityProvider,
},
wantFDIssuers: []*federationdomainproviders.FederationDomainIssuer{
federationDomainIssuerWithDefaultIDP(t, federationDomain1.Spec.Issuer, adIdentityProvider.ObjectMeta),
federationDomainIssuerWithDefaultIDP(t, federationDomain2.Spec.Issuer, adIdentityProvider.ObjectMeta),
},
wantStatusUpdates: []*configv1alpha1.FederationDomain{
expectedFederationDomainStatusUpdate(federationDomain1,
configv1alpha1.FederationDomainPhaseReady,
allHappyConditionsLegacyConfigurationSuccess(federationDomain1.Spec.Issuer, adIdentityProvider.Name, frozenMetav1Now, 123),
),
expectedFederationDomainStatusUpdate(federationDomain2,
configv1alpha1.FederationDomainPhaseReady,
allHappyConditionsLegacyConfigurationSuccess(federationDomain2.Spec.Issuer, adIdentityProvider.Name, frozenMetav1Now, 123),
),
},
},
{
name: "when there are two valid FederationDomains, but one is already up to date, the sync loop only updates " +
"the out-of-date FederationDomain",
@ -637,6 +674,40 @@ func TestTestFederationDomainWatcherControllerSync(t *testing.T) {
),
},
},
{
name: "when the status of the FederationDomains is based on an old generation, it is updated",
inputObjects: []runtime.Object{
oidcIdentityProvider,
&configv1alpha1.FederationDomain{
ObjectMeta: metav1.ObjectMeta{Name: federationDomain1.Name, Namespace: federationDomain1.Namespace, Generation: 123},
Spec: configv1alpha1.FederationDomainSpec{Issuer: federationDomain1.Spec.Issuer},
Status: configv1alpha1.FederationDomainStatus{
Phase: configv1alpha1.FederationDomainPhaseReady,
Conditions: allHappyConditionsLegacyConfigurationSuccess(
federationDomain1.Spec.Issuer,
oidcIdentityProvider.Name,
frozenMetav1Now,
2, // this is an older generation
),
},
},
},
wantFDIssuers: []*federationdomainproviders.FederationDomainIssuer{
federationDomainIssuerWithDefaultIDP(t, federationDomain1.Spec.Issuer, oidcIdentityProvider.ObjectMeta),
},
wantStatusUpdates: []*configv1alpha1.FederationDomain{
// only one update, because the other FederationDomain already had the right status
expectedFederationDomainStatusUpdate(federationDomain1,
configv1alpha1.FederationDomainPhaseReady,
allHappyConditionsLegacyConfigurationSuccess(
federationDomain1.Spec.Issuer,
oidcIdentityProvider.Name,
frozenMetav1Now,
123, // all conditions are updated to the new observed generation
),
),
},
},
{
name: "when there are two valid FederationDomains, but updating one fails, the status on the FederationDomain will not change",
inputObjects: []runtime.Object{
@ -1306,7 +1377,9 @@ func TestTestFederationDomainWatcherControllerSync(t *testing.T) {
replaceConditions(
allHappyConditionsSuccess("https://issuer1.com", frozenMetav1Now, 123),
[]configv1alpha1.Condition{
sadConstNamesUniqueCondition(`"duplicate1", "duplicate2"`, frozenMetav1Now, 123),
sadConstNamesUniqueCondition(
`the names specified by .spec.identityProviders[0].transforms.constants[].name contain duplicates: "duplicate1", "duplicate2"`,
frozenMetav1Now, 123),
sadReadyCondition(frozenMetav1Now, 123),
}),
),
@ -1351,8 +1424,7 @@ func TestTestFederationDomainWatcherControllerSync(t *testing.T) {
replaceConditions(
allHappyConditionsSuccess("https://issuer1.com", frozenMetav1Now, 123),
[]configv1alpha1.Condition{
sadTransformationExpressionsCondition(
here.Doc(
sadTransformationExpressionsCondition(here.Doc(
`spec.identityProvider[0].transforms.expressions[0].expression was invalid:
CEL expression compile error: ERROR: <input>:1:6: Syntax error: mismatched input 'is' expecting <EOF>
| this is not a valid cel expression
@ -1367,9 +1439,10 @@ func TestTestFederationDomainWatcherControllerSync(t *testing.T) {
CEL expression compile error: ERROR: <input>:1:7: Syntax error: mismatched input 'not' expecting <EOF>
| still not a valid cel expression
| ......^`,
),
), frozenMetav1Now, 123),
sadTransformationExamplesCondition(
"unable to check if the examples specified by .spec.identityProviders[0].transforms.examples[] had errors because an expression was invalid",
frozenMetav1Now, 123),
unknownTransformationExamplesCondition(frozenMetav1Now, 123),
sadReadyCondition(frozenMetav1Now, 123),
}),
),
@ -1497,8 +1570,7 @@ func TestTestFederationDomainWatcherControllerSync(t *testing.T) {
replaceConditions(
allHappyConditionsSuccess("https://issuer1.com", frozenMetav1Now, 123),
[]configv1alpha1.Condition{
sadTransformationExamplesCondition(
here.Doc(
sadTransformationExamplesCondition(here.Doc(
`.spec.identityProviders[0].transforms.examples[2] example failed:
expected: authentication to be rejected
actual: authentication was not rejected
@ -1534,8 +1606,7 @@ func TestTestFederationDomainWatcherControllerSync(t *testing.T) {
.spec.identityProviders[0].transforms.examples[9] example failed:
expected: groups []
actual: groups ["pre:a", "pre:b"]`,
),
frozenMetav1Now, 123),
), frozenMetav1Now, 123),
sadReadyCondition(frozenMetav1Now, 123),
}),
),
@ -1598,8 +1669,7 @@ func TestTestFederationDomainWatcherControllerSync(t *testing.T) {
replaceConditions(
allHappyConditionsSuccess("https://issuer1.com", frozenMetav1Now, 123),
[]configv1alpha1.Condition{
sadTransformationExamplesCondition(
here.Doc(
sadTransformationExamplesCondition(here.Doc(
`.spec.identityProviders[0].transforms.examples[0] example failed:
expected: no transformation errors
actual: transformations resulted in an unexpected error "identity transformation returned an empty username, which is not allowed"
@ -1607,7 +1677,220 @@ func TestTestFederationDomainWatcherControllerSync(t *testing.T) {
.spec.identityProviders[0].transforms.examples[1] example failed:
expected: no transformation errors
actual: transformations resulted in an unexpected error "identity transformation returned an empty username, which is not allowed"`,
), frozenMetav1Now, 123),
sadReadyCondition(frozenMetav1Now, 123),
}),
),
},
},
{
name: "the federation domain has lots of errors including errors from multiple IDPs, which are all shown in the status conditions using IDP indices in the messages",
inputObjects: []runtime.Object{
oidcIdentityProvider,
&configv1alpha1.FederationDomain{
ObjectMeta: metav1.ObjectMeta{Name: "config1", Namespace: namespace, Generation: 123},
Spec: configv1alpha1.FederationDomainSpec{
Issuer: "https://not-unique.com",
IdentityProviders: []configv1alpha1.FederationDomainIdentityProvider{
{
DisplayName: "not unique",
ObjectRef: corev1.TypedLocalObjectReference{
APIGroup: pointer.String(apiGroupSupervisor),
Kind: "OIDCIdentityProvider",
Name: "this will not be found",
},
Transforms: configv1alpha1.FederationDomainTransforms{
Constants: []configv1alpha1.FederationDomainTransformsConstant{
{Name: "foo", Type: "string", StringValue: "bar"},
{Name: "foo", Type: "string", StringValue: "baz"},
},
Expressions: []configv1alpha1.FederationDomainTransformsExpression{
{Type: "username/v1", Expression: `username + ":suffix"`},
},
Examples: []configv1alpha1.FederationDomainTransformsExample{
{ // this should fail
Username: "ryan",
Groups: []string{"a", "b"},
Expects: configv1alpha1.FederationDomainTransformsExampleExpects{
Username: "this is wrong string",
Groups: []string{"this is wrong string list"},
},
},
{ // this should fail
Username: "ryan",
Groups: []string{"a", "b"},
Expects: configv1alpha1.FederationDomainTransformsExampleExpects{
Username: "this is also wrong string",
Groups: []string{"this is also wrong string list"},
},
},
},
},
},
{
DisplayName: "not unique",
ObjectRef: corev1.TypedLocalObjectReference{
APIGroup: pointer.String(apiGroupSupervisor),
Kind: "this is wrong",
Name: "foo",
},
Transforms: configv1alpha1.FederationDomainTransforms{
Constants: []configv1alpha1.FederationDomainTransformsConstant{
{Name: "foo", Type: "string", StringValue: "bar"},
{Name: "foo", Type: "string", StringValue: "baz"},
},
Expressions: []configv1alpha1.FederationDomainTransformsExpression{
{Type: "username/v1", Expression: `username + ":suffix"`},
},
Examples: []configv1alpha1.FederationDomainTransformsExample{
{ // this should pass
Username: "ryan",
Groups: []string{"a", "b"},
Expects: configv1alpha1.FederationDomainTransformsExampleExpects{
Username: "ryan:suffix",
Groups: []string{"a", "b"},
},
},
{ // this should fail
Username: "ryan",
Groups: []string{"a", "b"},
Expects: configv1alpha1.FederationDomainTransformsExampleExpects{
Username: "this is still wrong string",
Groups: []string{"this is still wrong string list"},
},
},
},
},
},
{
DisplayName: "name1",
ObjectRef: corev1.TypedLocalObjectReference{
APIGroup: pointer.String("this is wrong"),
Kind: "OIDCIdentityProvider",
Name: "foo",
},
Transforms: configv1alpha1.FederationDomainTransforms{
Expressions: []configv1alpha1.FederationDomainTransformsExpression{
{Type: "username/v1", Expression: `username`},
{Type: "username/v1", Expression: `this does not compile`},
{Type: "username/v1", Expression: `username`},
{Type: "username/v1", Expression: `this also does not compile`},
},
},
},
},
},
},
&configv1alpha1.FederationDomain{
ObjectMeta: metav1.ObjectMeta{Name: "config2", Namespace: namespace, Generation: 123},
Spec: configv1alpha1.FederationDomainSpec{
Issuer: "https://not-unique.com",
IdentityProviders: []configv1alpha1.FederationDomainIdentityProvider{
{
DisplayName: "name1",
ObjectRef: corev1.TypedLocalObjectReference{
APIGroup: pointer.String(apiGroupSupervisor),
Kind: "OIDCIdentityProvider",
Name: oidcIdentityProvider.Name,
},
Transforms: configv1alpha1.FederationDomainTransforms{
Expressions: []configv1alpha1.FederationDomainTransformsExpression{
{Type: "username/v1", Expression: `username`},
{Type: "username/v1", Expression: `this still does not compile`},
{Type: "username/v1", Expression: `username`},
{Type: "username/v1", Expression: `this really does not compile`},
},
},
},
},
},
},
},
wantFDIssuers: []*federationdomainproviders.FederationDomainIssuer{},
wantStatusUpdates: []*configv1alpha1.FederationDomain{
expectedFederationDomainStatusUpdate(
&configv1alpha1.FederationDomain{
ObjectMeta: metav1.ObjectMeta{Name: "config1", Namespace: namespace, Generation: 123},
},
configv1alpha1.FederationDomainPhaseError,
replaceConditions(
allHappyConditionsSuccess("https://not-unique.com", frozenMetav1Now, 123),
[]configv1alpha1.Condition{
sadConstNamesUniqueCondition(here.Doc(
`the names specified by .spec.identityProviders[0].transforms.constants[].name contain duplicates: "foo"
the names specified by .spec.identityProviders[1].transforms.constants[].name contain duplicates: "foo"`,
), frozenMetav1Now, 123),
sadAPIGroupSuffixCondition(`"this is wrong"`, frozenMetav1Now, 123),
sadDisplayNamesUniqueCondition(`"not unique"`, frozenMetav1Now, 123),
sadIdentityProvidersFoundConditionIdentityProvidersObjectRefsNotFound(
`.spec.identityProviders[0] with displayName "not unique", .spec.identityProviders[1] with displayName "not unique", .spec.identityProviders[2] with displayName "name1"`,
frozenMetav1Now, 123),
sadIssuerIsUniqueCondition(frozenMetav1Now, 123),
sadKindCondition(`"this is wrong"`, frozenMetav1Now, 123),
sadTransformationExpressionsCondition(here.Doc(
`spec.identityProvider[2].transforms.expressions[1].expression was invalid:
CEL expression compile error: ERROR: <input>:1:6: Syntax error: mismatched input 'does' expecting <EOF>
| this does not compile
| .....^
spec.identityProvider[2].transforms.expressions[3].expression was invalid:
CEL expression compile error: ERROR: <input>:1:6: Syntax error: mismatched input 'also' expecting <EOF>
| this also does not compile
| .....^`,
), frozenMetav1Now, 123),
sadTransformationExamplesCondition(here.Doc(
`.spec.identityProviders[0].transforms.examples[0] example failed:
expected: username "this is wrong string"
actual: username "ryan:suffix"
.spec.identityProviders[0].transforms.examples[0] example failed:
expected: groups ["this is wrong string list"]
actual: groups ["a", "b"]
.spec.identityProviders[0].transforms.examples[1] example failed:
expected: username "this is also wrong string"
actual: username "ryan:suffix"
.spec.identityProviders[0].transforms.examples[1] example failed:
expected: groups ["this is also wrong string list"]
actual: groups ["a", "b"]
.spec.identityProviders[1].transforms.examples[1] example failed:
expected: username "this is still wrong string"
actual: username "ryan:suffix"
.spec.identityProviders[1].transforms.examples[1] example failed:
expected: groups ["this is still wrong string list"]
actual: groups ["a", "b"]
unable to check if the examples specified by .spec.identityProviders[2].transforms.examples[] had errors because an expression was invalid`,
), frozenMetav1Now, 123),
sadReadyCondition(frozenMetav1Now, 123),
}),
),
expectedFederationDomainStatusUpdate(
&configv1alpha1.FederationDomain{
ObjectMeta: metav1.ObjectMeta{Name: "config2", Namespace: namespace, Generation: 123},
},
configv1alpha1.FederationDomainPhaseError,
replaceConditions(
allHappyConditionsSuccess("https://not-unique.com", frozenMetav1Now, 123),
[]configv1alpha1.Condition{
sadIssuerIsUniqueCondition(frozenMetav1Now, 123),
sadTransformationExpressionsCondition(here.Doc(
`spec.identityProvider[0].transforms.expressions[1].expression was invalid:
CEL expression compile error: ERROR: <input>:1:6: Syntax error: mismatched input 'still' expecting <EOF>
| this still does not compile
| .....^
spec.identityProvider[0].transforms.expressions[3].expression was invalid:
CEL expression compile error: ERROR: <input>:1:6: Syntax error: mismatched input 'really' expecting <EOF>
| this really does not compile
| .....^`,
), frozenMetav1Now, 123),
sadTransformationExamplesCondition(
"unable to check if the examples specified by .spec.identityProviders[0].transforms.examples[] had errors because an expression was invalid",
frozenMetav1Now, 123),
sadReadyCondition(frozenMetav1Now, 123),
}),