mirror of
https://github.com/vmware-tanzu/pinniped.git
synced 2026-09-19 22:44:18 +00:00
Merge branch 'main' of github.com:vmware-tanzu/pinniped into credentialissuer-spec-api
This commit is contained in:
@@ -77,13 +77,15 @@ spec:
|
||||
| cfssljson -bare dex
|
||||
|
||||
# Cheat and add 127.0.0.1 as an IP SAN so we can use the ldaps port through port forwarding.
|
||||
# Also allow the server to be accessed by multiple Service names to different Services
|
||||
# can provide/hide different ports.
|
||||
echo "generating LDAP server certificate..."
|
||||
cfssl gencert \
|
||||
-ca ca.pem -ca-key ca-key.pem \
|
||||
-config /tmp/cfssl-default.json \
|
||||
-profile www \
|
||||
-cn "ldap.tools.svc.cluster.local" \
|
||||
-hostname "ldap.tools.svc.cluster.local,127.0.0.1" \
|
||||
-hostname "ldap.tools.svc.cluster.local,ldaps.tools.svc.cluster.local,ldapstarttls.tools.svc.cluster.local,127.0.0.1" \
|
||||
/tmp/csr.json \
|
||||
| cfssljson -bare ldap
|
||||
|
||||
|
||||
+119
-3
@@ -127,6 +127,63 @@ metadata:
|
||||
type: Opaque
|
||||
stringData: #@ ldapLIDIF()
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: ldap-server-config-before-ldif-files
|
||||
namespace: tools
|
||||
type: Opaque
|
||||
stringData:
|
||||
server-config.ldif: |
|
||||
# Load the memberof module.
|
||||
dn: cn=module,cn=config
|
||||
cn: module
|
||||
objectClass: olcModuleList
|
||||
objectClass: top
|
||||
olcModulePath: /opt/bitnami/openldap/lib/openldap
|
||||
olcModuleLoad: memberof
|
||||
|
||||
dn: olcOverlay={0}memberof,olcDatabase={2}hdb,cn=config
|
||||
objectClass: olcConfig
|
||||
objectClass: olcMemberOf
|
||||
objectClass: olcOverlayConfig
|
||||
objectClass: top
|
||||
olcOverlay: memberof
|
||||
olcMemberOfDangling: ignore
|
||||
olcMemberOfRefInt: TRUE
|
||||
olcMemberOfGroupOC: groupOfNames
|
||||
olcMemberOfMemberAD: member
|
||||
|
||||
# Load the refint module.
|
||||
dn: cn=module,cn=config
|
||||
cn: module
|
||||
objectclass: olcModuleList
|
||||
objectclass: top
|
||||
olcmodulepath: /opt/bitnami/openldap/lib/openldap
|
||||
olcmoduleload: refint
|
||||
|
||||
dn: olcOverlay={1}refint,olcDatabase={2}hdb,cn=config
|
||||
objectClass: olcConfig
|
||||
objectClass: olcOverlayConfig
|
||||
objectClass: olcRefintConfig
|
||||
objectClass: top
|
||||
olcOverlay: {1}refint
|
||||
olcRefintAttribute: memberof member manager owner
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: ldap-server-config-after-ldif-files
|
||||
namespace: tools
|
||||
type: Opaque
|
||||
stringData:
|
||||
server-config.ldif: |
|
||||
# Reject any further connections that do not use TLS or StartTLS
|
||||
dn: olcDatabase={2}hdb,cn=config
|
||||
changetype: modify
|
||||
add: olcSecurity
|
||||
olcSecurity: tls=1
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
@@ -149,7 +206,10 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: ldap
|
||||
image: docker.io/bitnami/openldap
|
||||
#! Use our own fork of docker.io/bitnami/openldap for now, because we added the
|
||||
#! LDAP_SERVER_CONFIG_BEFORE_CUSTOM_LDIF_DIR and LDAP_SERVER_CONFIG_AFTER_CUSTOM_LDIF_DIR options.
|
||||
#! See https://github.com/pinniped-ci-bot/bitnami-docker-openldap/tree/pinniped
|
||||
image: projects.registry.vmware.com/pinniped/test-ldap:latest
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- name: ldap
|
||||
@@ -173,8 +233,14 @@ spec:
|
||||
env:
|
||||
#! Example ldapsearch commands that can be run from within the container based on these env vars.
|
||||
#! These will print the whole LDAP tree starting at our root.
|
||||
#! ldapsearch -x -H 'ldap://ldap.tools.svc.cluster.local' -D 'cn=admin,dc=pinniped,dc=dev' -w password -b 'dc=pinniped,dc=dev'
|
||||
#! LDAPTLS_CACERT=/var/certs/ca.pem ldapsearch -x -H 'ldaps://ldap.tools.svc.cluster.local' -D 'cn=admin,dc=pinniped,dc=dev' -w password -b 'dc=pinniped,dc=dev'
|
||||
#! Using StartTLS (-ZZ) on the ldap port...
|
||||
#! LDAPTLS_CACERT=/var/certs/ca.pem ldapsearch -x -ZZ -H 'ldap://ldap.tools.svc.cluster.local' -D 'cn=admin,dc=pinniped,dc=dev' -w password -b 'dc=pinniped,dc=dev'
|
||||
#! Using ldaps...
|
||||
#! LDAPTLS_CACERT=/var/certs/ca.pem ldapsearch -x -H 'ldaps://ldap.tools.svc.cluster.local' -D 'cn=admin,dc=pinniped,dc=dev' -w password -b 'dc=pinniped,dc=dev'
|
||||
#! Note that the memberOf attribute is special and not returned by default. It must be specified as one of attributes to return in the search, e.g.:
|
||||
#! LDAPTLS_CACERT=/var/certs/ca.pem ldapsearch -x -H 'ldaps://ldap.tools.svc.cluster.local' -D 'cn=admin,dc=pinniped,dc=dev' -w password -b 'dc=pinniped,dc=dev' cn uidNumber mail member memberOf
|
||||
#! This should fail and report "TLS confidentiality required" because we require TLS and this does not use TLS or StartTLS...
|
||||
#! ldapsearch -x -H 'ldap://ldap.tools.svc.cluster.local' -D 'cn=admin,dc=pinniped,dc=dev' -w password -b 'dc=pinniped,dc=dev'
|
||||
- name: BITNAMI_DEBUG
|
||||
value: "true"
|
||||
- name: LDAP_ADMIN_USERNAME
|
||||
@@ -192,6 +258,10 @@ spec:
|
||||
#! Note that the custom LDIF file is only read at pod start-up time.
|
||||
- name: LDAP_CUSTOM_LDIF_DIR
|
||||
value: "/var/ldifs"
|
||||
- name: LDAP_SERVER_CONFIG_BEFORE_CUSTOM_LDIF_DIR
|
||||
value: "/var/server-config-before-ldifs"
|
||||
- name: LDAP_SERVER_CONFIG_AFTER_CUSTOM_LDIF_DIR
|
||||
value: "/var/server-config-after-ldifs"
|
||||
#! Seems like LDAP_ROOT is still required when using LDAP_CUSTOM_LDIF_DIR because it effects the admin user.
|
||||
#! Presumably this needs to match the root that we create in the LDIF file.
|
||||
- name: LDAP_ROOT
|
||||
@@ -203,6 +273,12 @@ spec:
|
||||
- name: ldifs
|
||||
mountPath: /var/ldifs
|
||||
readOnly: true
|
||||
- name: server-config-before-ldifs
|
||||
mountPath: /var/server-config-before-ldifs
|
||||
readOnly: true
|
||||
- name: server-config-after-ldifs
|
||||
mountPath: /var/server-config-after-ldifs
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: certs
|
||||
secret:
|
||||
@@ -210,6 +286,12 @@ spec:
|
||||
- name: ldifs
|
||||
secret:
|
||||
secretName: ldap-ldif-files
|
||||
- name: server-config-before-ldifs
|
||||
secret:
|
||||
secretName: ldap-server-config-before-ldif-files
|
||||
- name: server-config-after-ldifs
|
||||
secret:
|
||||
secretName: ldap-server-config-after-ldif-files
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
@@ -231,3 +313,37 @@ spec:
|
||||
port: 636
|
||||
targetPort: 1636
|
||||
name: ldaps
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: ldaps
|
||||
namespace: tools
|
||||
labels:
|
||||
app: ldap
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app: ldap
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 636
|
||||
targetPort: 1636
|
||||
name: ldaps
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: ldapstarttls
|
||||
namespace: tools
|
||||
labels:
|
||||
app: ldap
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app: ldap
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 389
|
||||
targetPort: 1389
|
||||
name: ldap
|
||||
|
||||
@@ -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"
|
||||
@@ -38,15 +37,19 @@ func TestLDAPSearch(t *testing.T) {
|
||||
cancelFunc() // this will send SIGKILL to the subprocess, just in case
|
||||
})
|
||||
|
||||
hostPorts := findRecentlyUnusedLocalhostPorts(t, 2)
|
||||
ldapHostPort := hostPorts[0]
|
||||
unusedHostPort := hostPorts[1]
|
||||
localhostPorts := findRecentlyUnusedLocalhostPorts(t, 3)
|
||||
ldapLocalhostPort := localhostPorts[0]
|
||||
ldapsLocalhostPort := localhostPorts[1]
|
||||
unusedLocalhostPort := localhostPorts[2]
|
||||
|
||||
// Expose the the test LDAP server's TLS port on the localhost.
|
||||
startKubectlPortForward(ctx, t, ldapHostPort, "ldaps", "ldap", env.ToolsNamespace)
|
||||
startKubectlPortForward(ctx, t, ldapsLocalhostPort, "ldaps", "ldap", env.ToolsNamespace)
|
||||
|
||||
// Expose the the test LDAP server's StartTLS port on the localhost.
|
||||
startKubectlPortForward(ctx, t, ldapLocalhostPort, "ldap", "ldap", env.ToolsNamespace)
|
||||
|
||||
providerConfig := func(editFunc func(p *upstreamldap.ProviderConfig)) *upstreamldap.ProviderConfig {
|
||||
providerConfig := defaultProviderConfig(env, ldapHostPort)
|
||||
providerConfig := defaultProviderConfig(env, ldapsLocalhostPort)
|
||||
if editFunc != nil {
|
||||
editFunc(providerConfig)
|
||||
}
|
||||
@@ -65,12 +68,24 @@ func TestLDAPSearch(t *testing.T) {
|
||||
wantUnauthenticated bool
|
||||
}{
|
||||
{
|
||||
name: "happy path",
|
||||
name: "happy path with TLS",
|
||||
username: "pinny",
|
||||
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"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "happy path with StartTLS",
|
||||
username: "pinny",
|
||||
password: pinnyPassword,
|
||||
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) {
|
||||
p.Host = "127.0.0.1:" + ldapLocalhostPort
|
||||
p.ConnectionProtocol = upstreamldap.StartTLS
|
||||
})),
|
||||
wantAuthResponse: &authenticator.Response{
|
||||
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{"ball-game-players", "seals"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -79,7 +94,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 +103,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 +115,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 +126,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 +137,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 +146,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 +155,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 +164,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 +177,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",
|
||||
@@ -183,6 +267,17 @@ func TestLDAPSearch(t *testing.T) {
|
||||
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.BindPassword = "wrong-password" })),
|
||||
wantError: `error binding as "cn=admin,dc=pinniped,dc=dev" before user search: LDAP Result Code 49 "Invalid Credentials": `,
|
||||
},
|
||||
{
|
||||
name: "when the bind user username is wrong with StartTLS: example of an error after successful connection with StartTLS",
|
||||
username: "pinny",
|
||||
password: pinnyPassword,
|
||||
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) {
|
||||
p.Host = "127.0.0.1:" + ldapLocalhostPort
|
||||
p.ConnectionProtocol = upstreamldap.StartTLS
|
||||
p.BindUsername = "cn=wrong,dc=pinniped,dc=dev"
|
||||
})),
|
||||
wantError: `error binding as "cn=wrong,dc=pinniped,dc=dev" before user search: LDAP Result Code 49 "Invalid Credentials": `,
|
||||
},
|
||||
{
|
||||
name: "when the end user password is wrong",
|
||||
username: "pinny",
|
||||
@@ -211,6 +306,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",
|
||||
@@ -221,32 +323,89 @@ func TestLDAPSearch(t *testing.T) {
|
||||
wantError: `error searching for user "pinny": LDAP Result Code 4 "Size Limit Exceeded": `,
|
||||
},
|
||||
{
|
||||
name: "when the server is unreachable",
|
||||
name: "when the server is unreachable with TLS",
|
||||
username: "pinny",
|
||||
password: pinnyPassword,
|
||||
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.Host = "127.0.0.1:" + unusedHostPort })),
|
||||
wantError: fmt.Sprintf(`error dialing host "127.0.0.1:%s": LDAP Result Code 200 "Network Error": dial tcp 127.0.0.1:%s: connect: connection refused`, unusedHostPort, unusedHostPort),
|
||||
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.Host = "127.0.0.1:" + unusedLocalhostPort })),
|
||||
wantError: fmt.Sprintf(`error dialing host "127.0.0.1:%s": LDAP Result Code 200 "Network Error": dial tcp 127.0.0.1:%s: connect: connection refused`, unusedLocalhostPort, unusedLocalhostPort),
|
||||
},
|
||||
{
|
||||
name: "when the server is not parsable",
|
||||
name: "when the server is unreachable with StartTLS",
|
||||
username: "pinny",
|
||||
password: pinnyPassword,
|
||||
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) {
|
||||
p.Host = "127.0.0.1:" + unusedLocalhostPort
|
||||
p.ConnectionProtocol = upstreamldap.StartTLS
|
||||
})),
|
||||
wantError: fmt.Sprintf(`error dialing host "127.0.0.1:%s": LDAP Result Code 200 "Network Error": dial tcp 127.0.0.1:%s: connect: connection refused`, unusedLocalhostPort, unusedLocalhostPort),
|
||||
},
|
||||
{
|
||||
name: "when the server is not parsable with TLS",
|
||||
username: "pinny",
|
||||
password: pinnyPassword,
|
||||
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.Host = "too:many:ports" })),
|
||||
wantError: `error dialing host "too:many:ports": LDAP Result Code 200 "Network Error": address too:many:ports: too many colons in address`,
|
||||
},
|
||||
{
|
||||
name: "when the CA bundle is not parsable",
|
||||
name: "when the server is not parsable with StartTLS",
|
||||
username: "pinny",
|
||||
password: pinnyPassword,
|
||||
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) {
|
||||
p.Host = "127.0.0.1:" + ldapLocalhostPort
|
||||
p.ConnectionProtocol = upstreamldap.StartTLS
|
||||
p.Host = "too:many:ports"
|
||||
})),
|
||||
wantError: `error dialing host "too:many:ports": LDAP Result Code 200 "Network Error": address too:many:ports: too many colons in address`,
|
||||
},
|
||||
{
|
||||
name: "when the CA bundle is not parsable with TLS",
|
||||
username: "pinny",
|
||||
password: pinnyPassword,
|
||||
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.CABundle = []byte("invalid-pem") })),
|
||||
wantError: fmt.Sprintf(`error dialing host "127.0.0.1:%s": LDAP Result Code 200 "Network Error": could not parse CA bundle`, ldapHostPort),
|
||||
wantError: fmt.Sprintf(`error dialing host "127.0.0.1:%s": LDAP Result Code 200 "Network Error": could not parse CA bundle`, ldapsLocalhostPort),
|
||||
},
|
||||
{
|
||||
name: "when the CA bundle does not cause the host to be trusted",
|
||||
name: "when the CA bundle is not parsable with StartTLS",
|
||||
username: "pinny",
|
||||
password: pinnyPassword,
|
||||
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) {
|
||||
p.Host = "127.0.0.1:" + ldapLocalhostPort
|
||||
p.ConnectionProtocol = upstreamldap.StartTLS
|
||||
p.CABundle = []byte("invalid-pem")
|
||||
})),
|
||||
wantError: fmt.Sprintf(`error dialing host "127.0.0.1:%s": LDAP Result Code 200 "Network Error": could not parse CA bundle`, ldapLocalhostPort),
|
||||
},
|
||||
{
|
||||
name: "when the CA bundle does not cause the host to be trusted with TLS",
|
||||
username: "pinny",
|
||||
password: pinnyPassword,
|
||||
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.CABundle = nil })),
|
||||
wantError: fmt.Sprintf(`error dialing host "127.0.0.1:%s": LDAP Result Code 200 "Network Error": x509: certificate signed by unknown authority`, ldapHostPort),
|
||||
wantError: fmt.Sprintf(`error dialing host "127.0.0.1:%s": LDAP Result Code 200 "Network Error": x509: certificate signed by unknown authority`, ldapsLocalhostPort),
|
||||
},
|
||||
{
|
||||
name: "when the CA bundle does not cause the host to be trusted with StartTLS",
|
||||
username: "pinny",
|
||||
password: pinnyPassword,
|
||||
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) {
|
||||
p.Host = "127.0.0.1:" + ldapLocalhostPort
|
||||
p.ConnectionProtocol = upstreamldap.StartTLS
|
||||
p.CABundle = nil
|
||||
})),
|
||||
wantError: fmt.Sprintf(`error dialing host "127.0.0.1:%s": LDAP Result Code 200 "Network Error": TLS handshake failed (x509: certificate signed by unknown authority)`, ldapLocalhostPort),
|
||||
},
|
||||
{
|
||||
name: "when trying to use TLS to connect to a port which only supports StartTLS",
|
||||
username: "pinny",
|
||||
password: pinnyPassword,
|
||||
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.Host = "127.0.0.1:" + ldapLocalhostPort })),
|
||||
wantError: fmt.Sprintf(`error dialing host "127.0.0.1:%s": LDAP Result Code 200 "Network Error": EOF`, ldapLocalhostPort),
|
||||
},
|
||||
{
|
||||
name: "when trying to use StartTLS to connect to a port which only supports TLS",
|
||||
username: "pinny",
|
||||
password: pinnyPassword,
|
||||
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.ConnectionProtocol = upstreamldap.StartTLS })),
|
||||
wantError: fmt.Sprintf(`error dialing host "127.0.0.1:%s": unable to read LDAP response packet: unexpected EOF`, ldapsLocalhostPort),
|
||||
},
|
||||
{
|
||||
name: "when the UsernameAttribute attribute has multiple values in the entry",
|
||||
@@ -293,6 +452,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 +479,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 +614,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)
|
||||
}
|
||||
}
|
||||
@@ -436,19 +625,25 @@ type authUserResult struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func defaultProviderConfig(env *library.TestEnv, ldapHostPort string) *upstreamldap.ProviderConfig {
|
||||
func defaultProviderConfig(env *library.TestEnv, port string) *upstreamldap.ProviderConfig {
|
||||
return &upstreamldap.ProviderConfig{
|
||||
Name: "test-ldap-provider",
|
||||
Host: "127.0.0.1:" + ldapHostPort,
|
||||
CABundle: []byte(env.SupervisorUpstreamLDAP.CABundle),
|
||||
BindUsername: "cn=admin,dc=pinniped,dc=dev",
|
||||
BindPassword: "password",
|
||||
Name: "test-ldap-provider",
|
||||
Host: "127.0.0.1:" + port,
|
||||
ConnectionProtocol: upstreamldap.TLS,
|
||||
CABundle: []byte(env.SupervisorUpstreamLDAP.CABundle),
|
||||
BindUsername: "cn=admin,dc=pinniped,dc=dev",
|
||||
BindPassword: "password",
|
||||
UserSearch: upstreamldap.UserSearchConfig{
|
||||
Base: "ou=users,dc=pinniped,dc=dev",
|
||||
Filter: "", // defaults to UsernameAttribute={}, i.e. "cn={}" in this case
|
||||
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 and using an LDAP provider which supports TLS",
|
||||
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 and using an LDAP provider which only supports StartTLS", // try another variation of configuration options
|
||||
createIDP: func(t *testing.T) {
|
||||
t.Helper()
|
||||
secret := library.CreateTestSecret(t, env.SupervisorNamespace, "ldap-service-account", v1.SecretTypeBasicAuth,
|
||||
@@ -126,7 +136,7 @@ func TestSupervisorLogin(t *testing.T) {
|
||||
},
|
||||
)
|
||||
ldapIDP := library.CreateTestLDAPIdentityProvider(t, idpv1alpha1.LDAPIdentityProviderSpec{
|
||||
Host: env.SupervisorUpstreamLDAP.Host,
|
||||
Host: env.SupervisorUpstreamLDAP.StartTLSOnlyHost,
|
||||
TLS: &idpv1alpha1.TLSSpec{
|
||||
CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte(env.SupervisorUpstreamLDAP.CABundle)),
|
||||
},
|
||||
@@ -141,10 +151,17 @@ 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"]`,
|
||||
env.SupervisorUpstreamLDAP.Host, env.SupervisorUpstreamLDAP.BindUsername,
|
||||
env.SupervisorUpstreamLDAP.StartTLSOnlyHost, env.SupervisorUpstreamLDAP.BindUsername,
|
||||
secret.Name, secret.ResourceVersion,
|
||||
)
|
||||
requireSuccessfulLDAPIdentityProviderConditions(t, ldapIDP, expectedMsg)
|
||||
@@ -159,10 +176,11 @@ 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
|
||||
wantDownstreamIDTokenSubjectToMatch: regexp.QuoteMeta(
|
||||
"ldaps://" + env.SupervisorUpstreamLDAP.Host + "?sub=" + env.SupervisorUpstreamLDAP.TestUserUniqueIDAttributeValue,
|
||||
"ldaps://" + env.SupervisorUpstreamLDAP.StartTLSOnlyHost + "?sub=" + env.SupervisorUpstreamLDAP.TestUserUniqueIDAttributeValue,
|
||||
),
|
||||
// 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)
|
||||
|
||||
@@ -437,7 +437,10 @@ func CreateTestLDAPIdentityProvider(t *testing.T, spec idpv1alpha1.LDAPIdentityP
|
||||
return false
|
||||
}
|
||||
return result.Status.Phase == expectedPhase
|
||||
}, 60*time.Second, 1*time.Second, "expected the LDAPIdentityProvider to go into phase %s, LDAPIdentityProvider was: %s", expectedPhase, Sdump(result))
|
||||
},
|
||||
2*time.Minute, // it takes 1 minute for a failed LDAP TLS connection test to timeout before it tries using StartTLS, so wait longer than that
|
||||
1*time.Second,
|
||||
"expected the LDAPIdentityProvider to go into phase %s, LDAPIdentityProvider was: %s", expectedPhase, Sdump(result))
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
+24
-12
@@ -7,6 +7,7 @@ import (
|
||||
"encoding/base64"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
@@ -71,18 +72,22 @@ 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"`
|
||||
StartTLSOnlyHost string `json:"startTLSOnlyHost"`
|
||||
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.
|
||||
@@ -236,18 +241,25 @@ func loadEnvVars(t *testing.T, result *TestEnv) {
|
||||
|
||||
result.SupervisorUpstreamLDAP = TestLDAPUpstream{
|
||||
Host: needEnv(t, "PINNIPED_TEST_LDAP_HOST"),
|
||||
StartTLSOnlyHost: needEnv(t, "PINNIPED_TEST_LDAP_STARTTLS_ONLY_HOST"),
|
||||
CABundle: base64Decoded(t, os.Getenv("PINNIPED_TEST_LDAP_LDAPS_CA_BUNDLE")),
|
||||
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