From 0baeccd32f047f42a5fe69d118fd6bbdc3d81b49 Mon Sep 17 00:00:00 2001 From: Harshal Neelkamal Date: Mon, 14 Apr 2025 22:33:39 +0000 Subject: [PATCH] KEP-740: promote ExternalJWTSigner feature to beta --- .../apiserver/options/options_test.go | 4 +- .../apiserver/options/validation_test.go | 4 +- pkg/features/kube_features.go | 1 + .../externaljwt/plugin/keycache.go | 8 +- .../externaljwt/plugin/keycache_test.go | 12 +- .../externaljwt/plugin/plugin.go | 14 +- .../externaljwt/plugin/plugin_test.go | 46 +- .../{v1alpha1 => v1}/externalsigner_mock.go | 24 +- .../src/k8s.io/externaljwt/apis/v1/api.pb.go | 592 ++++++++++++++++++ .../src/k8s.io/externaljwt/apis/v1/api.proto | 114 ++++ .../externaljwt/apis/v1alpha1/api.pb.go | 10 +- .../externaljwt/apis/v1alpha1/api.proto | 5 +- .../reference/versioned_feature_list.yaml | 4 + .../external_jwt_signer_test.go | 43 +- 14 files changed, 821 insertions(+), 60 deletions(-) rename pkg/serviceaccount/externaljwt/plugin/testing/{v1alpha1 => v1}/externalsigner_mock.go (90%) create mode 100644 staging/src/k8s.io/externaljwt/apis/v1/api.pb.go create mode 100644 staging/src/k8s.io/externaljwt/apis/v1/api.proto diff --git a/pkg/controlplane/apiserver/options/options_test.go b/pkg/controlplane/apiserver/options/options_test.go index 71d74314545..af1bcc24377 100644 --- a/pkg/controlplane/apiserver/options/options_test.go +++ b/pkg/controlplane/apiserver/options/options_test.go @@ -47,7 +47,7 @@ import ( "k8s.io/component-base/metrics" kubeoptions "k8s.io/kubernetes/pkg/kubeapiserver/options" "k8s.io/kubernetes/pkg/serviceaccount" - v1alpha1testing "k8s.io/kubernetes/pkg/serviceaccount/externaljwt/plugin/testing/v1alpha1" + v1testing "k8s.io/kubernetes/pkg/serviceaccount/externaljwt/plugin/testing/v1" netutils "k8s.io/utils/net" ) @@ -485,7 +485,7 @@ func TestCompleteForServiceAccount(t *testing.T) { if tc.externalSigner { // create and start mock signer. socketPath := utilnettesting.MakeSocketNameForTest(t, fmt.Sprintf("mock-external-jwt-signer-%d.sock", time.Now().Nanosecond())) - mockSigner := v1alpha1testing.NewMockSigner(t, socketPath) + mockSigner := v1testing.NewMockSigner(t, socketPath) defer mockSigner.CleanUp() mockSigner.MaxTokenExpirationSeconds = tc.externalMaxExpirationSec diff --git a/pkg/controlplane/apiserver/options/validation_test.go b/pkg/controlplane/apiserver/options/validation_test.go index cd8409c7f46..097a62da11b 100644 --- a/pkg/controlplane/apiserver/options/validation_test.go +++ b/pkg/controlplane/apiserver/options/validation_test.go @@ -387,9 +387,7 @@ func TestValidateServiceAccountTokenSigningConfig(t *testing.T) { } } - if test.featureEnabled { - featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ExternalServiceAccountTokenSigner, true) - } + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ExternalServiceAccountTokenSigner, test.featureEnabled) errs := validateServiceAccountTokenSigningConfig(test.options) if !reflect.DeepEqual(errs, test.expectedErrors) { t.Errorf("Expected errors message: %v \n but got: %v", test.expectedErrors, errs) diff --git a/pkg/features/kube_features.go b/pkg/features/kube_features.go index 5945765f4e7..f658ed0c755 100644 --- a/pkg/features/kube_features.go +++ b/pkg/features/kube_features.go @@ -1168,6 +1168,7 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate ExternalServiceAccountTokenSigner: { {Version: version.MustParse("1.32"), Default: false, PreRelease: featuregate.Alpha}, + {Version: version.MustParse("1.34"), Default: true, PreRelease: featuregate.Beta}, }, genericfeatures.AggregatedDiscoveryRemoveBetaType: { diff --git a/pkg/serviceaccount/externaljwt/plugin/keycache.go b/pkg/serviceaccount/externaljwt/plugin/keycache.go index f7ad53d0e86..3b7628fce7c 100644 --- a/pkg/serviceaccount/externaljwt/plugin/keycache.go +++ b/pkg/serviceaccount/externaljwt/plugin/keycache.go @@ -26,7 +26,7 @@ import ( "golang.org/x/sync/singleflight" - externaljwtv1alpha1 "k8s.io/externaljwt/apis/v1alpha1" + externaljwtv1 "k8s.io/externaljwt/apis/v1" "k8s.io/klog/v2" "k8s.io/kubernetes/pkg/serviceaccount" externaljwtmetrics "k8s.io/kubernetes/pkg/serviceaccount/externaljwt/metrics" @@ -35,7 +35,7 @@ import ( const fallbackRefreshDuration = 10 * time.Second type keyCache struct { - client externaljwtv1alpha1.ExternalJWTSignerClient + client externaljwtv1.ExternalJWTSignerClient syncGroup singleflight.Group listenersLock sync.Mutex @@ -45,7 +45,7 @@ type keyCache struct { } // newKeyCache constructs an implementation of KeyCache. -func newKeyCache(client externaljwtv1alpha1.ExternalJWTSignerClient) *keyCache { +func newKeyCache(client externaljwtv1.ExternalJWTSignerClient) *keyCache { cache := &keyCache{ client: client, } @@ -203,7 +203,7 @@ func (p *keyCache) broadcastUpdate() { // GetTokenVerificationKeys returns a map of supported external keyIDs to keys // the keys are PKIX-serialized. It calls external-jwt-signer with a timeout of keySyncTimeoutSec. func (p *keyCache) getTokenVerificationKeys(ctx context.Context) (*VerificationKeys, error) { - req := &externaljwtv1alpha1.FetchKeysRequest{} + req := &externaljwtv1.FetchKeysRequest{} resp, err := p.client.FetchKeys(ctx, req) if err != nil { return nil, fmt.Errorf("while getting externally supported jwt signing keys: %w", err) diff --git a/pkg/serviceaccount/externaljwt/plugin/keycache_test.go b/pkg/serviceaccount/externaljwt/plugin/keycache_test.go index 06da6067a1c..f217b4ec62f 100644 --- a/pkg/serviceaccount/externaljwt/plugin/keycache_test.go +++ b/pkg/serviceaccount/externaljwt/plugin/keycache_test.go @@ -32,7 +32,7 @@ import ( "google.golang.org/protobuf/types/known/timestamppb" "k8s.io/apimachinery/pkg/util/wait" - externaljwtv1alpha1 "k8s.io/externaljwt/apis/v1alpha1" + externaljwtv1 "k8s.io/externaljwt/apis/v1" "k8s.io/kubernetes/pkg/serviceaccount" utilnettesting "k8s.io/apimachinery/pkg/util/net/testing" @@ -48,7 +48,7 @@ func TestExternalPublicKeyGetter(t *testing.T) { wantVerificationKeys *VerificationKeys refreshHintSec int dataTimeStamp *timestamppb.Timestamp - supportedKeysOverride []*externaljwtv1alpha1.Key + supportedKeysOverride []*externaljwtv1.Key }{ { desc: "single key in signer", @@ -157,7 +157,7 @@ func TestExternalPublicKeyGetter(t *testing.T) { excludeFromOidc: true, }, }, - supportedKeysOverride: []*externaljwtv1alpha1.Key{ + supportedKeysOverride: []*externaljwtv1.Key{ { KeyId: "kid", Key: nil, @@ -186,7 +186,7 @@ func TestExternalPublicKeyGetter(t *testing.T) { } backend.DataTimeStamp = tc.dataTimeStamp backend.SupportedKeysOverride = tc.supportedKeysOverride - externaljwtv1alpha1.RegisterExternalJWTSignerServer(grpcServer, backend) + externaljwtv1.RegisterExternalJWTSignerServer(grpcServer, backend) defer grpcServer.Stop() go func() { @@ -268,7 +268,7 @@ func TestInitialFill(t *testing.T) { refreshHintSeconds: 10, DataTimeStamp: timestamppb.New(time.Time{}), } - externaljwtv1alpha1.RegisterExternalJWTSignerServer(grpcServer, backend) + externaljwtv1.RegisterExternalJWTSignerServer(grpcServer, backend) defer grpcServer.Stop() go func() { @@ -333,7 +333,7 @@ func TestReflectChanges(t *testing.T) { refreshHintSeconds: 10, DataTimeStamp: timestamppb.New(time.Time{}), } - externaljwtv1alpha1.RegisterExternalJWTSignerServer(grpcServer, backend) + externaljwtv1.RegisterExternalJWTSignerServer(grpcServer, backend) defer grpcServer.Stop() go func() { diff --git a/pkg/serviceaccount/externaljwt/plugin/plugin.go b/pkg/serviceaccount/externaljwt/plugin/plugin.go index d4766568326..0cd0ebac835 100644 --- a/pkg/serviceaccount/externaljwt/plugin/plugin.go +++ b/pkg/serviceaccount/externaljwt/plugin/plugin.go @@ -30,7 +30,7 @@ import ( jose "gopkg.in/go-jose/go-jose.v2" "gopkg.in/go-jose/go-jose.v2/jwt" - externaljwtv1alpha1 "k8s.io/externaljwt/apis/v1alpha1" + externaljwtv1 "k8s.io/externaljwt/apis/v1" "k8s.io/kubernetes/pkg/serviceaccount" externaljwtmetrics "k8s.io/kubernetes/pkg/serviceaccount/externaljwt/metrics" ) @@ -85,14 +85,14 @@ func New(ctx context.Context, issuer, socketPath string, keySyncTimeout time.Dur // enables plugging in an external jwt signer. type Plugin struct { iss string - client externaljwtv1alpha1.ExternalJWTSignerClient + client externaljwtv1.ExternalJWTSignerClient keyCache *keyCache allowSigningWithNonOIDCKeys bool } // newPlugin constructs an implementation of external JWT signer plugin. func newPlugin(iss string, conn *grpc.ClientConn, allowSigningWithNonOIDCKeys bool) *Plugin { - client := externaljwtv1alpha1.NewExternalJWTSignerClient(conn) + client := externaljwtv1.NewExternalJWTSignerClient(conn) plugin := &Plugin{ iss: iss, client: client, @@ -118,7 +118,7 @@ func (p *Plugin) signAndAssembleJWT(ctx context.Context, claims *jwt.Claims, pri payloadBase64 := base64.RawURLEncoding.EncodeToString(payload) - request := &externaljwtv1alpha1.SignJWTRequest{ + request := &externaljwtv1.SignJWTRequest{ Claims: payloadBase64, } @@ -140,12 +140,12 @@ func (p *Plugin) signAndAssembleJWT(ctx context.Context, claims *jwt.Claims, pri // GetServiceMetadata returns metadata associated with externalJWTSigner // It Includes details like max token lifetime supported by externalJWTSigner, etc. -func (p *Plugin) GetServiceMetadata(ctx context.Context) (*externaljwtv1alpha1.MetadataResponse, error) { - req := &externaljwtv1alpha1.MetadataRequest{} +func (p *Plugin) GetServiceMetadata(ctx context.Context) (*externaljwtv1.MetadataResponse, error) { + req := &externaljwtv1.MetadataRequest{} return p.client.Metadata(ctx, req) } -func (p *Plugin) validateJWTHeader(ctx context.Context, response *externaljwtv1alpha1.SignJWTResponse) error { +func (p *Plugin) validateJWTHeader(ctx context.Context, response *externaljwtv1.SignJWTResponse) error { jsonBytes, err := base64.RawURLEncoding.DecodeString(response.Header) if err != nil { return fmt.Errorf("while unwrapping header: %w", err) diff --git a/pkg/serviceaccount/externaljwt/plugin/plugin_test.go b/pkg/serviceaccount/externaljwt/plugin/plugin_test.go index 04f3700b859..3ff46a0503a 100644 --- a/pkg/serviceaccount/externaljwt/plugin/plugin_test.go +++ b/pkg/serviceaccount/externaljwt/plugin/plugin_test.go @@ -39,7 +39,7 @@ import ( "k8s.io/kubernetes/pkg/serviceaccount" utilnettesting "k8s.io/apimachinery/pkg/util/net/testing" - externaljwtv1alpha1 "k8s.io/externaljwt/apis/v1alpha1" + externaljwtv1 "k8s.io/externaljwt/apis/v1" ) var ( @@ -71,6 +71,7 @@ func TestExternalTokenGenerator(t *testing.T) { iss string backendSetKeyID string backendSetAlgorithm string + backendHeaderType string supportedKeys map[string]supportedKeyT allowSigningWithNonOIDCKeys bool @@ -98,6 +99,7 @@ func TestExternalTokenGenerator(t *testing.T) { }, iss: "some-issuer", backendSetKeyID: "key-id-1", + backendHeaderType: "JWT", backendSetAlgorithm: "RS256", supportedKeys: map[string]supportedKeyT{ "key-id-1": { @@ -143,6 +145,7 @@ func TestExternalTokenGenerator(t *testing.T) { }, iss: "some-issuer", backendSetKeyID: "key-id-1", + backendHeaderType: "JWT", backendSetAlgorithm: "RS256", supportedKeys: map[string]supportedKeyT{ "key-id-1": { @@ -174,6 +177,7 @@ func TestExternalTokenGenerator(t *testing.T) { }, iss: "some-issuer", backendSetKeyID: "key-id-1", + backendHeaderType: "JWT", backendSetAlgorithm: "RS256", supportedKeys: map[string]supportedKeyT{ "key-id-1": { @@ -204,6 +208,7 @@ func TestExternalTokenGenerator(t *testing.T) { desc: "empty key ID returned from signer", iss: "some-issuer", backendSetKeyID: "", + backendHeaderType: "JWT", backendSetAlgorithm: "RS256", supportedKeys: map[string]supportedKeyT{ "key-id-1": { @@ -217,6 +222,7 @@ func TestExternalTokenGenerator(t *testing.T) { desc: "key id longer than 1024 bytes returned from signer", iss: "some-issuer", backendSetKeyID: string(make([]byte, 1025)), + backendHeaderType: "JWT", backendSetAlgorithm: "RS256", supportedKeys: map[string]supportedKeyT{ "key-id-1": { @@ -230,6 +236,7 @@ func TestExternalTokenGenerator(t *testing.T) { desc: "unsupported alg returned from signer", iss: "some-issuer", backendSetKeyID: "key-id-1", + backendHeaderType: "JWT", backendSetAlgorithm: "something-unsupported", supportedKeys: map[string]supportedKeyT{ "key-id-1": { @@ -243,6 +250,7 @@ func TestExternalTokenGenerator(t *testing.T) { desc: "empty alg returned from signer", iss: "some-issuer", backendSetKeyID: "key-id-1", + backendHeaderType: "JWT", backendSetAlgorithm: "", supportedKeys: map[string]supportedKeyT{ "key-id-1": { @@ -252,6 +260,20 @@ func TestExternalTokenGenerator(t *testing.T) { }, wantErr: fmt.Errorf("while validating header: bad signing algorithm \"\""), }, + { + desc: "Invalid backend header type", + iss: "some-issuer", + backendSetKeyID: "key-id-1", + backendHeaderType: "WHAT", + backendSetAlgorithm: "RS256", + supportedKeys: map[string]supportedKeyT{ + "key-id-1": { + key: &rsaKey1.PublicKey, + excludeFromOidc: true, + }, + }, + wantErr: fmt.Errorf("while validating header: bad type"), + }, } for i, tc := range testCases { @@ -275,8 +297,9 @@ func TestExternalTokenGenerator(t *testing.T) { supportedKeys: tc.supportedKeys, refreshHintSeconds: 10, DataTimeStamp: timestamppb.New(time.Time{}), + headerType: tc.backendHeaderType, } - externaljwtv1alpha1.RegisterExternalJWTSignerServer(grpcServer, backend) + externaljwtv1.RegisterExternalJWTSignerServer(grpcServer, backend) go func() { if err := grpcServer.Serve(listener); err != nil { @@ -385,24 +408,25 @@ type supportedKeyT struct { } type dummyExtrnalSigner struct { - externaljwtv1alpha1.UnimplementedExternalJWTSignerServer + externaljwtv1.UnimplementedExternalJWTSignerServer // required for Sign() keyID string signingAlgorithm string signature string + headerType string // required for FetchKeys() keyLock sync.Mutex supportedKeys map[string]supportedKeyT refreshHintSeconds int DataTimeStamp *timestamppb.Timestamp - SupportedKeysOverride []*externaljwtv1alpha1.Key + SupportedKeysOverride []*externaljwtv1.Key } -func (des *dummyExtrnalSigner) Sign(ctx context.Context, r *externaljwtv1alpha1.SignJWTRequest) (*externaljwtv1alpha1.SignJWTResponse, error) { +func (des *dummyExtrnalSigner) Sign(ctx context.Context, r *externaljwtv1.SignJWTRequest) (*externaljwtv1.SignJWTResponse, error) { header := &headerT{ - Type: "JWT", + Type: des.headerType, Algorithm: des.signingAlgorithm, KeyID: des.keyID, } @@ -412,18 +436,18 @@ func (des *dummyExtrnalSigner) Sign(ctx context.Context, r *externaljwtv1alpha1. return nil, fmt.Errorf("failed to create header for JWT response") } - resp := &externaljwtv1alpha1.SignJWTResponse{ + resp := &externaljwtv1.SignJWTResponse{ Header: base64.RawURLEncoding.EncodeToString(headerJSON), Signature: des.signature, } return resp, nil } -func (des *dummyExtrnalSigner) FetchKeys(ctx context.Context, r *externaljwtv1alpha1.FetchKeysRequest) (*externaljwtv1alpha1.FetchKeysResponse, error) { +func (des *dummyExtrnalSigner) FetchKeys(ctx context.Context, r *externaljwtv1.FetchKeysRequest) (*externaljwtv1.FetchKeysResponse, error) { des.keyLock.Lock() defer des.keyLock.Unlock() - pbKeys := []*externaljwtv1alpha1.Key{} + pbKeys := []*externaljwtv1.Key{} if des.SupportedKeysOverride != nil { pbKeys = des.SupportedKeysOverride } else { @@ -432,7 +456,7 @@ func (des *dummyExtrnalSigner) FetchKeys(ctx context.Context, r *externaljwtv1al if err != nil { return nil, fmt.Errorf("while marshaling key: %w", err) } - pbKey := &externaljwtv1alpha1.Key{ + pbKey := &externaljwtv1.Key{ KeyId: kid, Key: keyBytes, ExcludeFromOidcDiscovery: k.excludeFromOidc, @@ -441,7 +465,7 @@ func (des *dummyExtrnalSigner) FetchKeys(ctx context.Context, r *externaljwtv1al } } - return &externaljwtv1alpha1.FetchKeysResponse{ + return &externaljwtv1.FetchKeysResponse{ Keys: pbKeys, DataTimestamp: des.DataTimeStamp, RefreshHintSeconds: int64(des.refreshHintSeconds), diff --git a/pkg/serviceaccount/externaljwt/plugin/testing/v1alpha1/externalsigner_mock.go b/pkg/serviceaccount/externaljwt/plugin/testing/v1/externalsigner_mock.go similarity index 90% rename from pkg/serviceaccount/externaljwt/plugin/testing/v1alpha1/externalsigner_mock.go rename to pkg/serviceaccount/externaljwt/plugin/testing/v1/externalsigner_mock.go index b6a59a25185..d9b80c4ef63 100644 --- a/pkg/serviceaccount/externaljwt/plugin/testing/v1alpha1/externalsigner_mock.go +++ b/pkg/serviceaccount/externaljwt/plugin/testing/v1/externalsigner_mock.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package v1alpha1 +package v1 import ( "context" @@ -35,7 +35,7 @@ import ( "google.golang.org/grpc" "google.golang.org/protobuf/types/known/timestamppb" - "k8s.io/externaljwt/apis/v1alpha1" + "k8s.io/externaljwt/apis/v1" "k8s.io/klog/v2" ) @@ -80,7 +80,7 @@ func NewMockSigner(t *testing.T, socketPath string) *MockSigner { t.Fatalf("failed to load keys for mock signer: %v", err) } - v1alpha1.RegisterExternalJWTSignerServer(server, m) + v1.RegisterExternalJWTSignerServer(server, m) if err := m.start(t); err != nil { t.Fatalf("failed to start Mock Signer with error: %v", err) } @@ -109,7 +109,7 @@ func (m *MockSigner) WaitForSupportedKeysFetch() { m.supportedKeysFetched.Wait() } -func (m *MockSigner) Sign(ctx context.Context, req *v1alpha1.SignJWTRequest) (*v1alpha1.SignJWTResponse, error) { +func (m *MockSigner) Sign(ctx context.Context, req *v1.SignJWTRequest) (*v1.SignJWTResponse, error) { header := &struct { Algorithm string `json:"alg,omitempty"` @@ -135,24 +135,24 @@ func (m *MockSigner) Sign(ctx context.Context, req *v1alpha1.SignJWTRequest) (*v return nil, fmt.Errorf("unable to sign payload: %w", err) } - return &v1alpha1.SignJWTResponse{ + return &v1.SignJWTResponse{ Header: base64Header, Signature: base64.RawURLEncoding.EncodeToString(signature), }, nil } -func (m *MockSigner) FetchKeys(ctx context.Context, req *v1alpha1.FetchKeysRequest) (*v1alpha1.FetchKeysResponse, error) { +func (m *MockSigner) FetchKeys(ctx context.Context, req *v1.FetchKeysRequest) (*v1.FetchKeysResponse, error) { m.errorLock.RLocker().Lock() defer m.errorLock.RLocker().Unlock() if m.FetchError != nil { return nil, m.FetchError } - keys := []*v1alpha1.Key{} + keys := []*v1.Key{} m.supportedKeysLock.RLock() for id, k := range m.supportedKeys { - keys = append(keys, &v1alpha1.Key{ + keys = append(keys, &v1.Key{ KeyId: id, Key: k.Key, ExcludeFromOidcDiscovery: k.ExcludeFromOidcDiscovery, @@ -162,20 +162,20 @@ func (m *MockSigner) FetchKeys(ctx context.Context, req *v1alpha1.FetchKeysReque m.supportedKeysLock.RUnlock() now := time.Now() - return &v1alpha1.FetchKeysResponse{ + return &v1.FetchKeysResponse{ RefreshHintSeconds: 5, DataTimestamp: ×tamppb.Timestamp{Seconds: now.Unix(), Nanos: int32(now.Nanosecond())}, Keys: keys, }, nil } -func (m *MockSigner) Metadata(ctx context.Context, req *v1alpha1.MetadataRequest) (*v1alpha1.MetadataResponse, error) { +func (m *MockSigner) Metadata(ctx context.Context, req *v1.MetadataRequest) (*v1.MetadataResponse, error) { m.errorLock.RLocker().Lock() defer m.errorLock.RLocker().Unlock() if m.MetadataError != nil { return nil, m.MetadataError } - return &v1alpha1.MetadataResponse{ + return &v1.MetadataResponse{ MaxTokenExpirationSeconds: m.MaxTokenExpirationSeconds, }, nil } @@ -251,7 +251,7 @@ func (m *MockSigner) waitForMockServerToStart() error { return fmt.Errorf("failed to start Mock signer: %w", ctx.Err()) default: } - if _, gRPCErr = m.FetchKeys(context.Background(), &v1alpha1.FetchKeysRequest{}); gRPCErr == nil { + if _, gRPCErr = m.FetchKeys(context.Background(), &v1.FetchKeysRequest{}); gRPCErr == nil { break } time.Sleep(time.Second) diff --git a/staging/src/k8s.io/externaljwt/apis/v1/api.pb.go b/staging/src/k8s.io/externaljwt/apis/v1/api.pb.go new file mode 100644 index 00000000000..e09bbe3672b --- /dev/null +++ b/staging/src/k8s.io/externaljwt/apis/v1/api.pb.go @@ -0,0 +1,592 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: api.proto + +package v1 + +import ( + context "context" + fmt "fmt" + proto "github.com/gogo/protobuf/proto" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + math "math" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +type SignJWTRequest struct { + // URL-safe base64 wrapped payload to be signed. + // Exactly as it appears in the second segment of the JWT + Claims string `protobuf:"bytes,1,opt,name=claims,proto3" json:"claims,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *SignJWTRequest) Reset() { *m = SignJWTRequest{} } +func (m *SignJWTRequest) String() string { return proto.CompactTextString(m) } +func (*SignJWTRequest) ProtoMessage() {} +func (*SignJWTRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_00212fb1f9d3bf1c, []int{0} +} +func (m *SignJWTRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_SignJWTRequest.Unmarshal(m, b) +} +func (m *SignJWTRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_SignJWTRequest.Marshal(b, m, deterministic) +} +func (m *SignJWTRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_SignJWTRequest.Merge(m, src) +} +func (m *SignJWTRequest) XXX_Size() int { + return xxx_messageInfo_SignJWTRequest.Size(m) +} +func (m *SignJWTRequest) XXX_DiscardUnknown() { + xxx_messageInfo_SignJWTRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_SignJWTRequest proto.InternalMessageInfo + +func (m *SignJWTRequest) GetClaims() string { + if m != nil { + return m.Claims + } + return "" +} + +type SignJWTResponse struct { + // header must contain only alg, kid, typ claims. + // typ must be “JWT”. + // kid must be non-empty, <=1024 characters, and its corresponding public key should not be excluded from OIDC discovery. + // alg must be one of the algorithms supported by kube-apiserver (currently RS256, ES256, ES384, ES512). + // header cannot have any additional data that kube-apiserver does not recognize. + // Already wrapped in URL-safe base64, exactly as it appears in the first segment of the JWT. + Header string `protobuf:"bytes,1,opt,name=header,proto3" json:"header,omitempty"` + // The signature for the JWT. + // Already wrapped in URL-safe base64, exactly as it appears in the final segment of the JWT. + Signature string `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *SignJWTResponse) Reset() { *m = SignJWTResponse{} } +func (m *SignJWTResponse) String() string { return proto.CompactTextString(m) } +func (*SignJWTResponse) ProtoMessage() {} +func (*SignJWTResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_00212fb1f9d3bf1c, []int{1} +} +func (m *SignJWTResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_SignJWTResponse.Unmarshal(m, b) +} +func (m *SignJWTResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_SignJWTResponse.Marshal(b, m, deterministic) +} +func (m *SignJWTResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_SignJWTResponse.Merge(m, src) +} +func (m *SignJWTResponse) XXX_Size() int { + return xxx_messageInfo_SignJWTResponse.Size(m) +} +func (m *SignJWTResponse) XXX_DiscardUnknown() { + xxx_messageInfo_SignJWTResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_SignJWTResponse proto.InternalMessageInfo + +func (m *SignJWTResponse) GetHeader() string { + if m != nil { + return m.Header + } + return "" +} + +func (m *SignJWTResponse) GetSignature() string { + if m != nil { + return m.Signature + } + return "" +} + +type FetchKeysRequest struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *FetchKeysRequest) Reset() { *m = FetchKeysRequest{} } +func (m *FetchKeysRequest) String() string { return proto.CompactTextString(m) } +func (*FetchKeysRequest) ProtoMessage() {} +func (*FetchKeysRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_00212fb1f9d3bf1c, []int{2} +} +func (m *FetchKeysRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_FetchKeysRequest.Unmarshal(m, b) +} +func (m *FetchKeysRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_FetchKeysRequest.Marshal(b, m, deterministic) +} +func (m *FetchKeysRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_FetchKeysRequest.Merge(m, src) +} +func (m *FetchKeysRequest) XXX_Size() int { + return xxx_messageInfo_FetchKeysRequest.Size(m) +} +func (m *FetchKeysRequest) XXX_DiscardUnknown() { + xxx_messageInfo_FetchKeysRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_FetchKeysRequest proto.InternalMessageInfo + +type FetchKeysResponse struct { + Keys []*Key `protobuf:"bytes,1,rep,name=keys,proto3" json:"keys,omitempty"` + // The timestamp when this data was pulled from the authoritative source of + // truth for verification keys. + // kube-apiserver can export this from metrics, to enable end-to-end SLOs. + DataTimestamp *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=data_timestamp,json=dataTimestamp,proto3" json:"data_timestamp,omitempty"` + // refresh interval for verification keys to pick changes if any. + // any value <= 0 is considered a misconfiguration. + RefreshHintSeconds int64 `protobuf:"varint,3,opt,name=refresh_hint_seconds,json=refreshHintSeconds,proto3" json:"refresh_hint_seconds,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *FetchKeysResponse) Reset() { *m = FetchKeysResponse{} } +func (m *FetchKeysResponse) String() string { return proto.CompactTextString(m) } +func (*FetchKeysResponse) ProtoMessage() {} +func (*FetchKeysResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_00212fb1f9d3bf1c, []int{3} +} +func (m *FetchKeysResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_FetchKeysResponse.Unmarshal(m, b) +} +func (m *FetchKeysResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_FetchKeysResponse.Marshal(b, m, deterministic) +} +func (m *FetchKeysResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_FetchKeysResponse.Merge(m, src) +} +func (m *FetchKeysResponse) XXX_Size() int { + return xxx_messageInfo_FetchKeysResponse.Size(m) +} +func (m *FetchKeysResponse) XXX_DiscardUnknown() { + xxx_messageInfo_FetchKeysResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_FetchKeysResponse proto.InternalMessageInfo + +func (m *FetchKeysResponse) GetKeys() []*Key { + if m != nil { + return m.Keys + } + return nil +} + +func (m *FetchKeysResponse) GetDataTimestamp() *timestamppb.Timestamp { + if m != nil { + return m.DataTimestamp + } + return nil +} + +func (m *FetchKeysResponse) GetRefreshHintSeconds() int64 { + if m != nil { + return m.RefreshHintSeconds + } + return 0 +} + +type Key struct { + // A unique identifier for this key. + // Length must be <=1024. + KeyId string `protobuf:"bytes,1,opt,name=key_id,json=keyId,proto3" json:"key_id,omitempty"` + // The public key, PKIX-serialized. + // must be a public key supported by kube-apiserver (currently RSA 256 or ECDSA 256/384/521) + Key []byte `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` + // Set only for keys that are not used to sign bound tokens. + // eg: supported keys for legacy tokens. + // If set, key is used for verification but excluded from OIDC discovery docs. + // if set, external signer should not use this key to sign a JWT. + ExcludeFromOidcDiscovery bool `protobuf:"varint,3,opt,name=exclude_from_oidc_discovery,json=excludeFromOidcDiscovery,proto3" json:"exclude_from_oidc_discovery,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Key) Reset() { *m = Key{} } +func (m *Key) String() string { return proto.CompactTextString(m) } +func (*Key) ProtoMessage() {} +func (*Key) Descriptor() ([]byte, []int) { + return fileDescriptor_00212fb1f9d3bf1c, []int{4} +} +func (m *Key) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Key.Unmarshal(m, b) +} +func (m *Key) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Key.Marshal(b, m, deterministic) +} +func (m *Key) XXX_Merge(src proto.Message) { + xxx_messageInfo_Key.Merge(m, src) +} +func (m *Key) XXX_Size() int { + return xxx_messageInfo_Key.Size(m) +} +func (m *Key) XXX_DiscardUnknown() { + xxx_messageInfo_Key.DiscardUnknown(m) +} + +var xxx_messageInfo_Key proto.InternalMessageInfo + +func (m *Key) GetKeyId() string { + if m != nil { + return m.KeyId + } + return "" +} + +func (m *Key) GetKey() []byte { + if m != nil { + return m.Key + } + return nil +} + +func (m *Key) GetExcludeFromOidcDiscovery() bool { + if m != nil { + return m.ExcludeFromOidcDiscovery + } + return false +} + +type MetadataRequest struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *MetadataRequest) Reset() { *m = MetadataRequest{} } +func (m *MetadataRequest) String() string { return proto.CompactTextString(m) } +func (*MetadataRequest) ProtoMessage() {} +func (*MetadataRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_00212fb1f9d3bf1c, []int{5} +} +func (m *MetadataRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_MetadataRequest.Unmarshal(m, b) +} +func (m *MetadataRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_MetadataRequest.Marshal(b, m, deterministic) +} +func (m *MetadataRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_MetadataRequest.Merge(m, src) +} +func (m *MetadataRequest) XXX_Size() int { + return xxx_messageInfo_MetadataRequest.Size(m) +} +func (m *MetadataRequest) XXX_DiscardUnknown() { + xxx_messageInfo_MetadataRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_MetadataRequest proto.InternalMessageInfo + +type MetadataResponse struct { + // used by kube-apiserver for defaulting/validation of JWT lifetime while accounting for configuration flag values: + // 1. `--service-account-max-token-expiration` + // 2. `--service-account-extend-token-expiration` + // + // * If `--service-account-max-token-expiration` is greater than `max_token_expiration_seconds`, kube-apiserver treats that as misconfiguration and exits. + // * If `--service-account-max-token-expiration` is not explicitly set, kube-apiserver defaults to `max_token_expiration_seconds`. + // * If `--service-account-extend-token-expiration` is true, the extended expiration is `min(1 year, max_token_expiration_seconds)`. + // + // `max_token_expiration_seconds` must be at least 600s. + MaxTokenExpirationSeconds int64 `protobuf:"varint,1,opt,name=max_token_expiration_seconds,json=maxTokenExpirationSeconds,proto3" json:"max_token_expiration_seconds,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *MetadataResponse) Reset() { *m = MetadataResponse{} } +func (m *MetadataResponse) String() string { return proto.CompactTextString(m) } +func (*MetadataResponse) ProtoMessage() {} +func (*MetadataResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_00212fb1f9d3bf1c, []int{6} +} +func (m *MetadataResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_MetadataResponse.Unmarshal(m, b) +} +func (m *MetadataResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_MetadataResponse.Marshal(b, m, deterministic) +} +func (m *MetadataResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MetadataResponse.Merge(m, src) +} +func (m *MetadataResponse) XXX_Size() int { + return xxx_messageInfo_MetadataResponse.Size(m) +} +func (m *MetadataResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MetadataResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MetadataResponse proto.InternalMessageInfo + +func (m *MetadataResponse) GetMaxTokenExpirationSeconds() int64 { + if m != nil { + return m.MaxTokenExpirationSeconds + } + return 0 +} + +func init() { + proto.RegisterType((*SignJWTRequest)(nil), "v1.SignJWTRequest") + proto.RegisterType((*SignJWTResponse)(nil), "v1.SignJWTResponse") + proto.RegisterType((*FetchKeysRequest)(nil), "v1.FetchKeysRequest") + proto.RegisterType((*FetchKeysResponse)(nil), "v1.FetchKeysResponse") + proto.RegisterType((*Key)(nil), "v1.Key") + proto.RegisterType((*MetadataRequest)(nil), "v1.MetadataRequest") + proto.RegisterType((*MetadataResponse)(nil), "v1.MetadataResponse") +} + +func init() { proto.RegisterFile("api.proto", fileDescriptor_00212fb1f9d3bf1c) } + +var fileDescriptor_00212fb1f9d3bf1c = []byte{ + // 476 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x5c, 0x92, 0x51, 0x6f, 0xd3, 0x30, + 0x10, 0xc7, 0xc9, 0x32, 0xc6, 0x7a, 0x83, 0xad, 0x35, 0x1d, 0x2a, 0x59, 0x25, 0xa6, 0x3c, 0xf5, + 0x29, 0xa1, 0xe5, 0x01, 0x84, 0x84, 0x10, 0x88, 0x0d, 0x58, 0x85, 0x90, 0xd2, 0x4a, 0x93, 0x78, + 0x89, 0xbc, 0xe4, 0xda, 0x9a, 0x34, 0x71, 0xb0, 0xdd, 0x92, 0x7c, 0x20, 0x3e, 0x05, 0x5f, 0x0e, + 0x39, 0x71, 0x32, 0xda, 0x37, 0xfb, 0x7f, 0xff, 0xf3, 0xdd, 0xef, 0xce, 0xd0, 0xa1, 0x39, 0xf3, + 0x72, 0xc1, 0x15, 0x27, 0x07, 0xdb, 0xb1, 0xf3, 0x62, 0xc9, 0xf9, 0x72, 0x8d, 0x7e, 0xa5, 0xdc, + 0x6d, 0x16, 0xbe, 0x62, 0x29, 0x4a, 0x45, 0xd3, 0xbc, 0x36, 0xb9, 0x23, 0x38, 0x9d, 0xb1, 0x65, + 0x76, 0x73, 0x3b, 0x0f, 0xf0, 0xd7, 0x06, 0xa5, 0x22, 0xcf, 0xe0, 0x28, 0x5a, 0x53, 0x96, 0xca, + 0x81, 0x75, 0x69, 0x8d, 0x3a, 0x81, 0xb9, 0xb9, 0x9f, 0xe1, 0xac, 0x75, 0xca, 0x9c, 0x67, 0x12, + 0xb5, 0x75, 0x85, 0x34, 0x46, 0xd1, 0x58, 0xeb, 0x1b, 0x19, 0x42, 0x47, 0xb2, 0x65, 0x46, 0xd5, + 0x46, 0xe0, 0xe0, 0xa0, 0x0a, 0xdd, 0x0b, 0x2e, 0x81, 0xee, 0x35, 0xaa, 0x68, 0x35, 0xc5, 0x52, + 0x9a, 0xa2, 0xee, 0x1f, 0x0b, 0x7a, 0xff, 0x89, 0xe6, 0xfd, 0x0b, 0x38, 0x4c, 0xb0, 0xd4, 0x8d, + 0xd8, 0xa3, 0x93, 0xc9, 0x23, 0x6f, 0x3b, 0xf6, 0xa6, 0x58, 0x06, 0x95, 0x48, 0x3e, 0xc0, 0x69, + 0x4c, 0x15, 0x0d, 0x5b, 0xa2, 0xaa, 0xd2, 0xc9, 0xc4, 0xf1, 0x6a, 0x66, 0xaf, 0x61, 0xf6, 0xe6, + 0x8d, 0x23, 0x78, 0xa2, 0x33, 0xda, 0x2b, 0x79, 0x09, 0x7d, 0x81, 0x0b, 0x81, 0x72, 0x15, 0xae, + 0x58, 0xa6, 0x42, 0x89, 0x11, 0xcf, 0x62, 0x39, 0xb0, 0x2f, 0xad, 0x91, 0x1d, 0x10, 0x13, 0xfb, + 0xc2, 0x32, 0x35, 0xab, 0x23, 0x6e, 0x0a, 0xf6, 0x14, 0x4b, 0x72, 0x0e, 0x47, 0x09, 0x96, 0x21, + 0x8b, 0x0d, 0xf8, 0xc3, 0x04, 0xcb, 0xaf, 0x31, 0xe9, 0x82, 0x9d, 0x60, 0x59, 0xf5, 0xf1, 0x38, + 0xd0, 0x47, 0xf2, 0x0e, 0x2e, 0xb0, 0x88, 0xd6, 0x9b, 0x18, 0xc3, 0x85, 0xe0, 0x69, 0xc8, 0x59, + 0x1c, 0x85, 0x31, 0x93, 0x11, 0xdf, 0xa2, 0x28, 0xab, 0x42, 0xc7, 0xc1, 0xc0, 0x58, 0xae, 0x05, + 0x4f, 0xbf, 0xb3, 0x38, 0xfa, 0xd4, 0xc4, 0xdd, 0x1e, 0x9c, 0x7d, 0x43, 0x45, 0x75, 0xd7, 0xcd, + 0xa4, 0x66, 0xd0, 0xbd, 0x97, 0xcc, 0x9c, 0xde, 0xc3, 0x30, 0xa5, 0x45, 0xa8, 0x78, 0x82, 0x59, + 0x88, 0x45, 0xce, 0x04, 0x55, 0x8c, 0x67, 0x2d, 0x8f, 0x55, 0xf1, 0x3c, 0x4f, 0x69, 0x31, 0xd7, + 0x96, 0xab, 0xd6, 0x61, 0xb0, 0x26, 0x7f, 0x2d, 0xe8, 0x5d, 0x15, 0x0a, 0x45, 0x46, 0xd7, 0x37, + 0xb7, 0x73, 0xbd, 0x67, 0x14, 0x64, 0x0c, 0x87, 0xfa, 0x44, 0x88, 0x1e, 0xfc, 0xee, 0x2f, 0x71, + 0x9e, 0xee, 0x68, 0x75, 0x1f, 0xee, 0x03, 0xf2, 0x16, 0x3a, 0xed, 0x1a, 0x49, 0x5f, 0x7b, 0xf6, + 0x57, 0xed, 0x9c, 0xef, 0xa9, 0x6d, 0xee, 0x6b, 0x38, 0x6e, 0xc8, 0x48, 0xf5, 0xfc, 0x1e, 0xba, + 0xd3, 0xdf, 0x15, 0x9b, 0xc4, 0x8f, 0xc3, 0x1f, 0x4e, 0xf2, 0x46, 0x7a, 0x8c, 0xfb, 0x68, 0x18, + 0x7e, 0xfe, 0x56, 0x3e, 0xcd, 0x99, 0xf4, 0xb7, 0xe3, 0xbb, 0xa3, 0xea, 0x1f, 0xbc, 0xfa, 0x17, + 0x00, 0x00, 0xff, 0xff, 0xc2, 0x92, 0x25, 0x65, 0x1a, 0x03, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// ExternalJWTSignerClient is the client API for ExternalJWTSigner service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type ExternalJWTSignerClient interface { + // Sign takes a serialized JWT payload, and returns the serialized header and + // signature. The caller can then assemble the JWT from the header, payload, + // and signature. Signature can be generated by signing + // `base64url(header) + "." + base64url(payload)` with signing key. + // + // The plugin MUST set a key id in the returned JWT header. + Sign(ctx context.Context, in *SignJWTRequest, opts ...grpc.CallOption) (*SignJWTResponse, error) + // FetchKeys returns the set of public keys that are trusted to sign + // Kubernetes service account tokens. Kube-apiserver will call this RPC: + // + // * Every time it tries to validate a JWT from the service account issuer with an unknown key ID, and + // + // - Periodically, so it can serve reasonably-up-to-date keys from the OIDC + // JWKs endpoint. + FetchKeys(ctx context.Context, in *FetchKeysRequest, opts ...grpc.CallOption) (*FetchKeysResponse, error) + // Metadata is meant to be called once on startup. + // Enables sharing metadata with kube-apiserver (eg: the max token lifetime that signer supports) + Metadata(ctx context.Context, in *MetadataRequest, opts ...grpc.CallOption) (*MetadataResponse, error) +} + +type externalJWTSignerClient struct { + cc *grpc.ClientConn +} + +func NewExternalJWTSignerClient(cc *grpc.ClientConn) ExternalJWTSignerClient { + return &externalJWTSignerClient{cc} +} + +func (c *externalJWTSignerClient) Sign(ctx context.Context, in *SignJWTRequest, opts ...grpc.CallOption) (*SignJWTResponse, error) { + out := new(SignJWTResponse) + err := c.cc.Invoke(ctx, "/v1.ExternalJWTSigner/Sign", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *externalJWTSignerClient) FetchKeys(ctx context.Context, in *FetchKeysRequest, opts ...grpc.CallOption) (*FetchKeysResponse, error) { + out := new(FetchKeysResponse) + err := c.cc.Invoke(ctx, "/v1.ExternalJWTSigner/FetchKeys", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *externalJWTSignerClient) Metadata(ctx context.Context, in *MetadataRequest, opts ...grpc.CallOption) (*MetadataResponse, error) { + out := new(MetadataResponse) + err := c.cc.Invoke(ctx, "/v1.ExternalJWTSigner/Metadata", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// ExternalJWTSignerServer is the server API for ExternalJWTSigner service. +type ExternalJWTSignerServer interface { + // Sign takes a serialized JWT payload, and returns the serialized header and + // signature. The caller can then assemble the JWT from the header, payload, + // and signature. Signature can be generated by signing + // `base64url(header) + "." + base64url(payload)` with signing key. + // + // The plugin MUST set a key id in the returned JWT header. + Sign(context.Context, *SignJWTRequest) (*SignJWTResponse, error) + // FetchKeys returns the set of public keys that are trusted to sign + // Kubernetes service account tokens. Kube-apiserver will call this RPC: + // + // * Every time it tries to validate a JWT from the service account issuer with an unknown key ID, and + // + // - Periodically, so it can serve reasonably-up-to-date keys from the OIDC + // JWKs endpoint. + FetchKeys(context.Context, *FetchKeysRequest) (*FetchKeysResponse, error) + // Metadata is meant to be called once on startup. + // Enables sharing metadata with kube-apiserver (eg: the max token lifetime that signer supports) + Metadata(context.Context, *MetadataRequest) (*MetadataResponse, error) +} + +// UnimplementedExternalJWTSignerServer can be embedded to have forward compatible implementations. +type UnimplementedExternalJWTSignerServer struct { +} + +func (*UnimplementedExternalJWTSignerServer) Sign(ctx context.Context, req *SignJWTRequest) (*SignJWTResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Sign not implemented") +} +func (*UnimplementedExternalJWTSignerServer) FetchKeys(ctx context.Context, req *FetchKeysRequest) (*FetchKeysResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method FetchKeys not implemented") +} +func (*UnimplementedExternalJWTSignerServer) Metadata(ctx context.Context, req *MetadataRequest) (*MetadataResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Metadata not implemented") +} + +func RegisterExternalJWTSignerServer(s *grpc.Server, srv ExternalJWTSignerServer) { + s.RegisterService(&_ExternalJWTSigner_serviceDesc, srv) +} + +func _ExternalJWTSigner_Sign_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SignJWTRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ExternalJWTSignerServer).Sign(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/v1.ExternalJWTSigner/Sign", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ExternalJWTSignerServer).Sign(ctx, req.(*SignJWTRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ExternalJWTSigner_FetchKeys_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(FetchKeysRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ExternalJWTSignerServer).FetchKeys(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/v1.ExternalJWTSigner/FetchKeys", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ExternalJWTSignerServer).FetchKeys(ctx, req.(*FetchKeysRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ExternalJWTSigner_Metadata_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MetadataRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ExternalJWTSignerServer).Metadata(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/v1.ExternalJWTSigner/Metadata", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ExternalJWTSignerServer).Metadata(ctx, req.(*MetadataRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _ExternalJWTSigner_serviceDesc = grpc.ServiceDesc{ + ServiceName: "v1.ExternalJWTSigner", + HandlerType: (*ExternalJWTSignerServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Sign", + Handler: _ExternalJWTSigner_Sign_Handler, + }, + { + MethodName: "FetchKeys", + Handler: _ExternalJWTSigner_FetchKeys_Handler, + }, + { + MethodName: "Metadata", + Handler: _ExternalJWTSigner_Metadata_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "api.proto", +} diff --git a/staging/src/k8s.io/externaljwt/apis/v1/api.proto b/staging/src/k8s.io/externaljwt/apis/v1/api.proto new file mode 100644 index 00000000000..723fddbfdc9 --- /dev/null +++ b/staging/src/k8s.io/externaljwt/apis/v1/api.proto @@ -0,0 +1,114 @@ +/* +Copyright 2025 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// To regenerate api.pb.go run `hack/update-codegen.sh protobindings` +syntax = "proto3"; + +package v1; + +option go_package = "k8s.io/externaljwt/apis/v1"; + +import "google/protobuf/timestamp.proto"; + +// This service is served by a process on a local Unix Domain Socket. +service ExternalJWTSigner { + // Sign takes a serialized JWT payload, and returns the serialized header and + // signature. The caller can then assemble the JWT from the header, payload, + // and signature. Signature can be generated by signing + // `base64url(header) + "." + base64url(payload)` with signing key. + // + // The plugin MUST set a key id in the returned JWT header. + rpc Sign(SignJWTRequest) returns (SignJWTResponse) {} + + // FetchKeys returns the set of public keys that are trusted to sign + // Kubernetes service account tokens. Kube-apiserver will call this RPC: + // + // * Every time it tries to validate a JWT from the service account issuer with an unknown key ID, and + // + // * Periodically, so it can serve reasonably-up-to-date keys from the OIDC + // JWKs endpoint. + rpc FetchKeys(FetchKeysRequest) returns (FetchKeysResponse) {} + + // Metadata is meant to be called once on startup. + // Enables sharing metadata with kube-apiserver (eg: the max token lifetime that signer supports) + rpc Metadata(MetadataRequest) returns (MetadataResponse) {} + } + + message SignJWTRequest { + // URL-safe base64 wrapped payload to be signed. + // Exactly as it appears in the second segment of the JWT + string claims = 1; + } + + message SignJWTResponse { + // header must contain only alg, kid, typ claims. + // typ must be “JWT”. + // kid must be non-empty, <=1024 characters, and its corresponding public key should not be excluded from OIDC discovery. + // alg must be one of the algorithms supported by kube-apiserver (currently RS256, ES256, ES384, ES512). + // header cannot have any additional data that kube-apiserver does not recognize. + // Already wrapped in URL-safe base64, exactly as it appears in the first segment of the JWT. + string header = 1; + + // The signature for the JWT. + // Already wrapped in URL-safe base64, exactly as it appears in the final segment of the JWT. + string signature = 2; + } + + message FetchKeysRequest {} + + message FetchKeysResponse { + repeated Key keys = 1; + + // The timestamp when this data was pulled from the authoritative source of + // truth for verification keys. + // kube-apiserver can export this from metrics, to enable end-to-end SLOs. + google.protobuf.Timestamp data_timestamp = 2; + + // refresh interval for verification keys to pick changes if any. + // any value <= 0 is considered a misconfiguration. + int64 refresh_hint_seconds = 3; + } + + message Key { + // A unique identifier for this key. + // Length must be <=1024. + string key_id = 1; + + // The public key, PKIX-serialized. + // must be a public key supported by kube-apiserver (currently RSA 256 or ECDSA 256/384/521) + bytes key = 2; + + // Set only for keys that are not used to sign bound tokens. + // eg: supported keys for legacy tokens. + // If set, key is used for verification but excluded from OIDC discovery docs. + // if set, external signer should not use this key to sign a JWT. + bool exclude_from_oidc_discovery = 3; + } + + message MetadataRequest {} + + message MetadataResponse { + // used by kube-apiserver for defaulting/validation of JWT lifetime while accounting for configuration flag values: + // 1. `--service-account-max-token-expiration` + // 2. `--service-account-extend-token-expiration` + // + // * If `--service-account-max-token-expiration` is greater than `max_token_expiration_seconds`, kube-apiserver treats that as misconfiguration and exits. + // * If `--service-account-max-token-expiration` is not explicitly set, kube-apiserver defaults to `max_token_expiration_seconds`. + // * If `--service-account-extend-token-expiration` is true, the extended expiration is `min(1 year, max_token_expiration_seconds)`. + // + // `max_token_expiration_seconds` must be at least 600s. + int64 max_token_expiration_seconds = 1; + } \ No newline at end of file diff --git a/staging/src/k8s.io/externaljwt/apis/v1alpha1/api.pb.go b/staging/src/k8s.io/externaljwt/apis/v1alpha1/api.pb.go index aafc917fc10..e7aae9c671e 100644 --- a/staging/src/k8s.io/externaljwt/apis/v1alpha1/api.pb.go +++ b/staging/src/k8s.io/externaljwt/apis/v1alpha1/api.pb.go @@ -423,8 +423,9 @@ const _ = grpc.SupportPackageIsVersion4 // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. type ExternalJWTSignerClient interface { // Sign takes a serialized JWT payload, and returns the serialized header and - // signature. The caller can then assemble the JWT from the header, payload, - // and signature. + // signature. The caller can then assemble the JWT from the header, payload, + // and signature. Signature can be generated by signing + // `base64url(header) + "." + base64url(payload)` with signing key. // // The plugin MUST set a key id in the returned JWT header. Sign(ctx context.Context, in *SignJWTRequest, opts ...grpc.CallOption) (*SignJWTResponse, error) @@ -479,8 +480,9 @@ func (c *externalJWTSignerClient) Metadata(ctx context.Context, in *MetadataRequ // ExternalJWTSignerServer is the server API for ExternalJWTSigner service. type ExternalJWTSignerServer interface { // Sign takes a serialized JWT payload, and returns the serialized header and - // signature. The caller can then assemble the JWT from the header, payload, - // and signature. + // signature. The caller can then assemble the JWT from the header, payload, + // and signature. Signature can be generated by signing + // `base64url(header) + "." + base64url(payload)` with signing key. // // The plugin MUST set a key id in the returned JWT header. Sign(context.Context, *SignJWTRequest) (*SignJWTResponse, error) diff --git a/staging/src/k8s.io/externaljwt/apis/v1alpha1/api.proto b/staging/src/k8s.io/externaljwt/apis/v1alpha1/api.proto index 97790479780..0d59eb57efd 100644 --- a/staging/src/k8s.io/externaljwt/apis/v1alpha1/api.proto +++ b/staging/src/k8s.io/externaljwt/apis/v1alpha1/api.proto @@ -26,8 +26,9 @@ import "google/protobuf/timestamp.proto"; // This service is served by a process on a local Unix Domain Socket. service ExternalJWTSigner { // Sign takes a serialized JWT payload, and returns the serialized header and - // signature. The caller can then assemble the JWT from the header, payload, - // and signature. + // signature. The caller can then assemble the JWT from the header, payload, + // and signature. Signature can be generated by signing + // `base64url(header) + "." + base64url(payload)` with signing key. // // The plugin MUST set a key id in the returned JWT header. rpc Sign(SignJWTRequest) returns (SignJWTResponse) {} diff --git a/test/compatibility_lifecycle/reference/versioned_feature_list.yaml b/test/compatibility_lifecycle/reference/versioned_feature_list.yaml index 2a0964cc46d..b12193d0fb3 100644 --- a/test/compatibility_lifecycle/reference/versioned_feature_list.yaml +++ b/test/compatibility_lifecycle/reference/versioned_feature_list.yaml @@ -495,6 +495,10 @@ lockToDefault: false preRelease: Alpha version: "1.32" + - default: true + lockToDefault: false + preRelease: Beta + version: "1.34" - name: GitRepoVolumeDriver versionedSpecs: - default: true diff --git a/test/integration/serviceaccount/external_jwt_signer_test.go b/test/integration/serviceaccount/external_jwt_signer_test.go index 8775344f6be..3f651908a6d 100644 --- a/test/integration/serviceaccount/external_jwt_signer_test.go +++ b/test/integration/serviceaccount/external_jwt_signer_test.go @@ -38,7 +38,7 @@ import ( "k8s.io/component-base/metrics/testutil" "k8s.io/kubernetes/cmd/kube-apiserver/app/options" "k8s.io/kubernetes/pkg/features" - v1alpha1testing "k8s.io/kubernetes/pkg/serviceaccount/externaljwt/plugin/testing/v1alpha1" + v1testing "k8s.io/kubernetes/pkg/serviceaccount/externaljwt/plugin/testing/v1" "k8s.io/kubernetes/test/integration/framework" "k8s.io/kubernetes/test/utils/ktesting" ) @@ -64,11 +64,11 @@ func TestExternalJWTSigningAndAuth(t *testing.T) { // create and start mock signer. socketPath := utilnettesting.MakeSocketNameForTest(t, fmt.Sprintf("mock-external-jwt-signer-%d.sock", time.Now().Nanosecond())) t.Cleanup(func() { _ = os.Remove(socketPath) }) - mockSigner := v1alpha1testing.NewMockSigner(t, socketPath) + mockSigner := v1testing.NewMockSigner(t, socketPath) defer mockSigner.CleanUp() // Start Api server configured with external signer. - client, _, tearDownFn := framework.StartTestServer(ctx, t, framework.TestServerSetup{ + client, clientConfig, tearDownFn := framework.StartTestServer(ctx, t, framework.TestServerSetup{ ModifyServerRunOptions: func(opt *options.ServerRunOptions) { opt.ServiceAccountSigningEndpoint = socketPath opt.ServiceAccountSigningKeyFile = "" @@ -133,11 +133,11 @@ func TestExternalJWTSigningAndAuth(t *testing.T) { mockSigner.SigningKey = key1 mockSigner.SigningKeyID = "updated-kid-1" - cpy := make(map[string]v1alpha1testing.KeyT) + cpy := make(map[string]v1testing.KeyT) for key, value := range mockSigner.GetSupportedKeys() { cpy[key] = value } - cpy["updated-kid-1"] = v1alpha1testing.KeyT{ + cpy["updated-kid-1"] = v1testing.KeyT{ Key: pubKey1Bytes, ExcludeFromOidcDiscovery: true, } @@ -176,7 +176,7 @@ func TestExternalJWTSigningAndAuth(t *testing.T) { mockSigner.SigningKey = key1 }, preValidationSignerUpdate: func(_ *testing.T) { - mockSigner.SetSupportedKeys(map[string]v1alpha1testing.KeyT{}) + mockSigner.SetSupportedKeys(map[string]v1testing.KeyT{}) }, shouldPassAuth: false, }, @@ -187,11 +187,11 @@ func TestExternalJWTSigningAndAuth(t *testing.T) { }, preValidationSignerUpdate: func(t *testing.T) { t.Helper() - cpy := make(map[string]v1alpha1testing.KeyT) + cpy := make(map[string]v1testing.KeyT) for key, value := range mockSigner.GetSupportedKeys() { cpy[key] = value } - cpy["kid-1"] = v1alpha1testing.KeyT{Key: pubKey1Bytes} + cpy["kid-1"] = v1testing.KeyT{Key: pubKey1Bytes} mockSigner.SetSupportedKeys(cpy) waitForDataTimestamp(t, client, time.Now()) }, @@ -245,6 +245,31 @@ func TestExternalJWTSigningAndAuth(t *testing.T) { } else if tokenReviewResult.Status.Authenticated && !tc.shouldPassAuth { t.Fatal("Expected Authentication to fail") } + + // Perform SSAR + if tc.shouldPassAuth { + config := *clientConfig + config.BearerToken = tokenRequest.Status.Token + + newClient, err := kubernetes.NewForConfig(&config) + if err != nil { + t.Fatalf("While creating a new client using token: %v", err) + } + + ssr, err := newClient.AuthenticationV1().SelfSubjectReviews().Create(ctx, &authv1.SelfSubjectReview{ + TypeMeta: metav1.TypeMeta{ + Kind: "SelfSubjectReview", + APIVersion: "authentication.k8s.io/v1", + }, + }, metav1.CreateOptions{}) + if err != nil { + t.Fatalf("Failed to perform SelfSubjectAccessReview: %v", err) + } + + if ssr.Status.UserInfo.Username != "system:serviceaccount:ns-1:sa-1" { + t.Fatalf("Unexpected username (%s) on SelfSubjectReview", ssr.Status.UserInfo.Username) + } + } }) } } @@ -292,7 +317,7 @@ func TestDelayedStartForSigner(t *testing.T) { t.Cleanup(func() { _ = os.Remove(socketPath) }) go func() { time.Sleep(20 * time.Second) - v1alpha1testing.NewMockSigner(t, socketPath) + v1testing.NewMockSigner(t, socketPath) }() // Start Api server configured with external signer.