PR feedback

This commit is contained in:
Margo Crawford 2021-07-26 16:03:12 -07:00
parent 5d23068690
commit cc3875f048
9 changed files with 209 additions and 26 deletions

View File

@ -84,7 +84,7 @@ while (("$#")); do
shift shift
# If there are no more command line arguments, or there is another command line argument but it starts with a dash, then error # If there are no more command line arguments, or there is another command line argument but it starts with a dash, then error
if [[ "$#" == "0" || "$1" == -* ]]; then if [[ "$#" == "0" || "$1" == -* ]]; then
log_error "-g|--get-active-directory-vars requires a script name to be specified" log_error "--get-active-directory-vars requires a script name to be specified"
exit 1 exit 1
fi fi
get_active_directory_vars=$1 get_active_directory_vars=$1
@ -111,6 +111,7 @@ if [[ "$help" == "yes" ]]; then
log_note " -c, --clean: destroy the current kind cluster and make a new one" log_note " -c, --clean: destroy the current kind cluster and make a new one"
log_note " -g, --api-group-suffix: deploy Pinniped with an alternate API group suffix" log_note " -g, --api-group-suffix: deploy Pinniped with an alternate API group suffix"
log_note " -s, --skip-build: reuse the most recently built image of the app instead of building" log_note " -s, --skip-build: reuse the most recently built image of the app instead of building"
log_note " --get-active-directory-vars: specify a script that exports active directory environment variables"
exit 1 exit 1
fi fi

View File

@ -100,9 +100,9 @@ func (s *activeDirectoryUpstreamGenericLDAPSpec) DetectAndSetSearchBase(ctx cont
if config.GroupSearch.Base != "" && config.UserSearch.Base != "" { if config.GroupSearch.Base != "" && config.UserSearch.Base != "" {
// Both were already set in spec so just return; no need to query the RootDSE // Both were already set in spec so just return; no need to query the RootDSE
return &v1alpha1.Condition{ return &v1alpha1.Condition{
Type: "SearchBaseFound", Type: upstreamwatchers.TypeSearchBaseFound,
Status: v1alpha1.ConditionTrue, Status: v1alpha1.ConditionTrue,
Reason: "Success", Reason: upstreamwatchers.ReasonUsingConfigurationFromSpec,
Message: "Using search base from ActiveDirectoryIdentityProvider config.", Message: "Using search base from ActiveDirectoryIdentityProvider config.",
} }
} }
@ -115,7 +115,7 @@ func (s *activeDirectoryUpstreamGenericLDAPSpec) DetectAndSetSearchBase(ctx cont
return &v1alpha1.Condition{ return &v1alpha1.Condition{
Type: upstreamwatchers.TypeSearchBaseFound, Type: upstreamwatchers.TypeSearchBaseFound,
Status: v1alpha1.ConditionFalse, Status: v1alpha1.ConditionFalse,
Reason: "Error", Reason: upstreamwatchers.ReasonErrorFetchingSearchBase,
Message: fmt.Sprintf(`Error finding search base: %s`, err.Error()), Message: fmt.Sprintf(`Error finding search base: %s`, err.Error()),
} }
} }

View File

@ -273,7 +273,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
Type: "SearchBaseFound", Type: "SearchBaseFound",
Status: "True", Status: "True",
LastTransitionTime: now, LastTransitionTime: now,
Reason: "Success", Reason: "UsingConfigurationFromSpec",
Message: "Using search base from ActiveDirectoryIdentityProvider config.", Message: "Using search base from ActiveDirectoryIdentityProvider config.",
ObservedGeneration: gen, ObservedGeneration: gen,
} }
@ -284,7 +284,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
Type: "SearchBaseFound", Type: "SearchBaseFound",
Status: "False", Status: "False",
LastTransitionTime: now, LastTransitionTime: now,
Reason: "Error", Reason: "ErrorFetchingSearchBase",
Message: message, Message: message,
ObservedGeneration: gen, ObservedGeneration: gen,
} }
@ -878,7 +878,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
testHost, testBindUsername, testBindUsername), testHost, testBindUsername, testBindUsername),
ObservedGeneration: 1234, ObservedGeneration: 1234,
}, },
searchBaseFoundErrorCondition(1234, "Error finding search base: error binding as \"test-bind-username\" before user search: some bind error"), searchBaseFoundErrorCondition(1234, "Error finding search base: error binding as \"test-bind-username\" before querying for defaultNamingContext: some bind error"),
tlsConfigurationValidLoadedTrueCondition(1234), tlsConfigurationValidLoadedTrueCondition(1234),
}, },
}, },
@ -1344,9 +1344,6 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
}, },
{ {
name: "when the input activedirectoryidentityprovider leaves group search base blank and query for defaultNamingContext fails", name: "when the input activedirectoryidentityprovider leaves group search base blank and query for defaultNamingContext fails",
// TODO is this a fatal error? I think so because leaving the search base blank and trying anyway does not seem expected.
// queries with an empty search base could potentially succeed but return something unexpected, like if you were
// pointing at global catalog but not intending to use the GC functionality...
inputUpstreams: []runtime.Object{editedValidUpstream(func(upstream *v1alpha1.ActiveDirectoryIdentityProvider) { inputUpstreams: []runtime.Object{editedValidUpstream(func(upstream *v1alpha1.ActiveDirectoryIdentityProvider) {
upstream.Spec.UserSearch.Attributes = v1alpha1.ActiveDirectoryIdentityProviderUserSearchAttributes{} upstream.Spec.UserSearch.Attributes = v1alpha1.ActiveDirectoryIdentityProviderUserSearchAttributes{}
upstream.Spec.GroupSearch.Base = "" upstream.Spec.GroupSearch.Base = ""

View File

@ -40,6 +40,8 @@ const (
reasonLDAPConnectionError = "LDAPConnectionError" reasonLDAPConnectionError = "LDAPConnectionError"
noTLSConfigurationMessage = "no TLS configuration provided" noTLSConfigurationMessage = "no TLS configuration provided"
loadedTLSConfigurationMessage = "loaded TLS configuration" loadedTLSConfigurationMessage = "loaded TLS configuration"
ReasonUsingConfigurationFromSpec = "UsingConfigurationFromSpec"
ReasonErrorFetchingSearchBase = "ErrorFetchingSearchBase"
) )
// An in-memory cache with an entry for each ActiveDirectoryIdentityProvider, to keep track of which ResourceVersion // An in-memory cache with an entry for each ActiveDirectoryIdentityProvider, to keep track of which ResourceVersion
@ -187,7 +189,7 @@ func HasPreviousSuccessfulSearchBaseConditionForCurrentGeneration(secretVersionC
// Found a previously successful condition for the current spec generation. // Found a previously successful condition for the current spec generation.
// Now figure out which version of the bind Secret was used during that previous validation, if any. // Now figure out which version of the bind Secret was used during that previous validation, if any.
validatedSettings := secretVersionCache.ValidatedSettingsByName[upstreamName] validatedSettings := secretVersionCache.ValidatedSettingsByName[upstreamName]
// Reload the TLS vs StartTLS setting that was previously validated. // Reload the user search and group search base settings that were previously validated.
config.UserSearch.Base = validatedSettings.UserSearchBase config.UserSearch.Base = validatedSettings.UserSearchBase
config.GroupSearch.Base = validatedSettings.GroupSearchBase config.GroupSearch.Base = validatedSettings.GroupSearchBase
return true return true

View File

@ -612,6 +612,17 @@ func TestAuthorizationEndpoint(t *testing.T) {
wantContentType: htmlContentType, wantContentType: htmlContentType,
wantBodyString: "Bad Gateway: unexpected error during upstream authentication\n", wantBodyString: "Bad Gateway: unexpected error during upstream authentication\n",
}, },
{
name: "error during upstream Active Directory authentication",
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithActiveDirectory(&erroringUpstreamLDAPIdentityProvider).Build(),
method: http.MethodGet,
path: happyGetRequestPath,
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
customPasswordHeader: pointer.StringPtr(happyLDAPPassword),
wantStatus: http.StatusBadGateway,
wantContentType: htmlContentType,
wantBodyString: "Bad Gateway: unexpected error during upstream authentication\n",
},
{ {
name: "wrong upstream password for LDAP authentication", name: "wrong upstream password for LDAP authentication",
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider).Build(), idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider).Build(),
@ -624,6 +635,18 @@ func TestAuthorizationEndpoint(t *testing.T) {
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithBadUsernamePasswordHintErrorQuery), wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithBadUsernamePasswordHintErrorQuery),
wantBodyString: "", wantBodyString: "",
}, },
{
name: "wrong upstream password for Active Directory authentication",
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithActiveDirectory(&upstreamLDAPIdentityProvider).Build(),
method: http.MethodGet,
path: happyGetRequestPath,
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
customPasswordHeader: pointer.StringPtr("wrong-password"),
wantStatus: http.StatusFound,
wantContentType: "application/json; charset=utf-8",
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithBadUsernamePasswordHintErrorQuery),
wantBodyString: "",
},
{ {
name: "wrong upstream username for LDAP authentication", name: "wrong upstream username for LDAP authentication",
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider).Build(), idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider).Build(),
@ -636,6 +659,18 @@ func TestAuthorizationEndpoint(t *testing.T) {
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithBadUsernamePasswordHintErrorQuery), wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithBadUsernamePasswordHintErrorQuery),
wantBodyString: "", wantBodyString: "",
}, },
{
name: "wrong upstream username for Active Directory authentication",
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithActiveDirectory(&upstreamLDAPIdentityProvider).Build(),
method: http.MethodGet,
path: happyGetRequestPath,
customUsernameHeader: pointer.StringPtr("wrong-username"),
customPasswordHeader: pointer.StringPtr(happyLDAPPassword),
wantStatus: http.StatusFound,
wantContentType: "application/json; charset=utf-8",
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithBadUsernamePasswordHintErrorQuery),
wantBodyString: "",
},
{ {
name: "missing upstream username on request for LDAP authentication", name: "missing upstream username on request for LDAP authentication",
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider).Build(), idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider).Build(),
@ -648,6 +683,18 @@ func TestAuthorizationEndpoint(t *testing.T) {
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithMissingUsernamePasswordHintErrorQuery), wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithMissingUsernamePasswordHintErrorQuery),
wantBodyString: "", wantBodyString: "",
}, },
{
name: "missing upstream username on request for Active Directory authentication",
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithActiveDirectory(&upstreamLDAPIdentityProvider).Build(),
method: http.MethodGet,
path: happyGetRequestPath,
customUsernameHeader: nil, // do not send header
customPasswordHeader: pointer.StringPtr(happyLDAPPassword),
wantStatus: http.StatusFound,
wantContentType: "application/json; charset=utf-8",
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithMissingUsernamePasswordHintErrorQuery),
wantBodyString: "",
},
{ {
name: "missing upstream password on request for LDAP authentication", name: "missing upstream password on request for LDAP authentication",
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider).Build(), idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider).Build(),
@ -660,6 +707,18 @@ func TestAuthorizationEndpoint(t *testing.T) {
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithMissingUsernamePasswordHintErrorQuery), wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithMissingUsernamePasswordHintErrorQuery),
wantBodyString: "", wantBodyString: "",
}, },
{
name: "missing upstream password on request for Active Directory authentication",
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithActiveDirectory(&upstreamLDAPIdentityProvider).Build(),
method: http.MethodGet,
path: happyGetRequestPath,
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
customPasswordHeader: nil, // do not send header
wantStatus: http.StatusFound,
wantContentType: "application/json; charset=utf-8",
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithMissingUsernamePasswordHintErrorQuery),
wantBodyString: "",
},
{ {
name: "downstream redirect uri does not match what is configured for client when using OIDC upstream", name: "downstream redirect uri does not match what is configured for client when using OIDC upstream",
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(&upstreamOIDCIdentityProvider).Build(), idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(&upstreamOIDCIdentityProvider).Build(),
@ -764,6 +823,18 @@ func TestAuthorizationEndpoint(t *testing.T) {
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeInvalidScopeErrorQuery), wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeInvalidScopeErrorQuery),
wantBodyString: "", wantBodyString: "",
}, },
{
name: "downstream scopes do not match what is configured for client using LDAP upstream",
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithActiveDirectory(&upstreamLDAPIdentityProvider).Build(),
method: http.MethodGet,
path: modifiedHappyGetRequestPath(map[string]string{"scope": "openid tuna"}),
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
customPasswordHeader: pointer.StringPtr(happyLDAPPassword),
wantStatus: http.StatusFound,
wantContentType: "application/json; charset=utf-8",
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeInvalidScopeErrorQuery),
wantBodyString: "",
},
{ {
name: "missing response type in request using OIDC upstream", name: "missing response type in request using OIDC upstream",
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(&upstreamOIDCIdentityProvider).Build(), idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(&upstreamOIDCIdentityProvider).Build(),
@ -789,6 +860,16 @@ func TestAuthorizationEndpoint(t *testing.T) {
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeMissingResponseTypeErrorQuery), wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeMissingResponseTypeErrorQuery),
wantBodyString: "", wantBodyString: "",
}, },
{
name: "missing response type in request using Active Directory upstream",
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithActiveDirectory(&upstreamLDAPIdentityProvider).Build(),
method: http.MethodGet,
path: modifiedHappyGetRequestPath(map[string]string{"response_type": ""}),
wantStatus: http.StatusFound,
wantContentType: "application/json; charset=utf-8",
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeMissingResponseTypeErrorQuery),
wantBodyString: "",
},
{ {
name: "missing client id in request using OIDC upstream", name: "missing client id in request using OIDC upstream",
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(&upstreamOIDCIdentityProvider).Build(), idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(&upstreamOIDCIdentityProvider).Build(),
@ -1122,6 +1203,15 @@ func TestAuthorizationEndpoint(t *testing.T) {
wantContentType: "text/plain; charset=utf-8", wantContentType: "text/plain; charset=utf-8",
wantBodyString: "Unprocessable Entity: Too many upstream providers are configured (support for multiple upstreams is not yet implemented)\n", wantBodyString: "Unprocessable Entity: Too many upstream providers are configured (support for multiple upstreams is not yet implemented)\n",
}, },
{
name: "too many upstream providers are configured: multiple Active Directory",
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithActiveDirectory(&upstreamLDAPIdentityProvider, &upstreamLDAPIdentityProvider).Build(), // more than one not allowed
method: http.MethodGet,
path: happyGetRequestPath,
wantStatus: http.StatusUnprocessableEntity,
wantContentType: "text/plain; charset=utf-8",
wantBodyString: "Unprocessable Entity: Too many upstream providers are configured (support for multiple upstreams is not yet implemented)\n",
},
{ {
name: "too many upstream providers are configured: both OIDC and LDAP", name: "too many upstream providers are configured: both OIDC and LDAP",
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(&upstreamOIDCIdentityProvider).WithLDAP(&upstreamLDAPIdentityProvider).Build(), // more than one not allowed idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(&upstreamOIDCIdentityProvider).WithLDAP(&upstreamLDAPIdentityProvider).Build(), // more than one not allowed
@ -1131,6 +1221,15 @@ func TestAuthorizationEndpoint(t *testing.T) {
wantContentType: "text/plain; charset=utf-8", wantContentType: "text/plain; charset=utf-8",
wantBodyString: "Unprocessable Entity: Too many upstream providers are configured (support for multiple upstreams is not yet implemented)\n", wantBodyString: "Unprocessable Entity: Too many upstream providers are configured (support for multiple upstreams is not yet implemented)\n",
}, },
{
name: "too many upstream providers are configured: OIDC, LDAP and AD",
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(&upstreamOIDCIdentityProvider).WithLDAP(&upstreamLDAPIdentityProvider).WithActiveDirectory(&upstreamLDAPIdentityProvider).Build(), // more than one not allowed
method: http.MethodGet,
path: happyGetRequestPath,
wantStatus: http.StatusUnprocessableEntity,
wantContentType: "text/plain; charset=utf-8",
wantBodyString: "Unprocessable Entity: Too many upstream providers are configured (support for multiple upstreams is not yet implemented)\n",
},
{ {
name: "PUT is a bad method", name: "PUT is a bad method",
idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(&upstreamOIDCIdentityProvider).Build(), idpLister: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(&upstreamOIDCIdentityProvider).Build(),

View File

@ -41,6 +41,7 @@ func TestIDPDiscovery(t *testing.T) {
{Name: "a-some-oidc-idp", Type: "oidc"}, {Name: "a-some-oidc-idp", Type: "oidc"},
{Name: "x-some-idp", Type: "ldap"}, {Name: "x-some-idp", Type: "ldap"},
{Name: "x-some-idp", Type: "oidc"}, {Name: "x-some-idp", Type: "oidc"},
{Name: "y-some-ad-idp", Type: "activedirectory"},
{Name: "z-some-ad-idp", Type: "activedirectory"}, {Name: "z-some-ad-idp", Type: "activedirectory"},
{Name: "z-some-ldap-idp", Type: "ldap"}, {Name: "z-some-ldap-idp", Type: "ldap"},
{Name: "z-some-oidc-idp", Type: "oidc"}, {Name: "z-some-oidc-idp", Type: "oidc"},
@ -49,6 +50,7 @@ func TestIDPDiscovery(t *testing.T) {
wantSecondResponseBodyJSON: &response{ wantSecondResponseBodyJSON: &response{
IDPs: []identityProviderResponse{ IDPs: []identityProviderResponse{
{Name: "some-other-ad-idp-1", Type: "activedirectory"}, {Name: "some-other-ad-idp-1", Type: "activedirectory"},
{Name: "some-other-ad-idp-2", Type: "activedirectory"},
{Name: "some-other-ldap-idp-1", Type: "ldap"}, {Name: "some-other-ldap-idp-1", Type: "ldap"},
{Name: "some-other-ldap-idp-2", Type: "ldap"}, {Name: "some-other-ldap-idp-2", Type: "ldap"},
{Name: "some-other-oidc-idp-1", Type: "oidc"}, {Name: "some-other-oidc-idp-1", Type: "oidc"},
@ -76,6 +78,7 @@ func TestIDPDiscovery(t *testing.T) {
WithLDAP(&oidctestutil.TestUpstreamLDAPIdentityProvider{Name: "z-some-ldap-idp"}). WithLDAP(&oidctestutil.TestUpstreamLDAPIdentityProvider{Name: "z-some-ldap-idp"}).
WithLDAP(&oidctestutil.TestUpstreamLDAPIdentityProvider{Name: "x-some-idp"}). WithLDAP(&oidctestutil.TestUpstreamLDAPIdentityProvider{Name: "x-some-idp"}).
WithActiveDirectory(&oidctestutil.TestUpstreamLDAPIdentityProvider{Name: "z-some-ad-idp"}). WithActiveDirectory(&oidctestutil.TestUpstreamLDAPIdentityProvider{Name: "z-some-ad-idp"}).
WithActiveDirectory(&oidctestutil.TestUpstreamLDAPIdentityProvider{Name: "y-some-ad-idp"}).
Build() Build()
handler := NewHandler(idpLister) handler := NewHandler(idpLister)
@ -108,6 +111,7 @@ func TestIDPDiscovery(t *testing.T) {
}) })
idpLister.SetActiveDirectoryIdentityProviders([]provider.UpstreamLDAPIdentityProviderI{ idpLister.SetActiveDirectoryIdentityProviders([]provider.UpstreamLDAPIdentityProviderI{
&oidctestutil.TestUpstreamLDAPIdentityProvider{Name: "some-other-ad-idp-2"},
&oidctestutil.TestUpstreamLDAPIdentityProvider{Name: "some-other-ad-idp-1"}, &oidctestutil.TestUpstreamLDAPIdentityProvider{Name: "some-other-ad-idp-1"},
}) })

View File

@ -393,7 +393,7 @@ func (p *Provider) validateConfig() error {
} }
func (p *Provider) SearchForDefaultNamingContext(ctx context.Context) (string, error) { func (p *Provider) SearchForDefaultNamingContext(ctx context.Context) (string, error) {
t := trace.FromContext(ctx).Nest("slow ldap authenticate user attempt", trace.Field{Key: "providerName", Value: p.GetName()}) t := trace.FromContext(ctx).Nest("slow ldap attempt when searching for default naming context", trace.Field{Key: "providerName", Value: p.GetName()})
defer t.LogIfLong(500 * time.Millisecond) // to help users debug slow LDAP searches defer t.LogIfLong(500 * time.Millisecond) // to help users debug slow LDAP searches
conn, err := p.dial(ctx) conn, err := p.dial(ctx)
@ -406,7 +406,7 @@ func (p *Provider) SearchForDefaultNamingContext(ctx context.Context) (string, e
err = conn.Bind(p.c.BindUsername, p.c.BindPassword) err = conn.Bind(p.c.BindUsername, p.c.BindPassword)
if err != nil { if err != nil {
p.traceAuthFailure(t, err) p.traceAuthFailure(t, err)
return "", fmt.Errorf(`error binding as "%s" before user search: %w`, p.c.BindUsername, err) return "", fmt.Errorf(`error binding as "%s" before querying for defaultNamingContext: %w`, p.c.BindUsername, err)
} }
searchResult, err := conn.Search(p.defaultNamingContextRequest()) searchResult, err := conn.Search(p.defaultNamingContextRequest())

View File

@ -281,12 +281,79 @@ func TestSupervisorLogin(t *testing.T) {
// the ID token Subject should be the Host URL plus the value pulled from the requested UserSearch.Attributes.UID attribute // the ID token Subject should be the Host URL plus the value pulled from the requested UserSearch.Attributes.UID attribute
wantDownstreamIDTokenSubjectToMatch: regexp.QuoteMeta( wantDownstreamIDTokenSubjectToMatch: regexp.QuoteMeta(
"ldaps://" + env.SupervisorUpstreamActiveDirectory.Host + "ldaps://" + env.SupervisorUpstreamActiveDirectory.Host +
"?base=" + url.QueryEscape("DC=activedirectory,DC=test,DC=pinniped,DC=dev") + "?base=" + url.QueryEscape(env.SupervisorUpstreamActiveDirectory.DefaultNamingContextSearchBase) +
"&sub=" + env.SupervisorUpstreamActiveDirectory.TestUserUniqueIDAttributeValue, "&sub=" + env.SupervisorUpstreamActiveDirectory.TestUserUniqueIDAttributeValue,
), ),
// the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute // the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute
wantDownstreamIDTokenUsernameToMatch: regexp.QuoteMeta(env.SupervisorUpstreamActiveDirectory.TestUserSAMAccountNameValue), wantDownstreamIDTokenUsernameToMatch: regexp.QuoteMeta(env.SupervisorUpstreamActiveDirectory.TestUserSAMAccountNameValue),
wantDownstreamIDTokenGroups: env.SupervisorUpstreamActiveDirectory.TestUserIndirectGroupsSAMAccountNames, wantDownstreamIDTokenGroups: env.SupervisorUpstreamActiveDirectory.TestUserIndirectGroupsSAMAccountNames,
}, {
name: "activedirectory with custom options",
maybeSkip: func(t *testing.T) {
t.Helper()
if len(env.ToolsNamespace) == 0 && !env.HasCapability(testlib.CanReachInternetLDAPPorts) {
t.Skip("LDAP integration test requires connectivity to an LDAP server")
}
if env.SupervisorUpstreamActiveDirectory.Host == "" {
t.Skip("Active Directory hostname not specified")
}
},
createIDP: func(t *testing.T) {
t.Helper()
secret := testlib.CreateTestSecret(t, env.SupervisorNamespace, "ad-service-account", v1.SecretTypeBasicAuth,
map[string]string{
v1.BasicAuthUsernameKey: env.SupervisorUpstreamActiveDirectory.BindUsername,
v1.BasicAuthPasswordKey: env.SupervisorUpstreamActiveDirectory.BindPassword,
},
)
adIDP := testlib.CreateTestActiveDirectoryIdentityProvider(t, idpv1alpha1.ActiveDirectoryIdentityProviderSpec{
Host: env.SupervisorUpstreamActiveDirectory.Host,
TLS: &idpv1alpha1.TLSSpec{
CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte(env.SupervisorUpstreamActiveDirectory.CABundle)),
},
Bind: idpv1alpha1.ActiveDirectoryIdentityProviderBind{
SecretName: secret.Name,
},
UserSearch: idpv1alpha1.ActiveDirectoryIdentityProviderUserSearch{
Base: env.SupervisorUpstreamActiveDirectory.UserSearchBase,
Filter: env.SupervisorUpstreamActiveDirectory.TestUserMailAttributeName + "={}",
Attributes: idpv1alpha1.ActiveDirectoryIdentityProviderUserSearchAttributes{
Username: env.SupervisorUpstreamActiveDirectory.TestUserMailAttributeName,
},
},
GroupSearch: idpv1alpha1.ActiveDirectoryIdentityProviderGroupSearch{
Filter: "member={}", // excluding nested groups
Base: env.SupervisorUpstreamActiveDirectory.GroupSearchBase,
Attributes: idpv1alpha1.ActiveDirectoryIdentityProviderGroupSearchAttributes{
GroupName: "dn",
},
},
}, idpv1alpha1.ActiveDirectoryPhaseReady)
expectedMsg := fmt.Sprintf(
`successfully able to connect to "%s" and bind as user "%s" [validated with Secret "%s" at version "%s"]`,
env.SupervisorUpstreamActiveDirectory.Host, env.SupervisorUpstreamActiveDirectory.BindUsername,
secret.Name, secret.ResourceVersion,
)
requireSuccessfulActiveDirectoryIdentityProviderConditions(t, adIDP, expectedMsg)
},
requestAuthorization: func(t *testing.T, downstreamAuthorizeURL, _ string, httpClient *http.Client) {
requestAuthorizationUsingLDAPIdentityProvider(t,
downstreamAuthorizeURL,
env.SupervisorUpstreamActiveDirectory.TestUserMailAttributeValue, // username to present to server during login
env.SupervisorUpstreamActiveDirectory.TestUserPassword, // password to present to server during login
httpClient,
false,
)
},
// the ID token Subject should be the Host URL plus the value pulled from the requested UserSearch.Attributes.UID attribute
wantDownstreamIDTokenSubjectToMatch: regexp.QuoteMeta(
"ldaps://" + env.SupervisorUpstreamActiveDirectory.Host +
"?base=" + url.QueryEscape(env.SupervisorUpstreamActiveDirectory.UserSearchBase) +
"&sub=" + env.SupervisorUpstreamActiveDirectory.TestUserUniqueIDAttributeValue,
),
// the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute
wantDownstreamIDTokenUsernameToMatch: regexp.QuoteMeta(env.SupervisorUpstreamActiveDirectory.TestUserMailAttributeValue),
wantDownstreamIDTokenGroups: env.SupervisorUpstreamActiveDirectory.TestUserDirectGroupsDNs,
}, },
{ {
name: "logging in to activedirectory with a deactivated user fails", name: "logging in to activedirectory with a deactivated user fails",
@ -395,11 +462,18 @@ func requireSuccessfulActiveDirectoryIdentityProviderConditions(t *testing.T, ad
} }
} }
expectedUserSearchReason := ""
if adIDP.Spec.UserSearch.Base == "" || adIDP.Spec.GroupSearch.Base == "" {
expectedUserSearchReason = "Success"
} else {
expectedUserSearchReason = "UsingConfigurationFromSpec"
}
require.ElementsMatch(t, [][]string{ require.ElementsMatch(t, [][]string{
{"BindSecretValid", "True", "Success"}, {"BindSecretValid", "True", "Success"},
{"TLSConfigurationValid", "True", "Success"}, {"TLSConfigurationValid", "True", "Success"},
{"LDAPConnectionValid", "True", "Success"}, {"LDAPConnectionValid", "True", "Success"},
{"SearchBaseFound", "True", "Success"}, {"SearchBaseFound", "True", expectedUserSearchReason},
}, conditionsSummary) }, conditionsSummary)
} }

View File

@ -88,6 +88,7 @@ type TestLDAPUpstream struct {
BindUsername string `json:"bindUsername"` BindUsername string `json:"bindUsername"`
BindPassword string `json:"bindPassword"` BindPassword string `json:"bindPassword"`
UserSearchBase string `json:"userSearchBase"` UserSearchBase string `json:"userSearchBase"`
DefaultNamingContextSearchBase string `json:"defaultNamingContextSearchBase"`
GroupSearchBase string `json:"groupSearchBase"` GroupSearchBase string `json:"groupSearchBase"`
TestUserDN string `json:"testUserDN"` TestUserDN string `json:"testUserDN"`
TestUserCN string `json:"testUserCN"` TestUserCN string `json:"testUserCN"`
@ -281,11 +282,16 @@ func loadEnvVars(t *testing.T, result *TestEnv) {
TestUserUniqueIDAttributeName: wantEnv("PINNIPED_TEST_AD_USER_UNIQUE_ID_ATTRIBUTE_NAME", ""), TestUserUniqueIDAttributeName: wantEnv("PINNIPED_TEST_AD_USER_UNIQUE_ID_ATTRIBUTE_NAME", ""),
TestUserUniqueIDAttributeValue: wantEnv("PINNIPED_TEST_AD_USER_UNIQUE_ID_ATTRIBUTE_VALUE", ""), TestUserUniqueIDAttributeValue: wantEnv("PINNIPED_TEST_AD_USER_UNIQUE_ID_ATTRIBUTE_VALUE", ""),
TestUserSAMAccountNameValue: wantEnv("PINNIPED_TEST_AD_USERNAME_ATTRIBUTE_VALUE", ""), TestUserSAMAccountNameValue: wantEnv("PINNIPED_TEST_AD_USERNAME_ATTRIBUTE_VALUE", ""),
TestUserMailAttributeValue: wantEnv("PINNIPED_TEST_AD_USER_EMAIL_ATTRIBUTE_VALUE", ""),
TestUserMailAttributeName: wantEnv("PINNIPED_TEST_AD_USER_EMAIL_ATTRIBUTE_NAME", ""),
TestUserDirectGroupsDNs: filterEmpty(strings.Split(wantEnv("PINNIPED_TEST_AD_USER_EXPECTED_GROUPS_DN", ""), ";")), TestUserDirectGroupsDNs: filterEmpty(strings.Split(wantEnv("PINNIPED_TEST_AD_USER_EXPECTED_GROUPS_DN", ""), ";")),
TestUserDirectGroupsCNs: filterEmpty(strings.Split(wantEnv("PINNIPED_TEST_AD_USER_EXPECTED_GROUPS_CN", ""), ";")), TestUserDirectGroupsCNs: filterEmpty(strings.Split(wantEnv("PINNIPED_TEST_AD_USER_EXPECTED_GROUPS_CN", ""), ";")),
TestUserIndirectGroupsSAMAccountNames: filterEmpty(strings.Split(wantEnv("PINNIPED_TEST_AD_USER_EXPECTED_GROUPS_SAMACCOUNTNAME", ""), ";")), TestUserIndirectGroupsSAMAccountNames: filterEmpty(strings.Split(wantEnv("PINNIPED_TEST_AD_USER_EXPECTED_GROUPS_SAMACCOUNTNAME", ""), ";")),
TestDeactivatedUserSAMAccountNameValue: wantEnv("PINNIPED_TEST_DEACTIVATED_AD_USER_SAMACCOUNTNAME", ""), TestDeactivatedUserSAMAccountNameValue: wantEnv("PINNIPED_TEST_DEACTIVATED_AD_USER_SAMACCOUNTNAME", ""),
TestDeactivatedUserPassword: wantEnv("PINNIPED_TEST_DEACTIVATED_AD_USER_PASSWORD", ""), TestDeactivatedUserPassword: wantEnv("PINNIPED_TEST_DEACTIVATED_AD_USER_PASSWORD", ""),
DefaultNamingContextSearchBase: wantEnv("PINNIPED_TEST_AD_DEFAULTNAMINGCONTEXT_DN", ""),
UserSearchBase: wantEnv("PINNIPED_TEST_AD_USERS_DN", ""),
GroupSearchBase: wantEnv("PINNIPED_TEST_AD_USERS_DN", ""),
} }
sort.Strings(result.SupervisorUpstreamLDAP.TestUserDirectGroupsCNs) sort.Strings(result.SupervisorUpstreamLDAP.TestUserDirectGroupsCNs)