Merge branch 'main' into initial_ldap
This commit is contained in:
commit
514ee5b883
@ -9,8 +9,10 @@ import (
|
|||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
"encoding/base32"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -564,7 +566,7 @@ func TestTokenEndpointWhenAuthcodeIsUsedTwice(t *testing.T) {
|
|||||||
require.JSONEq(t, fositeReusedAuthCodeErrorBody, reusedAuthcodeResponse.Body.String())
|
require.JSONEq(t, fositeReusedAuthCodeErrorBody, reusedAuthcodeResponse.Body.String())
|
||||||
|
|
||||||
// This was previously invalidated by the first request, so it remains invalidated
|
// This was previously invalidated by the first request, so it remains invalidated
|
||||||
requireInvalidAuthCodeStorage(t, authCode, oauthStore)
|
requireInvalidAuthCodeStorage(t, authCode, oauthStore, secrets)
|
||||||
// Has now invalidated the access token that was previously handed out by the first request
|
// Has now invalidated the access token that was previously handed out by the first request
|
||||||
requireInvalidAccessTokenStorage(t, parsedResponseBody, oauthStore)
|
requireInvalidAccessTokenStorage(t, parsedResponseBody, oauthStore)
|
||||||
// This was previously invalidated by the first request, so it remains invalidated
|
// This was previously invalidated by the first request, so it remains invalidated
|
||||||
@ -1189,8 +1191,8 @@ func requireTokenEndpointBehavior(
|
|||||||
wantIDToken := contains(test.wantSuccessBodyFields, "id_token")
|
wantIDToken := contains(test.wantSuccessBodyFields, "id_token")
|
||||||
wantRefreshToken := contains(test.wantSuccessBodyFields, "refresh_token")
|
wantRefreshToken := contains(test.wantSuccessBodyFields, "refresh_token")
|
||||||
|
|
||||||
requireInvalidAuthCodeStorage(t, authCode, oauthStore)
|
requireInvalidAuthCodeStorage(t, authCode, oauthStore, secrets)
|
||||||
requireValidAccessTokenStorage(t, parsedResponseBody, oauthStore, test.wantRequestedScopes, test.wantGrantedScopes)
|
requireValidAccessTokenStorage(t, parsedResponseBody, oauthStore, test.wantRequestedScopes, test.wantGrantedScopes, secrets)
|
||||||
requireInvalidPKCEStorage(t, authCode, oauthStore)
|
requireInvalidPKCEStorage(t, authCode, oauthStore)
|
||||||
requireValidOIDCStorage(t, parsedResponseBody, authCode, oauthStore, test.wantRequestedScopes, test.wantGrantedScopes)
|
requireValidOIDCStorage(t, parsedResponseBody, authCode, oauthStore, test.wantRequestedScopes, test.wantGrantedScopes)
|
||||||
|
|
||||||
@ -1204,7 +1206,7 @@ func requireTokenEndpointBehavior(
|
|||||||
requireValidIDToken(t, parsedResponseBody, jwtSigningKey, wantAtHashClaimInIDToken, wantNonceValueInIDToken, parsedResponseBody["access_token"].(string))
|
requireValidIDToken(t, parsedResponseBody, jwtSigningKey, wantAtHashClaimInIDToken, wantNonceValueInIDToken, parsedResponseBody["access_token"].(string))
|
||||||
}
|
}
|
||||||
if wantRefreshToken {
|
if wantRefreshToken {
|
||||||
requireValidRefreshTokenStorage(t, parsedResponseBody, oauthStore, test.wantRequestedScopes, test.wantGrantedScopes)
|
requireValidRefreshTokenStorage(t, parsedResponseBody, oauthStore, test.wantRequestedScopes, test.wantGrantedScopes, secrets)
|
||||||
}
|
}
|
||||||
|
|
||||||
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{crud.SecretLabelKey: authorizationcode.TypeLabelValue}, 1)
|
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{crud.SecretLabelKey: authorizationcode.TypeLabelValue}, 1)
|
||||||
@ -1407,12 +1409,15 @@ func requireInvalidAuthCodeStorage(
|
|||||||
t *testing.T,
|
t *testing.T,
|
||||||
code string,
|
code string,
|
||||||
storage oauth2.CoreStorage,
|
storage oauth2.CoreStorage,
|
||||||
|
secrets v1.SecretInterface,
|
||||||
) {
|
) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
// Make sure we have invalidated this auth code.
|
// Make sure we have invalidated this auth code.
|
||||||
_, err := storage.GetAuthorizeCodeSession(context.Background(), getFositeDataSignature(t, code), nil)
|
_, err := storage.GetAuthorizeCodeSession(context.Background(), getFositeDataSignature(t, code), nil)
|
||||||
require.True(t, errors.Is(err, fosite.ErrInvalidatedAuthorizeCode))
|
require.True(t, errors.Is(err, fosite.ErrInvalidatedAuthorizeCode))
|
||||||
|
// make sure that its still around in storage so if someone tries to use it again we invalidate everything
|
||||||
|
requireGarbageCollectTimeInDelta(t, code, "authcode", secrets, time.Now().Add(9*time.Hour).Add(10*time.Minute), 30*time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
func requireValidRefreshTokenStorage(
|
func requireValidRefreshTokenStorage(
|
||||||
@ -1421,6 +1426,7 @@ func requireValidRefreshTokenStorage(
|
|||||||
storage oauth2.CoreStorage,
|
storage oauth2.CoreStorage,
|
||||||
wantRequestedScopes []string,
|
wantRequestedScopes []string,
|
||||||
wantGrantedScopes []string,
|
wantGrantedScopes []string,
|
||||||
|
secrets v1.SecretInterface,
|
||||||
) {
|
) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
@ -1442,6 +1448,8 @@ func requireValidRefreshTokenStorage(
|
|||||||
wantGrantedScopes,
|
wantGrantedScopes,
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
requireGarbageCollectTimeInDelta(t, refreshTokenString, "refresh-token", secrets, time.Now().Add(9*time.Hour).Add(2*time.Minute), 1*time.Minute)
|
||||||
}
|
}
|
||||||
|
|
||||||
func requireValidAccessTokenStorage(
|
func requireValidAccessTokenStorage(
|
||||||
@ -1450,6 +1458,7 @@ func requireValidAccessTokenStorage(
|
|||||||
storage oauth2.CoreStorage,
|
storage oauth2.CoreStorage,
|
||||||
wantRequestedScopes []string,
|
wantRequestedScopes []string,
|
||||||
wantGrantedScopes []string,
|
wantGrantedScopes []string,
|
||||||
|
secrets v1.SecretInterface,
|
||||||
) {
|
) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
@ -1490,6 +1499,8 @@ func requireValidAccessTokenStorage(
|
|||||||
wantGrantedScopes,
|
wantGrantedScopes,
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
requireGarbageCollectTimeInDelta(t, accessTokenString, "access-token", secrets, time.Now().Add(9*time.Hour).Add(2*time.Minute), 1*time.Minute)
|
||||||
}
|
}
|
||||||
|
|
||||||
func requireInvalidAccessTokenStorage(
|
func requireInvalidAccessTokenStorage(
|
||||||
@ -1652,6 +1663,24 @@ func requireValidStoredRequest(
|
|||||||
require.Empty(t, session.Subject)
|
require.Empty(t, session.Subject)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func requireGarbageCollectTimeInDelta(t *testing.T, tokenString string, typeLabel string, secrets v1.SecretInterface, wantExpirationTime time.Time, deltaTime time.Duration) {
|
||||||
|
t.Helper()
|
||||||
|
signature := getFositeDataSignature(t, tokenString)
|
||||||
|
signatureBytes, err := base64.RawURLEncoding.DecodeString(signature)
|
||||||
|
require.NoError(t, err)
|
||||||
|
// lower case base32 encoding insures that our secret name is valid per ValidateSecretName in k/k
|
||||||
|
var b32 = base32.StdEncoding.WithPadding(base32.NoPadding)
|
||||||
|
signatureAsValidName := strings.ToLower(b32.EncodeToString(signatureBytes))
|
||||||
|
secretName := fmt.Sprintf("pinniped-storage-%s-%s", typeLabel, signatureAsValidName)
|
||||||
|
secret, err := secrets.Get(context.Background(), secretName, metav1.GetOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
refreshTokenGCTimeString := secret.Annotations["storage.pinniped.dev/garbage-collect-after"]
|
||||||
|
refreshTokenGCTime, err := time.Parse(crud.SecretLifetimeAnnotationDateFormat, refreshTokenGCTimeString)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
testutil.RequireTimeInDelta(t, refreshTokenGCTime, wantExpirationTime, deltaTime)
|
||||||
|
}
|
||||||
|
|
||||||
func requireValidIDToken(
|
func requireValidIDToken(
|
||||||
t *testing.T,
|
t *testing.T,
|
||||||
body map[string]interface{},
|
body map[string]interface{},
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/base32"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -21,17 +22,21 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"go.pinniped.dev/pkg/oidcclient/oidctypes"
|
||||||
|
|
||||||
coreosoidc "github.com/coreos/go-oidc/v3/oidc"
|
coreosoidc "github.com/coreos/go-oidc/v3/oidc"
|
||||||
"github.com/creack/pty"
|
"github.com/creack/pty"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
authorizationv1 "k8s.io/api/authorization/v1"
|
authorizationv1 "k8s.io/api/authorization/v1"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
rbacv1 "k8s.io/api/rbac/v1"
|
rbacv1 "k8s.io/api/rbac/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
authv1alpha "go.pinniped.dev/generated/latest/apis/concierge/authentication/v1alpha1"
|
authv1alpha "go.pinniped.dev/generated/latest/apis/concierge/authentication/v1alpha1"
|
||||||
configv1alpha1 "go.pinniped.dev/generated/latest/apis/supervisor/config/v1alpha1"
|
configv1alpha1 "go.pinniped.dev/generated/latest/apis/supervisor/config/v1alpha1"
|
||||||
idpv1alpha1 "go.pinniped.dev/generated/latest/apis/supervisor/idp/v1alpha1"
|
idpv1alpha1 "go.pinniped.dev/generated/latest/apis/supervisor/idp/v1alpha1"
|
||||||
"go.pinniped.dev/internal/certauthority"
|
"go.pinniped.dev/internal/certauthority"
|
||||||
|
"go.pinniped.dev/internal/crud"
|
||||||
"go.pinniped.dev/internal/here"
|
"go.pinniped.dev/internal/here"
|
||||||
"go.pinniped.dev/internal/oidc"
|
"go.pinniped.dev/internal/oidc"
|
||||||
"go.pinniped.dev/internal/testutil"
|
"go.pinniped.dev/internal/testutil"
|
||||||
@ -45,7 +50,7 @@ import (
|
|||||||
func TestE2EFullIntegration(t *testing.T) {
|
func TestE2EFullIntegration(t *testing.T) {
|
||||||
env := library.IntegrationEnv(t)
|
env := library.IntegrationEnv(t)
|
||||||
|
|
||||||
ctx, cancelFunc := context.WithTimeout(context.Background(), 5*time.Minute)
|
ctx, cancelFunc := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||||
defer cancelFunc()
|
defer cancelFunc()
|
||||||
|
|
||||||
// Build pinniped CLI.
|
// Build pinniped CLI.
|
||||||
@ -422,6 +427,8 @@ func requireUserCanUseKubectlWithoutAuthenticatingAgain(
|
|||||||
})
|
})
|
||||||
require.NotNil(t, token)
|
require.NotNil(t, token)
|
||||||
|
|
||||||
|
requireGCAnnotationsOnSessionStorage(ctx, t, env.SupervisorNamespace, startTime, token)
|
||||||
|
|
||||||
idTokenClaims := token.IDToken.Claims
|
idTokenClaims := token.IDToken.Claims
|
||||||
require.Equal(t, expectedUsername, idTokenClaims[oidc.DownstreamUsernameClaim])
|
require.Equal(t, expectedUsername, idTokenClaims[oidc.DownstreamUsernameClaim])
|
||||||
|
|
||||||
@ -482,6 +489,40 @@ func requireUserCanUseKubectlWithoutAuthenticatingAgain(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func requireGCAnnotationsOnSessionStorage(ctx context.Context, t *testing.T, supervisorNamespace string, startTime time.Time, token *oidctypes.Token) {
|
||||||
|
// check that the access token is new (since it's just been refreshed) and has close to two minutes left.
|
||||||
|
testutil.RequireTimeInDelta(t, startTime.Add(2*time.Minute), token.AccessToken.Expiry.Time, 15*time.Second)
|
||||||
|
|
||||||
|
kubeClient := library.NewKubernetesClientset(t).CoreV1()
|
||||||
|
|
||||||
|
// get the access token secret that matches the signature from the cache
|
||||||
|
accessTokenSignature := strings.Split(token.AccessToken.Token, ".")[1]
|
||||||
|
accessSecretName := getSecretNameFromSignature(t, accessTokenSignature, "access-token")
|
||||||
|
accessTokenSecret, err := kubeClient.Secrets(supervisorNamespace).Get(ctx, accessSecretName, metav1.GetOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Check that the access token garbage-collect-after value is 9 hours from now
|
||||||
|
accessTokenGCTimeString := accessTokenSecret.Annotations["storage.pinniped.dev/garbage-collect-after"]
|
||||||
|
accessTokenGCTime, err := time.Parse(crud.SecretLifetimeAnnotationDateFormat, accessTokenGCTimeString)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, accessTokenGCTime.After(time.Now().Add(9*time.Hour)))
|
||||||
|
|
||||||
|
// get the refresh token secret that matches the signature from the cache
|
||||||
|
refreshTokenSignature := strings.Split(token.RefreshToken.Token, ".")[1]
|
||||||
|
refreshSecretName := getSecretNameFromSignature(t, refreshTokenSignature, "refresh-token")
|
||||||
|
refreshTokenSecret, err := kubeClient.Secrets(supervisorNamespace).Get(ctx, refreshSecretName, metav1.GetOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Check that the refresh token garbage-collect-after value is 9 hours
|
||||||
|
refreshTokenGCTimeString := refreshTokenSecret.Annotations["storage.pinniped.dev/garbage-collect-after"]
|
||||||
|
refreshTokenGCTime, err := time.Parse(crud.SecretLifetimeAnnotationDateFormat, refreshTokenGCTimeString)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, refreshTokenGCTime.After(time.Now().Add(9*time.Hour)))
|
||||||
|
|
||||||
|
// the access token and the refresh token should be garbage collected at essentially the same time
|
||||||
|
testutil.RequireTimeInDelta(t, accessTokenGCTime, refreshTokenGCTime, 1*time.Minute)
|
||||||
|
}
|
||||||
|
|
||||||
func runPinnipedGetKubeconfig(t *testing.T, env *library.TestEnv, pinnipedExe string, tempDir string, pinnipedCLICommand []string) string {
|
func runPinnipedGetKubeconfig(t *testing.T, env *library.TestEnv, pinnipedExe string, tempDir string, pinnipedCLICommand []string) string {
|
||||||
// Run "pinniped get kubeconfig" to get a kubeconfig YAML.
|
// Run "pinniped get kubeconfig" to get a kubeconfig YAML.
|
||||||
envVarsWithProxy := append(os.Environ(), env.ProxyEnv()...)
|
envVarsWithProxy := append(os.Environ(), env.ProxyEnv()...)
|
||||||
@ -498,3 +539,14 @@ func runPinnipedGetKubeconfig(t *testing.T, env *library.TestEnv, pinnipedExe st
|
|||||||
|
|
||||||
return kubeconfigPath
|
return kubeconfigPath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getSecretNameFromSignature(t *testing.T, signature string, typeLabel string) string {
|
||||||
|
t.Helper()
|
||||||
|
// try to decode base64 signatures to prevent double encoding of binary data
|
||||||
|
signatureBytes, err := base64.RawURLEncoding.DecodeString(signature)
|
||||||
|
require.NoError(t, err)
|
||||||
|
// lower case base32 encoding insures that our secret name is valid per ValidateSecretName in k/k
|
||||||
|
var b32 = base32.StdEncoding.WithPadding(base32.NoPadding)
|
||||||
|
signatureAsValidName := strings.ToLower(b32.EncodeToString(signatureBytes))
|
||||||
|
return fmt.Sprintf("pinniped-storage-%s-%s", typeLabel, signatureAsValidName)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user