Merge branch 'main' into dynamic_clients

This commit is contained in:
Ryan Richard
2022-08-26 11:35:35 -07:00
329 changed files with 15446 additions and 1044 deletions
+10 -10
View File
@@ -10,22 +10,22 @@ import (
"k8s.io/apiserver/pkg/authentication/user"
)
// This interface is similar to the k8s token authenticator, but works with username/passwords instead
// UserAuthenticator is an interface is similar to the k8s token authenticator, but works with username/passwords instead
// of a single token string.
//
// The return values should be as follows.
// 1. For a successful authentication:
// - A response which includes the username, uid, and groups in the userInfo. The username and uid must not be blank.
// - true
// - nil error
// - A response which includes the username, uid, and groups in the userInfo. The username and uid must not be blank.
// - true
// - nil error
// 2. For an unsuccessful authentication, e.g. bad username or password:
// - nil response
// - false
// - nil error
// - nil response
// - false
// - nil error
// 3. For an unexpected error, e.g. a network problem:
// - nil response
// - false
// - an error
// - nil response
// - false
// - an error
// Other combinations of return values must be avoided.
//
// See k8s.io/apiserver/pkg/authentication/authenticator/interfaces.go for the token authenticator
+5 -5
View File
@@ -1,4 +1,4 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package certauthority
@@ -9,8 +9,8 @@ import (
"crypto/x509"
"fmt"
"io"
"io/ioutil"
"net"
"os"
"strings"
"testing"
"time"
@@ -23,10 +23,10 @@ import (
func loadFromFiles(t *testing.T, certPath string, keyPath string) (*CA, error) {
t.Helper()
certPEM, err := ioutil.ReadFile(certPath)
certPEM, err := os.ReadFile(certPath)
require.NoError(t, err)
keyPEM, err := ioutil.ReadFile(keyPath)
keyPEM, err := os.ReadFile(keyPath)
require.NoError(t, err)
ca, err := Load(string(certPEM), string(keyPEM))
@@ -206,7 +206,7 @@ func TestPool(t *testing.T) {
require.NoError(t, err)
pool := ca.Pool()
require.Len(t, pool.Subjects(), 1) // nolint: staticcheck // not system cert pool
require.Len(t, pool.Subjects(), 1)
}
type errSigner struct {
+2 -2
View File
@@ -1,4 +1,4 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package apiserver
@@ -76,7 +76,7 @@ func (c completedConfig) New() (*PinnipedServer, error) {
GenericAPIServer: genericServer,
}
var errs []error //nolint: prealloc
var errs []error //nolint:prealloc
for _, f := range []func() (schema.GroupVersionResource, rest.Storage){
func() (schema.GroupVersionResource, rest.Storage) {
tokenCredReqGVR := c.ExtraConfig.LoginConciergeGroupVersion.WithResource("tokencredentialrequests")
@@ -643,7 +643,7 @@ func getTransportForUser(ctx context.Context, userInfo user.Info, delegate, dele
}
func canImpersonateFully(userInfo user.Info) bool {
// nolint: gosimple // this structure is on purpose because we plan to expand this function
//nolint:gosimple // this structure is on purpose because we plan to expand this function
if len(userInfo.GetUID()) == 0 {
return true
}
+3 -3
View File
@@ -1,4 +1,4 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Package concierge contains functionality to load/store Config's from/to
@@ -8,7 +8,7 @@ package concierge
import (
"context"
"fmt"
"io/ioutil"
"os"
"strings"
"k8s.io/utils/pointer"
@@ -43,7 +43,7 @@ const (
// This function will decode that base64-encoded data to PEM bytes to be stored
// in the Config.
func FromPath(ctx context.Context, path string) (*Config, error) {
data, err := ioutil.ReadFile(path)
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("read file: %w", err)
}
+2 -3
View File
@@ -1,11 +1,10 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package concierge
import (
"context"
"io/ioutil"
"os"
"testing"
@@ -585,7 +584,7 @@ func TestFromPath(t *testing.T) {
// this is a serial test because it sets the global logger
// Write yaml to temp file
f, err := ioutil.TempFile("", "pinniped-test-config-yaml-*")
f, err := os.CreateTemp("", "pinniped-test-config-yaml-*")
require.NoError(t, err)
defer func() {
err := os.Remove(f.Name())
+2 -2
View File
@@ -8,8 +8,8 @@ package supervisor
import (
"context"
"fmt"
"io/ioutil"
"net"
"os"
"strings"
"k8s.io/utils/pointer"
@@ -36,7 +36,7 @@ const (
// defaults (from the Config documentation), and verifies that the config is
// valid (Config documentation).
func FromPath(ctx context.Context, path string) (*Config, error) {
data, err := ioutil.ReadFile(path)
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("read file: %w", err)
}
+1 -2
View File
@@ -6,7 +6,6 @@ package supervisor
import (
"context"
"fmt"
"io/ioutil"
"os"
"testing"
@@ -451,7 +450,7 @@ func TestFromPath(t *testing.T) {
// this is a serial test because it sets the global logger
// Write yaml to temp file
f, err := ioutil.TempFile("", "pinniped-test-config-yaml-*")
f, err := os.CreateTemp("", "pinniped-test-config-yaml-*")
require.NoError(t, err)
defer func() {
err := os.Remove(f.Name())
@@ -143,7 +143,7 @@ func TestController(t *testing.T) {
if tt.initialCache != nil {
tt.initialCache(t, cache)
}
testLog := testlogger.NewLegacy(t) //nolint: staticcheck // old test with lots of log statements
testLog := testlogger.NewLegacy(t) //nolint:staticcheck // old test with lots of log statements
webhooks := informers.Authentication().V1alpha1().WebhookAuthenticators()
jwtAuthenticators := informers.Authentication().V1alpha1().JWTAuthenticators()
@@ -375,7 +375,7 @@ func TestController(t *testing.T) {
fakeClient := pinnipedfake.NewSimpleClientset(tt.jwtAuthenticators...)
informers := pinnipedinformers.NewSharedInformerFactory(fakeClient, 0)
cache := authncache.New()
testLog := testlogger.NewLegacy(t) //nolint: staticcheck // old test with lots of log statements
testLog := testlogger.NewLegacy(t) //nolint:staticcheck // old test with lots of log statements
if tt.cache != nil {
tt.cache(t, cache, tt.wantClose)
@@ -1,4 +1,4 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Package webhookcachefiller implements a controller for filling an authncache.Cache with each added/updated WebhookAuthenticator.
@@ -6,7 +6,6 @@ package webhookcachefiller
import (
"fmt"
"io/ioutil"
"os"
"github.com/go-logr/logr"
@@ -64,7 +63,7 @@ func (c *controller) Sync(ctx controllerlib.Context) error {
return fmt.Errorf("failed to get WebhookAuthenticator %s/%s: %w", ctx.Key.Namespace, ctx.Key.Name, err)
}
webhookAuthenticator, err := newWebhookAuthenticator(&obj.Spec, ioutil.TempFile, clientcmd.WriteToFile)
webhookAuthenticator, err := newWebhookAuthenticator(&obj.Spec, os.CreateTemp, clientcmd.WriteToFile)
if err != nil {
return fmt.Errorf("failed to build webhook config: %w", err)
}
@@ -7,7 +7,7 @@ import (
"context"
"encoding/base64"
"fmt"
"io/ioutil"
"io"
"net/http"
"os"
"testing"
@@ -88,7 +88,7 @@ func TestController(t *testing.T) {
fakeClient := pinnipedfake.NewSimpleClientset(tt.webhooks...)
informers := pinnipedinformers.NewSharedInformerFactory(fakeClient, 0)
cache := authncache.New()
testLog := testlogger.NewLegacy(t) //nolint: staticcheck // old test with lots of log statements
testLog := testlogger.NewLegacy(t) //nolint:staticcheck // old test with lots of log statements
controller := New(cache, informers.Authentication().V1alpha1().WebhookAuthenticators(), testLog.Logger)
@@ -121,7 +121,7 @@ func TestNewWebhookAuthenticator(t *testing.T) {
t.Run("marshal failure", func(t *testing.T) {
marshalError := func(_ clientcmdapi.Config, _ string) error { return fmt.Errorf("some marshal error") }
res, err := newWebhookAuthenticator(&auth1alpha1.WebhookAuthenticatorSpec{}, ioutil.TempFile, marshalError)
res, err := newWebhookAuthenticator(&auth1alpha1.WebhookAuthenticatorSpec{}, os.CreateTemp, marshalError)
require.Nil(t, res)
require.EqualError(t, err, "unable to marshal kubeconfig: some marshal error")
})
@@ -130,7 +130,7 @@ func TestNewWebhookAuthenticator(t *testing.T) {
res, err := newWebhookAuthenticator(&auth1alpha1.WebhookAuthenticatorSpec{
Endpoint: "https://example.com",
TLS: &auth1alpha1.TLSSpec{CertificateAuthorityData: "invalid-base64"},
}, ioutil.TempFile, clientcmd.WriteToFile)
}, os.CreateTemp, clientcmd.WriteToFile)
require.Nil(t, res)
require.EqualError(t, err, "invalid TLS configuration: illegal base64 data at input byte 7")
})
@@ -139,7 +139,7 @@ func TestNewWebhookAuthenticator(t *testing.T) {
res, err := newWebhookAuthenticator(&auth1alpha1.WebhookAuthenticatorSpec{
Endpoint: "https://example.com",
TLS: &auth1alpha1.TLSSpec{CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte("bad data"))},
}, ioutil.TempFile, clientcmd.WriteToFile)
}, os.CreateTemp, clientcmd.WriteToFile)
require.Nil(t, res)
require.EqualError(t, err, "invalid TLS configuration: certificateAuthorityData is not valid PEM: data does not contain any valid RSA or ECDSA certificates")
})
@@ -147,14 +147,14 @@ func TestNewWebhookAuthenticator(t *testing.T) {
t.Run("valid config with no TLS spec", func(t *testing.T) {
res, err := newWebhookAuthenticator(&auth1alpha1.WebhookAuthenticatorSpec{
Endpoint: "https://example.com",
}, ioutil.TempFile, clientcmd.WriteToFile)
}, os.CreateTemp, clientcmd.WriteToFile)
require.NotNil(t, res)
require.NoError(t, err)
})
t.Run("success", func(t *testing.T) {
caBundle, url := testutil.TLSTestServer(t, func(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
body, err := io.ReadAll(r.Body)
require.NoError(t, err)
require.Contains(t, string(body), "test-token")
_, err = w.Write([]byte(`{}`))
@@ -166,7 +166,7 @@ func TestNewWebhookAuthenticator(t *testing.T) {
CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte(caBundle)),
},
}
res, err := newWebhookAuthenticator(spec, ioutil.TempFile, clientcmd.WriteToFile)
res, err := newWebhookAuthenticator(spec, os.CreateTemp, clientcmd.WriteToFile)
require.NoError(t, err)
require.NotNil(t, res)
@@ -11,7 +11,7 @@ import (
"encoding/pem"
"errors"
"fmt"
"io/ioutil"
"io"
"net"
"net/http"
"reflect"
@@ -92,7 +92,7 @@ func TestImpersonatorConfigControllerOptions(t *testing.T) {
nil,
caSignerName,
nil,
plog.Logr(), // nolint: staticcheck // old test with no log assertions
plog.Logr(), //nolint:staticcheck // old test with no log assertions
)
credIssuerInformerFilter = observableWithInformerOption.GetFilterForInformer(credIssuerInformer)
servicesInformerFilter = observableWithInformerOption.GetFilterForInformer(servicesInformer)
@@ -360,10 +360,13 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
}
testHTTPServerMutex.Lock() // this is to satisfy the race detector
testHTTPServer = &http.Server{Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
_, err := fmt.Fprint(w, fakeServerResponseBody)
r.NoError(err)
})}
testHTTPServer = &http.Server{
Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
_, err := fmt.Fprint(w, fakeServerResponseBody)
r.NoError(err)
}),
ReadHeaderTimeout: 10 * time.Second,
}
testHTTPServerMutex.Unlock()
// Start serving requests in the background.
@@ -480,7 +483,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
r.NoError(err)
r.Equal(http.StatusOK, resp.StatusCode)
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
r.NoError(resp.Body.Close())
r.NoError(err)
r.Equal(fakeServerResponseBody, string(body))
@@ -560,7 +563,7 @@ func TestImpersonatorConfigControllerSync(t *testing.T) {
impersonatorFunc,
caSignerName,
signingCertProvider,
plog.Logr(), // nolint: staticcheck // old test with no log assertions
plog.Logr(), //nolint:staticcheck // old test with no log assertions
)
controllerlib.TestWrap(t, subject, func(syncer controllerlib.Syncer) controllerlib.Syncer {
tlsServingCertDynamicCertProvider = syncer.(*impersonatorConfigController).tlsServingCertDynamicCertProvider
@@ -1,4 +1,4 @@
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Package issuerconfig contains helpers for updating CredentialIssuer status entries.
@@ -60,8 +60,7 @@ func mergeStrategy(configToUpdate *v1alpha1.CredentialIssuerStatus, strategy v1a
}
// weights are a set of priorities for each strategy type.
//nolint: gochecknoglobals
var weights = map[v1alpha1.StrategyType]int{
var weights = map[v1alpha1.StrategyType]int{ //nolint:gochecknoglobals
v1alpha1.KubeClusterSigningCertificateStrategyType: 2, // most preferred strategy
v1alpha1.ImpersonationProxyStrategyType: 1,
// unknown strategy types will have weight 0 by default
@@ -145,12 +145,12 @@ type agentController struct {
var (
// controllerManagerLabels are the Kubernetes labels we expect on the kube-controller-manager Pod.
controllerManagerLabels = labels.SelectorFromSet(map[string]string{ // nolint: gochecknoglobals
controllerManagerLabels = labels.SelectorFromSet(map[string]string{ //nolint:gochecknoglobals
"component": "kube-controller-manager",
})
// agentLabels are the Kubernetes labels we always expect on the kube-controller-manager Pod.
agentLabels = labels.SelectorFromSet(map[string]string{ // nolint: gochecknoglobals
agentLabels = labels.SelectorFromSet(map[string]string{ //nolint:gochecknoglobals
agentPodLabelKey: agentPodLabelValue,
})
)
@@ -179,7 +179,7 @@ func NewAgentController(
dynamicCertProvider,
&clock.RealClock{},
cache.NewExpiring(),
plog.Logr(), // nolint: staticcheck // old controller with lots of log statements
plog.Logr(), //nolint:staticcheck // old controller with lots of log statements
)
}
@@ -1110,7 +1110,7 @@ func TestAgentController(t *testing.T) {
require.NoError(t, err)
if tt.wantAgentDeployment == nil {
assert.Empty(t, deployments.Items, "did not expect an agent deployment")
} else { // nolint: gocritic
} else { //nolint:gocritic
if assert.Len(t, deployments.Items, 1, "expected a single agent deployment") {
assert.Equal(t, tt.wantAgentDeployment, &deployments.Items[0])
}
@@ -149,7 +149,7 @@ func TestLegacyPodCleanerController(t *testing.T) {
}
kubeInformers := informers.NewSharedInformerFactory(kubeClientset, 0)
log := testlogger.NewLegacy(t) //nolint: staticcheck // old test with lots of log statements
log := testlogger.NewLegacy(t) //nolint:staticcheck // old test with lots of log statements
controller := NewLegacyPodCleanerController(
AgentConfig{
Namespace: "concierge",
@@ -1,7 +1,7 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Package secretgenerator provides a supervisorSecretsController that can ensure existence of a generated secret.
// Package generator provides a supervisorSecretsController that can ensure existence of a generated secret.
package generator
import (
@@ -24,8 +24,7 @@ import (
)
// generateKey is stubbed out for the purpose of testing. The default behavior is to generate a symmetric key.
//nolint:gochecknoglobals
var generateKey = generateSymmetricKey
var generateKey = generateSymmetricKey //nolint:gochecknoglobals
type supervisorSecretsController struct {
labels map[string]string
@@ -50,8 +50,7 @@ const (
)
// generateKey is stubbed out for the purpose of testing. The default behavior is to generate an EC key.
//nolint:gochecknoglobals
var generateKey = generateECKey
var generateKey = generateECKey //nolint:gochecknoglobals
func generateECKey(r io.Reader) (interface{}, error) {
return ecdsa.GenerateKey(elliptic.P256(), r)
@@ -1,4 +1,4 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package supervisorconfig
@@ -10,7 +10,7 @@ import (
"encoding/pem"
"errors"
"io"
"io/ioutil"
"os"
"testing"
"github.com/stretchr/testify/require"
@@ -259,7 +259,7 @@ func TestJWKSWriterControllerSync(t *testing.T) {
const namespace = "tuna-namespace"
goodKeyPEM, err := ioutil.ReadFile("testdata/good-ec-key.pem")
goodKeyPEM, err := os.ReadFile("testdata/good-ec-key.pem")
require.NoError(t, err)
block, _ := pem.Decode(goodKeyPEM)
require.NotNil(t, block, "expected block to be non-nil...is goodKeyPEM a valid PEM?")
@@ -747,7 +747,7 @@ func TestJWKSWriterControllerSync(t *testing.T) {
func readJWKJSON(t *testing.T, path string) []byte {
t.Helper()
data, err := ioutil.ReadFile(path)
data, err := os.ReadFile(path)
require.NoError(t, err)
// Trim whitespace from our testdata so that we match the compact JSON encoding of
@@ -68,7 +68,7 @@ const (
)
var (
disallowedAdditionalAuthorizeParameters = map[string]bool{ // nolint: gochecknoglobals
disallowedAdditionalAuthorizeParameters = map[string]bool{ //nolint:gochecknoglobals
// Reject these AdditionalAuthorizeParameters to avoid allowing the user's config to overwrite the parameters
// that are always used by Pinniped in authcode authorization requests. The OIDC library used would otherwise
// happily treat the user's config as an override. Users can already set the "client_id" and "scope" params
@@ -91,7 +91,7 @@ func TestOIDCUpstreamWatcherControllerFilterSecret(t *testing.T) {
nil,
pinnipedInformers.IDP().V1alpha1().OIDCIdentityProviders(),
secretInformer,
plog.Logr(), // nolint: staticcheck // old test with no log assertions
plog.Logr(), //nolint:staticcheck // old test with no log assertions
withInformer.WithInformer,
)
@@ -1400,7 +1400,7 @@ oidc: issuer did not match the issuer returned by provider, expected "` + testIs
pinnipedInformers := pinnipedinformers.NewSharedInformerFactory(fakePinnipedClient, 0)
fakeKubeClient := fake.NewSimpleClientset(tt.inputSecrets...)
kubeInformers := informers.NewSharedInformerFactory(fakeKubeClient, 0)
testLog := testlogger.NewLegacy(t) // nolint: staticcheck // old test with lots of log statements
testLog := testlogger.NewLegacy(t) //nolint:staticcheck // old test with lots of log statements
cache := provider.NewDynamicUpstreamIDPProvider()
cache.SetOIDCIdentityProviders([]provider.UpstreamOIDCIdentityProviderI{
&upstreamoidc.ProviderConfig{Name: "initial-entry"},
@@ -1,4 +1,4 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package supervisorconfig
@@ -6,8 +6,8 @@ package supervisorconfig
import (
"context"
"crypto/tls"
"io/ioutil"
"net/url"
"os"
"testing"
"github.com/sclevine/spec"
@@ -170,7 +170,7 @@ func TestTLSCertObserverControllerSync(t *testing.T) {
}
var readTestFile = func(path string) []byte {
data, err := ioutil.ReadFile(path)
data, err := os.ReadFile(path)
r.NoError(err)
return data
}
+3 -3
View File
@@ -1,4 +1,4 @@
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package controllerinit
@@ -29,8 +29,8 @@ type Informer interface {
}
// Prepare returns RunnerBuilder that, when called:
// 1. Starts all provided informers and waits for them sync (and fails if they hang)
// 2. Returns a Runner that combines the Runner and RunnerWrapper passed into Prepare
// 1.) Starts all provided informers and waits for them sync (and fails if they hang), and
// 2.) Returns a Runner that combines the Runner and RunnerWrapper passed into Prepare.
func Prepare(controllers Runner, controllersWrapper RunnerWrapper, informers ...Informer) RunnerBuilder {
return func(ctx context.Context) (Runner, error) {
for _, informer := range informers {
@@ -97,8 +97,7 @@ type Config struct {
}
// PrepareControllers prepares the controllers and their informers and returns a function that will start them when called.
//nolint:funlen // Eh, fair, it is a really long function...but it is wiring the world...so...
func PrepareControllers(c *Config) (controllerinit.RunnerBuilder, error) {
func PrepareControllers(c *Config) (controllerinit.RunnerBuilder, error) { //nolint:funlen // Eh, fair, it is a really long function...but it is wiring the world...so...
loginConciergeGroupData, identityConciergeGroupData := groupsuffix.ConciergeAggregatedGroups(c.APIGroupSuffix)
dref, deployment, _, err := deploymentref.New(c.ServerInstallationInfo)
@@ -223,7 +222,7 @@ func PrepareControllers(c *Config) (controllerinit.RunnerBuilder, error) {
agentConfig,
client,
informers.installationNamespaceK8s.Core().V1().Pods(),
plog.Logr(), // nolint: staticcheck // old controller with lots of log statements
plog.Logr(), //nolint:staticcheck // old controller with lots of log statements
),
singletonWorker,
).
@@ -233,7 +232,7 @@ func PrepareControllers(c *Config) (controllerinit.RunnerBuilder, error) {
webhookcachefiller.New(
c.AuthenticatorCache,
informers.pinniped.Authentication().V1alpha1().WebhookAuthenticators(),
plog.Logr(), // nolint: staticcheck // old controller with lots of log statements
plog.Logr(), //nolint:staticcheck // old controller with lots of log statements
),
singletonWorker,
).
@@ -241,7 +240,7 @@ func PrepareControllers(c *Config) (controllerinit.RunnerBuilder, error) {
jwtcachefiller.New(
c.AuthenticatorCache,
informers.pinniped.Authentication().V1alpha1().JWTAuthenticators(),
plog.Logr(), // nolint: staticcheck // old controller with lots of log statements
plog.Logr(), //nolint:staticcheck // old controller with lots of log statements
),
singletonWorker,
).
@@ -250,7 +249,7 @@ func PrepareControllers(c *Config) (controllerinit.RunnerBuilder, error) {
c.AuthenticatorCache,
informers.pinniped.Authentication().V1alpha1().WebhookAuthenticators(),
informers.pinniped.Authentication().V1alpha1().JWTAuthenticators(),
plog.Logr(), // nolint: staticcheck // old controller with lots of log statements
plog.Logr(), //nolint:staticcheck // old controller with lots of log statements
),
singletonWorker,
).
@@ -276,7 +275,7 @@ func PrepareControllers(c *Config) (controllerinit.RunnerBuilder, error) {
impersonator.New,
c.NamesConfig.ImpersonationSignerSecret,
c.ImpersonationSigningCertProvider,
plog.Logr(), // nolint: staticcheck // old controller with lots of log statements
plog.Logr(), //nolint:staticcheck // old controller with lots of log statements
),
singletonWorker,
).
+1 -1
View File
@@ -169,7 +169,7 @@ func validateSecret(resource string, secret *corev1.Secret) error {
return nil
}
//nolint: gochecknoglobals
//nolint:gochecknoglobals
var b32 = base32.StdEncoding.WithPadding(base32.NoPadding)
func (s *secretsStorage) GetName(signature string) string {
+4 -4
View File
@@ -144,7 +144,7 @@ func TestMerge(t *testing.T) {
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, //nolint: gosec // yeah, I know it is a bad cipher, but AD sucks
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, //nolint:gosec // yeah, I know it is a bad cipher, but AD sucks
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
@@ -169,7 +169,7 @@ func TestMerge(t *testing.T) {
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, //nolint: gosec // yeah, I know it is a bad cipher, but AD sucks
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, //nolint:gosec // yeah, I know it is a bad cipher, but AD sucks
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
@@ -187,7 +187,7 @@ func TestMerge(t *testing.T) {
ServerName: "something-to-check-passthrough",
MinVersion: tls.VersionTLS12,
CipherSuites: []uint16{
tls.TLS_RSA_WITH_AES_128_CBC_SHA, //nolint: gosec // yeah, I know it is a bad cipher, this is the legacy config
tls.TLS_RSA_WITH_AES_128_CBC_SHA, //nolint:gosec // yeah, I know it is a bad cipher, this is the legacy config
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
@@ -219,7 +219,7 @@ func TestMerge(t *testing.T) {
ServerName: "a different thing for passthrough",
MinVersion: tls.VersionTLS12,
CipherSuites: []uint16{
tls.TLS_RSA_WITH_AES_128_CBC_SHA, //nolint: gosec // yeah, I know it is a bad cipher, this is the legacy config
tls.TLS_RSA_WITH_AES_128_CBC_SHA, //nolint:gosec // yeah, I know it is a bad cipher, this is the legacy config
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
+2 -3
View File
@@ -1,4 +1,4 @@
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package deploymentref
@@ -23,8 +23,7 @@ import (
// We would normally pass a kubernetes.Interface into New(), but the client we want to create in
// the calling code depends on the return value of New() (i.e., on the kubeclient.Option for the
// OwnerReference).
//nolint: gochecknoglobals
var getTempClient = func() (kubernetes.Interface, error) {
var getTempClient = func() (kubernetes.Interface, error) { //nolint:gochecknoglobals
client, err := kubeclient.New()
if err != nil {
return nil, err
+5 -5
View File
@@ -1,4 +1,4 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Package downward implements a client interface for interacting with Kubernetes "downwardAPI" volumes.
@@ -9,7 +9,7 @@ import (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
@@ -32,20 +32,20 @@ type PodInfo struct {
// Load pod metadata from a downwardAPI volume directory.
func Load(directory string) (*PodInfo, error) {
var result PodInfo
ns, err := ioutil.ReadFile(filepath.Join(directory, "namespace"))
ns, err := os.ReadFile(filepath.Join(directory, "namespace"))
if err != nil {
return nil, fmt.Errorf("could not load namespace: %w", err)
}
result.Namespace = strings.TrimSpace(string(ns))
name, err := ioutil.ReadFile(filepath.Join(directory, "name"))
name, err := os.ReadFile(filepath.Join(directory, "name"))
if err != nil {
plog.Warning("could not read 'name' downward API file")
} else {
result.Name = strings.TrimSpace(string(name))
}
labels, err := ioutil.ReadFile(filepath.Join(directory, "labels"))
labels, err := os.ReadFile(filepath.Join(directory, "labels"))
if err != nil {
return nil, fmt.Errorf("could not load labels: %w", err)
}
+7 -7
View File
@@ -1,4 +1,4 @@
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package dynamiccert
@@ -41,7 +41,7 @@ func TestProviderWithDynamicServingCertificateController(t *testing.T) {
cert, err := tls.X509KeyPair(certPEM, keyPEM)
require.NoError(t, err)
return pool.Subjects(), []tls.Certificate{cert} // nolint: staticcheck // not system cert pool
return pool.Subjects(), []tls.Certificate{cert}
},
},
{
@@ -69,7 +69,7 @@ func TestProviderWithDynamicServingCertificateController(t *testing.T) {
certKey.UnsetCertKeyContent()
return pool.Subjects(), []tls.Certificate{cert} // nolint: staticcheck // not system cert pool
return pool.Subjects(), []tls.Certificate{cert}
},
},
{
@@ -87,7 +87,7 @@ func TestProviderWithDynamicServingCertificateController(t *testing.T) {
cert, err := tls.X509KeyPair(certPEM, keyPEM)
require.NoError(t, err)
return newCA.Pool().Subjects(), []tls.Certificate{cert} // nolint: staticcheck // not system cert pool
return newCA.Pool().Subjects(), []tls.Certificate{cert}
},
},
{
@@ -110,7 +110,7 @@ func TestProviderWithDynamicServingCertificateController(t *testing.T) {
ok := pool.AppendCertsFromPEM(ca.CurrentCABundleContent())
require.True(t, ok, "should have valid non-empty CA bundle")
return pool.Subjects(), []tls.Certificate{cert} // nolint: staticcheck // not system cert pool
return pool.Subjects(), []tls.Certificate{cert}
},
},
{
@@ -137,7 +137,7 @@ func TestProviderWithDynamicServingCertificateController(t *testing.T) {
err = ca.SetCertKeyContent(newOtherCA.Bundle(), caKey)
require.NoError(t, err)
return newOtherCA.Pool().Subjects(), []tls.Certificate{cert} // nolint: staticcheck // not system cert pool
return newOtherCA.Pool().Subjects(), []tls.Certificate{cert}
},
},
}
@@ -221,7 +221,7 @@ func poolSubjects(pool *x509.CertPool) [][]byte {
if pool == nil {
return nil
}
return pool.Subjects() // nolint: staticcheck // not system cert pool
return pool.Subjects()
}
func TestNewServingCert(t *testing.T) {
+3 -4
View File
@@ -1,4 +1,4 @@
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package execcredcache
@@ -6,7 +6,6 @@ package execcredcache
import (
"errors"
"fmt"
"io/ioutil"
"os"
"sort"
"time"
@@ -51,7 +50,7 @@ type (
// readCache loads a credCache from a path on disk. If the requested path does not exist, it returns an empty cache.
func readCache(path string) (*credCache, error) {
cacheYAML, err := ioutil.ReadFile(path)
cacheYAML, err := os.ReadFile(path)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
// If the file was not found, generate a freshly initialized empty cache.
@@ -87,7 +86,7 @@ func (c *credCache) writeTo(path string) error {
// Marshal the cache back to YAML and save it to the file.
cacheYAML, err := yaml.Marshal(c)
if err == nil {
err = ioutil.WriteFile(path, cacheYAML, 0600)
err = os.WriteFile(path, cacheYAML, 0600)
}
return err
}
+5 -6
View File
@@ -1,11 +1,10 @@
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package execcredcache
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
@@ -52,7 +51,7 @@ func TestGet(t *testing.T) {
},
{
name: "file lock error",
makeTestFile: func(t *testing.T, tmp string) { require.NoError(t, ioutil.WriteFile(tmp, []byte(""), 0600)) },
makeTestFile: func(t *testing.T, tmp string) { require.NoError(t, os.WriteFile(tmp, []byte(""), 0600)) },
trylockFunc: func(t *testing.T) error { return fmt.Errorf("some lock error") },
unlockFunc: func(t *testing.T) error { require.Fail(t, "should not be called"); return nil },
key: testKey{},
@@ -61,7 +60,7 @@ func TestGet(t *testing.T) {
{
name: "invalid file",
makeTestFile: func(t *testing.T, tmp string) {
require.NoError(t, ioutil.WriteFile(tmp, []byte("invalid yaml"), 0600))
require.NoError(t, os.WriteFile(tmp, []byte("invalid yaml"), 0600))
},
key: testKey{},
wantErrors: []string{
@@ -70,7 +69,7 @@ func TestGet(t *testing.T) {
},
{
name: "invalid file, fail to unlock",
makeTestFile: func(t *testing.T, tmp string) { require.NoError(t, ioutil.WriteFile(tmp, []byte("invalid"), 0600)) },
makeTestFile: func(t *testing.T, tmp string) { require.NoError(t, os.WriteFile(tmp, []byte("invalid"), 0600)) },
trylockFunc: func(t *testing.T) error { return nil },
unlockFunc: func(t *testing.T) error { return fmt.Errorf("some unlock error") },
key: testKey{},
@@ -211,7 +210,7 @@ func TestPutToken(t *testing.T) {
{
name: "fail to create directory",
makeTestFile: func(t *testing.T, tmp string) {
require.NoError(t, ioutil.WriteFile(filepath.Dir(tmp), []byte{}, 0600))
require.NoError(t, os.WriteFile(filepath.Dir(tmp), []byte{}, 0600))
},
wantErrors: []string{
"could not create credential cache directory: mkdir TEMPDIR: not a directory",
@@ -236,6 +236,7 @@ const ExpectedAuthorizeCodeSessionJSONFromFuzzing = `{
"Host": "",
"Path": "",
"RawPath": "",
"OmitHost": false,
"ForceQuery": false,
"RawQuery": "",
"Fragment": "",
@@ -253,6 +254,7 @@ const ExpectedAuthorizeCodeSessionJSONFromFuzzing = `{
"Host": "",
"Path": "",
"RawPath": "",
"OmitHost": false,
"ForceQuery": false,
"RawQuery": "",
"Fragment": "",
@@ -270,6 +272,7 @@ const ExpectedAuthorizeCodeSessionJSONFromFuzzing = `{
"Host": "",
"Path": "",
"RawPath": "",
"OmitHost": false,
"ForceQuery": false,
"RawQuery": "",
"Fragment": "",
@@ -100,7 +100,7 @@ func TestOpenIdConnectStorage(t *testing.T) {
require.NoError(t, err)
require.Equal(t, request, newRequest)
err = storage.DeleteOpenIDConnectSession(ctx, "fancy-code.fancy-signature") //nolint: staticcheck // we know this is deprecated and never called. our GC controller cleans these up.
err = storage.DeleteOpenIDConnectSession(ctx, "fancy-code.fancy-signature") //nolint:staticcheck // we know this is deprecated and never called. our GC controller cleans these up.
require.NoError(t, err)
testutil.LogActualJSONFromCreateAction(t, client, 0) // makes it easier to update expected values when needed
+2 -2
View File
@@ -1,4 +1,4 @@
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package groupsuffix
@@ -175,7 +175,7 @@ func Unreplace(baseAPIGroup, apiGroupSuffix string) (string, bool) {
// makes sure that the provided apiGroupSuffix is a valid DNS-1123 subdomain with at least one dot,
// to match Kubernetes behavior.
func Validate(apiGroupSuffix string) error {
var errs []error // nolint: prealloc
var errs []error //nolint:prealloc
if len(strings.Split(apiGroupSuffix, ".")) < 2 {
errs = append(errs, constable.Error("must contain '.'"))
@@ -1,11 +1,11 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package securityheader
import (
"context"
"io/ioutil"
"io"
"net/http"
"net/http/httptest"
"testing"
@@ -74,7 +74,7 @@ func TestWrap(t *testing.T) {
defer resp.Body.Close()
require.Equal(t, http.StatusOK, resp.StatusCode)
respBody, err := ioutil.ReadAll(resp.Body)
respBody, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.Equal(t, "hello world", string(respBody))
+1 -2
View File
@@ -15,8 +15,7 @@ import (
)
// defaultServerUrlFor was copied from k8s.io/client-go/rest/url_utils.go.
//nolint:revive
func defaultServerUrlFor(config *restclient.Config) (*url.URL, string, error) {
func defaultServerUrlFor(config *restclient.Config) (*url.URL, string, error) { //nolint:revive
hasCA := len(config.CAFile) != 0 || len(config.CAData) != 0
hasCert := len(config.CertFile) != 0 || len(config.CertData) != 0
defaultTLS := hasCA || hasCert || config.Insecure
+1 -1
View File
@@ -211,7 +211,7 @@ func AssertSecureTransport(rt http.RoundTripper) error {
tlsConfigCopy := tlsConfig.Clone()
ptls.Merge(ptls.Secure, tlsConfigCopy) // only mutate the copy
//nolint: gosec // the empty TLS config here is not used
//nolint:gosec // the empty TLS config here is not used
if diff := cmp.Diff(tlsConfigCopy, tlsConfig,
cmpopts.IgnoreUnexported(tls.Config{}, x509.CertPool{}),
cmpopts.IgnoreFields(tls.Config{}, "GetClientCertificate"),
+2 -2
View File
@@ -949,7 +949,7 @@ func TestUnwrap(t *testing.T) {
server, restConfig := fakekubeapi.Start(t, nil)
serverSubjects := server.Client().Transport.(*http.Transport).TLSClientConfig.RootCAs.Subjects() // nolint: staticcheck // not system cert pool
serverSubjects := server.Client().Transport.(*http.Transport).TLSClientConfig.RootCAs.Subjects()
t.Run("regular client", func(t *testing.T) {
t.Parallel() // make sure to run in parallel to confirm that our client-go TLS cache busting works (i.e. assert no data races)
@@ -1121,7 +1121,7 @@ func testUnwrap(t *testing.T, client *Client, serverSubjects [][]byte) {
require.Equal(t, secureTLSConfig.NextProtos, tlsConfig.NextProtos)
// x509.CertPool has some embedded functions that make it hard to compare so just look at the subjects
require.Equal(t, serverSubjects, tlsConfig.RootCAs.Subjects()) // nolint: staticcheck // not system cert pool
require.Equal(t, serverSubjects, tlsConfig.RootCAs.Subjects())
})
}
}
+2 -2
View File
@@ -1,4 +1,4 @@
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package kubeclient
@@ -71,7 +71,7 @@ func (r *request) Namespace() string {
return r.namespace
}
//nolint: gochecknoglobals
//nolint:gochecknoglobals
var namespaceGVR = corev1.SchemeGroupVersion.WithResource("namespaces")
func (r *request) NamespaceScoped() bool {
+3 -3
View File
@@ -1,4 +1,4 @@
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package kubeclient
@@ -6,7 +6,7 @@ package kubeclient
import (
"bytes"
"context"
"io/ioutil"
"io"
"net/http"
"net/url"
"reflect"
@@ -142,7 +142,7 @@ func Test_updatePathNewGVK(t *testing.T) {
}
func Test_reqWithoutPrefix(t *testing.T) {
body := ioutil.NopCloser(bytes.NewBuffer([]byte("some body")))
body := io.NopCloser(bytes.NewBuffer([]byte("some body")))
newReq := func(rawurl string) *http.Request {
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, rawurl, body)
require.NoError(t, err)
+5 -5
View File
@@ -1,4 +1,4 @@
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package kubeclient
@@ -6,7 +6,7 @@ package kubeclient
import (
"bytes"
"fmt"
"io/ioutil"
"io"
"net/http"
apiequality "k8s.io/apimachinery/pkg/api/equality"
@@ -213,7 +213,7 @@ func handleCreateOrUpdate(
return true, nil, fmt.Errorf("get body failed: %w", err)
}
defer body.Close()
data, err := ioutil.ReadAll(body)
data, err := io.ReadAll(body)
if err != nil {
return true, nil, fmt.Errorf("read body failed: %w", err)
}
@@ -296,7 +296,7 @@ func handleResponseNewGVK(
// always make sure we close the body, even if reading from it fails
defer resp.Body.Close()
respData, err := ioutil.ReadAll(resp.Body)
respData, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response body: %w", err)
}
@@ -319,7 +319,7 @@ func handleResponseNewGVK(
newResp := &http.Response{}
*newResp = *resp
newResp.Body = ioutil.NopCloser(bytes.NewBuffer(fixedRespData))
newResp.Body = io.NopCloser(bytes.NewBuffer(fixedRespData))
return newResp, nil
}
+2 -3
View File
@@ -1,4 +1,4 @@
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package kubeclient
@@ -7,7 +7,6 @@ import (
stderrors "errors"
"fmt"
"io"
"io/ioutil"
"net/http"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -155,7 +154,7 @@ func drainAndMaybeCloseBody(resp *http.Response, close bool) {
// from k8s.io/client-go/rest/request.go...
const maxBodySlurpSize = 2 << 10
if resp.ContentLength <= maxBodySlurpSize {
_, _ = io.Copy(ioutil.Discard, &io.LimitedReader{R: resp.Body, N: maxBodySlurpSize})
_, _ = io.Copy(io.Discard, &io.LimitedReader{R: resp.Body, N: maxBodySlurpSize})
}
if close {
resp.Body.Close()
+2 -2
View File
@@ -1,4 +1,4 @@
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package leaderelection
@@ -184,7 +184,7 @@ func (t *isLeaderTracker) start() {
}
func (t *isLeaderTracker) stop() (didStop bool) {
return t.tracker.CAS(true, false)
return t.tracker.CompareAndSwap(true, false)
}
// note that resourcelock.Interface is an internal, unstable interface.
@@ -1,4 +1,4 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Package localuserauthenticator provides a authentication webhook program.
@@ -79,8 +79,9 @@ func (w *webhook) start(ctx context.Context, l net.Listener) error {
return &cert, err
}
server := http.Server{
Handler: w,
TLSConfig: c,
Handler: w,
TLSConfig: c,
ReadHeaderTimeout: 10 * time.Second,
}
errCh := make(chan error)
@@ -356,7 +357,7 @@ func run(ctx context.Context) error {
startControllers(ctx, dynamicCertProvider, client.Kubernetes, kubeInformers)
plog.Debug("controllers are ready")
// nolint: gosec // Intentionally binding to all network interfaces.
//nolint:gosec // Intentionally binding to all network interfaces.
l, err := net.Listen("tcp", ":8443")
if err != nil {
return fmt.Errorf("cannot create listener: %w", err)
@@ -1,4 +1,4 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package localuserauthenticator
@@ -10,7 +10,6 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/url"
@@ -385,7 +384,7 @@ func TestWebhook(t *testing.T) {
url: goodURL,
method: http.MethodPost,
headers: goodRequestHeaders,
body: func() (io.ReadCloser, error) { return ioutil.NopCloser(bytes.NewBuffer([]byte("invalid body"))), nil },
body: func() (io.ReadCloser, error) { return io.NopCloser(bytes.NewBuffer([]byte("invalid body"))), nil },
wantStatus: http.StatusBadRequest,
},
}
@@ -416,7 +415,7 @@ func TestWebhook(t *testing.T) {
}
}
responseBody, err := ioutil.ReadAll(rsp.Body)
responseBody, err := io.ReadAll(rsp.Body)
require.NoError(t, err)
if test.wantBody != nil {
require.NoError(t, err)
@@ -520,7 +519,7 @@ func newTokenReviewBodyWithGVK(token string, gvk *schema.GroupVersionKind) (io.R
},
}
err := json.NewEncoder(buf).Encode(&tr)
return ioutil.NopCloser(buf), err
return io.NopCloser(buf), err
}
func unauthenticatedResponseJSON() *authenticationv1beta1.TokenReview {
+2 -2
View File
@@ -58,10 +58,10 @@ func TestAuthorizationEndpoint(t *testing.T) {
oidcUpstreamSubject = "abc123-some guid" // has a space character which should get escaped in URL
oidcUpstreamSubjectQueryEscaped = "abc123-some+guid"
oidcUpstreamUsername = "test-oidc-pinniped-username"
oidcUpstreamPassword = "test-oidc-pinniped-password" //nolint: gosec
oidcUpstreamPassword = "test-oidc-pinniped-password" //nolint:gosec
oidcUpstreamUsernameClaim = "the-user-claim"
oidcUpstreamGroupsClaim = "the-groups-claim"
oidcPasswordGrantUpstreamRefreshToken = "some-opaque-token" //nolint: gosec
oidcPasswordGrantUpstreamRefreshToken = "some-opaque-token" //nolint:gosec
oidcUpstreamAccessToken = "some-access-token"
downstreamIssuer = "https://my-downstream-issuer.com/some-path"
+1 -1
View File
@@ -121,7 +121,7 @@ func (k KubeStorage) GetOpenIDConnectSession(ctx context.Context, fullAuthcode s
}
func (k KubeStorage) DeleteOpenIDConnectSession(ctx context.Context, fullAuthcode string) error {
return k.oidcStorage.DeleteOpenIDConnectSession(ctx, fullAuthcode) //nolint: staticcheck // we know this is deprecated and never called. our GC controller cleans these up.
return k.oidcStorage.DeleteOpenIDConnectSession(ctx, fullAuthcode) //nolint:staticcheck // we know this is deprecated and never called. our GC controller cleans these up.
}
//
+13 -13
View File
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
// Package loginhtml defines HTML templates used by the Supervisor.
//nolint: gochecknoglobals // This package uses globals to ensure that all parsing and minifying happens at init.
package loginhtml
import (
@@ -15,6 +14,7 @@ import (
"go.pinniped.dev/internal/oidc/provider/csp"
)
//nolint:gochecknoglobals // This package uses globals to ensure that all parsing and minifying happens at init.
var (
//go:embed login_form.css
rawCSS string
@@ -22,20 +22,20 @@ var (
//go:embed login_form.gohtml
rawHTMLTemplate string
// Parse the Go templated HTML and inject functions providing the minified inline CSS and JS.
parsedHTMLTemplate = template.Must(template.New("login_form.gohtml").Funcs(template.FuncMap{
"minifiedCSS": func() template.CSS { return template.CSS(CSS()) },
}).Parse(rawHTMLTemplate))
// Generate the CSP header value once since it's effectively constant.
cspValue = strings.Join([]string{
`default-src 'none'`,
`style-src '` + csp.Hash(minifiedCSS) + `'`,
`frame-ancestors 'none'`,
}, "; ")
)
// Parse the Go templated HTML and inject functions providing the minified inline CSS and JS.
var parsedHTMLTemplate = template.Must(template.New("login_form.gohtml").Funcs(template.FuncMap{
"minifiedCSS": func() template.CSS { return template.CSS(CSS()) },
}).Parse(rawHTMLTemplate))
// Generate the CSP header value once since it's effectively constant.
var cspValue = strings.Join([]string{
`default-src 'none'`,
`style-src '` + csp.Hash(minifiedCSS) + `'`,
`frame-ancestors 'none'`,
}, "; ")
func panicOnError(s string, err error) string {
if err != nil {
panic(err)
+6 -5
View File
@@ -244,11 +244,12 @@ func FositeOauth2Helper(
// passed to a plog function (e.g., plog.Info()).
//
// Sample usage:
// err := someFositeLibraryFunction()
// if err != nil {
// plog.Info("some error", FositeErrorForLog(err)...)
// ...
// }
//
// err := someFositeLibraryFunction()
// if err != nil {
// plog.Info("some error", FositeErrorForLog(err)...)
// ...
// }
func FositeErrorForLog(err error) []interface{} {
rfc6749Error := fosite.ErrorToRFC6749Error(err)
keysAndValues := make([]interface{}, 0)
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
// Package formposthtml defines HTML templates used by the Supervisor.
//nolint: gochecknoglobals // This package uses globals to ensure that all parsing and minifying happens at init.
package formposthtml
import (
@@ -15,6 +14,7 @@ import (
"go.pinniped.dev/internal/oidc/provider/csp"
)
//nolint:gochecknoglobals // This package uses globals to ensure that all parsing and minifying happens at init.
var (
//go:embed form_post.css
rawCSS string
@@ -26,24 +26,24 @@ var (
//go:embed form_post.gohtml
rawHTMLTemplate string
// Parse the Go templated HTML and inject functions providing the minified inline CSS and JS.
parsedHTMLTemplate = template.Must(template.New("form_post.gohtml").Funcs(template.FuncMap{
"minifiedCSS": func() template.CSS { return template.CSS(minifiedCSS) },
"minifiedJS": func() template.JS { return template.JS(minifiedJS) }, //nolint:gosec // This is 100% static input, not attacker-controlled.
}).Parse(rawHTMLTemplate))
// Generate the CSP header value once since it's effectively constant.
cspValue = strings.Join([]string{
`default-src 'none'`,
`script-src '` + csp.Hash(minifiedJS) + `'`,
`style-src '` + csp.Hash(minifiedCSS) + `'`,
`img-src data:`,
`connect-src *`,
`frame-ancestors 'none'`,
}, "; ")
)
// Parse the Go templated HTML and inject functions providing the minified inline CSS and JS.
var parsedHTMLTemplate = template.Must(template.New("form_post.gohtml").Funcs(template.FuncMap{
"minifiedCSS": func() template.CSS { return template.CSS(minifiedCSS) },
"minifiedJS": func() template.JS { return template.JS(minifiedJS) }, //nolint:gosec // This is 100% static input, not attacker-controlled.
}).Parse(rawHTMLTemplate))
// Generate the CSP header value once since it's effectively constant.
var cspValue = strings.Join([]string{
`default-src 'none'`,
`script-src '` + csp.Hash(minifiedJS) + `'`,
`style-src '` + csp.Hash(minifiedCSS) + `'`,
`img-src data:`,
`connect-src *`,
`frame-ancestors 'none'`,
}, "; ")
func panicOnError(s string, err error) string {
if err != nil {
panic(err)
@@ -8,7 +8,7 @@ import (
"crypto/ecdsa"
"encoding/json"
"fmt"
"io/ioutil"
"io"
"net/http"
"net/http/httptest"
"net/url"
@@ -84,7 +84,7 @@ func TestManager(t *testing.T) {
// Minimal check to ensure that the right discovery endpoint was called
r.Equal(http.StatusOK, recorder.Code)
responseBody, err := ioutil.ReadAll(recorder.Body)
responseBody, err := io.ReadAll(recorder.Body)
r.NoError(err)
parsedDiscoveryResult := discovery.Metadata{}
err = json.Unmarshal(responseBody, &parsedDiscoveryResult)
@@ -105,7 +105,7 @@ func TestManager(t *testing.T) {
// Minimal check to ensure that the right IDP discovery endpoint was called
r.Equal(http.StatusOK, recorder.Code)
responseBody, err := ioutil.ReadAll(recorder.Body)
responseBody, err := io.ReadAll(recorder.Body)
r.NoError(err)
r.Equal(
fmt.Sprintf(`{"pinniped_identity_providers":[{"name":"%s","type":"%s","flows":%s}]}`+"\n", expectedIDPName, expectedIDPType, expectedFlowsJSON),
@@ -230,7 +230,7 @@ func TestManager(t *testing.T) {
// Minimal check to ensure that the right JWKS endpoint was called
r.Equal(http.StatusOK, recorder.Code)
responseBody, err := ioutil.ReadAll(recorder.Body)
responseBody, err := io.ReadAll(recorder.Body)
r.NoError(err)
parsedJWKSResult := jose.JSONWebKeySet{}
err = json.Unmarshal(responseBody, &parsedJWKSResult)
+230 -92
View File
@@ -14,7 +14,6 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
@@ -52,6 +51,7 @@ import (
"go.pinniped.dev/internal/here"
"go.pinniped.dev/internal/httputil/httperr"
"go.pinniped.dev/internal/oidc"
"go.pinniped.dev/internal/oidc/clientregistry"
"go.pinniped.dev/internal/oidc/jwks"
"go.pinniped.dev/internal/oidc/oidcclientvalidator"
"go.pinniped.dev/internal/oidc/provider"
@@ -119,7 +119,7 @@ var (
fositeInvalidPayloadErrorBody = here.Doc(`
{
"error": "invalid_request",
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. The POST body can not be empty."
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Unable to parse HTTP body, make sure to send a properly formatted form request body."
}
`)
@@ -633,7 +633,7 @@ func TestTokenEndpointAuthcodeExchange(t *testing.T) {
name: "payload is not valid form serialization",
authcodeExchange: authcodeExchangeInputs{
modifyTokenRequest: func(r *http.Request, authCode string) {
r.Body = ioutil.NopCloser(strings.NewReader("this newline character is not allowed in a form serialization: \n"))
r.Body = io.NopCloser(strings.NewReader("this newline character is not allowed in a form serialization: \n"))
},
want: tokenEndpointResponseExpectedValues{
wantStatus: http.StatusBadRequest,
@@ -927,12 +927,13 @@ func TestTokenEndpointTokenExchange(t *testing.T) { // tests for grant_type "urn
authcodeExchange authcodeExchangeInputs
modifyRequestParams func(t *testing.T, params url.Values)
modifyRequestHeaders func(r *http.Request)
modifyStorage func(t *testing.T, storage *oidc.KubeStorage, pendingRequest *http.Request)
modifyStorage func(t *testing.T, storage *oidc.KubeStorage, secrets v1.SecretInterface, pendingRequest *http.Request)
requestedAudience string
kubeResources func(t *testing.T, supervisorClient *supervisorfake.Clientset, kubeClient *fake.Clientset)
wantStatus int
wantResponseBodyContains string
wantStatus int
wantErrorType string
wantErrorDescContains string
}{
{
name: "happy path",
@@ -1038,9 +1039,10 @@ func TestTokenEndpointTokenExchange(t *testing.T) { // tests for grant_type "urn
modifyRequestHeaders: func(r *http.Request) {
r.SetBasicAuth(dynamicClientID, testutil.PlaintextPassword1)
},
requestedAudience: "some-workload-cluster",
wantStatus: http.StatusBadRequest,
wantResponseBodyContains: `The client is not authorized to request a token using this method. The OAuth 2.0 Client is not allowed to use token exchange grant 'urn:ietf:params:oauth:grant-type:token-exchange'.`,
requestedAudience: "some-workload-cluster",
wantStatus: http.StatusBadRequest,
wantErrorType: "unauthorized_client",
wantErrorDescContains: `The client is not authorized to request a token using this method. The OAuth 2.0 Client is not allowed to use token exchange grant 'urn:ietf:params:oauth:grant-type:token-exchange'.`,
},
{
name: "dynamic client did not ask for the pinniped:request-audience scope in the original authorization request, so the access token submitted during token exchange lacks the scope",
@@ -1067,9 +1069,10 @@ func TestTokenEndpointTokenExchange(t *testing.T) { // tests for grant_type "urn
modifyRequestHeaders: func(r *http.Request) {
r.SetBasicAuth(dynamicClientID, testutil.PlaintextPassword1)
},
requestedAudience: "some-workload-cluster",
wantStatus: http.StatusForbidden,
wantResponseBodyContains: `The resource owner or authorization server denied the request. missing the 'pinniped:request-audience' scope`,
requestedAudience: "some-workload-cluster",
wantStatus: http.StatusForbidden,
wantErrorType: "access_denied",
wantErrorDescContains: `The resource owner or authorization server denied the request. Missing the 'pinniped:request-audience' scope.`,
},
{
name: "dynamic client did not ask for the openid scope in the original authorization request, so the access token submitted during token exchange lacks the scope",
@@ -1096,9 +1099,10 @@ func TestTokenEndpointTokenExchange(t *testing.T) { // tests for grant_type "urn
modifyRequestHeaders: func(r *http.Request) {
r.SetBasicAuth(dynamicClientID, testutil.PlaintextPassword1)
},
requestedAudience: "some-workload-cluster",
wantStatus: http.StatusForbidden,
wantResponseBodyContains: `The resource owner or authorization server denied the request. missing the 'openid' scope`,
requestedAudience: "some-workload-cluster",
wantStatus: http.StatusForbidden,
wantErrorType: "access_denied",
wantErrorDescContains: `The resource owner or authorization server denied the request. Missing the 'openid' scope.`,
},
{
name: "dynamic client did not ask for the username scope in the original authorization request, so the session during token exchange has no username associated with it",
@@ -1125,37 +1129,42 @@ func TestTokenEndpointTokenExchange(t *testing.T) { // tests for grant_type "urn
modifyRequestHeaders: func(r *http.Request) {
r.SetBasicAuth(dynamicClientID, testutil.PlaintextPassword1)
},
requestedAudience: "some-workload-cluster",
wantStatus: http.StatusForbidden,
wantResponseBodyContains: `The resource owner or authorization server denied the request. No username found in session. Ensure that the 'username' scope was requested and granted at the authorization endpoint.`,
requestedAudience: "some-workload-cluster",
wantStatus: http.StatusForbidden,
wantErrorType: "access_denied",
wantErrorDescContains: `The resource owner or authorization server denied the request. No username found in session. Ensure that the 'username' scope was requested and granted at the authorization endpoint.`,
},
{
name: "missing audience",
authcodeExchange: doValidAuthCodeExchange,
requestedAudience: "",
wantStatus: http.StatusBadRequest,
wantResponseBodyContains: "missing audience parameter",
name: "missing audience",
authcodeExchange: doValidAuthCodeExchange,
requestedAudience: "",
wantStatus: http.StatusBadRequest,
wantErrorType: "invalid_request",
wantErrorDescContains: "Missing 'audience' parameter.",
},
{
name: "bad requested audience when it looks like the name of an OIDCClient CR",
authcodeExchange: doValidAuthCodeExchange,
requestedAudience: "client.oauth.pinniped.dev-some-client-abc123",
wantStatus: http.StatusBadRequest,
wantResponseBodyContains: "requested audience cannot contain '.pinniped.dev'",
name: "bad requested audience when it looks like the name of an OIDCClient CR",
authcodeExchange: doValidAuthCodeExchange,
requestedAudience: "client.oauth.pinniped.dev-some-client-abc123",
wantStatus: http.StatusBadRequest,
wantErrorType: "invalid_request",
wantErrorDescContains: "requested audience cannot contain '.pinniped.dev'",
},
{
name: "bad requested audience when it contains the substring .pinniped.dev because it is reserved for potential future usage",
authcodeExchange: doValidAuthCodeExchange,
requestedAudience: "something.pinniped.dev/some_aud",
wantStatus: http.StatusBadRequest,
wantResponseBodyContains: "requested audience cannot contain '.pinniped.dev'",
name: "bad requested audience when it contains the substring .pinniped.dev because it is reserved for potential future usage",
authcodeExchange: doValidAuthCodeExchange,
requestedAudience: "something.pinniped.dev/some_aud",
wantStatus: http.StatusBadRequest,
wantErrorType: "invalid_request",
wantErrorDescContains: "requested audience cannot contain '.pinniped.dev'",
},
{
name: "bad requested audience when it is the same name as the static public client pinniped-cli",
authcodeExchange: doValidAuthCodeExchange,
requestedAudience: "pinniped-cli",
wantStatus: http.StatusBadRequest,
wantResponseBodyContains: "requested audience cannot equal 'pinniped-cli'",
name: "bad requested audience when it is the same name as the static public client pinniped-cli",
authcodeExchange: doValidAuthCodeExchange,
requestedAudience: "pinniped-cli",
wantStatus: http.StatusBadRequest,
wantErrorType: "invalid_request",
wantErrorDescContains: "requested audience cannot equal 'pinniped-cli'",
},
{
name: "missing subject_token",
@@ -1164,8 +1173,9 @@ func TestTokenEndpointTokenExchange(t *testing.T) { // tests for grant_type "urn
modifyRequestParams: func(t *testing.T, params url.Values) {
params.Del("subject_token")
},
wantStatus: http.StatusBadRequest,
wantResponseBodyContains: "missing subject_token parameter",
wantStatus: http.StatusBadRequest,
wantErrorType: "invalid_request",
wantErrorDescContains: "Missing 'subject_token' parameter.",
},
{
name: "wrong subject_token_type",
@@ -1174,8 +1184,9 @@ func TestTokenEndpointTokenExchange(t *testing.T) { // tests for grant_type "urn
modifyRequestParams: func(t *testing.T, params url.Values) {
params.Set("subject_token_type", "invalid")
},
wantStatus: http.StatusBadRequest,
wantResponseBodyContains: `unsupported subject_token_type parameter value`,
wantStatus: http.StatusBadRequest,
wantErrorType: "invalid_request",
wantErrorDescContains: `Unsupported 'subject_token_type' parameter value, must be 'urn:ietf:params:oauth:token-type:access_token'.`,
},
{
name: "wrong requested_token_type",
@@ -1184,8 +1195,9 @@ func TestTokenEndpointTokenExchange(t *testing.T) { // tests for grant_type "urn
modifyRequestParams: func(t *testing.T, params url.Values) {
params.Set("requested_token_type", "invalid")
},
wantStatus: http.StatusBadRequest,
wantResponseBodyContains: `unsupported requested_token_type parameter value`,
wantStatus: http.StatusBadRequest,
wantErrorType: "invalid_request",
wantErrorDescContains: `Unsupported 'requested_token_type' parameter value, must be 'urn:ietf:params:oauth:token-type:jwt'.`,
},
{
name: "unsupported RFC8693 parameter",
@@ -1194,8 +1206,9 @@ func TestTokenEndpointTokenExchange(t *testing.T) { // tests for grant_type "urn
modifyRequestParams: func(t *testing.T, params url.Values) {
params.Set("resource", "some-resource-parameter-value")
},
wantStatus: http.StatusBadRequest,
wantResponseBodyContains: `unsupported parameter resource`,
wantStatus: http.StatusBadRequest,
wantErrorType: "invalid_request",
wantErrorDescContains: `Unsupported parameter 'resource'.`,
},
{
name: "bogus access token",
@@ -1204,8 +1217,9 @@ func TestTokenEndpointTokenExchange(t *testing.T) { // tests for grant_type "urn
modifyRequestParams: func(t *testing.T, params url.Values) {
params.Set("subject_token", "some-bogus-value")
},
wantStatus: http.StatusBadRequest,
wantResponseBodyContains: `Invalid token format`,
wantStatus: http.StatusUnauthorized,
wantErrorType: "request_unauthorized",
wantErrorDescContains: `The request could not be authorized. Invalid 'subject_token' parameter value.`,
},
{
name: "bad client ID",
@@ -1214,8 +1228,9 @@ func TestTokenEndpointTokenExchange(t *testing.T) { // tests for grant_type "urn
modifyRequestParams: func(t *testing.T, params url.Values) {
params.Set("client_id", "some-bogus-value")
},
wantStatus: http.StatusUnauthorized,
wantResponseBodyContains: `Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method).`,
wantStatus: http.StatusUnauthorized,
wantErrorType: "invalid_client",
wantErrorDescContains: `Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method).`,
},
{
name: "dynamic client uses wrong client secret",
@@ -1227,9 +1242,10 @@ func TestTokenEndpointTokenExchange(t *testing.T) { // tests for grant_type "urn
modifyRequestHeaders: func(r *http.Request) {
r.SetBasicAuth(dynamicClientID, "bad client secret")
},
requestedAudience: "some-workload-cluster",
wantStatus: http.StatusUnauthorized,
wantResponseBodyContains: `Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method).`,
requestedAudience: "some-workload-cluster",
wantStatus: http.StatusUnauthorized,
wantErrorType: "invalid_client",
wantErrorDescContains: `Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method).`,
},
{
name: "dynamic client uses wrong auth method (must use basic auth)",
@@ -1243,9 +1259,10 @@ func TestTokenEndpointTokenExchange(t *testing.T) { // tests for grant_type "urn
modifyRequestHeaders: func(r *http.Request) {
// would usually set the basic auth header here, but we don't for this test case
},
requestedAudience: "some-workload-cluster",
wantStatus: http.StatusUnauthorized,
wantResponseBodyContains: `Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method). The OAuth 2.0 Client supports client authentication method 'client_secret_basic', but method 'client_secret_post' was requested. You must configure the OAuth 2.0 client's 'token_endpoint_auth_method' value to accept 'client_secret_post'.`,
requestedAudience: "some-workload-cluster",
wantStatus: http.StatusUnauthorized,
wantErrorType: "invalid_client",
wantErrorDescContains: `Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method). The OAuth 2.0 Client supports client authentication method 'client_secret_basic', but method 'client_secret_post' was requested. You must configure the OAuth 2.0 client's 'token_endpoint_auth_method' value to accept 'client_secret_post'.`,
},
{
name: "different client used between authorize/authcode calls and the call to token exchange",
@@ -1257,21 +1274,71 @@ func TestTokenEndpointTokenExchange(t *testing.T) { // tests for grant_type "urn
modifyRequestHeaders: func(r *http.Request) {
r.SetBasicAuth(dynamicClientID, testutil.PlaintextPassword1) // use dynamic client for token exchange
},
requestedAudience: "some-workload-cluster",
wantStatus: http.StatusBadRequest,
wantResponseBodyContains: `The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client. The OAuth 2.0 Client ID from this request does not match the one from the authorize request.`,
requestedAudience: "some-workload-cluster",
wantStatus: http.StatusBadRequest,
wantErrorType: "invalid_grant",
wantErrorDescContains: `The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client. The OAuth 2.0 Client ID from this request does not match the one from the authorize request.`,
},
{
name: "valid access token, but deleted from storage",
name: "valid access token, but it was already deleted from storage",
authcodeExchange: doValidAuthCodeExchange,
requestedAudience: "some-workload-cluster",
modifyStorage: func(t *testing.T, storage *oidc.KubeStorage, pendingRequest *http.Request) {
modifyStorage: func(t *testing.T, storage *oidc.KubeStorage, secrets v1.SecretInterface, pendingRequest *http.Request) {
parts := strings.Split(pendingRequest.Form.Get("subject_token"), ".")
require.Len(t, parts, 2)
require.NoError(t, storage.DeleteAccessTokenSession(context.Background(), parts[1]))
},
wantStatus: http.StatusUnauthorized,
wantResponseBodyContains: `invalid subject_token`,
wantStatus: http.StatusUnauthorized,
wantErrorType: "request_unauthorized",
wantErrorDescContains: `Invalid 'subject_token' parameter value.`,
},
{
name: "valid access token, but it has already expired",
authcodeExchange: doValidAuthCodeExchange,
requestedAudience: "some-workload-cluster",
modifyStorage: func(t *testing.T, storage *oidc.KubeStorage, secrets v1.SecretInterface, pendingRequest *http.Request) {
// The fosite storage APIs don't offer a way to update an access token, so we will instead find the underlying
// storage Secret and update it in a more manual way. First get the access token's signature.
parts := strings.Split(pendingRequest.Form.Get("subject_token"), ".")
require.Len(t, parts, 2)
// Find the storage Secret for the access token by using its signature to compute the Secret name.
accessTokenSignature := parts[1]
accessTokenSecretName := getSecretNameFromSignature(t, accessTokenSignature, "access-token") // "access-token" is the storage type used in the Secret's name
accessTokenSecret, err := secrets.Get(context.Background(), accessTokenSecretName, metav1.GetOptions{})
require.NoError(t, err)
// Parse the session from the storage Secret.
savedSessionJSON := accessTokenSecret.Data["pinniped-storage-data"]
// Declare the appropriate empty struct, similar to how our kubestorage implementation
// of GetAccessTokenSession() does when parsing a session from a storage Secret.
accessTokenSession := &accesstoken.Session{
Request: &fosite.Request{
Client: &clientregistry.Client{},
Session: &psession.PinnipedSession{},
},
}
// Parse the session JSON and fill the empty struct with its data.
err = json.Unmarshal(savedSessionJSON, accessTokenSession)
require.NoError(t, err)
// Change the access token's expiration time to be one hour ago, so it will be considered already expired.
oneHourAgoInUTC := time.Now().UTC().Add(-1 * time.Hour)
accessTokenSession.Request.Session.(*psession.PinnipedSession).Fosite.SetExpiresAt(fosite.AccessToken, oneHourAgoInUTC)
// Write the updated session back to the access token's storage Secret.
updatedSessionJSON, err := json.Marshal(accessTokenSession)
require.NoError(t, err)
accessTokenSecret.Data["pinniped-storage-data"] = updatedSessionJSON
_, err = secrets.Update(context.Background(), accessTokenSecret, metav1.UpdateOptions{})
require.NoError(t, err)
// Just to be sure that this test setup is valid, confirm that the code above correctly updated the
// access token's expiration time by reading it again, this time performing the read using the
// kubestorage API instead of the manual/direct approach used above.
session, err := storage.GetAccessTokenSession(context.Background(), accessTokenSignature, nil)
require.NoError(t, err)
expiresAt := session.GetSession().GetExpiresAt(fosite.AccessToken)
require.Equal(t, oneHourAgoInUTC, expiresAt)
},
wantStatus: http.StatusUnauthorized,
wantErrorType: "invalid_token",
wantErrorDescContains: `Token expired. Access token expired at `,
},
{
name: "access token missing pinniped:request-audience scope",
@@ -1289,9 +1356,10 @@ func TestTokenEndpointTokenExchange(t *testing.T) { // tests for grant_type "urn
wantGroups: goodGroups,
},
},
requestedAudience: "some-workload-cluster",
wantStatus: http.StatusForbidden,
wantResponseBodyContains: `missing the 'pinniped:request-audience' scope`,
requestedAudience: "some-workload-cluster",
wantStatus: http.StatusForbidden,
wantErrorType: "access_denied",
wantErrorDescContains: `Missing the 'pinniped:request-audience' scope.`,
},
{
name: "access token missing openid scope",
@@ -1309,9 +1377,10 @@ func TestTokenEndpointTokenExchange(t *testing.T) { // tests for grant_type "urn
wantGroups: goodGroups,
},
},
requestedAudience: "some-workload-cluster",
wantStatus: http.StatusForbidden,
wantResponseBodyContains: `missing the 'openid' scope`,
requestedAudience: "some-workload-cluster",
wantStatus: http.StatusForbidden,
wantErrorType: "access_denied",
wantErrorDescContains: `Missing the 'openid' scope.`,
},
{
name: "token minting failure",
@@ -1323,9 +1392,10 @@ func TestTokenEndpointTokenExchange(t *testing.T) { // tests for grant_type "urn
makeOathHelper: makeOauthHelperWithJWTKeyThatWorksOnlyOnce,
want: successfulAuthCodeExchange,
},
requestedAudience: "some-workload-cluster",
wantStatus: http.StatusServiceUnavailable,
wantResponseBodyContains: `The authorization server is currently unable to handle the request`,
requestedAudience: "some-workload-cluster",
wantStatus: http.StatusServiceUnavailable,
wantErrorType: "temporarily_unavailable",
wantErrorDescContains: `The authorization server is currently unable to handle the request`,
},
}
for _, test := range tests {
@@ -1341,13 +1411,13 @@ func TestTokenEndpointTokenExchange(t *testing.T) { // tests for grant_type "urn
request := happyTokenExchangeRequest(test.requestedAudience, parsedAuthcodeExchangeResponseBody["access_token"].(string))
if test.modifyStorage != nil {
test.modifyStorage(t, storage, request)
test.modifyStorage(t, storage, secrets, request)
}
if test.modifyRequestParams != nil {
test.modifyRequestParams(t, request.Form)
}
req := httptest.NewRequest("POST", "/path/shouldn't/matter", body(request.Form).ReadCloser())
req := httptest.NewRequest("POST", "/token/exchange/path/shouldn't/matter", body(request.Form).ReadCloser())
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
rsp = httptest.NewRecorder()
@@ -1369,12 +1439,23 @@ func TestTokenEndpointTokenExchange(t *testing.T) { // tests for grant_type "urn
require.Equal(t, test.wantStatus, rsp.Code)
testutil.RequireEqualContentType(t, rsp.Header().Get("Content-Type"), "application/json")
if test.wantResponseBodyContains != "" {
require.Contains(t, rsp.Body.String(), test.wantResponseBodyContains)
}
// The remaining assertions apply only to the happy path.
var parsedResponseBody map[string]interface{}
require.NoError(t, json.Unmarshal(rsp.Body.Bytes(), &parsedResponseBody))
if rsp.Code != http.StatusOK {
// All error responses should have two JSON keys.
require.Len(t, parsedResponseBody, 2)
errorType := parsedResponseBody["error"]
require.NotEmpty(t, errorType)
require.Equal(t, test.wantErrorType, errorType)
errorDesc := parsedResponseBody["error_description"]
require.NotEmpty(t, errorDesc)
require.Contains(t, errorDesc, test.wantErrorDescContains)
// The remaining assertions apply only to the happy path.
return
}
@@ -1384,15 +1465,12 @@ func TestTokenEndpointTokenExchange(t *testing.T) { // tests for grant_type "urn
err = firstIDTokenDecoded.UnsafeClaimsWithoutVerification(&claimsOfFirstIDToken)
require.NoError(t, err)
var responseBody map[string]interface{}
require.NoError(t, json.Unmarshal(rsp.Body.Bytes(), &responseBody))
require.Contains(t, responseBody, "access_token")
require.Equal(t, "N_A", responseBody["token_type"])
require.Equal(t, "urn:ietf:params:oauth:token-type:jwt", responseBody["issued_token_type"])
require.Contains(t, parsedResponseBody, "access_token")
require.Equal(t, "N_A", parsedResponseBody["token_type"])
require.Equal(t, "urn:ietf:params:oauth:token-type:jwt", parsedResponseBody["issued_token_type"])
// Parse the returned token.
parsedJWT, err := jose.ParseSigned(responseBody["access_token"].(string))
parsedJWT, err := jose.ParseSigned(parsedResponseBody["access_token"].(string))
require.NoError(t, err)
var tokenClaims map[string]interface{}
require.NoError(t, json.Unmarshal(parsedJWT.UnsafePayloadWithoutVerification(), &tokenClaims))
@@ -1674,7 +1752,7 @@ func TestRefreshGrant(t *testing.T) {
kubeResources func(t *testing.T, supervisorClient *supervisorfake.Clientset, kubeClient *fake.Clientset)
authcodeExchange authcodeExchangeInputs
refreshRequest refreshRequestInputs
modifyRefreshTokenStorage func(t *testing.T, oauthStore *oidc.KubeStorage, refreshToken string)
modifyRefreshTokenStorage func(t *testing.T, oauthStore *oidc.KubeStorage, secrets v1.SecretInterface, refreshToken string)
}{
{
name: "happy path refresh grant with openid scope granted (id token returned)",
@@ -2447,6 +2525,55 @@ func TestRefreshGrant(t *testing.T) {
),
},
},
{
name: "when a valid refresh token is sent in the refresh request, but the token has already expired",
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProviderBuilder().Build()),
authcodeExchange: happyAuthcodeExchangeInputsForOIDCUpstream,
modifyRefreshTokenStorage: func(t *testing.T, oauthStore *oidc.KubeStorage, secrets v1.SecretInterface, refreshToken string) {
// The fosite storage APIs don't offer a way to update a refresh token, so we will instead find the underlying
// storage Secret and update it in a more manual way. First get the refresh token's signature.
refreshTokenSignature := getFositeDataSignature(t, refreshToken)
// Find the storage Secret for the refresh token by using its signature to compute the Secret name.
refreshTokenSecretName := getSecretNameFromSignature(t, refreshTokenSignature, "refresh-token") // "refresh-token" is the storage type used in the Secret's name
refreshTokenSecret, err := secrets.Get(context.Background(), refreshTokenSecretName, metav1.GetOptions{})
require.NoError(t, err)
// Parse the session from the storage Secret.
savedSessionJSON := refreshTokenSecret.Data["pinniped-storage-data"]
// Declare the appropriate empty struct, similar to how our kubestorage implementation
// of GetRefreshTokenSession() does when parsing a session from a storage Secret.
refreshTokenSession := &refreshtoken.Session{
Request: &fosite.Request{
Client: &clientregistry.Client{},
Session: &psession.PinnipedSession{},
},
}
// Parse the session JSON and fill the empty struct with its data.
err = json.Unmarshal(savedSessionJSON, refreshTokenSession)
require.NoError(t, err)
// Change the refresh token's expiration time to be one hour ago, so it will be considered already expired.
oneHourAgoInUTC := time.Now().UTC().Add(-1 * time.Hour)
refreshTokenSession.Request.Session.(*psession.PinnipedSession).Fosite.SetExpiresAt(fosite.RefreshToken, oneHourAgoInUTC)
// Write the updated session back to the refresh token's storage Secret.
updatedSessionJSON, err := json.Marshal(refreshTokenSession)
require.NoError(t, err)
refreshTokenSecret.Data["pinniped-storage-data"] = updatedSessionJSON
_, err = secrets.Update(context.Background(), refreshTokenSecret, metav1.UpdateOptions{})
require.NoError(t, err)
// Just to be sure that this test setup is valid, confirm that the code above correctly updated the
// refresh token's expiration time by reading it again, this time performing the read using the
// kubestorage API instead of the manual/direct approach used above.
session, err := oauthStore.GetRefreshTokenSession(context.Background(), refreshTokenSignature, nil)
require.NoError(t, err)
expiresAt := session.GetSession().GetExpiresAt(fosite.RefreshToken)
require.Equal(t, oneHourAgoInUTC, expiresAt)
},
refreshRequest: refreshRequestInputs{
want: tokenEndpointResponseExpectedValues{
wantStatus: http.StatusBadRequest,
wantErrorResponseBody: fositeInvalidAuthCodeErrorBody,
},
},
},
{
name: "when a bad refresh token is sent in the refresh request",
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProviderBuilder().Build()),
@@ -3354,7 +3481,7 @@ func TestRefreshGrant(t *testing.T) {
URL: ldapUpstreamURL,
}),
authcodeExchange: happyAuthcodeExchangeInputsForLDAPUpstream,
modifyRefreshTokenStorage: func(t *testing.T, oauthStore *oidc.KubeStorage, refreshToken string) {
modifyRefreshTokenStorage: func(t *testing.T, oauthStore *oidc.KubeStorage, secrets v1.SecretInterface, refreshToken string) {
refreshTokenSignature := getFositeDataSignature(t, refreshToken)
firstRequester, err := oauthStore.GetRefreshTokenSession(context.Background(), refreshTokenSignature, nil)
require.NoError(t, err)
@@ -3391,7 +3518,7 @@ func TestRefreshGrant(t *testing.T) {
happyLDAPCustomSessionData,
),
},
modifyRefreshTokenStorage: func(t *testing.T, oauthStore *oidc.KubeStorage, refreshToken string) {
modifyRefreshTokenStorage: func(t *testing.T, oauthStore *oidc.KubeStorage, secrets v1.SecretInterface, refreshToken string) {
refreshTokenSignature := getFositeDataSignature(t, refreshToken)
firstRequester, err := oauthStore.GetRefreshTokenSession(context.Background(), refreshTokenSignature, nil)
require.NoError(t, err)
@@ -3428,7 +3555,7 @@ func TestRefreshGrant(t *testing.T) {
happyLDAPCustomSessionData,
),
},
modifyRefreshTokenStorage: func(t *testing.T, oauthStore *oidc.KubeStorage, refreshToken string) {
modifyRefreshTokenStorage: func(t *testing.T, oauthStore *oidc.KubeStorage, secrets v1.SecretInterface, refreshToken string) {
refreshTokenSignature := getFositeDataSignature(t, refreshToken)
firstRequester, err := oauthStore.GetRefreshTokenSession(context.Background(), refreshTokenSignature, nil)
require.NoError(t, err)
@@ -3505,7 +3632,7 @@ func TestRefreshGrant(t *testing.T) {
URL: ldapUpstreamURL,
}),
authcodeExchange: happyAuthcodeExchangeInputsForLDAPUpstream,
modifyRefreshTokenStorage: func(t *testing.T, oauthStore *oidc.KubeStorage, refreshToken string) {
modifyRefreshTokenStorage: func(t *testing.T, oauthStore *oidc.KubeStorage, secrets v1.SecretInterface, refreshToken string) {
refreshTokenSignature := getFositeDataSignature(t, refreshToken)
firstRequester, err := oauthStore.GetRefreshTokenSession(context.Background(), refreshTokenSignature, nil)
require.NoError(t, err)
@@ -3560,7 +3687,7 @@ func TestRefreshGrant(t *testing.T) {
require.NotEmpty(t, firstRefreshToken)
if test.modifyRefreshTokenStorage != nil {
test.modifyRefreshTokenStorage(t, oauthStore, firstRefreshToken)
test.modifyRefreshTokenStorage(t, oauthStore, secrets, firstRefreshToken)
}
reqContextWarningRecorder := &TestWarningRecorder{}
@@ -3899,7 +4026,7 @@ func (b body) WithPKCE(verifier string) body {
}
func (b body) ReadCloser() io.ReadCloser {
return ioutil.NopCloser(strings.NewReader(url.Values(b).Encode()))
return io.NopCloser(strings.NewReader(url.Values(b).Encode()))
}
func (b body) with(param, value string) body {
@@ -4573,3 +4700,14 @@ func (t *TestWarningRecorder) AddWarning(agent, text string) {
Text: text,
})
}
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)
}
+16 -13
View File
@@ -19,8 +19,8 @@ import (
)
const (
tokenTypeAccessToken = "urn:ietf:params:oauth:token-type:access_token" //nolint: gosec
tokenTypeJWT = "urn:ietf:params:oauth:token-type:jwt" //nolint: gosec
tokenTypeAccessToken = "urn:ietf:params:oauth:token-type:access_token" //nolint:gosec
tokenTypeJWT = "urn:ietf:params:oauth:token-type:jwt" //nolint:gosec
)
type stsParams struct {
@@ -83,10 +83,10 @@ func (t *TokenExchangeHandler) PopulateTokenEndpointResponse(ctx context.Context
// Require that the incoming access token has the pinniped:request-audience and OpenID scopes.
if !originalRequester.GetGrantedScopes().Has(oidcapi.ScopeRequestAudience) {
return errors.WithStack(fosite.ErrAccessDenied.WithHintf("missing the %q scope", oidcapi.ScopeRequestAudience))
return errors.WithStack(fosite.ErrAccessDenied.WithHintf("Missing the %q scope.", oidcapi.ScopeRequestAudience))
}
if !originalRequester.GetGrantedScopes().Has(oidcapi.ScopeOpenID) {
return errors.WithStack(fosite.ErrAccessDenied.WithHintf("missing the %q scope", oidcapi.ScopeOpenID))
return errors.WithStack(fosite.ErrAccessDenied.WithHintf("Missing the %q scope.", oidcapi.ScopeOpenID))
}
// Check that the stored session meets the minimum requirements for token exchange.
@@ -135,19 +135,19 @@ func (t *TokenExchangeHandler) validateParams(params url.Values) (*stsParams, er
// Validate some required parameters.
result.requestedAudience = params.Get("audience")
if result.requestedAudience == "" {
return nil, fosite.ErrInvalidRequest.WithHint("missing audience parameter")
return nil, fosite.ErrInvalidRequest.WithHint("Missing 'audience' parameter.")
}
result.subjectAccessToken = params.Get("subject_token")
if result.subjectAccessToken == "" {
return nil, fosite.ErrInvalidRequest.WithHint("missing subject_token parameter")
return nil, fosite.ErrInvalidRequest.WithHint("Missing 'subject_token' parameter.")
}
// Validate some parameters with hardcoded values we support.
if params.Get("subject_token_type") != tokenTypeAccessToken {
return nil, fosite.ErrInvalidRequest.WithHintf("unsupported subject_token_type parameter value, must be %q", tokenTypeAccessToken)
return nil, fosite.ErrInvalidRequest.WithHintf("Unsupported 'subject_token_type' parameter value, must be %q.", tokenTypeAccessToken)
}
if params.Get("requested_token_type") != tokenTypeJWT {
return nil, fosite.ErrInvalidRequest.WithHintf("unsupported requested_token_type parameter value, must be %q", tokenTypeJWT)
return nil, fosite.ErrInvalidRequest.WithHintf("Unsupported 'requested_token_type' parameter value, must be %q.", tokenTypeJWT)
}
// Validate that none of these unsupported parameters were sent. These are optional and we do not currently support them.
@@ -158,7 +158,7 @@ func (t *TokenExchangeHandler) validateParams(params url.Values) (*stsParams, er
"actor_token_type",
} {
if params.Get(param) != "" {
return nil, fosite.ErrInvalidRequest.WithHintf("unsupported parameter %s", param)
return nil, fosite.ErrInvalidRequest.WithHintf("Unsupported parameter %q.", param)
}
}
@@ -184,13 +184,16 @@ func (t *TokenExchangeHandler) validateParams(params url.Values) (*stsParams, er
}
func (t *TokenExchangeHandler) validateAccessToken(ctx context.Context, requester fosite.AccessRequester, accessToken string) (fosite.Requester, error) {
if err := t.accessTokenStrategy.ValidateAccessToken(ctx, requester, accessToken); err != nil {
return nil, errors.WithStack(err)
}
// Look up the access token's stored session data.
signature := t.accessTokenStrategy.AccessTokenSignature(accessToken)
originalRequester, err := t.accessTokenStorage.GetAccessTokenSession(ctx, signature, requester.GetSession())
if err != nil {
return nil, fosite.ErrRequestUnauthorized.WithWrap(err).WithHint("invalid subject_token")
// The access token was not found, or there was some other error while reading it.
return nil, fosite.ErrRequestUnauthorized.WithWrap(err).WithHint("Invalid 'subject_token' parameter value.")
}
// Validate the access token using its stored session data, which includes its expiration time.
if err := t.accessTokenStrategy.ValidateAccessToken(ctx, originalRequester, accessToken); err != nil {
return nil, errors.WithStack(err)
}
return originalRequester, nil
}
+2 -2
View File
@@ -1,4 +1,4 @@
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package ownerref
@@ -64,7 +64,7 @@ func New(refObj kubeclient.Object) kubeclient.Middleware {
})
}
//nolint: gochecknoglobals
//nolint:gochecknoglobals
var namespaceGVK = corev1.SchemeGroupVersion.WithKind("Namespace")
func isNamespace(obj kubeclient.Object) bool {
+1 -1
View File
@@ -88,7 +88,7 @@ func ValidateAndSetLogLevelAndFormatGlobally(ctx context.Context, spec LogSpec)
setGlobalLoggers(log, flush)
// nolint: exhaustive // the switch above is exhaustive for format already
//nolint:exhaustive // the switch above is exhaustive for format already
switch spec.Format {
case FormatCLI:
return nil // do not spawn go routines on the CLI to allow the CLI to call this more than once
+2 -2
View File
@@ -1,4 +1,4 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package plog
@@ -136,7 +136,7 @@ func TestFormat(t *testing.T) {
`go.pinniped.dev/internal/plog.TestFormat
%s/config_test.go:%d
testing.tRunner
%s/src/testing/testing.go:1439`,
%s/src/testing/testing.go:1446`,
wd, startLogLine+2+13+14+11+12+24, runtime.GOROOT(),
),
),
+2 -2
View File
@@ -15,7 +15,7 @@ import (
"k8s.io/klog/v2"
)
// nolint: gochecknoglobals
//nolint:gochecknoglobals
var (
// note that these globals have no locks on purpose - they are expected to be set at init and then again after config parsing.
globalLevel zap.AtomicLevel
@@ -26,7 +26,7 @@ var (
sinkMap sync.Map
)
// nolint: gochecknoinits
//nolint:gochecknoinits
func init() {
// make sure we always have a functional global logger
globalLevel = zap.NewAtomicLevelAt(0) // log at the 0 verbosity level to start with, i.e. the "always" logs
@@ -44,6 +44,8 @@ func (*REST) New() runtime.Object {
return &clientsecretapi.OIDCClientSecretRequest{}
}
func (*REST) Destroy() {}
func (*REST) NewList() runtime.Object {
return &clientsecretapi.OIDCClientSecretRequestList{}
}
+3 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Package credentialrequest provides REST functionality for the CredentialRequest resource.
@@ -59,6 +59,8 @@ func (*REST) New() runtime.Object {
return &loginapi.TokenCredentialRequest{}
}
func (*REST) Destroy() {}
func (*REST) NewList() runtime.Object {
return &loginapi.TokenCredentialRequestList{}
}
@@ -71,7 +71,7 @@ func TestCreate(t *testing.T) {
it.Before(func() {
r = require.New(t)
ctrl = gomock.NewController(t)
logger = testutil.NewTranscriptLogger(t) // nolint: staticcheck // old test with lots of log statements
logger = testutil.NewTranscriptLogger(t) //nolint:staticcheck // old test with lots of log statements
klog.SetLogger(logr.New(logger)) // this is unfortunately a global logger, so can't run these tests in parallel :(
})
+3 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package whoamirequest
@@ -45,6 +45,8 @@ func (*REST) New() runtime.Object {
return &identityapi.WhoAmIRequest{}
}
func (*REST) Destroy() {}
func (*REST) NewList() runtime.Object {
return &identityapi.WhoAmIRequestList{}
}
+1 -1
View File
@@ -71,7 +71,7 @@ func (c completedConfig) New() (*PinnipedServer, error) {
GenericAPIServer: genericServer,
}
var errs []error //nolint: prealloc
var errs []error //nolint:prealloc
for _, f := range []func() (schema.GroupVersionResource, rest.Storage){
func() (schema.GroupVersionResource, rest.Storage) {
clientSecretReqGVR := c.ExtraConfig.ClientSecretSupervisorGroupVersion.WithResource("oidcclientsecretrequests")
+4 -3
View File
@@ -77,8 +77,9 @@ func startServer(ctx context.Context, shutdown *sync.WaitGroup, l net.Listener,
handler = withBootstrapPaths(handler, "/healthz") // only health checks are allowed for bootstrap connections
server := http.Server{
Handler: handler,
ConnContext: withBootstrapConnCtx,
Handler: handler,
ConnContext: withBootstrapConnCtx,
ReadHeaderTimeout: 10 * time.Second,
}
shutdown.Add(1)
@@ -288,7 +289,7 @@ func prepareControllers(
pinnipedClient,
pinnipedInformers.IDP().V1alpha1().OIDCIdentityProviders(),
secretInformer,
plog.Logr(), // nolint: staticcheck // old controller with lots of log statements
plog.Logr(), //nolint:staticcheck // old controller with lots of log statements
controllerlib.WithInformer,
),
singletonWorker).
+20 -17
View File
@@ -1,25 +1,28 @@
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Package fakekubeapi contains a *very* simple httptest.Server that can be used to stand in for
// a real Kube API server in tests.
//
// Usage:
// func TestSomething(t *testing.T) {
// resources := map[string]kubeclient.Object{
// // store preexisting resources here
// "/api/v1/namespaces/default/pods/some-pod-name": &corev1.Pod{...},
// }
// server, restConfig := fakekubeapi.Start(t, resources)
// defer server.Close()
// client := kubeclient.New(kubeclient.WithConfig(restConfig))
// // do stuff with client...
// }
/*
Package fakekubeapi contains a *very* simple httptest.Server that can be used to stand in for
a real Kube API server in tests.
Usage:
func TestSomething(t *testing.T) {
resources := map[string]kubeclient.Object{
// store preexisting resources here
"/api/v1/namespaces/default/pods/some-pod-name": &corev1.Pod{...},
}
server, restConfig := fakekubeapi.Start(t, resources)
defer server.Close()
client := kubeclient.New(kubeclient.WithConfig(restConfig))
// do stuff with client...
}
*/
package fakekubeapi
import (
"fmt"
"io/ioutil"
"io"
"mime"
"net/http"
"net/http/httptest"
@@ -104,13 +107,13 @@ func decodeObj(r *http.Request) (runtime.Object, error) {
return nil, httperr.Wrap(http.StatusUnsupportedMediaType, "could not parse mime type from content-type header", err)
}
body, err := ioutil.ReadAll(r.Body)
body, err := io.ReadAll(r.Body)
if err != nil {
return nil, httperr.Wrap(http.StatusInternalServerError, "read body", err)
}
var obj runtime.Object
var errs []error //nolint: prealloc
var errs []error //nolint:prealloc
codecsThatWeUseInOurCode := []runtime.NegotiatedSerializer{
kubescheme.Codecs,
aggregatorclientscheme.Codecs,
+2 -3
View File
@@ -1,11 +1,10 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package testutil
import (
"io"
"io/ioutil"
"os"
"testing"
@@ -23,7 +22,7 @@ func (e *ErrorWriter) Write([]byte) (int, error) { return 0, e.ReturnError }
func WriteStringToTempFile(t *testing.T, filename string, fileBody string) *os.File {
t.Helper()
f, err := ioutil.TempFile("", filename)
f, err := os.CreateTemp("", filename)
require.NoError(t, err)
deferMe := func() {
err := os.Remove(f.Name())
+2 -1
View File
@@ -1,6 +1,7 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//go:build !go1.14
// +build !go1.14
package testutil
+2 -2
View File
@@ -1,13 +1,13 @@
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//nolint:goimports // not an import
//go:build go1.14
// +build go1.14
package testutil
import (
"io/ioutil"
"io/ioutil" //nolint:staticcheck // ioutil is deprecated, but this file is for go1.14
"os"
"testing"
+5 -3
View File
@@ -1,4 +1,4 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package testutil
@@ -9,6 +9,7 @@ import (
"net"
"net/http"
"testing"
"time"
"github.com/stretchr/testify/require"
@@ -33,8 +34,9 @@ func TLSTestServerWithCert(t *testing.T, handler http.HandlerFunc, certificate *
c.Certificates = []tls.Certificate{*certificate}
server := http.Server{
TLSConfig: c,
Handler: handler,
TLSConfig: c,
Handler: handler,
ReadHeaderTimeout: 10 * time.Second,
}
l, err := net.Listen("tcp", "127.0.0.1:0")
+4 -4
View File
@@ -74,16 +74,16 @@ func TestProviderConfig(t *testing.T) {
// Test JWTs generated with https://smallstep.com/docs/cli/crypto/jwt/:
// step crypto keypair key.pub key.priv --kty RSA --no-password --insecure --force && echo '{"at_hash": "invalid-at-hash"}' | step crypto jwt sign --key key.priv --aud test-client-id --sub test-user --subtle --kid="test-kid" --jti="test-jti"
invalidAccessTokenHashIDToken = "eyJhbGciOiJSUzI1NiIsImtpZCI6InRlc3Qta2lkIiwidHlwIjoiSldUIn0.eyJhdF9oYXNoIjoiaW52YWxpZC1hdC1oYXNoIiwiYXVkIjoidGVzdC1jbGllbnQtaWQiLCJpYXQiOjE2MDIyODM3OTEsImp0aSI6InRlc3QtanRpIiwibmJmIjoxNjAyMjgzNzkxLCJzdWIiOiJ0ZXN0LXVzZXIifQ.jryXr4jiwcf79wBLaHpjdclEYHoUFGhvTu95QyA6Hnk9NQ0x1vsWYurtj7a8uKydNPryC_HNZi9QTAE_tRIJjycseog3695-5y4B4EZlqL-a94rdOtffuF2O_lnPbKvoja9EKNrp0kLBCftFRHhLAEwuP0N9E5padZwPpIGK0yE_JqljnYgCySvzsQu7tasR38yaULny13h3mtp2WRHPG5DrLyuBuF8Z01hSgRi5hGcVpgzTwBgV5-eMaSUCUo-ZDkqUsLQI6dVlaikCSKYZRb53HeexH0tB_R9PJJHY7mIr-rS76kkQEx9pLuVnheIH9Oc6zbdYWg-zWMijopA8Pg" //nolint: gosec
invalidAccessTokenHashIDToken = "eyJhbGciOiJSUzI1NiIsImtpZCI6InRlc3Qta2lkIiwidHlwIjoiSldUIn0.eyJhdF9oYXNoIjoiaW52YWxpZC1hdC1oYXNoIiwiYXVkIjoidGVzdC1jbGllbnQtaWQiLCJpYXQiOjE2MDIyODM3OTEsImp0aSI6InRlc3QtanRpIiwibmJmIjoxNjAyMjgzNzkxLCJzdWIiOiJ0ZXN0LXVzZXIifQ.jryXr4jiwcf79wBLaHpjdclEYHoUFGhvTu95QyA6Hnk9NQ0x1vsWYurtj7a8uKydNPryC_HNZi9QTAE_tRIJjycseog3695-5y4B4EZlqL-a94rdOtffuF2O_lnPbKvoja9EKNrp0kLBCftFRHhLAEwuP0N9E5padZwPpIGK0yE_JqljnYgCySvzsQu7tasR38yaULny13h3mtp2WRHPG5DrLyuBuF8Z01hSgRi5hGcVpgzTwBgV5-eMaSUCUo-ZDkqUsLQI6dVlaikCSKYZRb53HeexH0tB_R9PJJHY7mIr-rS76kkQEx9pLuVnheIH9Oc6zbdYWg-zWMijopA8Pg" //nolint:gosec
// step crypto keypair key.pub key.priv --kty RSA --no-password --insecure --force && echo '{"nonce": "invalid-nonce"}' | step crypto jwt sign --key key.priv --aud test-client-id --sub test-user --subtle --kid="test-kid" --jti="test-jti"
invalidNonceIDToken = "eyJhbGciOiJSUzI1NiIsImtpZCI6InRlc3Qta2lkIiwidHlwIjoiSldUIn0.eyJhdWQiOiJ0ZXN0LWNsaWVudC1pZCIsImlhdCI6MTYwMjI4Mzc0MSwianRpIjoidGVzdC1qdGkiLCJuYmYiOjE2MDIyODM3NDEsIm5vbmNlIjoiaW52YWxpZC1ub25jZSIsInN1YiI6InRlc3QtdXNlciJ9.PRpq-7j5djaIAkraL-8t8ad9Xm4hM8RW67gyD1VIe0BecWeBFxsTuh3SZVKM9zmcwTgjudsyn8kQOwipDa49IN4PV8FcJA_uUJZi2wiqGJUSTG2K5I89doV_7e0RM1ZYIDDW1G2heKJNW7MbKkX7iEPr7u4MyEzswcPcupbyDA-CQFeL95vgwawoqa6yO94ympTbozqiNfj6Xyw_nHtThQnstjWsJZ9s2mUgppZezZv4HZYTQ7c3e_bzwhWgCzh2CSDJn9_Ra_n_4GcVkpHbsHTP35dFsnf0vactPx6CAu6A1-Apk-BruCktpZ3B4Ercf1UnUOHdGqzQKJtqvB03xQ" //nolint: gosec
invalidNonceIDToken = "eyJhbGciOiJSUzI1NiIsImtpZCI6InRlc3Qta2lkIiwidHlwIjoiSldUIn0.eyJhdWQiOiJ0ZXN0LWNsaWVudC1pZCIsImlhdCI6MTYwMjI4Mzc0MSwianRpIjoidGVzdC1qdGkiLCJuYmYiOjE2MDIyODM3NDEsIm5vbmNlIjoiaW52YWxpZC1ub25jZSIsInN1YiI6InRlc3QtdXNlciJ9.PRpq-7j5djaIAkraL-8t8ad9Xm4hM8RW67gyD1VIe0BecWeBFxsTuh3SZVKM9zmcwTgjudsyn8kQOwipDa49IN4PV8FcJA_uUJZi2wiqGJUSTG2K5I89doV_7e0RM1ZYIDDW1G2heKJNW7MbKkX7iEPr7u4MyEzswcPcupbyDA-CQFeL95vgwawoqa6yO94ympTbozqiNfj6Xyw_nHtThQnstjWsJZ9s2mUgppZezZv4HZYTQ7c3e_bzwhWgCzh2CSDJn9_Ra_n_4GcVkpHbsHTP35dFsnf0vactPx6CAu6A1-Apk-BruCktpZ3B4Ercf1UnUOHdGqzQKJtqvB03xQ" //nolint:gosec
// step crypto keypair key.pub key.priv --kty RSA --no-password --insecure --force && echo '{"foo": "bar", "bat": "baz"}' | step crypto jwt sign --key key.priv --aud test-client-id --sub '' --subtle --kid="test-kid" --jti="test-jti"
invalidSubClaim = "eyJhbGciOiJSUzI1NiIsImtpZCI6InRlc3Qta2lkIiwidHlwIjoiSldUIn0.eyJhdWQiOiJ0ZXN0LWNsaWVudC1pZCIsImJhdCI6ImJheiIsImZvbyI6ImJhciIsImlhdCI6MTYxMDIxOTY5MCwianRpIjoidGVzdC1qdGkiLCJuYmYiOjE2MTAyMTk2OTB9.CXgUarh9A8QByF_ddw0W1Cldl_n1qmry2cZh9U0Avi5sl7hb1y22MadDLQslvnx0NKx6EdbwI-El7QxDy0SzwomJomFL7WNd5gGk-Ilq9O_emaHekbpphZ5kxyudsAGUYGxrg1zysv1k5JPhnLnOUMcE7wa0uPLDWnrlAMzqHvnbjI3lakZ8v4-dfAKUIUGi3ycwuAh9BdpydwAsSNOpGBM55-O8911dqVfZKiFNNUeHYE1qlnbhCz7_ykLrljao0nRBbEf9FXGolCdhIaglt0LtaZvll9T9StIbSpcRaBGuRm8toTezmhmHjU-iCc0jGeVKsp8eTyOuJllqDSS-uw"
// step crypto keypair key.pub key.priv --kty RSA --no-password --insecure --force && echo '{"foo": "bar", "bat": "baz"}' | step crypto jwt sign --key key.priv --aud test-client-id --sub test-user --subtle --kid="test-kid" --jti="test-jti"
validIDToken = "eyJhbGciOiJSUzI1NiIsImtpZCI6InRlc3Qta2lkIiwidHlwIjoiSldUIn0.eyJhdWQiOiJ0ZXN0LWNsaWVudC1pZCIsImJhdCI6ImJheiIsImZvbyI6ImJhciIsImlhdCI6MTYwNjc2ODU5MywianRpIjoidGVzdC1qdGkiLCJuYmYiOjE2MDY3Njg1OTMsInN1YiI6InRlc3QtdXNlciJ9.DuqVZ7pGhHqKz7gNr4j2W1s1N8YrSltktH4wW19L4oD1OE2-O72jAnNj5xdjilsa8l7h9ox-5sMF0Tkh3BdRlHQK9dEtNm9tW-JreUnWJ3LCqUs-LZp4NG7edvq2sH_1Bn7O2_NQV51s8Pl04F60CndjQ4NM-6WkqDQTKyY6vJXU7idvM-6TM2HJZK-Na88cOJ9KIK37tL5DhcbsHVF47Dq8uPZ0KbjNQjJLAIi_1GeQBgc6yJhDUwRY4Xu6S0dtTHA6xTI8oSXoamt4bkViEHfJBp97LZQiNz8mku5pVc0aNwP1p4hMHxRHhLXrJjbh-Hx4YFjxtOnIq9t1mHlD4A" //nolint: gosec
validIDToken = "eyJhbGciOiJSUzI1NiIsImtpZCI6InRlc3Qta2lkIiwidHlwIjoiSldUIn0.eyJhdWQiOiJ0ZXN0LWNsaWVudC1pZCIsImJhdCI6ImJheiIsImZvbyI6ImJhciIsImlhdCI6MTYwNjc2ODU5MywianRpIjoidGVzdC1qdGkiLCJuYmYiOjE2MDY3Njg1OTMsInN1YiI6InRlc3QtdXNlciJ9.DuqVZ7pGhHqKz7gNr4j2W1s1N8YrSltktH4wW19L4oD1OE2-O72jAnNj5xdjilsa8l7h9ox-5sMF0Tkh3BdRlHQK9dEtNm9tW-JreUnWJ3LCqUs-LZp4NG7edvq2sH_1Bn7O2_NQV51s8Pl04F60CndjQ4NM-6WkqDQTKyY6vJXU7idvM-6TM2HJZK-Na88cOJ9KIK37tL5DhcbsHVF47Dq8uPZ0KbjNQjJLAIi_1GeQBgc6yJhDUwRY4Xu6S0dtTHA6xTI8oSXoamt4bkViEHfJBp97LZQiNz8mku5pVc0aNwP1p4hMHxRHhLXrJjbh-Hx4YFjxtOnIq9t1mHlD4A" //nolint:gosec
)
t.Run("PasswordCredentialsGrantAndValidateTokens", func(t *testing.T) {
@@ -699,7 +699,7 @@ func TestProviderConfig(t *testing.T) {
require.Equal(t, tt.wantNumRequests, numRequests,
"did not make expected number of requests to revocation endpoint")
if tt.wantErr != "" || tt.wantErrRegexp != "" { // nolint:nestif
if tt.wantErr != "" || tt.wantErrRegexp != "" { //nolint:nestif
if tt.wantErr != "" {
require.EqualError(t, err, tt.wantErr)
} else {