Allow dynamic clients to be used in downstream OIDC flows

This is only a first commit towards making this feature work.
- Hook dynamic clients into fosite by returning them from the storage
  interface (after finding and validating them)
- In the auth endpoint, prevent the use of the username and password
  headers for dynamic clients to force them to use the browser-based
  login flows for all the upstream types
- Add happy path integration tests in supervisor_login_test.go
- Add lots of comments (and some small refactors) in
  supervisor_login_test.go to make it much easier to understand
- Add lots of unit tests for the auth endpoint regarding dynamic clients
  (more unit tests to be added for other endpoints in follow-up commits)
- Enhance crud.go to make lifetime=0 mean never garbage collect,
  since we want client secret storage Secrets to last forever
- Move the OIDCClient validation code to a package where it can be
  shared between the controller and the fosite storage interface
- Make shared test helpers for tests that need to create OIDC client
  secret storage Secrets
- Create a public const for "pinniped-cli" now that we are using that
  string in several places in the production code
This commit is contained in:
Ryan Richard
2022-07-14 09:51:11 -07:00
parent be85e1ed0a
commit e0ecdc004b
28 changed files with 1692 additions and 461 deletions
@@ -18,6 +18,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
supervisorfake "go.pinniped.dev/generated/latest/client/supervisor/clientset/versioned/fake"
"go.pinniped.dev/internal/oidc"
"go.pinniped.dev/internal/oidc/jwks"
"go.pinniped.dev/internal/psession"
@@ -1072,11 +1073,12 @@ func TestCallbackEndpoint(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
client := fake.NewSimpleClientset()
secrets := client.CoreV1().Secrets("some-namespace")
oidcClientsClient := supervisorfake.NewSimpleClientset().ConfigV1alpha1().OIDCClients("some-namespace")
// Configure fosite the same way that the production code would.
// Inject this into our test subject at the last second so we get a fresh storage for every test.
timeoutsConfiguration := oidc.DefaultOIDCTimeoutsConfiguration()
oauthStore := oidc.NewKubeStorage(secrets, timeoutsConfiguration)
oauthStore := oidc.NewKubeStorage(secrets, oidcClientsClient, timeoutsConfiguration)
hmacSecretFunc := func() []byte { return []byte("some secret - must have at least 32 bytes") }
require.GreaterOrEqual(t, len(hmacSecretFunc()), 32, "fosite requires that hmac secrets have at least 32 bytes")
jwksProviderIsUnused := jwks.NewDynamicJWKSProvider()