KEP-740: promote ExternalJWTSigner feature to beta

This commit is contained in:
Harshal Neelkamal
2025-04-14 22:33:39 +00:00
parent 1a200abf92
commit 0baeccd32f
14 changed files with 821 additions and 60 deletions

View File

@@ -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

View File

@@ -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)

View File

@@ -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: {

View File

@@ -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)

View File

@@ -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() {

View File

@@ -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)

View File

@@ -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),

View File

@@ -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: &timestamppb.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)

View File

@@ -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",
}

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -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) {}

View File

@@ -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

View File

@@ -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.