Wire in new env vars for user info testing

Signed-off-by: Monis Khan <mok@vmware.com>
This commit is contained in:
Monis Khan 2021-01-11 14:58:07 -05:00 committed by Andrew Keesler
parent 3f08f2e11e
commit 3c3da9e75d
No known key found for this signature in database
GPG Key ID: 27CE0444346F9413
5 changed files with 84 additions and 22 deletions

View File

@ -305,11 +305,15 @@ export PINNIPED_TEST_CLI_OIDC_USERNAME=pinny@example.com
export PINNIPED_TEST_CLI_OIDC_PASSWORD=password export PINNIPED_TEST_CLI_OIDC_PASSWORD=password
export PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_ISSUER=https://dex.dex.svc.cluster.local/dex export PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_ISSUER=https://dex.dex.svc.cluster.local/dex
export PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_ISSUER_CA_BUNDLE="${test_ca_bundle_pem}" export PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_ISSUER_CA_BUNDLE="${test_ca_bundle_pem}"
export PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_ADDITIONAL_SCOPES=email
export PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_USERNAME_CLAIM=email
export PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_GROUPS_CLAIM=groups
export PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_CLIENT_ID=pinniped-supervisor export PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_CLIENT_ID=pinniped-supervisor
export PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_CLIENT_SECRET=pinniped-supervisor-secret export PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_CLIENT_SECRET=pinniped-supervisor-secret
export PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_CALLBACK_URL=https://pinniped-supervisor-clusterip.supervisor.svc.cluster.local/some/path/callback export PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_CALLBACK_URL=https://pinniped-supervisor-clusterip.supervisor.svc.cluster.local/some/path/callback
export PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_USERNAME=pinny@example.com export PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_USERNAME=pinny@example.com
export PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_PASSWORD=password export PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_PASSWORD=password
export PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_EXPECTED_GROUPS= # Dex's local user store does not let us configure groups.
read -r -d '' PINNIPED_TEST_CLUSTER_CAPABILITY_YAML << PINNIPED_TEST_CLUSTER_CAPABILITY_YAML_EOF || true read -r -d '' PINNIPED_TEST_CLUSTER_CAPABILITY_YAML << PINNIPED_TEST_CLUSTER_CAPABILITY_YAML_EOF || true
${pinniped_cluster_capability_file_content} ${pinniped_cluster_capability_file_content}

View File

@ -269,7 +269,7 @@ func getGroupsFromUpstreamIDToken(
"configuredGroupsClaim", upstreamIDPConfig.GetGroupsClaim(), "configuredGroupsClaim", upstreamIDPConfig.GetGroupsClaim(),
"groupsClaim", groupsClaim, "groupsClaim", groupsClaim,
) )
return nil, httperr.New(http.StatusUnprocessableEntity, "no groups claim in upstream ID token") return nil, nil // the upstream IDP may have omitted the claim if the user has no groups
} }
groupsAsArray, okAsArray := groupsAsInterface.([]string) groupsAsArray, okAsArray := groupsAsInterface.([]string)

View File

@ -430,8 +430,16 @@ func TestCallbackEndpoint(t *testing.T) {
method: http.MethodGet, method: http.MethodGet,
path: newRequestPath().WithState(happyState).String(), path: newRequestPath().WithState(happyState).String(),
csrfCookie: happyCSRFCookie, csrfCookie: happyCSRFCookie,
wantStatus: http.StatusUnprocessableEntity, wantStatus: http.StatusFound,
wantBody: "Unprocessable Entity: no groups claim in upstream ID token\n", wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp,
wantBody: "",
wantDownstreamIDTokenSubject: upstreamIssuer + "?sub=" + upstreamSubject,
wantDownstreamIDTokenUsername: upstreamUsername,
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
wantDownstreamGrantedScopes: happyDownstreamScopesGranted,
wantDownstreamNonce: downstreamNonce,
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
wantExchangeAndValidateTokensCall: happyExchangeAndValidateTokensArgs, wantExchangeAndValidateTokensCall: happyExchangeAndValidateTokensArgs,
}, },
{ {

View File

@ -16,20 +16,24 @@ import (
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"regexp" "regexp"
"sort"
"strings" "strings"
"testing" "testing"
"time" "time"
v1 "k8s.io/api/core/v1" coreosoidc "github.com/coreos/go-oidc"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1" rbacv1 "k8s.io/api/rbac/v1"
authv1alpha "go.pinniped.dev/generated/1.20/apis/concierge/authentication/v1alpha1" authv1alpha "go.pinniped.dev/generated/1.20/apis/concierge/authentication/v1alpha1"
configv1alpha1 "go.pinniped.dev/generated/1.20/apis/supervisor/config/v1alpha1" configv1alpha1 "go.pinniped.dev/generated/1.20/apis/supervisor/config/v1alpha1"
idpv1alpha1 "go.pinniped.dev/generated/1.20/apis/supervisor/idp/v1alpha1" idpv1alpha1 "go.pinniped.dev/generated/1.20/apis/supervisor/idp/v1alpha1"
"go.pinniped.dev/internal/certauthority" "go.pinniped.dev/internal/certauthority"
"go.pinniped.dev/internal/oidc"
"go.pinniped.dev/internal/testutil" "go.pinniped.dev/internal/testutil"
"go.pinniped.dev/pkg/oidcclient"
"go.pinniped.dev/pkg/oidcclient/filesession"
"go.pinniped.dev/test/library" "go.pinniped.dev/test/library"
"go.pinniped.dev/test/library/browsertest" "go.pinniped.dev/test/library/browsertest"
) )
@ -86,7 +90,7 @@ func TestE2EFullIntegration(t *testing.T) {
certSecret := library.CreateTestSecret(t, certSecret := library.CreateTestSecret(t,
env.SupervisorNamespace, env.SupervisorNamespace,
"oidc-provider-tls", "oidc-provider-tls",
v1.SecretTypeTLS, corev1.SecretTypeTLS,
map[string]string{"tls.crt": string(certPEM), "tls.key": string(keyPEM)}, map[string]string{"tls.crt": string(certPEM), "tls.key": string(keyPEM)},
) )
@ -104,10 +108,11 @@ func TestE2EFullIntegration(t *testing.T) {
CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte(env.SupervisorTestUpstream.CABundle)), CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte(env.SupervisorTestUpstream.CABundle)),
}, },
AuthorizationConfig: idpv1alpha1.OIDCAuthorizationConfig{ AuthorizationConfig: idpv1alpha1.OIDCAuthorizationConfig{
AdditionalScopes: []string{"email"}, AdditionalScopes: env.SupervisorTestUpstream.AdditionalScopes,
}, },
Claims: idpv1alpha1.OIDCClaims{ Claims: idpv1alpha1.OIDCClaims{
Username: "email", Username: env.SupervisorTestUpstream.UsernameClaim,
Groups: env.SupervisorTestUpstream.GroupsClaim,
}, },
Client: idpv1alpha1.OIDCClient{ Client: idpv1alpha1.OIDCClient{
SecretName: library.CreateClientCredsSecret(t, env.SupervisorTestUpstream.ClientID, env.SupervisorTestUpstream.ClientSecret).Name, SecretName: library.CreateClientCredsSecret(t, env.SupervisorTestUpstream.ClientID, env.SupervisorTestUpstream.ClientSecret).Name,
@ -270,4 +275,31 @@ func TestE2EFullIntegration(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Greaterf(t, len(bytes.Split(kubectlOutput2, []byte("\n"))), 2, "expected some namespaces to be returned again") require.Greaterf(t, len(bytes.Split(kubectlOutput2, []byte("\n"))), 2, "expected some namespaces to be returned again")
t.Logf("second kubectl command took %s", time.Since(start).String()) t.Logf("second kubectl command took %s", time.Since(start).String())
// probe our cache for the current ID token as a proxy for a whoami API
cache := filesession.New(sessionCachePath, filesession.WithErrorReporter(func(err error) {
require.NoError(t, err)
}))
downstreamScopes := []string{coreosoidc.ScopeOfflineAccess, coreosoidc.ScopeOpenID, "pinniped:request-audience"}
sort.Strings(downstreamScopes)
token := cache.GetToken(oidcclient.SessionCacheKey{
Issuer: downstream.Spec.Issuer,
ClientID: "pinniped-cli",
Scopes: downstreamScopes,
RedirectURI: "http://localhost:0/callback",
})
require.NotNil(t, token)
idTokenClaims := token.IDToken.Claims
username := idTokenClaims[oidc.DownstreamUsernameClaim].(string)
groups, _ := idTokenClaims[oidc.DownstreamGroupsClaim].([]string)
require.Equal(t, env.SupervisorTestUpstream.Username, username)
if len(env.SupervisorTestUpstream.ExpectedGroups) == 0 {
// We only put a groups claim in our downstream ID token if we got groups from the upstream.
require.Nil(t, groups)
} else {
require.Equal(t, env.SupervisorTestUpstream.ExpectedGroups, groups)
}
} }

View File

@ -50,13 +50,17 @@ type TestEnv struct {
} }
type TestOIDCUpstream struct { type TestOIDCUpstream struct {
Issuer string `json:"issuer"` Issuer string `json:"issuer"`
CABundle string `json:"caBundle" ` CABundle string `json:"caBundle"`
ClientID string `json:"clientID"` AdditionalScopes []string `json:"additionalScopes"`
ClientSecret string `json:"clientSecret"` UsernameClaim string `json:"usernameClaim"`
CallbackURL string `json:"callback"` GroupsClaim string `json:"groupsClaim"`
Username string `json:"username"` ClientID string `json:"clientID"`
Password string `json:"password"` ClientSecret string `json:"clientSecret"`
CallbackURL string `json:"callback"`
Username string `json:"username"`
Password string `json:"password"`
ExpectedGroups []string `json:"expectedGroups"`
} }
// ProxyEnv returns a set of environment variable strings (e.g., to combine with os.Environ()) which set up the configured test HTTP proxy. // ProxyEnv returns a set of environment variable strings (e.g., to combine with os.Environ()) which set up the configured test HTTP proxy.
@ -102,6 +106,16 @@ func needEnv(t *testing.T, key string) string {
return value return value
} }
func filterEmpty(ss []string) []string {
filtered := []string{}
for _, s := range ss {
if len(s) != 0 {
filtered = append(filtered, s)
}
}
return filtered
}
func loadEnvVars(t *testing.T, result *TestEnv) { func loadEnvVars(t *testing.T, result *TestEnv) {
t.Helper() t.Helper()
@ -151,13 +165,17 @@ func loadEnvVars(t *testing.T, result *TestEnv) {
} }
result.SupervisorTestUpstream = TestOIDCUpstream{ result.SupervisorTestUpstream = TestOIDCUpstream{
Issuer: needEnv(t, "PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_ISSUER"), Issuer: needEnv(t, "PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_ISSUER"),
CABundle: os.Getenv("PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_ISSUER_CA_BUNDLE"), CABundle: os.Getenv("PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_ISSUER_CA_BUNDLE"),
ClientID: needEnv(t, "PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_CLIENT_ID"), AdditionalScopes: strings.Fields(os.Getenv("PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_ADDITIONAL_SCOPES")),
ClientSecret: needEnv(t, "PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_CLIENT_SECRET"), UsernameClaim: os.Getenv("PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_USERNAME_CLAIM"),
CallbackURL: needEnv(t, "PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_CALLBACK_URL"), GroupsClaim: os.Getenv("PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_GROUPS_CLAIM"),
Username: needEnv(t, "PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_USERNAME"), ClientID: needEnv(t, "PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_CLIENT_ID"),
Password: needEnv(t, "PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_PASSWORD"), ClientSecret: needEnv(t, "PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_CLIENT_SECRET"),
CallbackURL: needEnv(t, "PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_CALLBACK_URL"),
Username: needEnv(t, "PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_USERNAME"),
Password: needEnv(t, "PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_PASSWORD"),
ExpectedGroups: filterEmpty(strings.Split(strings.ReplaceAll(os.Getenv("PINNIPED_TEST_SUPERVISOR_UPSTREAM_OIDC_EXPECTED_GROUPS"), " ", ""), ",")),
} }
} }