mirror of
https://github.com/vmware-tanzu/pinniped.git
synced 2026-09-18 22:14:22 +00:00
Merge branch 'initial_ldap_group_support' into ldap_starttls
This commit is contained in:
@@ -278,7 +278,7 @@ func TestE2EFullIntegration(t *testing.T) {
|
||||
// Add an LDAP upstream IDP and try using it to authenticate during kubectl commands.
|
||||
t.Run("with Supervisor LDAP upstream IDP", func(t *testing.T) {
|
||||
expectedUsername := env.SupervisorUpstreamLDAP.TestUserMailAttributeValue
|
||||
expectedGroups := []string{} // LDAP groups are not implemented yet
|
||||
expectedGroups := env.SupervisorUpstreamLDAP.TestUserDirectGroupsCNs
|
||||
|
||||
// Create a ClusterRoleBinding to give our test user from the upstream read-only access to the cluster.
|
||||
library.CreateTestClusterRoleBinding(t,
|
||||
@@ -317,6 +317,13 @@ func TestE2EFullIntegration(t *testing.T) {
|
||||
UID: env.SupervisorUpstreamLDAP.TestUserUniqueIDAttributeName,
|
||||
},
|
||||
},
|
||||
GroupSearch: idpv1alpha1.LDAPIdentityProviderGroupSearch{
|
||||
Base: env.SupervisorUpstreamLDAP.GroupSearchBase,
|
||||
Filter: "", // use the default value of "member={}"
|
||||
Attributes: idpv1alpha1.LDAPIdentityProviderGroupSearchAttributes{
|
||||
GroupName: "", // use the default value of "cn"
|
||||
},
|
||||
},
|
||||
}, idpv1alpha1.LDAPPhaseReady)
|
||||
|
||||
// Use a specific session cache for this test.
|
||||
@@ -438,17 +445,10 @@ func requireUserCanUseKubectlWithoutAuthenticatingAgain(
|
||||
for _, g := range expectedGroups {
|
||||
expectedGroupsAsEmptyInterfaces = append(expectedGroupsAsEmptyInterfaces, g)
|
||||
}
|
||||
require.Equal(t, expectedGroupsAsEmptyInterfaces, idTokenClaims[oidc.DownstreamGroupsClaim])
|
||||
require.ElementsMatch(t, expectedGroupsAsEmptyInterfaces, idTokenClaims[oidc.DownstreamGroupsClaim])
|
||||
|
||||
expectedYAMLGroups := func() string {
|
||||
var b strings.Builder
|
||||
for _, g := range expectedGroups {
|
||||
b.WriteString("\n")
|
||||
b.WriteString(` - `)
|
||||
b.WriteString(g)
|
||||
}
|
||||
return b.String()
|
||||
}()
|
||||
expectedGroupsPlusAuthenticated := append([]string{}, expectedGroups...)
|
||||
expectedGroupsPlusAuthenticated = append(expectedGroupsPlusAuthenticated, "system:authenticated")
|
||||
|
||||
// Confirm we are the right user according to Kube by calling the whoami API.
|
||||
kubectlCmd3 := exec.CommandContext(ctx, "kubectl", "create", "-f", "-", "-o", "yaml", "--kubeconfig", kubeconfigPath)
|
||||
@@ -456,28 +456,15 @@ func requireUserCanUseKubectlWithoutAuthenticatingAgain(
|
||||
kubectlCmd3.Stdin = strings.NewReader(here.Docf(`
|
||||
apiVersion: identity.concierge.%s/v1alpha1
|
||||
kind: WhoAmIRequest
|
||||
`, env.APIGroupSuffix))
|
||||
`, env.APIGroupSuffix))
|
||||
|
||||
kubectlOutput3, err := kubectlCmd3.CombinedOutput()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, here.Docf(`
|
||||
apiVersion: identity.concierge.%s/v1alpha1
|
||||
kind: WhoAmIRequest
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
spec: {}
|
||||
status:
|
||||
kubernetesUserInfo:
|
||||
user:
|
||||
groups:%s
|
||||
- system:authenticated
|
||||
username: %s
|
||||
`, env.APIGroupSuffix, expectedYAMLGroups, expectedUsername),
|
||||
string(kubectlOutput3))
|
||||
whoAmI := deserializeWhoAmIRequest(t, string(kubectlOutput3), env.APIGroupSuffix)
|
||||
require.Equal(t, expectedUsername, whoAmI.Status.KubernetesUserInfo.User.Username)
|
||||
require.ElementsMatch(t, expectedGroupsPlusAuthenticated, whoAmI.Status.KubernetesUserInfo.User.Groups)
|
||||
|
||||
expectedGroupsPlusAuthenticated := append([]string{}, expectedGroups...)
|
||||
expectedGroupsPlusAuthenticated = append(expectedGroupsPlusAuthenticated, "system:authenticated")
|
||||
// Validate that `pinniped whoami` returns the correct identity.
|
||||
assertWhoami(
|
||||
ctx,
|
||||
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
@@ -70,7 +69,7 @@ func TestLDAPSearch(t *testing.T) {
|
||||
password: pinnyPassword,
|
||||
provider: upstreamldap.New(*providerConfig(nil)),
|
||||
wantAuthResponse: &authenticator.Response{
|
||||
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{}},
|
||||
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{"ball-game-players", "seals"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -79,7 +78,7 @@ func TestLDAPSearch(t *testing.T) {
|
||||
password: pinnyPassword,
|
||||
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.UserSearch.Base = "dc=pinniped,dc=dev" })),
|
||||
wantAuthResponse: &authenticator.Response{
|
||||
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{}},
|
||||
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{"ball-game-players", "seals"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -88,7 +87,7 @@ func TestLDAPSearch(t *testing.T) {
|
||||
password: pinnyPassword,
|
||||
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.UserSearch.Filter = "(cn={})" })),
|
||||
wantAuthResponse: &authenticator.Response{
|
||||
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{}},
|
||||
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{"ball-game-players", "seals"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -100,7 +99,7 @@ func TestLDAPSearch(t *testing.T) {
|
||||
p.UserSearch.Filter = "cn={}"
|
||||
})),
|
||||
wantAuthResponse: &authenticator.Response{
|
||||
User: &user.DefaultInfo{Name: "cn=pinny,ou=users,dc=pinniped,dc=dev", UID: "1000", Groups: []string{}},
|
||||
User: &user.DefaultInfo{Name: "cn=pinny,ou=users,dc=pinniped,dc=dev", UID: "1000", Groups: []string{"ball-game-players", "seals"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -111,7 +110,7 @@ func TestLDAPSearch(t *testing.T) {
|
||||
p.UserSearch.Filter = "(|(cn={})(mail={}))"
|
||||
})),
|
||||
wantAuthResponse: &authenticator.Response{
|
||||
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{}},
|
||||
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{"ball-game-players", "seals"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -122,7 +121,7 @@ func TestLDAPSearch(t *testing.T) {
|
||||
p.UserSearch.Filter = "(|(cn={})(mail={}))"
|
||||
})),
|
||||
wantAuthResponse: &authenticator.Response{
|
||||
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{}},
|
||||
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{"ball-game-players", "seals"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -131,7 +130,7 @@ func TestLDAPSearch(t *testing.T) {
|
||||
password: pinnyPassword,
|
||||
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.UserSearch.UIDAttribute = "dn" })),
|
||||
wantAuthResponse: &authenticator.Response{
|
||||
User: &user.DefaultInfo{Name: "pinny", UID: "cn=pinny,ou=users,dc=pinniped,dc=dev", Groups: []string{}},
|
||||
User: &user.DefaultInfo{Name: "pinny", UID: "cn=pinny,ou=users,dc=pinniped,dc=dev", Groups: []string{"ball-game-players", "seals"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -140,7 +139,7 @@ func TestLDAPSearch(t *testing.T) {
|
||||
password: pinnyPassword,
|
||||
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.UserSearch.UIDAttribute = "sn" })),
|
||||
wantAuthResponse: &authenticator.Response{
|
||||
User: &user.DefaultInfo{Name: "pinny", UID: "Seal", Groups: []string{}},
|
||||
User: &user.DefaultInfo{Name: "pinny", UID: "Seal", Groups: []string{"ball-game-players", "seals"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -149,7 +148,7 @@ func TestLDAPSearch(t *testing.T) {
|
||||
password: pinnyPassword,
|
||||
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.UserSearch.UsernameAttribute = "sn" })),
|
||||
wantAuthResponse: &authenticator.Response{
|
||||
User: &user.DefaultInfo{Name: "Seal", UID: "1000", Groups: []string{}}, // note that the final answer has case preserved from the entry
|
||||
User: &user.DefaultInfo{Name: "Seal", UID: "1000", Groups: []string{"ball-game-players", "seals"}}, // note that the final answer has case preserved from the entry
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -162,6 +161,75 @@ func TestLDAPSearch(t *testing.T) {
|
||||
})),
|
||||
wantError: `must specify UserSearch Filter when UserSearch UsernameAttribute is "dn"`,
|
||||
},
|
||||
{
|
||||
name: "group search disabled",
|
||||
username: "pinny",
|
||||
password: pinnyPassword,
|
||||
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) {
|
||||
p.GroupSearch.Base = ""
|
||||
})),
|
||||
wantAuthResponse: &authenticator.Response{
|
||||
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "group search base causes no groups to be found for user",
|
||||
username: "pinny",
|
||||
password: pinnyPassword,
|
||||
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) {
|
||||
p.GroupSearch.Base = "ou=users,dc=pinniped,dc=dev" // there are no groups under this part of the tree
|
||||
})),
|
||||
wantAuthResponse: &authenticator.Response{
|
||||
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "using dn as the group name attribute",
|
||||
username: "pinny",
|
||||
password: pinnyPassword,
|
||||
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) {
|
||||
p.GroupSearch.GroupNameAttribute = "dn"
|
||||
})),
|
||||
wantAuthResponse: &authenticator.Response{
|
||||
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{
|
||||
"cn=ball-game-players,ou=beach-groups,ou=groups,dc=pinniped,dc=dev",
|
||||
"cn=seals,ou=groups,dc=pinniped,dc=dev",
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "using some other custom group name attribute",
|
||||
username: "pinny",
|
||||
password: pinnyPassword,
|
||||
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) {
|
||||
p.GroupSearch.GroupNameAttribute = "objectClass" // silly example, but still a meaningful test
|
||||
})),
|
||||
wantAuthResponse: &authenticator.Response{
|
||||
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{"groupOfNames", "groupOfNames"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "using a more complex group search filter",
|
||||
username: "pinny",
|
||||
password: pinnyPassword,
|
||||
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) {
|
||||
p.GroupSearch.Filter = "(&(&(objectClass=groupOfNames)(member={}))(cn=seals))"
|
||||
})),
|
||||
wantAuthResponse: &authenticator.Response{
|
||||
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{"seals"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "using a group filter which causes no groups to be found for the user",
|
||||
username: "pinny",
|
||||
password: pinnyPassword,
|
||||
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) {
|
||||
p.GroupSearch.Filter = "foobar={}" // foobar is not a valid attribute name for this LDAP server's schema
|
||||
})),
|
||||
wantAuthResponse: &authenticator.Response{
|
||||
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "when the bind user username is not a valid DN",
|
||||
username: "pinny",
|
||||
@@ -211,6 +279,13 @@ func TestLDAPSearch(t *testing.T) {
|
||||
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.UserSearch.Filter = "*" })),
|
||||
wantError: `error searching for user "pinny": LDAP Result Code 201 "Filter Compile Error": ldap: error parsing filter`,
|
||||
},
|
||||
{
|
||||
name: "when the group search filter does not compile",
|
||||
username: "pinny",
|
||||
password: pinnyPassword,
|
||||
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.GroupSearch.Filter = "*" })),
|
||||
wantError: `error searching for group memberships for user with DN "cn=pinny,ou=users,dc=pinniped,dc=dev": LDAP Result Code 201 "Filter Compile Error": ldap: error parsing filter`,
|
||||
},
|
||||
{
|
||||
name: "when there are too many search results for the user",
|
||||
username: "pinny",
|
||||
@@ -293,6 +368,13 @@ func TestLDAPSearch(t *testing.T) {
|
||||
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.UserSearch.UIDAttribute = "SN" })), // this is case-sensitive
|
||||
wantError: `found 0 values for attribute "SN" while searching for user "pinny", but expected 1 result`,
|
||||
},
|
||||
{
|
||||
name: "when the GroupNameAttribute has the wrong case",
|
||||
username: "pinny",
|
||||
password: pinnyPassword,
|
||||
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.GroupSearch.GroupNameAttribute = "CN" })), // this is case-sensitive
|
||||
wantError: `error searching for group memberships for user with DN "cn=pinny,ou=users,dc=pinniped,dc=dev": found 0 values for attribute "CN" while searching for user "cn=pinny,ou=users,dc=pinniped,dc=dev", but expected 1 result`,
|
||||
},
|
||||
{
|
||||
name: "when the UsernameAttribute is DN and has the wrong case",
|
||||
username: "pinny",
|
||||
@@ -313,21 +395,44 @@ func TestLDAPSearch(t *testing.T) {
|
||||
wantError: `found 0 values for attribute "DN" while searching for user "pinny", but expected 1 result`,
|
||||
},
|
||||
{
|
||||
name: "when the search base is invalid",
|
||||
name: "when the GroupNameAttribute is DN and has the wrong case",
|
||||
username: "pinny",
|
||||
password: pinnyPassword,
|
||||
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) {
|
||||
p.GroupSearch.GroupNameAttribute = "DN" // dn must be lower-case
|
||||
})),
|
||||
wantError: `error searching for group memberships for user with DN "cn=pinny,ou=users,dc=pinniped,dc=dev": found 0 values for attribute "DN" while searching for user "cn=pinny,ou=users,dc=pinniped,dc=dev", but expected 1 result`,
|
||||
},
|
||||
{
|
||||
name: "when the user search base is invalid",
|
||||
username: "pinny",
|
||||
password: pinnyPassword,
|
||||
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.UserSearch.Base = "invalid-base" })),
|
||||
wantError: `error searching for user "pinny": LDAP Result Code 34 "Invalid DN Syntax": invalid DN`,
|
||||
},
|
||||
{
|
||||
name: "when the search base does not exist",
|
||||
name: "when the group search base is invalid",
|
||||
username: "pinny",
|
||||
password: pinnyPassword,
|
||||
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.GroupSearch.Base = "invalid-base" })),
|
||||
wantError: `error searching for group memberships for user with DN "cn=pinny,ou=users,dc=pinniped,dc=dev": LDAP Result Code 34 "Invalid DN Syntax": invalid DN`,
|
||||
},
|
||||
{
|
||||
name: "when the user search base does not exist",
|
||||
username: "pinny",
|
||||
password: pinnyPassword,
|
||||
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.UserSearch.Base = "ou=does-not-exist,dc=pinniped,dc=dev" })),
|
||||
wantError: `error searching for user "pinny": LDAP Result Code 32 "No Such Object": `,
|
||||
},
|
||||
{
|
||||
name: "when the search base causes no search results",
|
||||
name: "when the group search base does not exist",
|
||||
username: "pinny",
|
||||
password: pinnyPassword,
|
||||
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.GroupSearch.Base = "ou=does-not-exist,dc=pinniped,dc=dev" })),
|
||||
wantError: `error searching for group memberships for user with DN "cn=pinny,ou=users,dc=pinniped,dc=dev": LDAP Result Code 32 "No Such Object": `,
|
||||
},
|
||||
{
|
||||
name: "when the user search base causes no search results",
|
||||
username: "pinny",
|
||||
password: pinnyPassword,
|
||||
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.UserSearch.Base = "ou=groups,dc=pinniped,dc=dev" })),
|
||||
@@ -425,7 +530,7 @@ func TestSimultaneousRequestsOnSingleProvider(t *testing.T) {
|
||||
assert.NoError(t, result.err)
|
||||
assert.True(t, result.authenticated, "expected the user to be authenticated, but they were not")
|
||||
assert.Equal(t, &authenticator.Response{
|
||||
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{}},
|
||||
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{"ball-game-players", "seals"}},
|
||||
}, result.response)
|
||||
}
|
||||
}
|
||||
@@ -449,6 +554,11 @@ func defaultProviderConfig(env *library.TestEnv, ldapHostPort string) *upstreaml
|
||||
UsernameAttribute: "cn",
|
||||
UIDAttribute: "uidNumber",
|
||||
},
|
||||
GroupSearch: upstreamldap.GroupSearchConfig{
|
||||
Base: "ou=groups,dc=pinniped,dc=dev",
|
||||
Filter: "", // defaults to member={}
|
||||
GroupNameAttribute: "", // defaults to cn
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ func TestSupervisorLogin(t *testing.T) {
|
||||
requestAuthorization func(t *testing.T, downstreamAuthorizeURL, downstreamCallbackURL string, httpClient *http.Client)
|
||||
wantDownstreamIDTokenSubjectToMatch string
|
||||
wantDownstreamIDTokenUsernameToMatch string
|
||||
wantDownstreamIDTokenGroups []string
|
||||
}{
|
||||
{
|
||||
name: "oidc",
|
||||
@@ -65,9 +66,10 @@ func TestSupervisorLogin(t *testing.T) {
|
||||
wantDownstreamIDTokenSubjectToMatch: regexp.QuoteMeta(env.SupervisorUpstreamOIDC.Issuer+"?sub=") + ".+",
|
||||
// the ID token Username should include the upstream user ID after the upstream issuer name
|
||||
wantDownstreamIDTokenUsernameToMatch: regexp.QuoteMeta(env.SupervisorUpstreamOIDC.Issuer+"?sub=") + ".+",
|
||||
wantDownstreamIDTokenGroups: env.SupervisorUpstreamOIDC.ExpectedGroups,
|
||||
},
|
||||
{
|
||||
name: "ldap with email as username",
|
||||
name: "ldap with email as username and groups names as DNs",
|
||||
createIDP: func(t *testing.T) {
|
||||
t.Helper()
|
||||
secret := library.CreateTestSecret(t, env.SupervisorNamespace, "ldap-service-account", v1.SecretTypeBasicAuth,
|
||||
@@ -92,6 +94,13 @@ func TestSupervisorLogin(t *testing.T) {
|
||||
UID: env.SupervisorUpstreamLDAP.TestUserUniqueIDAttributeName,
|
||||
},
|
||||
},
|
||||
GroupSearch: idpv1alpha1.LDAPIdentityProviderGroupSearch{
|
||||
Base: env.SupervisorUpstreamLDAP.GroupSearchBase,
|
||||
Filter: "",
|
||||
Attributes: idpv1alpha1.LDAPIdentityProviderGroupSearchAttributes{
|
||||
GroupName: "dn",
|
||||
},
|
||||
},
|
||||
}, idpv1alpha1.LDAPPhaseReady)
|
||||
expectedMsg := fmt.Sprintf(
|
||||
`successfully able to connect to "%s" and bind as user "%s" [validated with Secret "%s" at version "%s"]`,
|
||||
@@ -114,9 +123,10 @@ func TestSupervisorLogin(t *testing.T) {
|
||||
),
|
||||
// the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute
|
||||
wantDownstreamIDTokenUsernameToMatch: regexp.QuoteMeta(env.SupervisorUpstreamLDAP.TestUserMailAttributeValue),
|
||||
wantDownstreamIDTokenGroups: env.SupervisorUpstreamLDAP.TestUserDirectGroupsDNs,
|
||||
},
|
||||
{
|
||||
name: "ldap with CN as username ", // try another variation of configuration options
|
||||
name: "ldap with CN as username and group names as CNs", // try another variation of configuration options
|
||||
createIDP: func(t *testing.T) {
|
||||
t.Helper()
|
||||
secret := library.CreateTestSecret(t, env.SupervisorNamespace, "ldap-service-account", v1.SecretTypeBasicAuth,
|
||||
@@ -141,6 +151,13 @@ func TestSupervisorLogin(t *testing.T) {
|
||||
UID: env.SupervisorUpstreamLDAP.TestUserUniqueIDAttributeName,
|
||||
},
|
||||
},
|
||||
GroupSearch: idpv1alpha1.LDAPIdentityProviderGroupSearch{
|
||||
Base: env.SupervisorUpstreamLDAP.GroupSearchBase,
|
||||
Filter: "",
|
||||
Attributes: idpv1alpha1.LDAPIdentityProviderGroupSearchAttributes{
|
||||
GroupName: "cn",
|
||||
},
|
||||
},
|
||||
}, idpv1alpha1.LDAPPhaseReady)
|
||||
expectedMsg := fmt.Sprintf(
|
||||
`successfully able to connect to "%s" and bind as user "%s" [validated with Secret "%s" at version "%s"]`,
|
||||
@@ -163,6 +180,7 @@ func TestSupervisorLogin(t *testing.T) {
|
||||
),
|
||||
// the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute
|
||||
wantDownstreamIDTokenUsernameToMatch: regexp.QuoteMeta(env.SupervisorUpstreamLDAP.TestUserDN),
|
||||
wantDownstreamIDTokenGroups: env.SupervisorUpstreamLDAP.TestUserDirectGroupsCNs,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
@@ -173,6 +191,7 @@ func TestSupervisorLogin(t *testing.T) {
|
||||
test.requestAuthorization,
|
||||
test.wantDownstreamIDTokenSubjectToMatch,
|
||||
test.wantDownstreamIDTokenUsernameToMatch,
|
||||
test.wantDownstreamIDTokenGroups,
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -207,7 +226,7 @@ func testSupervisorLogin(
|
||||
t *testing.T,
|
||||
createIDP func(t *testing.T),
|
||||
requestAuthorization func(t *testing.T, downstreamAuthorizeURL, downstreamCallbackURL string, httpClient *http.Client),
|
||||
wantDownstreamIDTokenSubjectToMatch, wantDownstreamIDTokenUsernameToMatch string,
|
||||
wantDownstreamIDTokenSubjectToMatch, wantDownstreamIDTokenUsernameToMatch string, wantDownstreamIDTokenGroups []string,
|
||||
) {
|
||||
env := library.IntegrationEnv(t)
|
||||
|
||||
@@ -350,7 +369,7 @@ func testSupervisorLogin(
|
||||
expectedIDTokenClaims := []string{"iss", "exp", "sub", "aud", "auth_time", "iat", "jti", "nonce", "rat", "username", "groups"}
|
||||
verifyTokenResponse(t,
|
||||
tokenResponse, discovery, downstreamOAuth2Config, nonceParam,
|
||||
expectedIDTokenClaims, wantDownstreamIDTokenSubjectToMatch, wantDownstreamIDTokenUsernameToMatch)
|
||||
expectedIDTokenClaims, wantDownstreamIDTokenSubjectToMatch, wantDownstreamIDTokenUsernameToMatch, wantDownstreamIDTokenGroups)
|
||||
|
||||
// token exchange on the original token
|
||||
doTokenExchange(t, &downstreamOAuth2Config, tokenResponse, httpClient, discovery)
|
||||
@@ -363,7 +382,7 @@ func testSupervisorLogin(
|
||||
expectedIDTokenClaims = append(expectedIDTokenClaims, "at_hash")
|
||||
verifyTokenResponse(t,
|
||||
refreshedTokenResponse, discovery, downstreamOAuth2Config, "",
|
||||
expectedIDTokenClaims, wantDownstreamIDTokenSubjectToMatch, wantDownstreamIDTokenUsernameToMatch)
|
||||
expectedIDTokenClaims, wantDownstreamIDTokenSubjectToMatch, wantDownstreamIDTokenUsernameToMatch, wantDownstreamIDTokenGroups)
|
||||
|
||||
require.NotEqual(t, tokenResponse.AccessToken, refreshedTokenResponse.AccessToken)
|
||||
require.NotEqual(t, tokenResponse.RefreshToken, refreshedTokenResponse.RefreshToken)
|
||||
@@ -380,7 +399,7 @@ func verifyTokenResponse(
|
||||
downstreamOAuth2Config oauth2.Config,
|
||||
nonceParam nonce.Nonce,
|
||||
expectedIDTokenClaims []string,
|
||||
wantDownstreamIDTokenSubjectToMatch, wantDownstreamIDTokenUsernameToMatch string,
|
||||
wantDownstreamIDTokenSubjectToMatch, wantDownstreamIDTokenUsernameToMatch string, wantDownstreamIDTokenGroups []string,
|
||||
) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||
defer cancel()
|
||||
@@ -415,6 +434,9 @@ func verifyTokenResponse(
|
||||
// Check username claim of the ID token.
|
||||
require.Regexp(t, wantDownstreamIDTokenUsernameToMatch, idTokenClaims["username"].(string))
|
||||
|
||||
// Check the groups claim.
|
||||
require.ElementsMatch(t, wantDownstreamIDTokenGroups, idTokenClaims["groups"])
|
||||
|
||||
// Some light verification of the other tokens that were returned.
|
||||
require.NotEmpty(t, tokenResponse.AccessToken)
|
||||
require.Equal(t, "bearer", tokenResponse.TokenType)
|
||||
|
||||
+22
-12
@@ -7,6 +7,7 @@ import (
|
||||
"encoding/base64"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
@@ -71,18 +72,21 @@ type TestOIDCUpstream struct {
|
||||
}
|
||||
|
||||
type TestLDAPUpstream struct {
|
||||
Host string `json:"host"`
|
||||
CABundle string `json:"caBundle"`
|
||||
BindUsername string `json:"bindUsername"`
|
||||
BindPassword string `json:"bindPassword"`
|
||||
UserSearchBase string `json:"userSearchBase"`
|
||||
TestUserDN string `json:"testUserDN"`
|
||||
TestUserCN string `json:"testUserCN"`
|
||||
TestUserPassword string `json:"testUserPassword"`
|
||||
TestUserMailAttributeName string `json:"testUserMailAttributeName"`
|
||||
TestUserMailAttributeValue string `json:"testUserMailAttributeValue"`
|
||||
TestUserUniqueIDAttributeName string `json:"testUserUniqueIDAttributeName"`
|
||||
TestUserUniqueIDAttributeValue string `json:"testUserUniqueIDAttributeValue"`
|
||||
Host string `json:"host"`
|
||||
CABundle string `json:"caBundle"`
|
||||
BindUsername string `json:"bindUsername"`
|
||||
BindPassword string `json:"bindPassword"`
|
||||
UserSearchBase string `json:"userSearchBase"`
|
||||
GroupSearchBase string `json:"groupSearchBase"`
|
||||
TestUserDN string `json:"testUserDN"`
|
||||
TestUserCN string `json:"testUserCN"`
|
||||
TestUserPassword string `json:"testUserPassword"`
|
||||
TestUserMailAttributeName string `json:"testUserMailAttributeName"`
|
||||
TestUserMailAttributeValue string `json:"testUserMailAttributeValue"`
|
||||
TestUserUniqueIDAttributeName string `json:"testUserUniqueIDAttributeName"`
|
||||
TestUserUniqueIDAttributeValue string `json:"testUserUniqueIDAttributeValue"`
|
||||
TestUserDirectGroupsCNs []string `json:"testUserDirectGroupsCNs"`
|
||||
TestUserDirectGroupsDNs []string `json:"testUserDirectGroupsDNs"` //nolint:golint // this is "distinguished names", not "DNS"
|
||||
}
|
||||
|
||||
// ProxyEnv returns a set of environment variable strings (e.g., to combine with os.Environ()) which set up the configured test HTTP proxy.
|
||||
@@ -240,14 +244,20 @@ func loadEnvVars(t *testing.T, result *TestEnv) {
|
||||
BindUsername: needEnv(t, "PINNIPED_TEST_LDAP_BIND_ACCOUNT_USERNAME"),
|
||||
BindPassword: needEnv(t, "PINNIPED_TEST_LDAP_BIND_ACCOUNT_PASSWORD"),
|
||||
UserSearchBase: needEnv(t, "PINNIPED_TEST_LDAP_USERS_SEARCH_BASE"),
|
||||
GroupSearchBase: needEnv(t, "PINNIPED_TEST_LDAP_GROUPS_SEARCH_BASE"),
|
||||
TestUserDN: needEnv(t, "PINNIPED_TEST_LDAP_USER_DN"),
|
||||
TestUserCN: needEnv(t, "PINNIPED_TEST_LDAP_USER_CN"),
|
||||
TestUserUniqueIDAttributeName: needEnv(t, "PINNIPED_TEST_LDAP_USER_UNIQUE_ID_ATTRIBUTE_NAME"),
|
||||
TestUserUniqueIDAttributeValue: needEnv(t, "PINNIPED_TEST_LDAP_USER_UNIQUE_ID_ATTRIBUTE_VALUE"),
|
||||
TestUserMailAttributeName: needEnv(t, "PINNIPED_TEST_LDAP_USER_EMAIL_ATTRIBUTE_NAME"),
|
||||
TestUserMailAttributeValue: needEnv(t, "PINNIPED_TEST_LDAP_USER_EMAIL_ATTRIBUTE_VALUE"),
|
||||
TestUserDirectGroupsCNs: filterEmpty(strings.Split(needEnv(t, "PINNIPED_TEST_LDAP_EXPECTED_DIRECT_GROUPS_CN"), ";")),
|
||||
TestUserDirectGroupsDNs: filterEmpty(strings.Split(needEnv(t, "PINNIPED_TEST_LDAP_EXPECTED_DIRECT_GROUPS_DN"), ";")),
|
||||
TestUserPassword: needEnv(t, "PINNIPED_TEST_LDAP_USER_PASSWORD"),
|
||||
}
|
||||
|
||||
sort.Strings(result.SupervisorUpstreamLDAP.TestUserDirectGroupsCNs)
|
||||
sort.Strings(result.SupervisorUpstreamLDAP.TestUserDirectGroupsDNs)
|
||||
}
|
||||
|
||||
func (e *TestEnv) HasCapability(cap Capability) bool {
|
||||
|
||||
Reference in New Issue
Block a user