2022-01-07 23:04:58 +00:00
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
2020-11-17 15:21:17 +00:00
// SPDX-License-Identifier: Apache-2.0
package integration
import (
"context"
2021-10-28 19:00:56 +00:00
"crypto/rand"
2020-11-17 15:21:17 +00:00
"crypto/tls"
2021-10-28 19:00:56 +00:00
"crypto/x509"
2020-11-17 15:21:17 +00:00
"encoding/base64"
2021-10-28 19:00:56 +00:00
"encoding/hex"
2020-12-09 16:23:10 +00:00
"encoding/json"
2020-12-17 01:59:39 +00:00
"fmt"
2021-04-07 19:56:09 +00:00
"io/ioutil"
2021-10-28 19:00:56 +00:00
"math/big"
"net"
2020-11-17 15:21:17 +00:00
"net/http"
2020-12-02 21:50:42 +00:00
"net/http/httptest"
2020-11-17 15:21:17 +00:00
"net/url"
2020-12-02 21:50:42 +00:00
"regexp"
2020-11-30 14:58:08 +00:00
"strings"
2020-11-17 15:21:17 +00:00
"testing"
"time"
2021-01-20 17:54:44 +00:00
coreosoidc "github.com/coreos/go-oidc/v3/oidc"
2021-10-28 19:00:56 +00:00
"github.com/go-ldap/ldap/v3"
2020-12-03 00:07:52 +00:00
"github.com/stretchr/testify/assert"
2020-11-17 15:21:17 +00:00
"github.com/stretchr/testify/require"
"golang.org/x/oauth2"
2021-10-28 19:00:56 +00:00
"golang.org/x/text/encoding/unicode"
2021-04-06 17:10:01 +00:00
v1 "k8s.io/api/core/v1"
2021-09-03 00:17:15 +00:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2020-11-17 15:21:17 +00:00
2021-02-16 19:00:08 +00:00
configv1alpha1 "go.pinniped.dev/generated/latest/apis/supervisor/config/v1alpha1"
idpv1alpha1 "go.pinniped.dev/generated/latest/apis/supervisor/idp/v1alpha1"
2020-12-02 21:50:42 +00:00
"go.pinniped.dev/internal/certauthority"
2021-12-09 22:02:40 +00:00
"go.pinniped.dev/internal/crypto/ptls"
2020-12-10 18:14:54 +00:00
"go.pinniped.dev/internal/oidc"
2021-10-13 22:12:19 +00:00
"go.pinniped.dev/internal/psession"
2020-12-05 01:07:04 +00:00
"go.pinniped.dev/internal/testutil"
2020-11-17 18:46:54 +00:00
"go.pinniped.dev/pkg/oidcclient/nonce"
"go.pinniped.dev/pkg/oidcclient/pkce"
"go.pinniped.dev/pkg/oidcclient/state"
2021-06-22 15:23:19 +00:00
"go.pinniped.dev/test/testlib"
"go.pinniped.dev/test/testlib/browsertest"
2020-11-17 15:21:17 +00:00
)
2021-11-17 00:31:32 +00:00
// nolint:gocyclo
2022-02-16 14:20:28 +00:00
func TestSupervisorLogin_Browser ( t * testing . T ) {
2021-06-22 15:23:19 +00:00
env := testlib . IntegrationEnv ( t )
2021-04-07 19:56:09 +00:00
2021-04-06 17:10:01 +00:00
tests := [ ] struct {
2022-02-15 21:52:33 +00:00
name string
maybeSkip func ( t * testing . T )
createTestUser func ( t * testing . T ) ( string , string )
deleteTestUser func ( t * testing . T , username string )
requestAuthorization func ( t * testing . T , downstreamAuthorizeURL , downstreamCallbackURL , username , password string , httpClient * http . Client )
createIDP func ( t * testing . T ) string
wantDownstreamIDTokenSubjectToMatch string
wantDownstreamIDTokenUsernameToMatch func ( username string ) string
wantDownstreamIDTokenGroups [ ] string
wantDownstreamIDTokenGroupsAfterRefresh [ ] string
wantErrorDescription string
wantErrorType string
2021-10-13 22:12:19 +00:00
2021-10-28 19:00:56 +00:00
// Either revoke the user's session on the upstream provider, or manipulate the user's session
2021-10-13 22:12:19 +00:00
// data in such a way that it should cause the next upstream refresh attempt to fail.
2021-10-28 19:00:56 +00:00
breakRefreshSessionData func ( t * testing . T , sessionData * psession . PinnipedSession , idpName , username string )
2022-01-26 00:19:56 +00:00
// Edit the refresh session data between the initial login and the refresh, which is expected to
// succeed.
2022-02-15 21:52:33 +00:00
editRefreshSessionDataWithoutBreaking func ( t * testing . T , sessionData * psession . PinnipedSession , idpName , username string )
2021-04-06 17:10:01 +00:00
} {
{
2021-05-28 17:37:46 +00:00
name : "oidc with default username and groups claim settings" ,
2021-05-29 00:06:01 +00:00
maybeSkip : func ( t * testing . T ) {
// never need to skip this test
} ,
2021-11-05 21:18:54 +00:00
createIDP : func ( t * testing . T ) string {
2021-04-06 17:10:01 +00:00
t . Helper ( )
2021-11-05 21:18:54 +00:00
oidcIDP := testlib . CreateTestOIDCIdentityProvider ( t , idpv1alpha1 . OIDCIdentityProviderSpec {
2021-04-07 19:56:09 +00:00
Issuer : env . SupervisorUpstreamOIDC . Issuer ,
2021-04-06 17:10:01 +00:00
TLS : & idpv1alpha1 . TLSSpec {
2021-04-07 19:56:09 +00:00
CertificateAuthorityData : base64 . StdEncoding . EncodeToString ( [ ] byte ( env . SupervisorUpstreamOIDC . CABundle ) ) ,
2021-04-06 17:10:01 +00:00
} ,
Client : idpv1alpha1 . OIDCClient {
2021-06-22 15:23:19 +00:00
SecretName : testlib . CreateClientCredsSecret ( t , env . SupervisorUpstreamOIDC . ClientID , env . SupervisorUpstreamOIDC . ClientSecret ) . Name ,
2021-04-06 17:10:01 +00:00
} ,
} , idpv1alpha1 . PhaseReady )
2021-11-05 21:18:54 +00:00
return oidcIDP . Name
2021-04-06 17:10:01 +00:00
} ,
2021-08-12 17:00:18 +00:00
requestAuthorization : requestAuthorizationUsingBrowserAuthcodeFlow ,
2021-10-28 19:00:56 +00:00
breakRefreshSessionData : func ( t * testing . T , pinnipedSession * psession . PinnipedSession , _ , _ string ) {
2022-01-07 23:04:58 +00:00
pinnipedSessionData := pinnipedSession . Custom
pinnipedSessionData . OIDC . UpstreamIssuer = "wrong-issuer"
2021-10-13 22:12:19 +00:00
} ,
2021-04-07 19:56:09 +00:00
// the ID token Subject should include the upstream user ID after the upstream issuer name
2021-08-26 23:18:05 +00:00
wantDownstreamIDTokenSubjectToMatch : "^" + regexp . QuoteMeta ( env . SupervisorUpstreamOIDC . Issuer + "?sub=" ) + ".+" ,
2021-04-07 19:56:09 +00:00
// the ID token Username should include the upstream user ID after the upstream issuer name
2021-10-28 19:00:56 +00:00
wantDownstreamIDTokenUsernameToMatch : func ( _ string ) string { return "^" + regexp . QuoteMeta ( env . SupervisorUpstreamOIDC . Issuer + "?sub=" ) + ".+" } ,
2021-05-28 17:37:46 +00:00
} ,
{
name : "oidc with custom username and groups claim settings" ,
2021-05-29 00:06:01 +00:00
maybeSkip : func ( t * testing . T ) {
// never need to skip this test
} ,
2021-11-05 21:18:54 +00:00
createIDP : func ( t * testing . T ) string {
2021-05-28 17:37:46 +00:00
t . Helper ( )
2021-11-05 21:18:54 +00:00
oidcIDP := testlib . CreateTestOIDCIdentityProvider ( t , idpv1alpha1 . OIDCIdentityProviderSpec {
2021-05-28 17:37:46 +00:00
Issuer : env . SupervisorUpstreamOIDC . Issuer ,
TLS : & idpv1alpha1 . TLSSpec {
CertificateAuthorityData : base64 . StdEncoding . EncodeToString ( [ ] byte ( env . SupervisorUpstreamOIDC . CABundle ) ) ,
} ,
Client : idpv1alpha1 . OIDCClient {
2021-06-22 15:23:19 +00:00
SecretName : testlib . CreateClientCredsSecret ( t , env . SupervisorUpstreamOIDC . ClientID , env . SupervisorUpstreamOIDC . ClientSecret ) . Name ,
2021-05-28 17:37:46 +00:00
} ,
Claims : idpv1alpha1 . OIDCClaims {
Username : env . SupervisorUpstreamOIDC . UsernameClaim ,
Groups : env . SupervisorUpstreamOIDC . GroupsClaim ,
} ,
AuthorizationConfig : idpv1alpha1 . OIDCAuthorizationConfig {
AdditionalScopes : env . SupervisorUpstreamOIDC . AdditionalScopes ,
} ,
} , idpv1alpha1 . PhaseReady )
2021-11-05 21:18:54 +00:00
return oidcIDP . Name
2021-05-28 17:37:46 +00:00
} ,
2021-10-13 22:12:19 +00:00
requestAuthorization : requestAuthorizationUsingBrowserAuthcodeFlow ,
2021-10-28 19:00:56 +00:00
breakRefreshSessionData : func ( t * testing . T , pinnipedSession * psession . PinnipedSession , _ , _ string ) {
2021-12-14 19:59:52 +00:00
fositeSessionData := pinnipedSession . Fosite
fositeSessionData . Claims . Extra [ "username" ] = "some-incorrect-username"
2021-10-13 22:12:19 +00:00
} ,
2021-08-26 23:18:05 +00:00
wantDownstreamIDTokenSubjectToMatch : "^" + regexp . QuoteMeta ( env . SupervisorUpstreamOIDC . Issuer + "?sub=" ) + ".+" ,
2021-10-28 19:00:56 +00:00
wantDownstreamIDTokenUsernameToMatch : func ( _ string ) string { return "^" + regexp . QuoteMeta ( env . SupervisorUpstreamOIDC . Username ) + "$" } ,
2021-05-17 18:10:26 +00:00
wantDownstreamIDTokenGroups : env . SupervisorUpstreamOIDC . ExpectedGroups ,
2022-02-15 21:52:33 +00:00
editRefreshSessionDataWithoutBreaking : func ( t * testing . T , sessionData * psession . PinnipedSession , _ , _ string ) {
2022-01-26 00:19:56 +00:00
// even if we update this group to the wrong thing, we expect that it will return to the correct
// value after we refresh.
// However if there are no expected groups then they will not update, so we should skip this.
if len ( env . SupervisorUpstreamOIDC . ExpectedGroups ) > 0 {
sessionData . Fosite . Claims . Extra [ "groups" ] = [ ] string { "some-wrong-group" , "some-other-group" }
}
} ,
2021-04-06 17:10:01 +00:00
} ,
2022-01-05 18:31:38 +00:00
{
name : "oidc without refresh token" ,
maybeSkip : func ( t * testing . T ) {
// never need to skip this test
} ,
createIDP : func ( t * testing . T ) string {
t . Helper ( )
2022-01-19 21:29:26 +00:00
var additionalScopes [ ] string
// keep all the scopes except for offline access so we can test the access token based refresh flow.
if len ( env . ToolsNamespace ) == 0 {
additionalScopes = env . SupervisorUpstreamOIDC . AdditionalScopes
} else {
for _ , additionalScope := range env . SupervisorUpstreamOIDC . AdditionalScopes {
if additionalScope != "offline_access" {
additionalScopes = append ( additionalScopes , additionalScope )
}
}
}
2022-01-05 18:31:38 +00:00
oidcIDP := testlib . CreateTestOIDCIdentityProvider ( t , idpv1alpha1 . OIDCIdentityProviderSpec {
Issuer : env . SupervisorUpstreamOIDC . Issuer ,
TLS : & idpv1alpha1 . TLSSpec {
CertificateAuthorityData : base64 . StdEncoding . EncodeToString ( [ ] byte ( env . SupervisorUpstreamOIDC . CABundle ) ) ,
} ,
Client : idpv1alpha1 . OIDCClient {
SecretName : testlib . CreateClientCredsSecret ( t , env . SupervisorUpstreamOIDC . ClientID , env . SupervisorUpstreamOIDC . ClientSecret ) . Name ,
} ,
Claims : idpv1alpha1 . OIDCClaims {
Username : env . SupervisorUpstreamOIDC . UsernameClaim ,
Groups : env . SupervisorUpstreamOIDC . GroupsClaim ,
} ,
AuthorizationConfig : idpv1alpha1 . OIDCAuthorizationConfig {
2022-01-19 21:29:26 +00:00
AdditionalScopes : additionalScopes ,
2022-01-05 18:31:38 +00:00
} ,
} , idpv1alpha1 . PhaseReady )
return oidcIDP . Name
} ,
requestAuthorization : requestAuthorizationUsingBrowserAuthcodeFlow ,
breakRefreshSessionData : func ( t * testing . T , pinnipedSession * psession . PinnipedSession , _ , _ string ) {
fositeSessionData := pinnipedSession . Fosite
fositeSessionData . Claims . Extra [ "username" ] = "some-incorrect-username"
} ,
wantDownstreamIDTokenSubjectToMatch : "^" + regexp . QuoteMeta ( env . SupervisorUpstreamOIDC . Issuer + "?sub=" ) + ".+" ,
wantDownstreamIDTokenUsernameToMatch : func ( _ string ) string { return "^" + regexp . QuoteMeta ( env . SupervisorUpstreamOIDC . Username ) + "$" } ,
wantDownstreamIDTokenGroups : env . SupervisorUpstreamOIDC . ExpectedGroups ,
} ,
2021-08-12 17:00:18 +00:00
{
name : "oidc with CLI password flow" ,
maybeSkip : func ( t * testing . T ) {
// never need to skip this test
} ,
2021-11-05 21:18:54 +00:00
createIDP : func ( t * testing . T ) string {
2021-08-12 17:00:18 +00:00
t . Helper ( )
2021-11-05 21:18:54 +00:00
oidcIDP := testlib . CreateTestOIDCIdentityProvider ( t , idpv1alpha1 . OIDCIdentityProviderSpec {
2021-08-12 17:00:18 +00:00
Issuer : env . SupervisorUpstreamOIDC . Issuer ,
TLS : & idpv1alpha1 . TLSSpec {
CertificateAuthorityData : base64 . StdEncoding . EncodeToString ( [ ] byte ( env . SupervisorUpstreamOIDC . CABundle ) ) ,
} ,
Client : idpv1alpha1 . OIDCClient {
SecretName : testlib . CreateClientCredsSecret ( t , env . SupervisorUpstreamOIDC . ClientID , env . SupervisorUpstreamOIDC . ClientSecret ) . Name ,
} ,
AuthorizationConfig : idpv1alpha1 . OIDCAuthorizationConfig {
AllowPasswordGrant : true , // allow the CLI password flow for this OIDCIdentityProvider
} ,
} , idpv1alpha1 . PhaseReady )
2021-11-05 21:18:54 +00:00
return oidcIDP . Name
2021-08-12 17:00:18 +00:00
} ,
2021-10-28 19:00:56 +00:00
requestAuthorization : func ( t * testing . T , downstreamAuthorizeURL , _ , _ , _ string , httpClient * http . Client ) {
2021-08-12 17:00:18 +00:00
requestAuthorizationUsingCLIPasswordFlow ( t ,
downstreamAuthorizeURL ,
env . SupervisorUpstreamOIDC . Username , // username to present to server during login
env . SupervisorUpstreamOIDC . Password , // password to present to server during login
httpClient ,
2021-08-24 19:19:29 +00:00
false ,
2021-08-12 17:00:18 +00:00
)
} ,
2021-10-28 19:00:56 +00:00
breakRefreshSessionData : func ( t * testing . T , pinnipedSession * psession . PinnipedSession , _ , _ string ) {
2021-10-26 23:24:02 +00:00
customSessionData := pinnipedSession . Custom
2021-10-13 22:12:19 +00:00
require . Equal ( t , psession . ProviderTypeOIDC , customSessionData . ProviderType )
require . NotEmpty ( t , customSessionData . OIDC . UpstreamRefreshToken )
customSessionData . OIDC . UpstreamRefreshToken = "invalid-updated-refresh-token"
} ,
2021-08-12 17:00:18 +00:00
// the ID token Subject should include the upstream user ID after the upstream issuer name
2021-08-26 23:18:05 +00:00
wantDownstreamIDTokenSubjectToMatch : "^" + regexp . QuoteMeta ( env . SupervisorUpstreamOIDC . Issuer + "?sub=" ) + ".+" ,
2021-08-12 17:00:18 +00:00
// the ID token Username should include the upstream user ID after the upstream issuer name
2021-10-28 19:00:56 +00:00
wantDownstreamIDTokenUsernameToMatch : func ( _ string ) string { return "^" + regexp . QuoteMeta ( env . SupervisorUpstreamOIDC . Issuer + "?sub=" ) + ".+" } ,
2021-08-12 17:00:18 +00:00
} ,
2021-04-06 17:10:01 +00:00
{
2021-05-20 20:39:48 +00:00
name : "ldap with email as username and groups names as DNs and using an LDAP provider which supports TLS" ,
2021-05-28 23:12:57 +00:00
maybeSkip : func ( t * testing . T ) {
t . Helper ( )
2021-06-22 15:23:19 +00:00
if len ( env . ToolsNamespace ) == 0 && ! env . HasCapability ( testlib . CanReachInternetLDAPPorts ) {
2021-05-28 23:12:57 +00:00
t . Skip ( "LDAP integration test requires connectivity to an LDAP server" )
}
} ,
2021-11-05 21:18:54 +00:00
createIDP : func ( t * testing . T ) string {
2021-04-06 17:10:01 +00:00
t . Helper ( )
2021-06-22 15:23:19 +00:00
secret := testlib . CreateTestSecret ( t , env . SupervisorNamespace , "ldap-service-account" , v1 . SecretTypeBasicAuth ,
2021-04-07 19:56:09 +00:00
map [ string ] string {
v1 . BasicAuthUsernameKey : env . SupervisorUpstreamLDAP . BindUsername ,
v1 . BasicAuthPasswordKey : env . SupervisorUpstreamLDAP . BindPassword ,
} ,
)
2021-06-22 15:23:19 +00:00
ldapIDP := testlib . CreateTestLDAPIdentityProvider ( t , idpv1alpha1 . LDAPIdentityProviderSpec {
2021-04-07 19:56:09 +00:00
Host : env . SupervisorUpstreamLDAP . Host ,
2021-04-27 19:43:09 +00:00
TLS : & idpv1alpha1 . TLSSpec {
2021-04-14 01:11:16 +00:00
CertificateAuthorityData : base64 . StdEncoding . EncodeToString ( [ ] byte ( env . SupervisorUpstreamLDAP . CABundle ) ) ,
2021-04-07 19:56:09 +00:00
} ,
2021-04-27 19:43:09 +00:00
Bind : idpv1alpha1 . LDAPIdentityProviderBind {
2021-04-07 19:56:09 +00:00
SecretName : secret . Name ,
} ,
2021-04-27 19:43:09 +00:00
UserSearch : idpv1alpha1 . LDAPIdentityProviderUserSearch {
2021-04-07 19:56:09 +00:00
Base : env . SupervisorUpstreamLDAP . UserSearchBase ,
Filter : "" ,
2021-04-27 19:43:09 +00:00
Attributes : idpv1alpha1 . LDAPIdentityProviderUserSearchAttributes {
2021-07-15 23:58:26 +00:00
Username : env . SupervisorUpstreamLDAP . TestUserMailAttributeName ,
2021-04-27 19:43:09 +00:00
UID : env . SupervisorUpstreamLDAP . TestUserUniqueIDAttributeName ,
2021-04-07 19:56:09 +00:00
} ,
} ,
2021-05-17 18:10:26 +00:00
GroupSearch : idpv1alpha1 . LDAPIdentityProviderGroupSearch {
Base : env . SupervisorUpstreamLDAP . GroupSearchBase ,
Filter : "" ,
Attributes : idpv1alpha1 . LDAPIdentityProviderGroupSearchAttributes {
GroupName : "dn" ,
} ,
} ,
2021-04-14 01:11:16 +00:00
} , idpv1alpha1 . LDAPPhaseReady )
2021-04-16 21:04:05 +00:00
expectedMsg := fmt . Sprintf (
2021-04-28 21:26:57 +00:00
` successfully able to connect to "%s" and bind as user "%s" [validated with Secret "%s" at version "%s"] ` ,
env . SupervisorUpstreamLDAP . Host , env . SupervisorUpstreamLDAP . BindUsername ,
2021-04-16 21:04:05 +00:00
secret . Name , secret . ResourceVersion ,
)
requireSuccessfulLDAPIdentityProviderConditions ( t , ldapIDP , expectedMsg )
2021-11-05 21:18:54 +00:00
return ldapIDP . Name
2021-04-07 19:56:09 +00:00
} ,
2021-10-28 19:00:56 +00:00
requestAuthorization : func ( t * testing . T , downstreamAuthorizeURL , _ , _ , _ string , httpClient * http . Client ) {
2021-08-12 17:00:18 +00:00
requestAuthorizationUsingCLIPasswordFlow ( t ,
2021-04-07 19:56:09 +00:00
downstreamAuthorizeURL ,
2021-07-15 23:58:26 +00:00
env . SupervisorUpstreamLDAP . TestUserMailAttributeValue , // username to present to server during login
2021-04-07 19:56:09 +00:00
env . SupervisorUpstreamLDAP . TestUserPassword , // password to present to server during login
httpClient ,
2021-07-22 23:15:44 +00:00
false ,
2021-04-07 19:56:09 +00:00
)
2021-04-06 17:10:01 +00:00
} ,
2022-02-15 21:52:33 +00:00
editRefreshSessionDataWithoutBreaking : func ( t * testing . T , sessionData * psession . PinnipedSession , _ , _ string ) {
2022-01-26 00:19:56 +00:00
// even if we update this group to the wrong thing, we expect that it will return to the correct
// value after we refresh.
sessionData . Fosite . Claims . Extra [ "groups" ] = [ ] string { "some-wrong-group" , "some-other-group" }
} ,
2021-10-28 19:00:56 +00:00
breakRefreshSessionData : func ( t * testing . T , pinnipedSession * psession . PinnipedSession , _ , _ string ) {
2021-10-26 23:24:02 +00:00
customSessionData := pinnipedSession . Custom
2021-10-22 20:57:30 +00:00
require . Equal ( t , psession . ProviderTypeLDAP , customSessionData . ProviderType )
require . NotEmpty ( t , customSessionData . LDAP . UserDN )
2021-10-26 23:24:02 +00:00
fositeSessionData := pinnipedSession . Fosite
fositeSessionData . Claims . Subject = "not-right"
2021-10-22 20:57:30 +00:00
} ,
2021-04-07 19:56:09 +00:00
// the ID token Subject should be the Host URL plus the value pulled from the requested UserSearch.Attributes.UID attribute
2021-08-26 23:18:05 +00:00
wantDownstreamIDTokenSubjectToMatch : "^" + regexp . QuoteMeta (
"ldaps://" + env . SupervisorUpstreamLDAP . Host +
"?base=" + url . QueryEscape ( env . SupervisorUpstreamLDAP . UserSearchBase ) +
"&sub=" + base64 . RawURLEncoding . EncodeToString ( [ ] byte ( env . SupervisorUpstreamLDAP . TestUserUniqueIDAttributeValue ) ) ,
) + "$" ,
2021-04-07 19:56:09 +00:00
// the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute
2021-10-28 19:00:56 +00:00
wantDownstreamIDTokenUsernameToMatch : func ( _ string ) string {
return "^" + regexp . QuoteMeta ( env . SupervisorUpstreamLDAP . TestUserMailAttributeValue ) + "$"
} ,
wantDownstreamIDTokenGroups : env . SupervisorUpstreamLDAP . TestUserDirectGroupsDNs ,
2021-04-06 17:10:01 +00:00
} ,
2022-02-11 18:23:44 +00:00
{
name : "ldap with email as username and group search base that doesn't return anything, and using an LDAP provider which supports TLS" ,
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 . SupervisorUpstreamLDAP . UserSearchBase == env . SupervisorUpstreamLDAP . GroupSearchBase {
// This test relies on using the user search base as the group search base, to simulate
// searching for groups and not finding any.
// If the users and groups are stored in the same place, then we will get groups
// back, so this test wouldn't make sense.
t . Skip ( "must have a different user search base than group search base" )
}
} ,
createIDP : func ( t * testing . T ) string {
t . Helper ( )
secret := testlib . CreateTestSecret ( t , env . SupervisorNamespace , "ldap-service-account" , v1 . SecretTypeBasicAuth ,
map [ string ] string {
v1 . BasicAuthUsernameKey : env . SupervisorUpstreamLDAP . BindUsername ,
v1 . BasicAuthPasswordKey : env . SupervisorUpstreamLDAP . BindPassword ,
} ,
)
ldapIDP := testlib . CreateTestLDAPIdentityProvider ( t , idpv1alpha1 . LDAPIdentityProviderSpec {
Host : env . SupervisorUpstreamLDAP . Host ,
TLS : & idpv1alpha1 . TLSSpec {
CertificateAuthorityData : base64 . StdEncoding . EncodeToString ( [ ] byte ( env . SupervisorUpstreamLDAP . CABundle ) ) ,
} ,
Bind : idpv1alpha1 . LDAPIdentityProviderBind {
SecretName : secret . Name ,
} ,
UserSearch : idpv1alpha1 . LDAPIdentityProviderUserSearch {
Base : env . SupervisorUpstreamLDAP . UserSearchBase ,
Filter : "" ,
Attributes : idpv1alpha1 . LDAPIdentityProviderUserSearchAttributes {
Username : env . SupervisorUpstreamLDAP . TestUserMailAttributeName ,
UID : env . SupervisorUpstreamLDAP . TestUserUniqueIDAttributeName ,
} ,
} ,
GroupSearch : idpv1alpha1 . LDAPIdentityProviderGroupSearch {
Base : env . SupervisorUpstreamLDAP . UserSearchBase , // groups not stored at the user search base
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"] ` ,
env . SupervisorUpstreamLDAP . Host , env . SupervisorUpstreamLDAP . BindUsername ,
secret . Name , secret . ResourceVersion ,
)
requireSuccessfulLDAPIdentityProviderConditions ( t , ldapIDP , expectedMsg )
return ldapIDP . Name
} ,
requestAuthorization : func ( t * testing . T , downstreamAuthorizeURL , _ , _ , _ string , httpClient * http . Client ) {
requestAuthorizationUsingCLIPasswordFlow ( t ,
downstreamAuthorizeURL ,
env . SupervisorUpstreamLDAP . TestUserMailAttributeValue , // username to present to server during login
env . SupervisorUpstreamLDAP . TestUserPassword , // password to present to server during login
httpClient ,
false ,
)
} ,
2022-02-15 21:52:33 +00:00
editRefreshSessionDataWithoutBreaking : func ( t * testing . T , sessionData * psession . PinnipedSession , _ , _ string ) {
2022-02-11 18:23:44 +00:00
// even if we update this group to the wrong thing, we expect that it will return to the correct
// value after we refresh.
sessionData . Fosite . Claims . Extra [ "groups" ] = [ ] string { "some-wrong-group" , "some-other-group" }
} ,
breakRefreshSessionData : func ( t * testing . T , pinnipedSession * psession . PinnipedSession , _ , _ string ) {
customSessionData := pinnipedSession . Custom
require . Equal ( t , psession . ProviderTypeLDAP , customSessionData . ProviderType )
require . NotEmpty ( t , customSessionData . LDAP . UserDN )
fositeSessionData := pinnipedSession . Fosite
fositeSessionData . Claims . Subject = "not-right"
} ,
// 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 +
"?base=" + url . QueryEscape ( env . SupervisorUpstreamLDAP . UserSearchBase ) +
"&sub=" + base64 . RawURLEncoding . EncodeToString ( [ ] byte ( env . SupervisorUpstreamLDAP . TestUserUniqueIDAttributeValue ) ) ,
) + "$" ,
// the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute
wantDownstreamIDTokenUsernameToMatch : func ( _ string ) string {
return "^" + regexp . QuoteMeta ( env . SupervisorUpstreamLDAP . TestUserMailAttributeValue ) + "$"
} ,
wantDownstreamIDTokenGroups : [ ] string { } ,
} ,
2021-04-16 21:04:05 +00:00
{
2021-05-20 20:39:48 +00:00
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
2021-05-28 23:12:57 +00:00
maybeSkip : func ( t * testing . T ) {
t . Helper ( )
2021-06-22 15:23:19 +00:00
if len ( env . ToolsNamespace ) == 0 && ! env . HasCapability ( testlib . CanReachInternetLDAPPorts ) {
2021-05-28 23:12:57 +00:00
t . Skip ( "LDAP integration test requires connectivity to an LDAP server" )
}
} ,
2021-11-05 21:18:54 +00:00
createIDP : func ( t * testing . T ) string {
2021-04-16 21:04:05 +00:00
t . Helper ( )
2021-06-22 15:23:19 +00:00
secret := testlib . CreateTestSecret ( t , env . SupervisorNamespace , "ldap-service-account" , v1 . SecretTypeBasicAuth ,
2021-04-16 21:04:05 +00:00
map [ string ] string {
v1 . BasicAuthUsernameKey : env . SupervisorUpstreamLDAP . BindUsername ,
v1 . BasicAuthPasswordKey : env . SupervisorUpstreamLDAP . BindPassword ,
} ,
)
2021-06-22 15:23:19 +00:00
ldapIDP := testlib . CreateTestLDAPIdentityProvider ( t , idpv1alpha1 . LDAPIdentityProviderSpec {
2021-05-20 20:39:48 +00:00
Host : env . SupervisorUpstreamLDAP . StartTLSOnlyHost ,
2021-04-27 19:43:09 +00:00
TLS : & idpv1alpha1 . TLSSpec {
2021-04-16 21:04:05 +00:00
CertificateAuthorityData : base64 . StdEncoding . EncodeToString ( [ ] byte ( env . SupervisorUpstreamLDAP . CABundle ) ) ,
} ,
2021-04-27 19:43:09 +00:00
Bind : idpv1alpha1 . LDAPIdentityProviderBind {
2021-04-16 21:04:05 +00:00
SecretName : secret . Name ,
} ,
2021-04-27 19:43:09 +00:00
UserSearch : idpv1alpha1 . LDAPIdentityProviderUserSearch {
2021-04-16 21:04:05 +00:00
Base : env . SupervisorUpstreamLDAP . UserSearchBase ,
Filter : "cn={}" , // try using a non-default search filter
2021-04-27 19:43:09 +00:00
Attributes : idpv1alpha1 . LDAPIdentityProviderUserSearchAttributes {
2021-04-16 21:04:05 +00:00
Username : "dn" , // try using the user's DN as the downstream username
2021-04-27 19:43:09 +00:00
UID : env . SupervisorUpstreamLDAP . TestUserUniqueIDAttributeName ,
2021-04-16 21:04:05 +00:00
} ,
} ,
2021-05-17 18:10:26 +00:00
GroupSearch : idpv1alpha1 . LDAPIdentityProviderGroupSearch {
Base : env . SupervisorUpstreamLDAP . GroupSearchBase ,
Filter : "" ,
Attributes : idpv1alpha1 . LDAPIdentityProviderGroupSearchAttributes {
GroupName : "cn" ,
} ,
} ,
2021-04-16 21:04:05 +00:00
} , idpv1alpha1 . LDAPPhaseReady )
expectedMsg := fmt . Sprintf (
` successfully able to connect to "%s" and bind as user "%s" [validated with Secret "%s" at version "%s"] ` ,
2021-05-20 20:39:48 +00:00
env . SupervisorUpstreamLDAP . StartTLSOnlyHost , env . SupervisorUpstreamLDAP . BindUsername ,
2021-04-16 21:04:05 +00:00
secret . Name , secret . ResourceVersion ,
)
requireSuccessfulLDAPIdentityProviderConditions ( t , ldapIDP , expectedMsg )
2021-11-05 21:18:54 +00:00
return ldapIDP . Name
2021-04-16 21:04:05 +00:00
} ,
2021-10-28 19:00:56 +00:00
requestAuthorization : func ( t * testing . T , downstreamAuthorizeURL , _ , _ , _ string , httpClient * http . Client ) {
2021-08-12 17:00:18 +00:00
requestAuthorizationUsingCLIPasswordFlow ( t ,
2021-04-16 21:04:05 +00:00
downstreamAuthorizeURL ,
env . SupervisorUpstreamLDAP . TestUserCN , // username to present to server during login
env . SupervisorUpstreamLDAP . TestUserPassword , // password to present to server during login
httpClient ,
2021-07-22 23:15:44 +00:00
false ,
2021-04-16 21:04:05 +00:00
)
} ,
2021-10-28 19:00:56 +00:00
breakRefreshSessionData : func ( t * testing . T , pinnipedSession * psession . PinnipedSession , _ , _ string ) {
2021-10-26 23:24:02 +00:00
customSessionData := pinnipedSession . Custom
2021-10-22 20:57:30 +00:00
require . Equal ( t , psession . ProviderTypeLDAP , customSessionData . ProviderType )
require . NotEmpty ( t , customSessionData . LDAP . UserDN )
2021-10-26 23:24:02 +00:00
fositeSessionData := pinnipedSession . Fosite
fositeSessionData . Claims . Extra [ "username" ] = "not-the-same"
2021-10-22 20:57:30 +00:00
} ,
2021-04-16 21:04:05 +00:00
// the ID token Subject should be the Host URL plus the value pulled from the requested UserSearch.Attributes.UID attribute
2021-08-26 23:18:05 +00:00
wantDownstreamIDTokenSubjectToMatch : "^" + regexp . QuoteMeta (
"ldaps://" + env . SupervisorUpstreamLDAP . StartTLSOnlyHost +
"?base=" + url . QueryEscape ( env . SupervisorUpstreamLDAP . UserSearchBase ) +
"&sub=" + base64 . RawURLEncoding . EncodeToString ( [ ] byte ( env . SupervisorUpstreamLDAP . TestUserUniqueIDAttributeValue ) ) ,
) + "$" ,
2021-04-16 21:04:05 +00:00
// the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute
2021-10-28 19:00:56 +00:00
wantDownstreamIDTokenUsernameToMatch : func ( _ string ) string { return "^" + regexp . QuoteMeta ( env . SupervisorUpstreamLDAP . TestUserDN ) + "$" } ,
2021-05-17 18:10:26 +00:00
wantDownstreamIDTokenGroups : env . SupervisorUpstreamLDAP . TestUserDirectGroupsCNs ,
2021-04-16 21:04:05 +00:00
} ,
2021-07-26 23:32:46 +00:00
{
name : "logging in to ldap with the wrong password fails" ,
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" )
}
} ,
2021-11-05 21:18:54 +00:00
createIDP : func ( t * testing . T ) string {
2021-07-26 23:32:46 +00:00
t . Helper ( )
secret := testlib . CreateTestSecret ( t , env . SupervisorNamespace , "ldap-service-account" , v1 . SecretTypeBasicAuth ,
map [ string ] string {
v1 . BasicAuthUsernameKey : env . SupervisorUpstreamLDAP . BindUsername ,
v1 . BasicAuthPasswordKey : env . SupervisorUpstreamLDAP . BindPassword ,
} ,
)
ldapIDP := testlib . CreateTestLDAPIdentityProvider ( t , idpv1alpha1 . LDAPIdentityProviderSpec {
Host : env . SupervisorUpstreamLDAP . Host ,
TLS : & idpv1alpha1 . TLSSpec {
CertificateAuthorityData : base64 . StdEncoding . EncodeToString ( [ ] byte ( env . SupervisorUpstreamLDAP . CABundle ) ) ,
} ,
Bind : idpv1alpha1 . LDAPIdentityProviderBind {
SecretName : secret . Name ,
} ,
UserSearch : idpv1alpha1 . LDAPIdentityProviderUserSearch {
Base : env . SupervisorUpstreamLDAP . UserSearchBase ,
Filter : "" ,
Attributes : idpv1alpha1 . LDAPIdentityProviderUserSearchAttributes {
Username : env . SupervisorUpstreamLDAP . TestUserMailAttributeName ,
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"] ` ,
env . SupervisorUpstreamLDAP . Host , env . SupervisorUpstreamLDAP . BindUsername ,
secret . Name , secret . ResourceVersion ,
)
requireSuccessfulLDAPIdentityProviderConditions ( t , ldapIDP , expectedMsg )
2021-11-05 21:18:54 +00:00
return ldapIDP . Name
2021-07-26 23:32:46 +00:00
} ,
2021-10-28 19:00:56 +00:00
requestAuthorization : func ( t * testing . T , downstreamAuthorizeURL , _ , _ , _ string , httpClient * http . Client ) {
2021-08-24 19:19:29 +00:00
requestAuthorizationUsingCLIPasswordFlow ( t ,
2021-07-26 23:32:46 +00:00
downstreamAuthorizeURL ,
env . SupervisorUpstreamLDAP . TestUserMailAttributeValue , // username to present to server during login
"incorrect" , // password to present to server during login
httpClient ,
true ,
)
} ,
2021-10-22 20:57:30 +00:00
wantErrorDescription : "The resource owner or authorization server denied the request. Username/password not accepted by LDAP provider." ,
wantErrorType : "access_denied" ,
2021-07-26 23:32:46 +00:00
} ,
2021-09-07 18:45:32 +00:00
{
name : "ldap login still works after updating bind secret" ,
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" )
}
} ,
2021-11-05 21:18:54 +00:00
createIDP : func ( t * testing . T ) string {
2021-09-07 18:45:32 +00:00
t . Helper ( )
secret := testlib . CreateTestSecret ( t , env . SupervisorNamespace , "ldap-service-account" , v1 . SecretTypeBasicAuth ,
map [ string ] string {
v1 . BasicAuthUsernameKey : env . SupervisorUpstreamLDAP . BindUsername ,
v1 . BasicAuthPasswordKey : env . SupervisorUpstreamLDAP . BindPassword ,
} ,
)
secretName := secret . Name
ldapIDP := testlib . CreateTestLDAPIdentityProvider ( t , idpv1alpha1 . LDAPIdentityProviderSpec {
Host : env . SupervisorUpstreamLDAP . Host ,
TLS : & idpv1alpha1 . TLSSpec {
CertificateAuthorityData : base64 . StdEncoding . EncodeToString ( [ ] byte ( env . SupervisorUpstreamLDAP . CABundle ) ) ,
} ,
Bind : idpv1alpha1 . LDAPIdentityProviderBind {
SecretName : secretName ,
} ,
UserSearch : idpv1alpha1 . LDAPIdentityProviderUserSearch {
Base : env . SupervisorUpstreamLDAP . UserSearchBase ,
Filter : "" ,
Attributes : idpv1alpha1 . LDAPIdentityProviderUserSearchAttributes {
Username : env . SupervisorUpstreamLDAP . TestUserMailAttributeName ,
UID : env . SupervisorUpstreamLDAP . TestUserUniqueIDAttributeName ,
} ,
} ,
GroupSearch : idpv1alpha1 . LDAPIdentityProviderGroupSearch {
Base : env . SupervisorUpstreamLDAP . GroupSearchBase ,
Filter : "" ,
Attributes : idpv1alpha1 . LDAPIdentityProviderGroupSearchAttributes {
GroupName : "dn" ,
} ,
} ,
} , idpv1alpha1 . LDAPPhaseReady )
secret . Annotations = map [ string ] string { "pinniped.dev/test" : "" , "another-label" : "another-key" }
// update that secret, which will cause the cache to recheck tls and search base values
client := testlib . NewKubernetesClientset ( t )
ctx , cancel := context . WithTimeout ( context . Background ( ) , time . Minute )
defer cancel ( )
updatedSecret , err := client . CoreV1 ( ) . Secrets ( env . SupervisorNamespace ) . Update ( ctx , secret , metav1 . UpdateOptions { } )
require . NoError ( t , err )
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 ,
updatedSecret . Name , updatedSecret . ResourceVersion ,
)
supervisorClient := testlib . NewSupervisorClientset ( t )
testlib . RequireEventually ( t , func ( requireEventually * require . Assertions ) {
ctx , cancel := context . WithTimeout ( context . Background ( ) , 10 * time . Second )
defer cancel ( )
ldapIDP , err = supervisorClient . IDPV1alpha1 ( ) . LDAPIdentityProviders ( env . SupervisorNamespace ) . Get ( ctx , ldapIDP . Name , metav1 . GetOptions { } )
requireEventually . NoError ( err )
requireEventuallySuccessfulLDAPIdentityProviderConditions ( t , requireEventually , ldapIDP , expectedMsg )
} , time . Minute , 500 * time . Millisecond )
2021-11-05 21:18:54 +00:00
return ldapIDP . Name
2021-09-07 18:45:32 +00:00
} ,
2021-10-28 19:00:56 +00:00
requestAuthorization : func ( t * testing . T , downstreamAuthorizeURL , _ , _ , _ string , httpClient * http . Client ) {
2021-09-07 18:45:32 +00:00
requestAuthorizationUsingCLIPasswordFlow ( t ,
downstreamAuthorizeURL ,
env . SupervisorUpstreamLDAP . TestUserMailAttributeValue , // username to present to server during login
env . SupervisorUpstreamLDAP . TestUserPassword , // password to present to server during login
httpClient ,
false ,
)
} ,
2021-10-28 19:00:56 +00:00
breakRefreshSessionData : func ( t * testing . T , pinnipedSession * psession . PinnipedSession , _ , _ string ) {
2021-10-26 23:24:02 +00:00
customSessionData := pinnipedSession . Custom
2021-10-22 20:57:30 +00:00
require . Equal ( t , psession . ProviderTypeLDAP , customSessionData . ProviderType )
require . NotEmpty ( t , customSessionData . LDAP . UserDN )
customSessionData . LDAP . UserDN = "cn=not-a-user,dc=pinniped,dc=dev"
} ,
2021-09-07 18:45:32 +00:00
// 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 +
"?base=" + url . QueryEscape ( env . SupervisorUpstreamLDAP . UserSearchBase ) +
"&sub=" + base64 . RawURLEncoding . EncodeToString ( [ ] byte ( env . SupervisorUpstreamLDAP . TestUserUniqueIDAttributeValue ) ) ,
) + "$" ,
// the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute
2021-10-28 19:00:56 +00:00
wantDownstreamIDTokenUsernameToMatch : func ( _ string ) string {
return "^" + regexp . QuoteMeta ( env . SupervisorUpstreamLDAP . TestUserMailAttributeValue ) + "$"
} ,
wantDownstreamIDTokenGroups : env . SupervisorUpstreamLDAP . TestUserDirectGroupsDNs ,
2021-09-07 18:45:32 +00:00
} ,
{
name : "ldap login still works after deleting and recreating the bind secret" ,
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" )
}
} ,
2021-11-05 21:18:54 +00:00
createIDP : func ( t * testing . T ) string {
2021-09-07 18:45:32 +00:00
t . Helper ( )
secret := testlib . CreateTestSecret ( t , env . SupervisorNamespace , "ldap-service-account" , v1 . SecretTypeBasicAuth ,
map [ string ] string {
v1 . BasicAuthUsernameKey : env . SupervisorUpstreamLDAP . BindUsername ,
v1 . BasicAuthPasswordKey : env . SupervisorUpstreamLDAP . BindPassword ,
} ,
)
secretName := secret . Name
ldapIDP := testlib . CreateTestLDAPIdentityProvider ( t , idpv1alpha1 . LDAPIdentityProviderSpec {
Host : env . SupervisorUpstreamLDAP . Host ,
TLS : & idpv1alpha1 . TLSSpec {
CertificateAuthorityData : base64 . StdEncoding . EncodeToString ( [ ] byte ( env . SupervisorUpstreamLDAP . CABundle ) ) ,
} ,
Bind : idpv1alpha1 . LDAPIdentityProviderBind {
SecretName : secretName ,
} ,
UserSearch : idpv1alpha1 . LDAPIdentityProviderUserSearch {
Base : env . SupervisorUpstreamLDAP . UserSearchBase ,
Filter : "" ,
Attributes : idpv1alpha1 . LDAPIdentityProviderUserSearchAttributes {
Username : env . SupervisorUpstreamLDAP . TestUserMailAttributeName ,
UID : env . SupervisorUpstreamLDAP . TestUserUniqueIDAttributeName ,
} ,
} ,
GroupSearch : idpv1alpha1 . LDAPIdentityProviderGroupSearch {
Base : env . SupervisorUpstreamLDAP . GroupSearchBase ,
Filter : "" ,
Attributes : idpv1alpha1 . LDAPIdentityProviderGroupSearchAttributes {
GroupName : "dn" ,
} ,
} ,
} , idpv1alpha1 . LDAPPhaseReady )
// delete, then recreate that secret, which will cause the cache to recheck tls and search base values
client := testlib . NewKubernetesClientset ( t )
deleteCtx , deleteCancel := context . WithTimeout ( context . Background ( ) , time . Minute )
defer deleteCancel ( )
err := client . CoreV1 ( ) . Secrets ( env . SupervisorNamespace ) . Delete ( deleteCtx , secretName , metav1 . DeleteOptions { } )
require . NoError ( t , err )
// create the secret again
recreateCtx , recreateCancel := context . WithTimeout ( context . Background ( ) , time . Minute )
defer recreateCancel ( )
recreatedSecret , err := client . CoreV1 ( ) . Secrets ( env . SupervisorNamespace ) . Create ( recreateCtx , & v1 . Secret {
ObjectMeta : metav1 . ObjectMeta {
Name : secretName ,
Namespace : env . SupervisorNamespace ,
} ,
Type : v1 . SecretTypeBasicAuth ,
StringData : map [ string ] string {
v1 . BasicAuthUsernameKey : env . SupervisorUpstreamLDAP . BindUsername ,
v1 . BasicAuthPasswordKey : env . SupervisorUpstreamLDAP . BindPassword ,
} ,
} , metav1 . CreateOptions { } )
require . NoError ( t , err )
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 ,
recreatedSecret . Name , recreatedSecret . ResourceVersion ,
)
supervisorClient := testlib . NewSupervisorClientset ( t )
testlib . RequireEventually ( t , func ( requireEventually * require . Assertions ) {
ctx , cancel := context . WithTimeout ( context . Background ( ) , 10 * time . Second )
defer cancel ( )
ldapIDP , err = supervisorClient . IDPV1alpha1 ( ) . LDAPIdentityProviders ( env . SupervisorNamespace ) . Get ( ctx , ldapIDP . Name , metav1 . GetOptions { } )
requireEventually . NoError ( err )
requireEventuallySuccessfulLDAPIdentityProviderConditions ( t , requireEventually , ldapIDP , expectedMsg )
} , time . Minute , 500 * time . Millisecond )
2021-11-05 21:18:54 +00:00
return ldapIDP . Name
2021-09-07 18:45:32 +00:00
} ,
2021-10-28 19:00:56 +00:00
requestAuthorization : func ( t * testing . T , downstreamAuthorizeURL , _ , _ , _ string , httpClient * http . Client ) {
2021-09-07 18:45:32 +00:00
requestAuthorizationUsingCLIPasswordFlow ( t ,
downstreamAuthorizeURL ,
env . SupervisorUpstreamLDAP . TestUserMailAttributeValue , // username to present to server during login
env . SupervisorUpstreamLDAP . TestUserPassword , // password to present to server during login
httpClient ,
false ,
)
} ,
2021-10-28 19:00:56 +00:00
breakRefreshSessionData : func ( t * testing . T , pinnipedSession * psession . PinnipedSession , _ , _ string ) {
2021-10-26 23:24:02 +00:00
customSessionData := pinnipedSession . Custom
2021-10-22 20:57:30 +00:00
require . Equal ( t , psession . ProviderTypeLDAP , customSessionData . ProviderType )
require . NotEmpty ( t , customSessionData . LDAP . UserDN )
customSessionData . LDAP . UserDN = "cn=not-a-user,dc=pinniped,dc=dev"
} ,
2021-09-07 18:45:32 +00:00
// 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 +
"?base=" + url . QueryEscape ( env . SupervisorUpstreamLDAP . UserSearchBase ) +
"&sub=" + base64 . RawURLEncoding . EncodeToString ( [ ] byte ( env . SupervisorUpstreamLDAP . TestUserUniqueIDAttributeValue ) ) ,
) + "$" ,
// the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute
2021-10-28 19:00:56 +00:00
wantDownstreamIDTokenUsernameToMatch : func ( _ string ) string {
return "^" + regexp . QuoteMeta ( env . SupervisorUpstreamLDAP . TestUserMailAttributeValue ) + "$"
} ,
wantDownstreamIDTokenGroups : env . SupervisorUpstreamLDAP . TestUserDirectGroupsDNs ,
2021-09-07 18:45:32 +00:00
} ,
2021-07-06 18:34:54 +00:00
{
name : "activedirectory with all default options" ,
2021-07-07 16:23:32 +00:00
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" )
}
} ,
2021-11-05 21:18:54 +00:00
createIDP : func ( t * testing . T ) string {
2021-07-07 16:23:32 +00:00
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 {
2021-07-08 22:00:04 +00:00
Host : env . SupervisorUpstreamActiveDirectory . Host ,
2021-07-07 16:23:32 +00:00
TLS : & idpv1alpha1 . TLSSpec {
CertificateAuthorityData : base64 . StdEncoding . EncodeToString ( [ ] byte ( env . SupervisorUpstreamActiveDirectory . CABundle ) ) ,
} ,
Bind : idpv1alpha1 . ActiveDirectoryIdentityProviderBind {
SecretName : secret . Name ,
} ,
} , 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 ,
)
2021-07-21 20:24:54 +00:00
requireSuccessfulActiveDirectoryIdentityProviderConditions ( t , adIDP , expectedMsg )
2021-11-05 21:18:54 +00:00
return adIDP . Name
2021-07-07 16:23:32 +00:00
} ,
2021-10-28 19:00:56 +00:00
requestAuthorization : func ( t * testing . T , downstreamAuthorizeURL , _ , _ , _ string , httpClient * http . Client ) {
2021-08-24 19:19:29 +00:00
requestAuthorizationUsingCLIPasswordFlow ( t ,
2021-07-07 16:23:32 +00:00
downstreamAuthorizeURL ,
2021-08-18 20:18:53 +00:00
env . SupervisorUpstreamActiveDirectory . TestUserPrincipalNameValue , // username to present to server during login
env . SupervisorUpstreamActiveDirectory . TestUserPassword , // password to present to server during login
2021-07-07 16:23:32 +00:00
httpClient ,
2021-07-22 23:15:44 +00:00
false ,
2021-07-07 16:23:32 +00:00
)
} ,
2021-10-28 19:00:56 +00:00
breakRefreshSessionData : func ( t * testing . T , pinnipedSession * psession . PinnipedSession , _ , _ string ) {
2021-10-26 23:24:02 +00:00
customSessionData := pinnipedSession . Custom
2021-10-22 20:57:30 +00:00
require . Equal ( t , psession . ProviderTypeActiveDirectory , customSessionData . ProviderType )
require . NotEmpty ( t , customSessionData . ActiveDirectory . UserDN )
2021-10-26 23:24:02 +00:00
fositeSessionData := pinnipedSession . Fosite
fositeSessionData . Claims . Extra [ "username" ] = "not-the-same"
2021-10-22 20:57:30 +00:00
} ,
2021-07-07 16:23:32 +00:00
// the ID token Subject should be the Host URL plus the value pulled from the requested UserSearch.Attributes.UID attribute
2021-08-26 23:18:05 +00:00
wantDownstreamIDTokenSubjectToMatch : "^" + regexp . QuoteMeta (
"ldaps://" + env . SupervisorUpstreamActiveDirectory . Host +
"?base=" + url . QueryEscape ( env . SupervisorUpstreamActiveDirectory . DefaultNamingContextSearchBase ) +
"&sub=" + env . SupervisorUpstreamActiveDirectory . TestUserUniqueIDAttributeValue ,
) + "$" ,
2021-07-07 16:23:32 +00:00
// the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute
2021-10-28 19:00:56 +00:00
wantDownstreamIDTokenUsernameToMatch : func ( _ string ) string {
return "^" + regexp . QuoteMeta ( env . SupervisorUpstreamActiveDirectory . TestUserPrincipalNameValue ) + "$"
} ,
wantDownstreamIDTokenGroups : env . SupervisorUpstreamActiveDirectory . TestUserIndirectGroupsSAMAccountPlusDomainNames ,
2021-07-26 23:03:12 +00:00
} , {
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" )
}
} ,
2021-11-05 21:18:54 +00:00
createIDP : func ( t * testing . T ) string {
2021-07-26 23:03:12 +00:00
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 )
2021-11-05 21:18:54 +00:00
return adIDP . Name
2021-07-26 23:03:12 +00:00
} ,
2021-10-28 19:00:56 +00:00
requestAuthorization : func ( t * testing . T , downstreamAuthorizeURL , _ , _ , _ string , httpClient * http . Client ) {
2021-08-24 19:19:29 +00:00
requestAuthorizationUsingCLIPasswordFlow ( t ,
2021-07-26 23:03:12 +00:00
downstreamAuthorizeURL ,
env . SupervisorUpstreamActiveDirectory . TestUserMailAttributeValue , // username to present to server during login
env . SupervisorUpstreamActiveDirectory . TestUserPassword , // password to present to server during login
httpClient ,
false ,
)
} ,
2021-10-28 19:00:56 +00:00
breakRefreshSessionData : func ( t * testing . T , pinnipedSession * psession . PinnipedSession , _ , _ string ) {
2021-10-26 23:24:02 +00:00
customSessionData := pinnipedSession . Custom
2021-10-22 20:57:30 +00:00
require . Equal ( t , psession . ProviderTypeActiveDirectory , customSessionData . ProviderType )
require . NotEmpty ( t , customSessionData . ActiveDirectory . UserDN )
2021-10-26 23:24:02 +00:00
fositeSessionData := pinnipedSession . Fosite
fositeSessionData . Claims . Subject = "not-right"
2021-10-22 20:57:30 +00:00
} ,
2021-07-26 23:03:12 +00:00
// the ID token Subject should be the Host URL plus the value pulled from the requested UserSearch.Attributes.UID attribute
2021-08-26 23:18:05 +00:00
wantDownstreamIDTokenSubjectToMatch : "^" + regexp . QuoteMeta (
"ldaps://" + env . SupervisorUpstreamActiveDirectory . Host +
"?base=" + url . QueryEscape ( env . SupervisorUpstreamActiveDirectory . UserSearchBase ) +
"&sub=" + env . SupervisorUpstreamActiveDirectory . TestUserUniqueIDAttributeValue ,
) + "$" ,
2021-07-26 23:03:12 +00:00
// the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute
2021-10-28 19:00:56 +00:00
wantDownstreamIDTokenUsernameToMatch : func ( _ string ) string {
return "^" + regexp . QuoteMeta ( env . SupervisorUpstreamActiveDirectory . TestUserMailAttributeValue ) + "$"
} ,
wantDownstreamIDTokenGroups : env . SupervisorUpstreamActiveDirectory . TestUserDirectGroupsDNs ,
2021-07-06 18:34:54 +00:00
} ,
2021-09-03 00:17:15 +00:00
{
2021-09-07 18:45:32 +00:00
name : "active directory login still works after updating bind secret" ,
2021-09-03 00:17:15 +00:00
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" )
}
} ,
2021-11-05 21:18:54 +00:00
createIDP : func ( t * testing . T ) string {
2021-09-03 00:17:15 +00:00
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 ,
} ,
)
secretName := secret . Name
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 : secretName ,
} ,
} , idpv1alpha1 . ActiveDirectoryPhaseReady )
secret . Annotations = map [ string ] string { "pinniped.dev/test" : "" , "another-label" : "another-key" }
2021-09-07 18:45:32 +00:00
// update that secret, which will cause the cache to recheck tls and search base values
2021-09-03 00:17:15 +00:00
client := testlib . NewKubernetesClientset ( t )
ctx , cancel := context . WithTimeout ( context . Background ( ) , time . Minute )
defer cancel ( )
updatedSecret , err := client . CoreV1 ( ) . Secrets ( env . SupervisorNamespace ) . Update ( ctx , secret , metav1 . UpdateOptions { } )
require . NoError ( t , err )
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 ,
updatedSecret . Name , updatedSecret . ResourceVersion ,
)
supervisorClient := testlib . NewSupervisorClientset ( t )
testlib . RequireEventually ( t , func ( requireEventually * require . Assertions ) {
ctx , cancel := context . WithTimeout ( context . Background ( ) , 10 * time . Second )
defer cancel ( )
adIDP , err = supervisorClient . IDPV1alpha1 ( ) . ActiveDirectoryIdentityProviders ( env . SupervisorNamespace ) . Get ( ctx , adIDP . Name , metav1 . GetOptions { } )
requireEventually . NoError ( err )
requireEventuallySuccessfulActiveDirectoryIdentityProviderConditions ( t , requireEventually , adIDP , expectedMsg )
} , time . Minute , 500 * time . Millisecond )
2021-11-05 21:18:54 +00:00
return adIDP . Name
2021-09-03 00:17:15 +00:00
} ,
2021-10-28 19:00:56 +00:00
requestAuthorization : func ( t * testing . T , downstreamAuthorizeURL , _ , _ , _ string , httpClient * http . Client ) {
2021-09-03 00:17:15 +00:00
requestAuthorizationUsingCLIPasswordFlow ( t ,
downstreamAuthorizeURL ,
env . SupervisorUpstreamActiveDirectory . TestUserPrincipalNameValue , // username to present to server during login
env . SupervisorUpstreamActiveDirectory . TestUserPassword , // password to present to server during login
httpClient ,
false ,
)
} ,
2021-10-28 19:00:56 +00:00
breakRefreshSessionData : func ( t * testing . T , pinnipedSession * psession . PinnipedSession , _ , _ string ) {
2021-10-26 23:24:02 +00:00
customSessionData := pinnipedSession . Custom
2021-10-22 20:57:30 +00:00
require . Equal ( t , psession . ProviderTypeActiveDirectory , customSessionData . ProviderType )
require . NotEmpty ( t , customSessionData . ActiveDirectory . UserDN )
customSessionData . ActiveDirectory . UserDN = "cn=not-a-user,dc=pinniped,dc=dev"
} ,
2021-09-03 00:17:15 +00:00
// 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 . DefaultNamingContextSearchBase ) +
"&sub=" + env . SupervisorUpstreamActiveDirectory . TestUserUniqueIDAttributeValue ,
) + "$" ,
// the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute
2021-10-28 19:00:56 +00:00
wantDownstreamIDTokenUsernameToMatch : func ( _ string ) string {
return "^" + regexp . QuoteMeta ( env . SupervisorUpstreamActiveDirectory . TestUserPrincipalNameValue ) + "$"
} ,
wantDownstreamIDTokenGroups : env . SupervisorUpstreamActiveDirectory . TestUserIndirectGroupsSAMAccountPlusDomainNames ,
2021-09-03 00:17:15 +00:00
} ,
2021-09-07 18:45:32 +00:00
{
name : "active directory login still works after deleting and recreating bind secret" ,
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" )
}
} ,
2021-11-05 21:18:54 +00:00
createIDP : func ( t * testing . T ) string {
2021-09-07 18:45:32 +00:00
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 ,
} ,
)
secretName := secret . Name
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 : secretName ,
} ,
} , idpv1alpha1 . ActiveDirectoryPhaseReady )
// delete the secret
client := testlib . NewKubernetesClientset ( t )
deleteCtx , deleteCancel := context . WithTimeout ( context . Background ( ) , time . Minute )
defer deleteCancel ( )
err := client . CoreV1 ( ) . Secrets ( env . SupervisorNamespace ) . Delete ( deleteCtx , secretName , metav1 . DeleteOptions { } )
require . NoError ( t , err )
// create the secret again
recreateCtx , recreateCancel := context . WithTimeout ( context . Background ( ) , time . Minute )
defer recreateCancel ( )
recreatedSecret , err := client . CoreV1 ( ) . Secrets ( env . SupervisorNamespace ) . Create ( recreateCtx , & v1 . Secret {
ObjectMeta : metav1 . ObjectMeta {
Name : secretName ,
Namespace : env . SupervisorNamespace ,
} ,
Type : v1 . SecretTypeBasicAuth ,
StringData : map [ string ] string {
v1 . BasicAuthUsernameKey : env . SupervisorUpstreamActiveDirectory . BindUsername ,
v1 . BasicAuthPasswordKey : env . SupervisorUpstreamActiveDirectory . BindPassword ,
} ,
} , metav1 . CreateOptions { } )
require . NoError ( t , err )
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 ,
recreatedSecret . Name , recreatedSecret . ResourceVersion ,
)
supervisorClient := testlib . NewSupervisorClientset ( t )
testlib . RequireEventually ( t , func ( requireEventually * require . Assertions ) {
ctx , cancel := context . WithTimeout ( context . Background ( ) , 10 * time . Second )
defer cancel ( )
adIDP , err = supervisorClient . IDPV1alpha1 ( ) . ActiveDirectoryIdentityProviders ( env . SupervisorNamespace ) . Get ( ctx , adIDP . Name , metav1 . GetOptions { } )
requireEventually . NoError ( err )
requireEventuallySuccessfulActiveDirectoryIdentityProviderConditions ( t , requireEventually , adIDP , expectedMsg )
} , time . Minute , 500 * time . Millisecond )
2021-11-05 21:18:54 +00:00
return adIDP . Name
2021-09-07 18:45:32 +00:00
} ,
2021-10-28 19:00:56 +00:00
requestAuthorization : func ( t * testing . T , downstreamAuthorizeURL , _ , _ , _ string , httpClient * http . Client ) {
2021-09-07 18:45:32 +00:00
requestAuthorizationUsingCLIPasswordFlow ( t ,
downstreamAuthorizeURL ,
env . SupervisorUpstreamActiveDirectory . TestUserPrincipalNameValue , // username to present to server during login
env . SupervisorUpstreamActiveDirectory . TestUserPassword , // password to present to server during login
httpClient ,
false ,
)
} ,
2021-10-28 19:00:56 +00:00
breakRefreshSessionData : func ( t * testing . T , pinnipedSession * psession . PinnipedSession , _ , _ string ) {
2021-10-26 23:24:02 +00:00
customSessionData := pinnipedSession . Custom
2021-10-22 20:57:30 +00:00
require . Equal ( t , psession . ProviderTypeActiveDirectory , customSessionData . ProviderType )
require . NotEmpty ( t , customSessionData . ActiveDirectory . UserDN )
customSessionData . ActiveDirectory . UserDN = "cn=not-a-user,dc=pinniped,dc=dev"
} ,
2021-09-07 18:45:32 +00:00
// 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 . DefaultNamingContextSearchBase ) +
"&sub=" + env . SupervisorUpstreamActiveDirectory . TestUserUniqueIDAttributeValue ,
) + "$" ,
// the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute
2021-10-28 19:00:56 +00:00
wantDownstreamIDTokenUsernameToMatch : func ( _ string ) string {
return "^" + regexp . QuoteMeta ( env . SupervisorUpstreamActiveDirectory . TestUserPrincipalNameValue ) + "$"
} ,
wantDownstreamIDTokenGroups : env . SupervisorUpstreamActiveDirectory . TestUserIndirectGroupsSAMAccountPlusDomainNames ,
} ,
{
name : "active directory login fails after the user password is changed" ,
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 ) string {
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 ,
} ,
} , 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 )
return adIDP . Name
} ,
createTestUser : func ( t * testing . T ) ( string , string ) {
return createFreshADTestUser ( t , env )
} ,
deleteTestUser : func ( t * testing . T , username string ) {
deleteTestADUser ( t , env , username )
} ,
requestAuthorization : func ( t * testing . T , downstreamAuthorizeURL , _ , testUserName , testUserPassword string , httpClient * http . Client ) {
requestAuthorizationUsingCLIPasswordFlow ( t ,
downstreamAuthorizeURL ,
testUserName , // username to present to server during login
testUserPassword , // password to present to server during login
httpClient ,
false ,
)
} ,
breakRefreshSessionData : func ( t * testing . T , sessionData * psession . PinnipedSession , _ , username string ) {
2021-11-05 18:53:07 +00:00
changeADTestUserPassword ( t , env , username )
} ,
// we can't know the subject ahead of time because we created a new user and don't know their uid,
// so skip wantDownstreamIDTokenSubjectToMatch
// the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute
wantDownstreamIDTokenUsernameToMatch : func ( username string ) string {
return "^" + regexp . QuoteMeta ( username + "@" + env . SupervisorUpstreamActiveDirectory . Domain ) + "$"
} ,
wantDownstreamIDTokenGroups : [ ] string { } , // none for now.
} ,
{
name : "active directory login fails after the user is deactivated" ,
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 ) string {
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 ,
} ,
} , 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 )
return adIDP . Name
} ,
createTestUser : func ( t * testing . T ) ( string , string ) {
return createFreshADTestUser ( t , env )
} ,
deleteTestUser : func ( t * testing . T , username string ) {
deleteTestADUser ( t , env , username )
} ,
requestAuthorization : func ( t * testing . T , downstreamAuthorizeURL , _ , testUserName , testUserPassword string , httpClient * http . Client ) {
requestAuthorizationUsingCLIPasswordFlow ( t ,
downstreamAuthorizeURL ,
testUserName , // username to present to server during login
testUserPassword , // password to present to server during login
httpClient ,
false ,
)
} ,
breakRefreshSessionData : func ( t * testing . T , sessionData * psession . PinnipedSession , _ , username string ) {
deactivateADTestUser ( t , env , username )
2021-10-28 19:00:56 +00:00
} ,
// we can't know the subject ahead of time because we created a new user and don't know their uid,
// so skip wantDownstreamIDTokenSubjectToMatch
// the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute
wantDownstreamIDTokenUsernameToMatch : func ( username string ) string {
return "^" + regexp . QuoteMeta ( username + "@" + env . SupervisorUpstreamActiveDirectory . Domain ) + "$"
} ,
wantDownstreamIDTokenGroups : [ ] string { } , // none for now.
2021-09-07 18:45:32 +00:00
} ,
2021-11-17 00:31:32 +00:00
{
name : "active directory login fails after the user is locked" ,
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 ) string {
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 ,
} ,
} , 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 )
return adIDP . Name
} ,
createTestUser : func ( t * testing . T ) ( string , string ) {
return createFreshADTestUser ( t , env )
} ,
deleteTestUser : func ( t * testing . T , username string ) {
deleteTestADUser ( t , env , username )
} ,
requestAuthorization : func ( t * testing . T , downstreamAuthorizeURL , _ , testUserName , testUserPassword string , httpClient * http . Client ) {
requestAuthorizationUsingCLIPasswordFlow ( t ,
downstreamAuthorizeURL ,
testUserName , // username to present to server during login
testUserPassword , // password to present to server during login
httpClient ,
false ,
)
} ,
breakRefreshSessionData : func ( t * testing . T , sessionData * psession . PinnipedSession , _ , username string ) {
lockADTestUser ( t , env , username )
} ,
// we can't know the subject ahead of time because we created a new user and don't know their uid,
// so skip wantDownstreamIDTokenSubjectToMatch
// the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute
wantDownstreamIDTokenUsernameToMatch : func ( username string ) string {
return "^" + regexp . QuoteMeta ( username + "@" + env . SupervisorUpstreamActiveDirectory . Domain ) + "$"
} ,
wantDownstreamIDTokenGroups : [ ] string { } ,
} ,
2021-07-22 23:15:44 +00:00
{
name : "logging in to activedirectory with a deactivated user fails" ,
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" )
}
} ,
2021-11-05 21:18:54 +00:00
createIDP : func ( t * testing . T ) string {
2021-07-22 23:15:44 +00:00
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 ,
} ,
} , 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 )
2021-11-05 21:18:54 +00:00
return adIDP . Name
2021-07-22 23:15:44 +00:00
} ,
2021-10-28 19:00:56 +00:00
requestAuthorization : func ( t * testing . T , downstreamAuthorizeURL , _ , _ , _ string , httpClient * http . Client ) {
2021-08-24 19:19:29 +00:00
requestAuthorizationUsingCLIPasswordFlow ( t ,
2021-07-22 23:15:44 +00:00
downstreamAuthorizeURL ,
env . SupervisorUpstreamActiveDirectory . TestDeactivatedUserSAMAccountNameValue , // username to present to server during login
env . SupervisorUpstreamActiveDirectory . TestDeactivatedUserPassword , // password to present to server during login
httpClient ,
true ,
)
} ,
2021-10-22 20:57:30 +00:00
breakRefreshSessionData : nil ,
2021-10-13 22:12:19 +00:00
wantErrorDescription : "The resource owner or authorization server denied the request. Username/password not accepted by LDAP provider." ,
wantErrorType : "access_denied" ,
2021-07-22 23:15:44 +00:00
} ,
2021-11-05 21:18:54 +00:00
{
name : "ldap refresh fails when username changes from email as username to dn as username" ,
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" )
}
} ,
createIDP : func ( t * testing . T ) string {
t . Helper ( )
secret := testlib . CreateTestSecret ( t , env . SupervisorNamespace , "ldap-service-account" , v1 . SecretTypeBasicAuth ,
map [ string ] string {
v1 . BasicAuthUsernameKey : env . SupervisorUpstreamLDAP . BindUsername ,
v1 . BasicAuthPasswordKey : env . SupervisorUpstreamLDAP . BindPassword ,
} ,
)
ldapIDP := testlib . CreateTestLDAPIdentityProvider ( t , idpv1alpha1 . LDAPIdentityProviderSpec {
Host : env . SupervisorUpstreamLDAP . Host ,
TLS : & idpv1alpha1 . TLSSpec {
CertificateAuthorityData : base64 . StdEncoding . EncodeToString ( [ ] byte ( env . SupervisorUpstreamLDAP . CABundle ) ) ,
} ,
Bind : idpv1alpha1 . LDAPIdentityProviderBind {
SecretName : secret . Name ,
} ,
UserSearch : idpv1alpha1 . LDAPIdentityProviderUserSearch {
Base : env . SupervisorUpstreamLDAP . UserSearchBase ,
Filter : "" ,
Attributes : idpv1alpha1 . LDAPIdentityProviderUserSearchAttributes {
Username : env . SupervisorUpstreamLDAP . TestUserMailAttributeName ,
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"] ` ,
env . SupervisorUpstreamLDAP . Host , env . SupervisorUpstreamLDAP . BindUsername ,
secret . Name , secret . ResourceVersion ,
)
requireSuccessfulLDAPIdentityProviderConditions ( t , ldapIDP , expectedMsg )
return ldapIDP . Name
} ,
2021-10-28 19:00:56 +00:00
requestAuthorization : func ( t * testing . T , downstreamAuthorizeURL , _ , _ , _ string , httpClient * http . Client ) {
2021-11-05 21:18:54 +00:00
requestAuthorizationUsingCLIPasswordFlow ( t ,
downstreamAuthorizeURL ,
env . SupervisorUpstreamLDAP . TestUserMailAttributeValue , // username to present to server during login
env . SupervisorUpstreamLDAP . TestUserPassword , // password to present to server during login
httpClient ,
false ,
)
} ,
2021-10-28 19:00:56 +00:00
breakRefreshSessionData : func ( t * testing . T , pinnipedSession * psession . PinnipedSession , idpName , _ string ) {
2021-11-05 21:18:54 +00:00
// get the idp, update the config.
client := testlib . NewSupervisorClientset ( t )
ctx , cancel := context . WithTimeout ( context . Background ( ) , 5 * time . Minute )
defer cancel ( )
upstreams := client . IDPV1alpha1 ( ) . LDAPIdentityProviders ( env . SupervisorNamespace )
ldapIDP , err := upstreams . Get ( ctx , idpName , metav1 . GetOptions { } )
require . NoError ( t , err )
ldapIDP . Spec . UserSearch . Attributes . Username = "dn"
_ , err = upstreams . Update ( ctx , ldapIDP , metav1 . UpdateOptions { } )
require . NoError ( t , err )
time . Sleep ( 10 * time . Second ) // wait for controllers to pick up the change
} ,
// 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 +
"?base=" + url . QueryEscape ( env . SupervisorUpstreamLDAP . UserSearchBase ) +
"&sub=" + base64 . RawURLEncoding . EncodeToString ( [ ] byte ( env . SupervisorUpstreamLDAP . TestUserUniqueIDAttributeValue ) ) ,
) + "$" ,
// the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute
2021-11-17 00:31:32 +00:00
wantDownstreamIDTokenUsernameToMatch : func ( _ string ) string {
2021-10-28 19:00:56 +00:00
return "^" + regexp . QuoteMeta ( env . SupervisorUpstreamLDAP . TestUserMailAttributeValue ) + "$"
} ,
wantDownstreamIDTokenGroups : env . SupervisorUpstreamLDAP . TestUserDirectGroupsDNs ,
2021-11-05 21:18:54 +00:00
} ,
2022-02-15 21:52:33 +00:00
{
name : "ldap refresh updates groups to be empty after deleting the group search base" ,
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" )
}
} ,
createIDP : func ( t * testing . T ) string {
t . Helper ( )
secret := testlib . CreateTestSecret ( t , env . SupervisorNamespace , "ldap-service-account" , v1 . SecretTypeBasicAuth ,
map [ string ] string {
v1 . BasicAuthUsernameKey : env . SupervisorUpstreamLDAP . BindUsername ,
v1 . BasicAuthPasswordKey : env . SupervisorUpstreamLDAP . BindPassword ,
} ,
)
secretName := secret . Name
ldapIDP := testlib . CreateTestLDAPIdentityProvider ( t , idpv1alpha1 . LDAPIdentityProviderSpec {
Host : env . SupervisorUpstreamLDAP . Host ,
TLS : & idpv1alpha1 . TLSSpec {
CertificateAuthorityData : base64 . StdEncoding . EncodeToString ( [ ] byte ( env . SupervisorUpstreamLDAP . CABundle ) ) ,
} ,
Bind : idpv1alpha1 . LDAPIdentityProviderBind {
SecretName : secretName ,
} ,
UserSearch : idpv1alpha1 . LDAPIdentityProviderUserSearch {
Base : env . SupervisorUpstreamLDAP . UserSearchBase ,
Filter : "" ,
Attributes : idpv1alpha1 . LDAPIdentityProviderUserSearchAttributes {
Username : env . SupervisorUpstreamLDAP . TestUserMailAttributeName ,
UID : env . SupervisorUpstreamLDAP . TestUserUniqueIDAttributeName ,
} ,
} ,
GroupSearch : idpv1alpha1 . LDAPIdentityProviderGroupSearch {
Base : env . SupervisorUpstreamLDAP . GroupSearchBase ,
Filter : "" ,
Attributes : idpv1alpha1 . LDAPIdentityProviderGroupSearchAttributes {
GroupName : "dn" ,
} ,
} ,
} , idpv1alpha1 . LDAPPhaseReady )
return ldapIDP . Name
} ,
requestAuthorization : func ( t * testing . T , downstreamAuthorizeURL , _ , _ , _ string , httpClient * http . Client ) {
requestAuthorizationUsingCLIPasswordFlow ( t ,
downstreamAuthorizeURL ,
env . SupervisorUpstreamLDAP . TestUserMailAttributeValue , // username to present to server during login
env . SupervisorUpstreamLDAP . TestUserPassword , // password to present to server during login
httpClient ,
false ,
)
} ,
editRefreshSessionDataWithoutBreaking : func ( t * testing . T , pinnipedSession * psession . PinnipedSession , idpName , _ string ) {
// get the idp, update the config.
client := testlib . NewSupervisorClientset ( t )
ctx , cancel := context . WithTimeout ( context . Background ( ) , 5 * time . Minute )
defer cancel ( )
upstreams := client . IDPV1alpha1 ( ) . LDAPIdentityProviders ( env . SupervisorNamespace )
ldapIDP , err := upstreams . Get ( ctx , idpName , metav1 . GetOptions { } )
require . NoError ( t , err )
ldapIDP . Spec . GroupSearch . Base = ""
_ , err = upstreams . Update ( ctx , ldapIDP , metav1 . UpdateOptions { } )
require . NoError ( t , err )
time . Sleep ( 10 * time . Second ) // wait for controllers to pick up the change
} ,
// 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 +
"?base=" + url . QueryEscape ( env . SupervisorUpstreamLDAP . UserSearchBase ) +
"&sub=" + base64 . RawURLEncoding . EncodeToString ( [ ] byte ( env . SupervisorUpstreamLDAP . TestUserUniqueIDAttributeValue ) ) ,
) + "$" ,
// the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute
wantDownstreamIDTokenUsernameToMatch : func ( _ string ) string {
return "^" + regexp . QuoteMeta ( env . SupervisorUpstreamLDAP . TestUserMailAttributeValue ) + "$"
} ,
wantDownstreamIDTokenGroups : env . SupervisorUpstreamLDAP . TestUserDirectGroupsDNs ,
wantDownstreamIDTokenGroupsAfterRefresh : [ ] string { } ,
} ,
2021-04-06 17:10:01 +00:00
}
for _ , test := range tests {
2021-05-28 23:12:57 +00:00
tt := test
t . Run ( tt . name , func ( t * testing . T ) {
tt . maybeSkip ( t )
2021-04-07 19:56:09 +00:00
testSupervisorLogin ( t ,
2021-05-28 23:12:57 +00:00
tt . createIDP ,
tt . requestAuthorization ,
2022-01-26 00:19:56 +00:00
tt . editRefreshSessionDataWithoutBreaking ,
2021-10-13 22:12:19 +00:00
tt . breakRefreshSessionData ,
2021-10-28 19:00:56 +00:00
tt . createTestUser ,
tt . deleteTestUser ,
2021-05-28 23:12:57 +00:00
tt . wantDownstreamIDTokenSubjectToMatch ,
tt . wantDownstreamIDTokenUsernameToMatch ,
tt . wantDownstreamIDTokenGroups ,
2022-02-15 21:52:33 +00:00
tt . wantDownstreamIDTokenGroupsAfterRefresh ,
2021-10-13 22:12:19 +00:00
tt . wantErrorDescription ,
tt . wantErrorType ,
2021-04-07 19:56:09 +00:00
)
2021-04-06 17:10:01 +00:00
} )
}
}
2021-04-16 21:04:05 +00:00
func requireSuccessfulLDAPIdentityProviderConditions ( t * testing . T , ldapIDP * idpv1alpha1 . LDAPIdentityProvider , expectedLDAPConnectionValidMessage string ) {
require . Len ( t , ldapIDP . Status . Conditions , 3 )
conditionsSummary := [ ] [ ] string { }
for _ , condition := range ldapIDP . Status . Conditions {
conditionsSummary = append ( conditionsSummary , [ ] string { condition . Type , string ( condition . Status ) , condition . Reason } )
t . Logf ( "Saw LDAPIdentityProvider Status.Condition Type=%s Status=%s Reason=%s Message=%s" ,
condition . Type , string ( condition . Status ) , condition . Reason , condition . Message )
switch condition . Type {
case "BindSecretValid" :
require . Equal ( t , "loaded bind secret" , condition . Message )
case "TLSConfigurationValid" :
require . Equal ( t , "loaded TLS configuration" , condition . Message )
case "LDAPConnectionValid" :
require . Equal ( t , expectedLDAPConnectionValidMessage , condition . Message )
}
}
require . ElementsMatch ( t , [ ] [ ] string {
{ "BindSecretValid" , "True" , "Success" } ,
{ "TLSConfigurationValid" , "True" , "Success" } ,
{ "LDAPConnectionValid" , "True" , "Success" } ,
} , conditionsSummary )
}
2021-07-07 16:23:32 +00:00
func requireSuccessfulActiveDirectoryIdentityProviderConditions ( t * testing . T , adIDP * idpv1alpha1 . ActiveDirectoryIdentityProvider , expectedActiveDirectoryConnectionValidMessage string ) {
2021-07-21 20:24:54 +00:00
require . Len ( t , adIDP . Status . Conditions , 4 )
2021-07-07 16:23:32 +00:00
conditionsSummary := [ ] [ ] string { }
for _ , condition := range adIDP . Status . Conditions {
conditionsSummary = append ( conditionsSummary , [ ] string { condition . Type , string ( condition . Status ) , condition . Reason } )
t . Logf ( "Saw ActiveDirectoryIdentityProvider Status.Condition Type=%s Status=%s Reason=%s Message=%s" ,
condition . Type , string ( condition . Status ) , condition . Reason , condition . Message )
switch condition . Type {
case "BindSecretValid" :
require . Equal ( t , "loaded bind secret" , condition . Message )
case "TLSConfigurationValid" :
require . Equal ( t , "loaded TLS configuration" , condition . Message )
2021-07-19 20:54:07 +00:00
case "LDAPConnectionValid" :
2021-07-07 16:23:32 +00:00
require . Equal ( t , expectedActiveDirectoryConnectionValidMessage , condition . Message )
}
}
2021-07-26 23:03:12 +00:00
expectedUserSearchReason := ""
if adIDP . Spec . UserSearch . Base == "" || adIDP . Spec . GroupSearch . Base == "" {
expectedUserSearchReason = "Success"
} else {
expectedUserSearchReason = "UsingConfigurationFromSpec"
}
2021-07-07 16:23:32 +00:00
require . ElementsMatch ( t , [ ] [ ] string {
{ "BindSecretValid" , "True" , "Success" } ,
2021-09-03 00:17:15 +00:00
{ "TLSConfigurationValid" , "True" , "Success" } ,
{ "LDAPConnectionValid" , "True" , "Success" } ,
{ "SearchBaseFound" , "True" , expectedUserSearchReason } ,
} , conditionsSummary )
}
2021-09-07 18:45:32 +00:00
func requireEventuallySuccessfulLDAPIdentityProviderConditions ( t * testing . T , requireEventually * require . Assertions , ldapIDP * idpv1alpha1 . LDAPIdentityProvider , expectedLDAPConnectionValidMessage string ) {
t . Helper ( )
requireEventually . Len ( ldapIDP . Status . Conditions , 3 )
conditionsSummary := [ ] [ ] string { }
for _ , condition := range ldapIDP . Status . Conditions {
conditionsSummary = append ( conditionsSummary , [ ] string { condition . Type , string ( condition . Status ) , condition . Reason } )
t . Logf ( "Saw ActiveDirectoryIdentityProvider Status.Condition Type=%s Status=%s Reason=%s Message=%s" ,
condition . Type , string ( condition . Status ) , condition . Reason , condition . Message )
switch condition . Type {
case "BindSecretValid" :
requireEventually . Equal ( "loaded bind secret" , condition . Message )
case "TLSConfigurationValid" :
requireEventually . Equal ( "loaded TLS configuration" , condition . Message )
case "LDAPConnectionValid" :
requireEventually . Equal ( expectedLDAPConnectionValidMessage , condition . Message )
}
}
requireEventually . ElementsMatch ( [ ] [ ] string {
{ "BindSecretValid" , "True" , "Success" } ,
{ "TLSConfigurationValid" , "True" , "Success" } ,
{ "LDAPConnectionValid" , "True" , "Success" } ,
} , conditionsSummary )
}
2021-09-03 00:17:15 +00:00
func requireEventuallySuccessfulActiveDirectoryIdentityProviderConditions ( t * testing . T , requireEventually * require . Assertions , adIDP * idpv1alpha1 . ActiveDirectoryIdentityProvider , expectedActiveDirectoryConnectionValidMessage string ) {
t . Helper ( )
requireEventually . Len ( adIDP . Status . Conditions , 4 )
conditionsSummary := [ ] [ ] string { }
for _ , condition := range adIDP . Status . Conditions {
conditionsSummary = append ( conditionsSummary , [ ] string { condition . Type , string ( condition . Status ) , condition . Reason } )
t . Logf ( "Saw ActiveDirectoryIdentityProvider Status.Condition Type=%s Status=%s Reason=%s Message=%s" ,
condition . Type , string ( condition . Status ) , condition . Reason , condition . Message )
switch condition . Type {
case "BindSecretValid" :
requireEventually . Equal ( "loaded bind secret" , condition . Message )
case "TLSConfigurationValid" :
requireEventually . Equal ( "loaded TLS configuration" , condition . Message )
case "LDAPConnectionValid" :
requireEventually . Equal ( expectedActiveDirectoryConnectionValidMessage , condition . Message )
}
}
expectedUserSearchReason := ""
if adIDP . Spec . UserSearch . Base == "" || adIDP . Spec . GroupSearch . Base == "" {
expectedUserSearchReason = "Success"
} else {
expectedUserSearchReason = "UsingConfigurationFromSpec"
}
requireEventually . ElementsMatch ( [ ] [ ] string {
{ "BindSecretValid" , "True" , "Success" } ,
2021-07-07 16:23:32 +00:00
{ "TLSConfigurationValid" , "True" , "Success" } ,
2021-07-19 20:54:07 +00:00
{ "LDAPConnectionValid" , "True" , "Success" } ,
2021-07-26 23:03:12 +00:00
{ "SearchBaseFound" , "True" , expectedUserSearchReason } ,
2021-07-07 16:23:32 +00:00
} , conditionsSummary )
}
2021-04-16 21:04:05 +00:00
2021-04-06 17:10:01 +00:00
func testSupervisorLogin (
t * testing . T ,
2021-11-05 21:18:54 +00:00
createIDP func ( t * testing . T ) string ,
2022-01-26 00:19:56 +00:00
requestAuthorization func ( t * testing . T , downstreamAuthorizeURL string , downstreamCallbackURL string , username string , password string , httpClient * http . Client ) ,
2022-02-15 21:52:33 +00:00
editRefreshSessionDataWithoutBreaking func ( t * testing . T , pinnipedSession * psession . PinnipedSession , idpName , username string ) ,
breakRefreshSessionData func ( t * testing . T , pinnipedSession * psession . PinnipedSession , idpName , username string ) ,
2021-10-28 19:00:56 +00:00
createTestUser func ( t * testing . T ) ( string , string ) ,
deleteTestUser func ( t * testing . T , username string ) ,
wantDownstreamIDTokenSubjectToMatch string ,
wantDownstreamIDTokenUsernameToMatch func ( username string ) string ,
wantDownstreamIDTokenGroups [ ] string ,
2022-02-15 21:52:33 +00:00
wantDownstreamIDTokenGroupsAfterRefresh [ ] string ,
2021-10-28 19:00:56 +00:00
wantErrorDescription string ,
wantErrorType string ,
2021-04-06 17:10:01 +00:00
) {
2021-06-22 15:23:19 +00:00
env := testlib . IntegrationEnv ( t )
2020-12-03 15:35:28 +00:00
2020-11-17 15:21:17 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , 5 * time . Minute )
defer cancel ( )
2020-12-02 21:50:42 +00:00
// Infer the downstream issuer URL from the callback associated with the upstream test client registration.
2021-04-07 19:56:09 +00:00
issuerURL , err := url . Parse ( env . SupervisorUpstreamOIDC . CallbackURL )
2020-12-02 21:50:42 +00:00
require . NoError ( t , err )
require . True ( t , strings . HasSuffix ( issuerURL . Path , "/callback" ) )
issuerURL . Path = strings . TrimSuffix ( issuerURL . Path , "/callback" )
t . Logf ( "testing with downstream issuer URL %s" , issuerURL . String ( ) )
2020-11-17 15:21:17 +00:00
2020-12-02 21:50:42 +00:00
// Generate a CA bundle with which to serve this provider.
t . Logf ( "generating test CA" )
2021-03-13 00:09:16 +00:00
ca , err := certauthority . New ( "Downstream Test CA" , 1 * time . Hour )
2020-12-02 21:50:42 +00:00
require . NoError ( t , err )
2020-11-17 15:21:17 +00:00
2020-12-02 21:50:42 +00:00
// Create an HTTP client that can reach the downstream discovery endpoint using the CA certs.
2020-12-16 03:42:11 +00:00
httpClient := & http . Client {
Transport : & http . Transport {
TLSClientConfig : & tls . Config { RootCAs : ca . Pool ( ) } ,
Proxy : func ( req * http . Request ) ( * url . URL , error ) {
2021-04-14 01:11:16 +00:00
if strings . HasPrefix ( req . URL . Host , "127.0.0.1" ) {
// don't proxy requests to localhost to avoid proxying calls to our local callback listener
return nil , nil
}
2020-12-16 03:42:11 +00:00
if env . Proxy == "" {
2021-06-22 15:23:19 +00:00
t . Logf ( "passing request for %s with no proxy" , testlib . RedactURLParams ( req . URL ) )
2020-12-16 03:42:11 +00:00
return nil , nil
}
proxyURL , err := url . Parse ( env . Proxy )
require . NoError ( t , err )
2021-06-22 15:23:19 +00:00
t . Logf ( "passing request for %s through proxy %s" , testlib . RedactURLParams ( req . URL ) , proxyURL . String ( ) )
2020-12-16 03:42:11 +00:00
return proxyURL , nil
} ,
2020-12-02 21:50:42 +00:00
} ,
2020-12-16 03:42:11 +00:00
// Don't follow redirects automatically.
CheckRedirect : func ( req * http . Request , via [ ] * http . Request ) error {
return http . ErrUseLastResponse
} ,
}
2020-12-10 18:14:54 +00:00
oidcHTTPClientContext := coreosoidc . ClientContext ( ctx , httpClient )
2020-12-02 21:50:42 +00:00
// Use the CA to issue a TLS server cert.
t . Logf ( "issuing test certificate" )
2021-03-13 00:09:16 +00:00
tlsCert , err := ca . IssueServerCert ( [ ] string { issuerURL . Hostname ( ) } , nil , 1 * time . Hour )
2020-12-02 21:50:42 +00:00
require . NoError ( t , err )
certPEM , keyPEM , err := certauthority . ToPEM ( tlsCert )
2020-11-30 14:58:08 +00:00
require . NoError ( t , err )
2020-12-02 21:50:42 +00:00
// Write the serving cert to a secret.
2021-06-22 15:23:19 +00:00
certSecret := testlib . CreateTestSecret ( t ,
2020-12-02 21:50:42 +00:00
env . SupervisorNamespace ,
"oidc-provider-tls" ,
2020-12-18 23:10:17 +00:00
v1 . SecretTypeTLS ,
2020-12-02 21:50:42 +00:00
map [ string ] string { "tls.crt" : string ( certPEM ) , "tls.key" : string ( keyPEM ) } ,
2020-11-30 14:58:08 +00:00
)
2020-12-16 22:27:09 +00:00
// Create the downstream FederationDomain and expect it to go into the success status condition.
2021-06-22 15:23:19 +00:00
downstream := testlib . CreateTestFederationDomain ( ctx , t ,
2020-12-02 21:50:42 +00:00
issuerURL . String ( ) ,
certSecret . Name ,
2020-12-16 22:27:09 +00:00
configv1alpha1 . SuccessFederationDomainStatusCondition ,
2020-12-02 21:50:42 +00:00
)
2020-11-30 14:58:08 +00:00
2020-12-17 01:59:39 +00:00
// Ensure the the JWKS data is created and ready for the new FederationDomain by waiting for
// the `/jwks.json` endpoint to succeed, because there is no point in proceeding and eventually
// calling the token endpoint from this test until the JWKS data has been loaded into
// the server's in-memory JWKS cache for the token endpoint to use.
requestJWKSEndpoint , err := http . NewRequestWithContext (
ctx ,
http . MethodGet ,
fmt . Sprintf ( "%s/jwks.json" , issuerURL . String ( ) ) ,
nil ,
)
require . NoError ( t , err )
2021-06-22 15:23:19 +00:00
testlib . RequireEventually ( t , func ( requireEventually * require . Assertions ) {
2020-12-17 01:59:39 +00:00
rsp , err := httpClient . Do ( requestJWKSEndpoint )
2021-06-16 22:51:23 +00:00
requireEventually . NoError ( err )
requireEventually . NoError ( rsp . Body . Close ( ) )
requireEventually . Equal ( http . StatusOK , rsp . StatusCode )
2020-12-17 01:59:39 +00:00
} , 30 * time . Second , 200 * time . Millisecond )
2021-04-06 17:10:01 +00:00
// Create upstream IDP and wait for it to become ready.
2021-11-05 21:18:54 +00:00
idpName := createIDP ( t )
2020-12-02 21:50:42 +00:00
2021-10-28 19:00:56 +00:00
username , password := "" , ""
if createTestUser != nil {
username , password = createTestUser ( t )
defer deleteTestUser ( t , username )
}
2020-12-02 21:50:42 +00:00
// Perform OIDC discovery for our downstream.
2020-12-10 18:14:54 +00:00
var discovery * coreosoidc . Provider
2021-06-22 15:23:19 +00:00
testlib . RequireEventually ( t , func ( requireEventually * require . Assertions ) {
2021-06-16 22:51:23 +00:00
var err error
2020-12-10 18:14:54 +00:00
discovery , err = coreosoidc . NewProvider ( oidcHTTPClientContext , downstream . Spec . Issuer )
2021-06-16 22:51:23 +00:00
requireEventually . NoError ( err )
2020-12-03 19:22:27 +00:00
} , 30 * time . Second , 200 * time . Millisecond )
2020-12-02 21:50:42 +00:00
// Start a callback server on localhost.
localCallbackServer := startLocalCallbackServer ( t )
2020-11-30 14:58:08 +00:00
2020-12-02 21:50:42 +00:00
// Form the OAuth2 configuration corresponding to our CLI client.
2020-11-17 15:21:17 +00:00
downstreamOAuth2Config := oauth2 . Config {
// This is the hardcoded public client that the supervisor supports.
2020-12-02 21:50:42 +00:00
ClientID : "pinniped-cli" ,
Endpoint : discovery . Endpoint ( ) ,
RedirectURL : localCallbackServer . URL ,
2020-12-16 03:59:57 +00:00
Scopes : [ ] string { "openid" , "pinniped:request-audience" , "offline_access" } ,
2020-11-17 15:21:17 +00:00
}
2020-12-02 21:50:42 +00:00
// Build a valid downstream authorize URL for the supervisor.
stateParam , err := state . Generate ( )
2020-11-17 15:21:17 +00:00
require . NoError ( t , err )
2020-12-02 21:50:42 +00:00
nonceParam , err := nonce . Generate ( )
2020-11-17 15:21:17 +00:00
require . NoError ( t , err )
2020-12-02 21:50:42 +00:00
pkceParam , err := pkce . Generate ( )
2020-11-17 15:21:17 +00:00
require . NoError ( t , err )
2020-12-02 21:50:42 +00:00
downstreamAuthorizeURL := downstreamOAuth2Config . AuthCodeURL (
stateParam . String ( ) ,
nonceParam . Param ( ) ,
pkceParam . Challenge ( ) ,
pkceParam . Method ( ) ,
)
2020-11-17 15:21:17 +00:00
2021-04-06 17:10:01 +00:00
// Perform parameterized auth code acquisition.
2021-10-28 19:00:56 +00:00
requestAuthorization ( t , downstreamAuthorizeURL , localCallbackServer . URL , username , password , httpClient )
2020-12-02 21:50:42 +00:00
// Expect that our callback handler was invoked.
callback := localCallbackServer . waitForCallback ( 10 * time . Second )
2021-06-22 15:23:19 +00:00
t . Logf ( "got callback request: %s" , testlib . MaskTokens ( callback . URL . String ( ) ) )
2021-07-22 23:15:44 +00:00
if wantErrorType == "" {
require . Equal ( t , stateParam . String ( ) , callback . URL . Query ( ) . Get ( "state" ) )
require . ElementsMatch ( t , [ ] string { "openid" , "pinniped:request-audience" , "offline_access" } , strings . Split ( callback . URL . Query ( ) . Get ( "scope" ) , " " ) )
authcode := callback . URL . Query ( ) . Get ( "code" )
require . NotEmpty ( t , authcode )
// Call the token endpoint to get tokens.
tokenResponse , err := downstreamOAuth2Config . Exchange ( oidcHTTPClientContext , authcode , pkceParam . Verifier ( ) )
require . NoError ( t , err )
expectedIDTokenClaims := [ ] string { "iss" , "exp" , "sub" , "aud" , "auth_time" , "iat" , "jti" , "nonce" , "rat" , "username" , "groups" }
verifyTokenResponse ( t ,
tokenResponse , discovery , downstreamOAuth2Config , nonceParam ,
2021-10-28 19:00:56 +00:00
expectedIDTokenClaims , wantDownstreamIDTokenSubjectToMatch , wantDownstreamIDTokenUsernameToMatch ( username ) , wantDownstreamIDTokenGroups )
2021-07-22 23:15:44 +00:00
// token exchange on the original token
doTokenExchange ( t , & downstreamOAuth2Config , tokenResponse , httpClient , discovery )
2022-01-26 00:19:56 +00:00
if editRefreshSessionDataWithoutBreaking != nil {
latestRefreshToken := tokenResponse . RefreshToken
signatureOfLatestRefreshToken := getFositeDataSignature ( t , latestRefreshToken )
// First use the latest downstream refresh token to look up the corresponding session in the Supervisor's storage.
kubeClient := testlib . NewKubernetesClientset ( t )
supervisorSecretsClient := kubeClient . CoreV1 ( ) . Secrets ( env . SupervisorNamespace )
oauthStore := oidc . NewKubeStorage ( supervisorSecretsClient , oidc . DefaultOIDCTimeoutsConfiguration ( ) )
storedRefreshSession , err := oauthStore . GetRefreshTokenSession ( ctx , signatureOfLatestRefreshToken , nil )
require . NoError ( t , err )
// Next mutate the part of the session that is used during upstream refresh.
pinnipedSession , ok := storedRefreshSession . GetSession ( ) . ( * psession . PinnipedSession )
require . True ( t , ok , "should have been able to cast session data to PinnipedSession" )
2022-02-15 21:52:33 +00:00
editRefreshSessionDataWithoutBreaking ( t , pinnipedSession , idpName , username )
2022-01-26 00:19:56 +00:00
// Then save the mutated Secret back to Kubernetes.
// There is no update function, so delete and create again at the same name.
require . NoError ( t , oauthStore . DeleteRefreshTokenSession ( ctx , signatureOfLatestRefreshToken ) )
require . NoError ( t , oauthStore . CreateRefreshTokenSession ( ctx , signatureOfLatestRefreshToken , storedRefreshSession ) )
}
2021-07-22 23:15:44 +00:00
// Use the refresh token to get new tokens
refreshSource := downstreamOAuth2Config . TokenSource ( oidcHTTPClientContext , & oauth2 . Token { RefreshToken : tokenResponse . RefreshToken } )
refreshedTokenResponse , err := refreshSource . Token ( )
require . NoError ( t , err )
// When refreshing, expect to get an "at_hash" claim, but no "nonce" claim.
expectRefreshedIDTokenClaims := [ ] string { "iss" , "exp" , "sub" , "aud" , "auth_time" , "iat" , "jti" , "rat" , "username" , "groups" , "at_hash" }
2022-02-15 21:52:33 +00:00
if wantDownstreamIDTokenGroupsAfterRefresh == nil {
wantDownstreamIDTokenGroupsAfterRefresh = wantDownstreamIDTokenGroups
}
2021-07-22 23:15:44 +00:00
verifyTokenResponse ( t ,
refreshedTokenResponse , discovery , downstreamOAuth2Config , "" ,
2022-02-15 21:52:33 +00:00
expectRefreshedIDTokenClaims , wantDownstreamIDTokenSubjectToMatch , wantDownstreamIDTokenUsernameToMatch ( username ) , wantDownstreamIDTokenGroupsAfterRefresh )
2021-07-22 23:15:44 +00:00
require . NotEqual ( t , tokenResponse . AccessToken , refreshedTokenResponse . AccessToken )
require . NotEqual ( t , tokenResponse . RefreshToken , refreshedTokenResponse . RefreshToken )
require . NotEqual ( t , tokenResponse . Extra ( "id_token" ) , refreshedTokenResponse . Extra ( "id_token" ) )
// token exchange on the refreshed token
doTokenExchange ( t , & downstreamOAuth2Config , refreshedTokenResponse , httpClient , discovery )
2021-10-13 22:12:19 +00:00
// Now that we have successfully performed a refresh, let's test what happens when an
// upstream refresh fails during the next downstream refresh.
if breakRefreshSessionData != nil {
latestRefreshToken := refreshedTokenResponse . RefreshToken
signatureOfLatestRefreshToken := getFositeDataSignature ( t , latestRefreshToken )
// First use the latest downstream refresh token to look up the corresponding session in the Supervisor's storage.
kubeClient := testlib . NewKubernetesClientset ( t )
supervisorSecretsClient := kubeClient . CoreV1 ( ) . Secrets ( env . SupervisorNamespace )
oauthStore := oidc . NewKubeStorage ( supervisorSecretsClient , oidc . DefaultOIDCTimeoutsConfiguration ( ) )
storedRefreshSession , err := oauthStore . GetRefreshTokenSession ( ctx , signatureOfLatestRefreshToken , nil )
require . NoError ( t , err )
// Next mutate the part of the session that is used during upstream refresh.
pinnipedSession , ok := storedRefreshSession . GetSession ( ) . ( * psession . PinnipedSession )
require . True ( t , ok , "should have been able to cast session data to PinnipedSession" )
2021-10-28 19:00:56 +00:00
breakRefreshSessionData ( t , pinnipedSession , idpName , username )
2021-10-13 22:12:19 +00:00
// Then save the mutated Secret back to Kubernetes.
// There is no update function, so delete and create again at the same name.
require . NoError ( t , oauthStore . DeleteRefreshTokenSession ( ctx , signatureOfLatestRefreshToken ) )
require . NoError ( t , oauthStore . CreateRefreshTokenSession ( ctx , signatureOfLatestRefreshToken , storedRefreshSession ) )
// Now try to perform a downstream refresh again, knowing that the corresponding upstream refresh should fail.
_ , err = downstreamOAuth2Config . TokenSource ( oidcHTTPClientContext , & oauth2 . Token { RefreshToken : latestRefreshToken } ) . Token ( )
// Should have got an error since the upstream refresh should have failed.
require . Error ( t , err )
require . Regexp ( t ,
regexp . QuoteMeta ( "oauth2: cannot fetch token: 401 Unauthorized\n" ) +
2021-11-03 22:17:50 +00:00
regexp . QuoteMeta ( ` Response: { "error":"error","error_description":"Error during upstream refresh. Upstream refresh failed ` ) +
"[^']+" ,
2021-10-13 22:12:19 +00:00
err . Error ( ) ,
)
}
2021-07-22 23:15:44 +00:00
} else {
errorDescription := callback . URL . Query ( ) . Get ( "error_description" )
errorType := callback . URL . Query ( ) . Get ( "error" )
require . Equal ( t , errorDescription , wantErrorDescription )
require . Equal ( t , errorType , wantErrorType )
}
2020-12-10 01:07:37 +00:00
}
2021-10-13 22:12:19 +00:00
// getFositeDataSignature returns the signature of the provided data. The provided data could be an auth code, access
// token, etc. It is assumed that the code is of the format "data.signature", which is how Fosite generates auth codes
// and access tokens.
func getFositeDataSignature ( t * testing . T , data string ) string {
split := strings . Split ( data , "." )
require . Len ( t , split , 2 )
return split [ 1 ]
}
2020-12-10 01:07:37 +00:00
func verifyTokenResponse (
t * testing . T ,
tokenResponse * oauth2 . Token ,
2020-12-10 18:14:54 +00:00
discovery * coreosoidc . Provider ,
2020-12-10 01:07:37 +00:00
downstreamOAuth2Config oauth2 . Config ,
nonceParam nonce . Nonce ,
expectedIDTokenClaims [ ] string ,
2021-05-17 18:10:26 +00:00
wantDownstreamIDTokenSubjectToMatch , wantDownstreamIDTokenUsernameToMatch string , wantDownstreamIDTokenGroups [ ] string ,
2020-12-10 01:07:37 +00:00
) {
2021-03-05 01:25:43 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , time . Minute )
2020-12-10 01:07:37 +00:00
defer cancel ( )
2020-12-05 01:07:04 +00:00
// Verify the ID Token.
rawIDToken , ok := tokenResponse . Extra ( "id_token" ) . ( string )
require . True ( t , ok , "expected to get an ID token but did not" )
2020-12-10 18:14:54 +00:00
var verifier = discovery . Verifier ( & coreosoidc . Config { ClientID : downstreamOAuth2Config . ClientID } )
2020-12-05 01:07:04 +00:00
idToken , err := verifier . Verify ( ctx , rawIDToken )
require . NoError ( t , err )
2021-04-07 19:56:09 +00:00
// Check the sub claim of the ID token.
require . Regexp ( t , wantDownstreamIDTokenSubjectToMatch , idToken . Subject )
// Check the nonce claim of the ID token.
2020-12-05 01:07:04 +00:00
require . NoError ( t , nonceParam . Validate ( idToken ) )
2021-04-07 19:56:09 +00:00
// Check the exp claim of the ID token.
2020-12-10 18:14:54 +00:00
expectedIDTokenLifetime := oidc . DefaultOIDCTimeoutsConfiguration ( ) . IDTokenLifespan
testutil . RequireTimeInDelta ( t , time . Now ( ) . UTC ( ) . Add ( expectedIDTokenLifetime ) , idToken . Expiry , time . Second * 30 )
2021-04-07 19:56:09 +00:00
// Check the full list of claim names of the ID token.
2020-12-05 01:07:04 +00:00
idTokenClaims := map [ string ] interface { } { }
err = idToken . Claims ( & idTokenClaims )
require . NoError ( t , err )
idTokenClaimNames := [ ] string { }
for k := range idTokenClaims {
idTokenClaimNames = append ( idTokenClaimNames , k )
}
2020-12-10 01:07:37 +00:00
require . ElementsMatch ( t , expectedIDTokenClaims , idTokenClaimNames )
2021-04-07 19:56:09 +00:00
// Check username claim of the ID token.
require . Regexp ( t , wantDownstreamIDTokenUsernameToMatch , idTokenClaims [ "username" ] . ( string ) )
2020-12-05 01:07:04 +00:00
2021-05-17 18:10:26 +00:00
// Check the groups claim.
require . ElementsMatch ( t , wantDownstreamIDTokenGroups , idTokenClaims [ "groups" ] )
2020-12-05 01:07:04 +00:00
// Some light verification of the other tokens that were returned.
require . NotEmpty ( t , tokenResponse . AccessToken )
require . Equal ( t , "bearer" , tokenResponse . TokenType )
require . NotZero ( t , tokenResponse . Expiry )
2020-12-10 18:14:54 +00:00
expectedAccessTokenLifetime := oidc . DefaultOIDCTimeoutsConfiguration ( ) . AccessTokenLifespan
testutil . RequireTimeInDelta ( t , time . Now ( ) . UTC ( ) . Add ( expectedAccessTokenLifetime ) , tokenResponse . Expiry , time . Second * 30 )
2020-12-05 01:07:04 +00:00
2020-12-10 01:07:37 +00:00
require . NotEmpty ( t , tokenResponse . RefreshToken )
2020-12-02 21:50:42 +00:00
}
2020-11-17 15:21:17 +00:00
2021-10-28 19:00:56 +00:00
func requestAuthorizationUsingBrowserAuthcodeFlow ( t * testing . T , downstreamAuthorizeURL , downstreamCallbackURL , _ , _ string , httpClient * http . Client ) {
2021-04-06 17:10:01 +00:00
t . Helper ( )
2021-06-22 15:23:19 +00:00
env := testlib . IntegrationEnv ( t )
2021-04-06 17:10:01 +00:00
2021-04-14 01:11:16 +00:00
ctx , cancelFunc := context . WithTimeout ( context . Background ( ) , time . Minute )
defer cancelFunc ( )
// Make the authorize request once "manually" so we can check its response security headers.
authorizeRequest , err := http . NewRequestWithContext ( ctx , http . MethodGet , downstreamAuthorizeURL , nil )
require . NoError ( t , err )
authorizeResp , err := httpClient . Do ( authorizeRequest )
require . NoError ( t , err )
require . NoError ( t , authorizeResp . Body . Close ( ) )
expectSecurityHeaders ( t , authorizeResp , false )
2021-04-06 17:10:01 +00:00
// Open the web browser and navigate to the downstream authorize URL.
page := browsertest . Open ( t )
2021-06-22 15:23:19 +00:00
t . Logf ( "opening browser to downstream authorize URL %s" , testlib . MaskTokens ( downstreamAuthorizeURL ) )
2021-04-06 17:10:01 +00:00
require . NoError ( t , page . Navigate ( downstreamAuthorizeURL ) )
// Expect to be redirected to the upstream provider and log in.
2021-04-07 19:56:09 +00:00
browsertest . LoginToUpstream ( t , page , env . SupervisorUpstreamOIDC )
2021-04-06 17:10:01 +00:00
// Wait for the login to happen and us be redirected back to a localhost callback.
t . Logf ( "waiting for redirect to callback" )
callbackURLPattern := regexp . MustCompile ( ` \A ` + regexp . QuoteMeta ( downstreamCallbackURL ) + ` \?.+\z ` )
browsertest . WaitForURL ( t , page , callbackURLPattern )
}
2021-08-24 19:19:29 +00:00
func requestAuthorizationUsingCLIPasswordFlow ( t * testing . T , downstreamAuthorizeURL , upstreamUsername , upstreamPassword string , httpClient * http . Client , wantErr bool ) {
2021-04-06 17:10:01 +00:00
t . Helper ( )
2021-04-07 19:56:09 +00:00
2021-07-15 18:32:15 +00:00
ctx , cancelFunc := context . WithTimeout ( context . Background ( ) , time . Minute )
2021-04-07 19:56:09 +00:00
defer cancelFunc ( )
authRequest , err := http . NewRequestWithContext ( ctx , http . MethodGet , downstreamAuthorizeURL , nil )
require . NoError ( t , err )
// Set the custom username/password headers for the LDAP authorize request.
2021-05-12 20:06:08 +00:00
authRequest . Header . Set ( "Pinniped-Username" , upstreamUsername )
authRequest . Header . Set ( "Pinniped-Password" , upstreamPassword )
2021-04-07 19:56:09 +00:00
2021-06-02 18:36:48 +00:00
// At this point in the test, we've already waited for the LDAPIdentityProvider to be loaded and marked healthy by
// at least one Supervisor pod, but we can't be sure that _all_ of them have loaded the provider, so we may need
// to retry this request multiple times until we get the expected 302 status response.
var authResponse * http . Response
var responseBody [ ] byte
2021-06-22 15:23:19 +00:00
testlib . RequireEventuallyWithoutError ( t , func ( ) ( bool , error ) {
2021-06-02 18:36:48 +00:00
authResponse , err = httpClient . Do ( authRequest )
if err != nil {
t . Logf ( "got authorization response with error %v" , err )
return false , nil
}
defer func ( ) { _ = authResponse . Body . Close ( ) } ( )
responseBody , err = ioutil . ReadAll ( authResponse . Body )
if err != nil {
return false , nil
}
t . Logf ( "got authorization response with code %d (%d byte body)" , authResponse . StatusCode , len ( responseBody ) )
if authResponse . StatusCode != http . StatusFound {
return false , nil
}
return true , nil
2021-08-18 23:24:05 +00:00
} , 60 * time . Second , 200 * time . Millisecond )
2021-06-02 18:36:48 +00:00
2021-04-14 01:11:16 +00:00
expectSecurityHeaders ( t , authResponse , true )
// A successful authorize request results in a redirect to our localhost callback listener with an authcode param.
require . Equalf ( t , http . StatusFound , authResponse . StatusCode , "response body was: %s" , string ( responseBody ) )
redirectLocation := authResponse . Header . Get ( "Location" )
require . Contains ( t , redirectLocation , "127.0.0.1" )
require . Contains ( t , redirectLocation , "/callback" )
2021-07-22 23:15:44 +00:00
if wantErr {
require . Contains ( t , redirectLocation , "error_description" )
} else {
require . Contains ( t , redirectLocation , "code=" )
}
2021-04-07 19:56:09 +00:00
2021-04-14 01:11:16 +00:00
// Follow the redirect.
callbackRequest , err := http . NewRequestWithContext ( ctx , http . MethodGet , redirectLocation , nil )
require . NoError ( t , err )
2021-04-07 19:56:09 +00:00
2021-04-14 01:11:16 +00:00
// Our localhost callback listener should have returned 200 OK.
callbackResponse , err := httpClient . Do ( callbackRequest )
require . NoError ( t , err )
defer callbackResponse . Body . Close ( )
require . Equal ( t , http . StatusOK , callbackResponse . StatusCode )
2021-04-06 17:10:01 +00:00
}
2020-12-02 21:50:42 +00:00
func startLocalCallbackServer ( t * testing . T ) * localCallbackServer {
// Handle the callback by sending the *http.Request object back through a channel.
callbacks := make ( chan * http . Request , 1 )
server := httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
callbacks <- r
} ) )
2020-12-02 22:11:22 +00:00
server . URL += "/callback"
2020-12-02 21:50:42 +00:00
t . Cleanup ( server . Close )
t . Cleanup ( func ( ) { close ( callbacks ) } )
return & localCallbackServer { Server : server , t : t , callbacks : callbacks }
}
2020-11-17 15:21:17 +00:00
2020-12-02 21:50:42 +00:00
type localCallbackServer struct {
* httptest . Server
t * testing . T
callbacks <- chan * http . Request
}
2020-11-17 15:21:17 +00:00
2020-12-02 21:50:42 +00:00
func ( s * localCallbackServer ) waitForCallback ( timeout time . Duration ) * http . Request {
select {
case callback := <- s . callbacks :
return callback
case <- time . After ( timeout ) :
require . Fail ( s . t , "timed out waiting for callback request" )
return nil
}
2020-11-17 15:21:17 +00:00
}
2020-12-09 16:23:10 +00:00
2020-12-10 18:14:54 +00:00
func doTokenExchange ( t * testing . T , config * oauth2 . Config , tokenResponse * oauth2 . Token , httpClient * http . Client , provider * coreosoidc . Provider ) {
2021-03-05 01:25:43 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , time . Minute )
2020-12-09 16:23:10 +00:00
defer cancel ( )
// Form the HTTP POST request with the parameters specified by RFC8693.
reqBody := strings . NewReader ( url . Values {
"grant_type" : [ ] string { "urn:ietf:params:oauth:grant-type:token-exchange" } ,
"audience" : [ ] string { "cluster-1234" } ,
"client_id" : [ ] string { config . ClientID } ,
"subject_token" : [ ] string { tokenResponse . AccessToken } ,
"subject_token_type" : [ ] string { "urn:ietf:params:oauth:token-type:access_token" } ,
"requested_token_type" : [ ] string { "urn:ietf:params:oauth:token-type:jwt" } ,
} . Encode ( ) )
req , err := http . NewRequestWithContext ( ctx , http . MethodPost , config . Endpoint . TokenURL , reqBody )
require . NoError ( t , err )
req . Header . Set ( "content-type" , "application/x-www-form-urlencoded" )
resp , err := httpClient . Do ( req )
require . NoError ( t , err )
2020-12-17 01:59:39 +00:00
require . Equal ( t , resp . StatusCode , http . StatusOK )
2020-12-09 16:23:10 +00:00
defer func ( ) { _ = resp . Body . Close ( ) } ( )
var respBody struct {
AccessToken string ` json:"access_token" `
IssuedTokenType string ` json:"issued_token_type" `
TokenType string ` json:"token_type" `
}
require . NoError ( t , json . NewDecoder ( resp . Body ) . Decode ( & respBody ) )
2020-12-10 18:14:54 +00:00
var clusterVerifier = provider . Verifier ( & coreosoidc . Config { ClientID : "cluster-1234" } )
2020-12-09 16:23:10 +00:00
exchangedToken , err := clusterVerifier . Verify ( ctx , respBody . AccessToken )
require . NoError ( t , err )
var claims map [ string ] interface { }
require . NoError ( t , exchangedToken . Claims ( & claims ) )
indentedClaims , err := json . MarshalIndent ( claims , " " , " " )
require . NoError ( t , err )
t . Logf ( "exchanged token claims:\n%s" , string ( indentedClaims ) )
}
2020-12-16 03:42:11 +00:00
2021-04-14 01:11:16 +00:00
func expectSecurityHeaders ( t * testing . T , response * http . Response , expectFositeToOverrideSome bool ) {
2020-12-16 03:42:11 +00:00
h := response . Header
assert . Equal ( t , "default-src 'none'; frame-ancestors 'none'" , h . Get ( "Content-Security-Policy" ) )
assert . Equal ( t , "DENY" , h . Get ( "X-Frame-Options" ) )
assert . Equal ( t , "1; mode=block" , h . Get ( "X-XSS-Protection" ) )
assert . Equal ( t , "nosniff" , h . Get ( "X-Content-Type-Options" ) )
assert . Equal ( t , "no-referrer" , h . Get ( "Referrer-Policy" ) )
assert . Equal ( t , "off" , h . Get ( "X-DNS-Prefetch-Control" ) )
2021-04-14 01:11:16 +00:00
if expectFositeToOverrideSome {
assert . Equal ( t , "no-store" , h . Get ( "Cache-Control" ) )
} else {
assert . Equal ( t , "no-cache,no-store,max-age=0,must-revalidate" , h . Get ( "Cache-Control" ) )
}
2020-12-16 03:42:11 +00:00
assert . Equal ( t , "no-cache" , h . Get ( "Pragma" ) )
assert . Equal ( t , "0" , h . Get ( "Expires" ) )
}
2021-10-28 19:00:56 +00:00
// create a fresh test user in AD to use for this test.
func createFreshADTestUser ( t * testing . T , env * testlib . TestEnv ) ( string , string ) {
t . Helper ( )
// dial tls
conn := dialTLS ( t , env )
// bind
err := conn . Bind ( env . SupervisorUpstreamActiveDirectory . BindUsername , env . SupervisorUpstreamActiveDirectory . BindPassword )
require . NoError ( t , err )
testUserName := "user-" + createRandomHexString ( t , 7 ) // sAMAccountNames are limited to 20 characters, so this is as long as we can make it.
// create
userDN := fmt . Sprintf ( "CN=%s,OU=test-users,%s" , testUserName , env . SupervisorUpstreamActiveDirectory . UserSearchBase )
a := ldap . NewAddRequest ( userDN , [ ] ldap . Control { } )
a . Attribute ( "objectClass" , [ ] string { "top" , "person" , "organizationalPerson" , "user" } )
a . Attribute ( "userPrincipalName" , [ ] string { fmt . Sprintf ( "%s@%s" , testUserName , env . SupervisorUpstreamActiveDirectory . Domain ) } )
a . Attribute ( "sAMAccountName" , [ ] string { testUserName } )
err = conn . Add ( a )
require . NoError ( t , err )
// modify password and enable account
testUserPassword := createRandomASCIIString ( t , 20 )
enc := unicode . UTF16 ( unicode . LittleEndian , unicode . IgnoreBOM ) . NewEncoder ( )
encodedTestUserPassword , err := enc . String ( "\"" + testUserPassword + "\"" )
require . NoError ( t , err )
m := ldap . NewModifyRequest ( userDN , [ ] ldap . Control { } )
m . Replace ( "unicodePwd" , [ ] string { encodedTestUserPassword } )
m . Replace ( "userAccountControl" , [ ] string { "512" } )
err = conn . Modify ( m )
require . NoError ( t , err )
2021-11-17 00:31:32 +00:00
time . Sleep ( 20 * time . Second ) // intrasite domain controller replication can take up to 15 seconds, so wait to ensure the change has propogated.
2021-10-28 19:00:56 +00:00
return testUserName , testUserPassword
}
2021-11-17 00:31:32 +00:00
// deactivate the test user.
2021-11-05 18:53:07 +00:00
func deactivateADTestUser ( t * testing . T , env * testlib . TestEnv , testUserName string ) {
conn := dialTLS ( t , env )
// bind
err := conn . Bind ( env . SupervisorUpstreamActiveDirectory . BindUsername , env . SupervisorUpstreamActiveDirectory . BindPassword )
require . NoError ( t , err )
userDN := fmt . Sprintf ( "CN=%s,OU=test-users,%s" , testUserName , env . SupervisorUpstreamActiveDirectory . UserSearchBase )
m := ldap . NewModifyRequest ( userDN , [ ] ldap . Control { } )
m . Replace ( "userAccountControl" , [ ] string { "514" } ) // normal user, account disabled
err = conn . Modify ( m )
require . NoError ( t , err )
2021-11-17 00:31:32 +00:00
time . Sleep ( 20 * time . Second ) // intrasite domain controller replication can take up to 15 seconds, so wait to ensure the change has propogated.
}
// lock the test user's account by entering the wrong password a bunch of times.
func lockADTestUser ( t * testing . T , env * testlib . TestEnv , testUserName string ) {
userDN := fmt . Sprintf ( "CN=%s,OU=test-users,%s" , testUserName , env . SupervisorUpstreamActiveDirectory . UserSearchBase )
conn := dialTLS ( t , env )
2021-12-08 00:57:39 +00:00
// our password policy allows 20 wrong attempts before locking the account, so do 21.
// these wrong password attempts could go to different domain controllers, but account
// lockout changes are urgently replicated, meaning that the domain controllers will be
// synced asap rather than in the usual 15 second interval.
// See https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-2000-server/cc961787(v=technet.10)#urgent-replication-of-account-lockout-changes
for i := 0 ; i <= 21 ; i ++ {
2021-11-17 00:31:32 +00:00
err := conn . Bind ( userDN , "not-the-right-password-" + fmt . Sprint ( i ) )
require . Error ( t , err ) // this should be an error
}
err := conn . Bind ( env . SupervisorUpstreamActiveDirectory . BindUsername , env . SupervisorUpstreamActiveDirectory . BindPassword )
require . NoError ( t , err )
time . Sleep ( 20 * time . Second ) // intrasite domain controller replication can take up to 15 seconds, so wait to ensure the change has propogated.
2021-11-05 18:53:07 +00:00
}
2021-10-28 19:00:56 +00:00
// change the user's password to a new one.
func changeADTestUserPassword ( t * testing . T , env * testlib . TestEnv , testUserName string ) {
conn := dialTLS ( t , env )
// bind
err := conn . Bind ( env . SupervisorUpstreamActiveDirectory . BindUsername , env . SupervisorUpstreamActiveDirectory . BindPassword )
require . NoError ( t , err )
newTestUserPassword := createRandomASCIIString ( t , 20 )
enc := unicode . UTF16 ( unicode . LittleEndian , unicode . IgnoreBOM ) . NewEncoder ( )
2021-12-07 00:24:31 +00:00
encodedTestUserPassword , err := enc . String ( ` " ` + newTestUserPassword + ` " ` )
2021-10-28 19:00:56 +00:00
require . NoError ( t , err )
userDN := fmt . Sprintf ( "CN=%s,OU=test-users,%s" , testUserName , env . SupervisorUpstreamActiveDirectory . UserSearchBase )
m := ldap . NewModifyRequest ( userDN , [ ] ldap . Control { } )
m . Replace ( "unicodePwd" , [ ] string { encodedTestUserPassword } )
err = conn . Modify ( m )
require . NoError ( t , err )
2021-11-17 00:31:32 +00:00
time . Sleep ( 20 * time . Second ) // intrasite domain controller replication can take up to 15 seconds, so wait to ensure the change has propogated.
2021-10-28 19:00:56 +00:00
// don't bother to return the new password... we won't be using it, just checking that it's changed.
}
// delete the test user created for this test.
func deleteTestADUser ( t * testing . T , env * testlib . TestEnv , testUserName string ) {
t . Helper ( )
conn := dialTLS ( t , env )
// bind
err := conn . Bind ( env . SupervisorUpstreamActiveDirectory . BindUsername , env . SupervisorUpstreamActiveDirectory . BindPassword )
require . NoError ( t , err )
userDN := fmt . Sprintf ( "CN=%s,OU=test-users,%s" , testUserName , env . SupervisorUpstreamActiveDirectory . UserSearchBase )
d := ldap . NewDelRequest ( userDN , [ ] ldap . Control { } )
err = conn . Del ( d )
require . NoError ( t , err )
}
func dialTLS ( t * testing . T , env * testlib . TestEnv ) * ldap . Conn {
t . Helper ( )
// dial tls
rootCAs := x509 . NewCertPool ( )
success := rootCAs . AppendCertsFromPEM ( [ ] byte ( env . SupervisorUpstreamActiveDirectory . CABundle ) )
require . True ( t , success )
2021-12-07 00:24:31 +00:00
tlsConfig := ptls . DefaultLDAP ( rootCAs )
2021-10-28 19:00:56 +00:00
dialer := & tls . Dialer { NetDialer : & net . Dialer { Timeout : time . Minute } , Config : tlsConfig }
c , err := dialer . DialContext ( context . Background ( ) , "tcp" , env . SupervisorUpstreamActiveDirectory . Host )
require . NoError ( t , err )
conn := ldap . NewConn ( c , true )
conn . Start ( )
return conn
}
func createRandomHexString ( t * testing . T , length int ) string {
t . Helper ( )
bytes := make ( [ ] byte , length )
_ , err := rand . Read ( bytes )
require . NoError ( t , err )
randomString := hex . EncodeToString ( bytes )
return randomString
}
func createRandomASCIIString ( t * testing . T , length int ) string {
result := ""
for {
if len ( result ) >= length {
return result
}
num , err := rand . Int ( rand . Reader , big . NewInt ( int64 ( 127 ) ) )
require . NoError ( t , err )
n := num . Int64 ( )
// Make sure that the number/byte/letter is inside
// the range of printable ASCII characters (excluding space and DEL)
if n > 32 && n < 127 {
result += string ( rune ( n ) )
}
}
}