log response audit-id for tokencredentialrequests made from CLI

Only logged when PINNIPED_DEBUG=true is used.

Co-authored-by: Joshua Casey <joshuatcasey@gmail.com>
This commit is contained in:
Ryan Richard
2024-11-18 15:23:31 -08:00
committed by Joshua Casey
parent 26ec7fa346
commit 6bf9b64778
7 changed files with 68 additions and 27 deletions

View File

@@ -0,0 +1,26 @@
// Copyright 2024 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package cmd
import (
"net/http"
"go.pinniped.dev/internal/httputil/roundtripper"
"go.pinniped.dev/internal/plog"
)
func LogAuditIDTransportWrapper(rt http.RoundTripper) http.RoundTripper {
return roundtripper.WrapFunc(rt, func(r *http.Request) (*http.Response, error) {
response, responseErr := rt.RoundTrip(r)
if response != nil && response.Header.Get("audit-ID") != "" {
plog.Info("Received auditID for request",
// Use the request path from the response's request, in case the
// original request was modified by any other roudtrippers in the chain.
"path", response.Request.URL.Path,
"statusCode", response.StatusCode,
"auditID", response.Header.Get("audit-ID"))
}
return response, responseErr
})
}

View File

@@ -224,6 +224,7 @@ func runOIDCLogin(cmd *cobra.Command, deps oidcLoginCommandDeps, flags oidcLogin
conciergeclient.WithBase64CABundle(flags.conciergeCABundle),
conciergeclient.WithAuthenticator(flags.conciergeAuthenticatorType, flags.conciergeAuthenticatorName),
conciergeclient.WithAPIGroupSuffix(flags.conciergeAPIGroupSuffix),
conciergeclient.WithTransportWrapper(LogAuditIDTransportWrapper),
)
if err != nil {
return fmt.Errorf("invalid Concierge parameters: %w", err)

View File

@@ -274,8 +274,8 @@ func TestLoginOIDCCommand(t *testing.T) {
wantOptionsCount: 4,
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{"interactive":false},"status":{"expirationTimestamp":"3020-10-12T13:14:15Z","token":"test-id-token"}}` + "\n",
wantLogs: []string{
nowStr + ` cmd/login_oidc.go:267 Performing OIDC login {"issuer": "test-issuer", "client id": "test-client-id"}`,
nowStr + ` cmd/login_oidc.go:287 No concierge configured, skipping token credential exchange`,
nowStr + ` cmd/login_oidc.go:268 Performing OIDC login {"issuer": "test-issuer", "client id": "test-client-id"}`,
nowStr + ` cmd/login_oidc.go:288 No concierge configured, skipping token credential exchange`,
},
},
{
@@ -319,10 +319,10 @@ func TestLoginOIDCCommand(t *testing.T) {
wantOptionsCount: 12,
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{"interactive":false},"status":{"token":"exchanged-token"}}` + "\n",
wantLogs: []string{
nowStr + ` cmd/login_oidc.go:267 Performing OIDC login {"issuer": "test-issuer", "client id": "test-client-id"}`,
nowStr + ` cmd/login_oidc.go:277 Exchanging token for cluster credential {"endpoint": "https://127.0.0.1:1234/", "authenticator type": "webhook", "authenticator name": "test-authenticator"}`,
nowStr + ` cmd/login_oidc.go:285 Successfully exchanged token for cluster credential.`,
nowStr + ` cmd/login_oidc.go:292 caching cluster credential for future use.`,
nowStr + ` cmd/login_oidc.go:268 Performing OIDC login {"issuer": "test-issuer", "client id": "test-client-id"}`,
nowStr + ` cmd/login_oidc.go:278 Exchanging token for cluster credential {"endpoint": "https://127.0.0.1:1234/", "authenticator type": "webhook", "authenticator name": "test-authenticator"}`,
nowStr + ` cmd/login_oidc.go:286 Successfully exchanged token for cluster credential.`,
nowStr + ` cmd/login_oidc.go:293 caching cluster credential for future use.`,
},
},
}

View File

@@ -113,6 +113,7 @@ func runStaticLogin(cmd *cobra.Command, deps staticLoginDeps, flags staticLoginP
conciergeclient.WithBase64CABundle(flags.conciergeCABundle),
conciergeclient.WithAuthenticator(flags.conciergeAuthenticatorType, flags.conciergeAuthenticatorName),
conciergeclient.WithAPIGroupSuffix(flags.conciergeAPIGroupSuffix),
conciergeclient.WithTransportWrapper(LogAuditIDTransportWrapper),
)
if err != nil {
return fmt.Errorf("invalid Concierge parameters: %w", err)

View File

@@ -147,7 +147,7 @@ func TestLoginStaticCommand(t *testing.T) {
Error: could not complete Concierge credential exchange: some concierge error
`),
wantLogs: []string{
nowStr + ` cmd/login_static.go:159 exchanging static token for cluster credential {"endpoint": "https://127.0.0.1/", "authenticator type": "webhook", "authenticator name": "test-authenticator"}`,
nowStr + ` cmd/login_static.go:160 exchanging static token for cluster credential {"endpoint": "https://127.0.0.1/", "authenticator type": "webhook", "authenticator name": "test-authenticator"}`,
},
},
{

View File

@@ -31,25 +31,25 @@ func configWithWrapper(config *restclient.Config, scheme *runtime.Scheme, negoti
return config // invalid input config, will fail existing client-go validation
}
// no need for any wrapping when we have no middleware to inject
if len(middlewares) == 0 {
return config
var middlewareWrapper transport.WrapperFunc
if len(middlewares) > 0 {
info, ok := runtime.SerializerInfoForMediaType(negotiatedSerializer.SupportedMediaTypes(), config.ContentType)
if !ok {
panic(fmt.Errorf("unknown content type: %s ", config.ContentType)) // static input, programmer error
}
regSerializer := info.Serializer // should perform no conversion
resolver := server.NewRequestInfoResolver(server.NewConfig(serializer.CodecFactory{}))
schemeRestMapperFunc := schemeRestMapper(scheme)
middlewareWrapper = newWrapper(hostURL, apiPathPrefix, config, resolver, regSerializer, negotiatedSerializer, schemeRestMapperFunc, middlewares)
}
info, ok := runtime.SerializerInfoForMediaType(negotiatedSerializer.SupportedMediaTypes(), config.ContentType)
if !ok {
panic(fmt.Errorf("unknown content type: %s ", config.ContentType)) // static input, programmer error
}
regSerializer := info.Serializer // should perform no conversion
resolver := server.NewRequestInfoResolver(server.NewConfig(serializer.CodecFactory{}))
schemeRestMapperFunc := schemeRestMapper(scheme)
f := newWrapper(hostURL, apiPathPrefix, config, resolver, regSerializer, negotiatedSerializer, schemeRestMapperFunc, middlewares)
cc := restclient.CopyConfig(config)
cc.Wrap(f)
if middlewareWrapper != nil {
cc.Wrap(middlewareWrapper)
}
if wrapper != nil {
cc.Wrap(wrapper)
}

View File

@@ -17,6 +17,7 @@ import (
clientauthenticationv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"k8s.io/client-go/transport"
authenticationv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/authentication/v1alpha1"
loginv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/login/v1alpha1"
@@ -34,10 +35,11 @@ type Option func(*Client) error
// Client is a configuration for talking to the Pinniped concierge.
type Client struct {
authenticator *corev1.TypedLocalObjectReference
caBundle string
endpoint *url.URL
apiGroupSuffix string
authenticator *corev1.TypedLocalObjectReference
caBundle string
endpoint *url.URL
apiGroupSuffix string
transportWrapper transport.WrapperFunc
}
// WithAuthenticator configures the authenticator reference (spec.authenticator) of the TokenCredentialRequests.
@@ -116,6 +118,16 @@ func WithAPIGroupSuffix(apiGroupSuffix string) Option {
}
}
func WithTransportWrapper(wrapper transport.WrapperFunc) Option {
return func(c *Client) error {
if wrapper == nil {
return fmt.Errorf("transport wrapper cannot be nil")
}
c.transportWrapper = wrapper
return nil
}
}
// New validates the specified options and returns a newly initialized *Client.
func New(opts ...Option) (*Client, error) {
c := Client{apiGroupSuffix: groupsuffix.PinnipedDefaultSuffix}
@@ -158,6 +170,7 @@ func (c *Client) clientset() (conciergeclientset.Interface, error) {
client, err := kubeclient.New(
kubeclient.WithConfig(cfg),
kubeclient.WithMiddleware(groupsuffix.New(c.apiGroupSuffix)),
kubeclient.WithTransportWrapper(c.transportWrapper),
)
if err != nil {
return nil, err