From 519a8946d90da3826a65e592ba20715ef7be6466 Mon Sep 17 00:00:00 2001 From: Morten Mjelva Date: Mon, 30 Oct 2023 13:06:05 +0100 Subject: [PATCH] JWT: Enable reading a JSON Web Key Set from a file (#180) * Add ForwardingSignatureValidator ForwardingSignatureValidator holds a pointer to some other SignatureValidator and will forward ValidateSignature requests to this validator. The validator it references can be changed by calling Replace(). * Make JWKS a oneoff with either inline or file content This changes the jwt.proto definition to take either an inline JWKS struct, or a message containing a file path and a refresh interval. The intention is that when a file path and refresh interval is provided, we create a ForwardingSignatureValidator, and periodically update its internal SignatureValidator with content from the referenced file. When passing inline content, behavior should remain unchanged. * Load JWKS from a file When loading the JWT configuration, check if the config provides inline JWKS content, or a reference to a file. If we get a reference to a file, we set up a goroutine to periodically fetch the file and update a ForwardingSignatureValidator. --- pkg/global/apply_configuration.go | 14 +-- pkg/grpc/authenticator.go | 9 +- pkg/grpc/server.go | 2 +- pkg/http/authenticator.go | 9 +- pkg/http/server.go | 46 +++++---- pkg/jwt/BUILD.bazel | 2 + pkg/jwt/configuration.go | 90 +++++++++++++--- pkg/jwt/forwarding_signature_validator.go | 33 ++++++ pkg/proto/configuration/jwt/BUILD.bazel | 1 + pkg/proto/configuration/jwt/jwt.pb.go | 119 +++++++++++++++------- pkg/proto/configuration/jwt/jwt.proto | 10 +- 11 files changed, 244 insertions(+), 91 deletions(-) create mode 100644 pkg/jwt/forwarding_signature_validator.go diff --git a/pkg/global/apply_configuration.go b/pkg/global/apply_configuration.go index a00b49df..208d3d45 100644 --- a/pkg/global/apply_configuration.go +++ b/pkg/global/apply_configuration.go @@ -70,16 +70,10 @@ func (ls *LifecycleState) MarkReadyAndWait(group program.Group) { router.Handle("/active_spans", httpHandler) } - group.Go(func(ctx context.Context, siblingsGroup, dependenciesGroup program.Group) error { - if err := bb_http.NewServersFromConfigurationAndServe( - ls.config.HttpServers, - bb_http.NewMetricsHandler(router, "Diagnostics"), - group, - ); err != nil { - return util.StatusWrap(err, "Failed to launch diagnostics HTTP server") - } - return nil - }) + bb_http.NewServersFromConfigurationAndServe( + ls.config.HttpServers, + bb_http.NewMetricsHandler(router, "Diagnostics"), + group) } } diff --git a/pkg/grpc/authenticator.go b/pkg/grpc/authenticator.go index d09752ef..eadc0448 100644 --- a/pkg/grpc/authenticator.go +++ b/pkg/grpc/authenticator.go @@ -7,6 +7,7 @@ import ( "github.com/buildbarn/bb-storage/pkg/auth" "github.com/buildbarn/bb-storage/pkg/clock" "github.com/buildbarn/bb-storage/pkg/jwt" + "github.com/buildbarn/bb-storage/pkg/program" configuration "github.com/buildbarn/bb-storage/pkg/proto/configuration/grpc" "github.com/buildbarn/bb-storage/pkg/util" "github.com/jmespath/go-jmespath" @@ -24,7 +25,7 @@ type Authenticator interface { // NewAuthenticatorFromConfiguration creates a tree of Authenticator // objects based on a configuration file. -func NewAuthenticatorFromConfiguration(policy *configuration.AuthenticationPolicy) (Authenticator, bool, error) { +func NewAuthenticatorFromConfiguration(policy *configuration.AuthenticationPolicy, group program.Group) (Authenticator, bool, error) { if policy == nil { return nil, false, status.Error(codes.InvalidArgument, "Authentication policy not specified") } @@ -39,7 +40,7 @@ func NewAuthenticatorFromConfiguration(policy *configuration.AuthenticationPolic children := make([]Authenticator, 0, len(policyKind.Any.Policies)) needsPeerTransportCredentials := false for _, childConfiguration := range policyKind.Any.Policies { - child, childNeedsPeerTransportCredentials, err := NewAuthenticatorFromConfiguration(childConfiguration) + child, childNeedsPeerTransportCredentials, err := NewAuthenticatorFromConfiguration(childConfiguration, group) if err != nil { return nil, false, err } @@ -51,7 +52,7 @@ func NewAuthenticatorFromConfiguration(policy *configuration.AuthenticationPolic children := make([]Authenticator, 0, len(policyKind.All.Policies)) needsPeerTransportCredentials := false for _, childConfiguration := range policyKind.All.Policies { - child, childNeedsPeerTransportCredentials, err := NewAuthenticatorFromConfiguration(childConfiguration) + child, childNeedsPeerTransportCredentials, err := NewAuthenticatorFromConfiguration(childConfiguration, group) if err != nil { return nil, false, err } @@ -81,7 +82,7 @@ func NewAuthenticatorFromConfiguration(policy *configuration.AuthenticationPolic metadataExtractor, ), false, nil case *configuration.AuthenticationPolicy_Jwt: - authorizationHeaderParser, err := jwt.NewAuthorizationHeaderParserFromConfiguration(policyKind.Jwt) + authorizationHeaderParser, err := jwt.NewAuthorizationHeaderParserFromConfiguration(policyKind.Jwt, group) if err != nil { return nil, false, util.StatusWrap(err, "Failed to create authorization header parser for JWT authentication policy") } diff --git a/pkg/grpc/server.go b/pkg/grpc/server.go index 510e0635..eda6073e 100644 --- a/pkg/grpc/server.go +++ b/pkg/grpc/server.go @@ -36,7 +36,7 @@ func init() { func NewServersFromConfigurationAndServe(configurations []*configuration.ServerConfiguration, registrationFunc func(grpc.ServiceRegistrar), group program.Group) error { for _, configuration := range configurations { // Create an authenticator for requests. - authenticator, needsPeerTransportCredentials, err := NewAuthenticatorFromConfiguration(configuration.AuthenticationPolicy) + authenticator, needsPeerTransportCredentials, err := NewAuthenticatorFromConfiguration(configuration.AuthenticationPolicy, group) if err != nil { return err } diff --git a/pkg/http/authenticator.go b/pkg/http/authenticator.go index 535cfa1a..6db0f7f5 100644 --- a/pkg/http/authenticator.go +++ b/pkg/http/authenticator.go @@ -10,6 +10,7 @@ import ( "github.com/buildbarn/bb-storage/pkg/auth" "github.com/buildbarn/bb-storage/pkg/clock" "github.com/buildbarn/bb-storage/pkg/jwt" + "github.com/buildbarn/bb-storage/pkg/program" configuration "github.com/buildbarn/bb-storage/pkg/proto/configuration/http" "github.com/buildbarn/bb-storage/pkg/random" "github.com/buildbarn/bb-storage/pkg/util" @@ -30,7 +31,7 @@ type Authenticator interface { // NewAuthenticatorFromConfiguration creates a tree of Authenticator // objects based on a configuration file. -func NewAuthenticatorFromConfiguration(policy *configuration.AuthenticationPolicy) (Authenticator, error) { +func NewAuthenticatorFromConfiguration(policy *configuration.AuthenticationPolicy, group program.Group) (Authenticator, error) { if policy == nil { return nil, status.Error(codes.InvalidArgument, "Authentication policy not specified") } @@ -44,7 +45,7 @@ func NewAuthenticatorFromConfiguration(policy *configuration.AuthenticationPolic case *configuration.AuthenticationPolicy_Any: children := make([]Authenticator, 0, len(policyKind.Any.Policies)) for _, childConfiguration := range policyKind.Any.Policies { - child, err := NewAuthenticatorFromConfiguration(childConfiguration) + child, err := NewAuthenticatorFromConfiguration(childConfiguration, group) if err != nil { return nil, err } @@ -54,7 +55,7 @@ func NewAuthenticatorFromConfiguration(policy *configuration.AuthenticationPolic case *configuration.AuthenticationPolicy_Deny: return NewDenyAuthenticator(policyKind.Deny), nil case *configuration.AuthenticationPolicy_Jwt: - authorizationHeaderParser, err := jwt.NewAuthorizationHeaderParserFromConfiguration(policyKind.Jwt) + authorizationHeaderParser, err := jwt.NewAuthorizationHeaderParserFromConfiguration(policyKind.Jwt, group) if err != nil { return nil, util.StatusWrap(err, "Failed to create authorization header parser for JWT authentication policy") } @@ -118,7 +119,7 @@ func NewAuthenticatorFromConfiguration(policy *configuration.AuthenticationPolic cookieAEAD, clock.SystemClock) case *configuration.AuthenticationPolicy_AcceptHeader: - base, err := NewAuthenticatorFromConfiguration(policyKind.AcceptHeader.Policy) + base, err := NewAuthenticatorFromConfiguration(policyKind.AcceptHeader.Policy, group) if err != nil { return nil, err } diff --git a/pkg/http/server.go b/pkg/http/server.go index a6adabe3..b259f6ea 100644 --- a/pkg/http/server.go +++ b/pkg/http/server.go @@ -13,29 +13,31 @@ import ( // program.Group, based on a configuration message. The web servers are // automatically terminated if the context associated with the group is // canceled. -func NewServersFromConfigurationAndServe(configurations []*configuration.ServerConfiguration, handler http.Handler, group program.Group) error { - for _, configuration := range configurations { - authenticator, err := NewAuthenticatorFromConfiguration(configuration.AuthenticationPolicy) - if err != nil { - return err - } - authenticatedHandler := NewAuthenticatingHandler(handler, authenticator) - for _, listenAddress := range configuration.ListenAddresses { - server := http.Server{ - Addr: listenAddress, - Handler: authenticatedHandler, +func NewServersFromConfigurationAndServe(configurations []*configuration.ServerConfiguration, handler http.Handler, group program.Group) { + group.Go(func(ctx context.Context, siblingsGroup, dependenciesGroup program.Group) error { + for _, configuration := range configurations { + authenticator, err := NewAuthenticatorFromConfiguration(configuration.AuthenticationPolicy, dependenciesGroup) + if err != nil { + return err } - group.Go(func(ctx context.Context, siblingsGroup, dependenciesGroup program.Group) error { - <-ctx.Done() - return server.Close() - }) - group.Go(func(ctx context.Context, siblingsGroup, dependenciesGroup program.Group) error { - if err := server.ListenAndServe(); err != http.ErrServerClosed { - return util.StatusWrapf(err, "Failed to launch HTTP server %#v", server.Addr) + authenticatedHandler := NewAuthenticatingHandler(handler, authenticator) + for _, listenAddress := range configuration.ListenAddresses { + server := http.Server{ + Addr: listenAddress, + Handler: authenticatedHandler, } - return nil - }) + group.Go(func(ctx context.Context, siblingsGroup, dependenciesGroup program.Group) error { + <-ctx.Done() + return server.Close() + }) + group.Go(func(ctx context.Context, siblingsGroup, dependenciesGroup program.Group) error { + if err := server.ListenAndServe(); err != http.ErrServerClosed { + return util.StatusWrapf(err, "Failed to launch HTTP server %#v", server.Addr) + } + return nil + }) + } } - } - return nil + return nil + }) } diff --git a/pkg/jwt/BUILD.bazel b/pkg/jwt/BUILD.bazel index 9ca0ddaa..f3435060 100644 --- a/pkg/jwt/BUILD.bazel +++ b/pkg/jwt/BUILD.bazel @@ -9,6 +9,7 @@ go_library( "ecdsa_sha_signature_generator.go", "ecdsa_sha_signature_validator.go", "ed25519_signature_validator.go", + "forwarding_signature_validator.go", "generate_authorization_header.go", "hmac_sha_signature_validator.go", "rsa_sha_signature_validator.go", @@ -21,6 +22,7 @@ go_library( "//pkg/auth", "//pkg/clock", "//pkg/eviction", + "//pkg/program", "//pkg/proto/configuration/jwt", "//pkg/random", "//pkg/util", diff --git a/pkg/jwt/configuration.go b/pkg/jwt/configuration.go index e27ded4f..1d3bf937 100644 --- a/pkg/jwt/configuration.go +++ b/pkg/jwt/configuration.go @@ -1,14 +1,19 @@ package jwt import ( + "context" "crypto/ecdsa" "crypto/ed25519" "crypto/rsa" "encoding/json" + "log" + "os" "reflect" + "time" "github.com/buildbarn/bb-storage/pkg/clock" "github.com/buildbarn/bb-storage/pkg/eviction" + "github.com/buildbarn/bb-storage/pkg/program" configuration "github.com/buildbarn/bb-storage/pkg/proto/configuration/jwt" "github.com/buildbarn/bb-storage/pkg/util" jose "github.com/go-jose/go-jose/v3" @@ -22,18 +27,31 @@ import ( // NewAuthorizationHeaderParserFromConfiguration creates a new HTTP // "Authorization" header parser based on options stored in a // configuration file. -func NewAuthorizationHeaderParserFromConfiguration(config *configuration.AuthorizationHeaderParserConfiguration) (*AuthorizationHeaderParser, error) { - jwksJSON, err := protojson.Marshal(config.JwksInline) - if err != nil { - return nil, util.StatusWrapWithCode(err, codes.InvalidArgument, "Failed to marshal JSON Web Key Set") - } - var jwks jose.JSONWebKeySet - if err := json.Unmarshal(jwksJSON, &jwks); err != nil { - return nil, util.StatusWrapWithCode(err, codes.InvalidArgument, "Failed to unmarshal JSON Web Key Set") - } - signatureValidator, err := NewSignatureValidatorFromJSONWebKeySet(&jwks) - if err != nil { - return nil, err +func NewAuthorizationHeaderParserFromConfiguration(config *configuration.AuthorizationHeaderParserConfiguration, group program.Group) (*AuthorizationHeaderParser, error) { + var signatureValidator SignatureValidator + + switch key := config.Jwks.(type) { + case *configuration.AuthorizationHeaderParserConfiguration_JwksInline: + jwksJSON, err := protojson.Marshal(key.JwksInline) + if err != nil { + return nil, util.StatusWrapWithCode(err, codes.InvalidArgument, "Failed to marshal JSON Web Key Set") + } + var jwks jose.JSONWebKeySet + if err := json.Unmarshal(jwksJSON, &jwks); err != nil { + return nil, util.StatusWrapWithCode(err, codes.InvalidArgument, "Failed to unmarshal JSON Web Key Set") + } + signatureValidator, err = NewSignatureValidatorFromJSONWebKeySet(&jwks) + if err != nil { + return nil, err + } + case *configuration.AuthorizationHeaderParserConfiguration_JwksFile: + var err error + signatureValidator, err = NewSignatureValidatorFromJSONWebKeySetFile(key.JwksFile, group) + if err != nil { + return nil, err + } + default: + return nil, status.Error(codes.InvalidArgument, "No key type provided") } evictionSet, err := eviction.NewSetFromConfiguration[string](config.CacheReplacementPolicy) @@ -102,3 +120,51 @@ func NewSignatureValidatorFromJSONWebKeySet(jwks *jose.JSONWebKeySet) (Signature return NewDemultiplexingSignatureValidator(namedSignatureValidators, allSignatureValidators), nil } + +// NewSignatureValidatorFromJSONWebKeySetFile creates a new +// SignatureValidator capable of validating JWTs matching keys contained +// in a JSON Web Key Set read from a file. The content of the file is +// periodically refreshed. +func NewSignatureValidatorFromJSONWebKeySetFile(path string, group program.Group) (SignatureValidator, error) { + internalValidator, err := getJWKSFromFile(path) + if err != nil { + return nil, util.StatusWrapf(err, "Unable to read JWKS content from file at %#v", path) + } + forwardingValidator := NewForwardingSignatureValidator(internalValidator) + + group.Go(func(ctx context.Context, siblingsGroup, dependenciesGroup program.Group) error { + t := time.NewTicker(300 * time.Second) + defer t.Stop() + + for { + select { + case <-t.C: + internalValidator, err := getJWKSFromFile(path) + if err != nil { + log.Printf("Failed to read JWKS content from file at %#v: %s", path, err) + continue + } + forwardingValidator.Replace(internalValidator) + + case <-ctx.Done(): + return util.StatusFromContext(ctx) + } + } + }) + + return forwardingValidator, nil +} + +func getJWKSFromFile(path string) (SignatureValidator, error) { + jwksJSON, err := os.ReadFile(path) + if err != nil { + return nil, err + } + + var jwks jose.JSONWebKeySet + if err := json.Unmarshal(jwksJSON, &jwks); err != nil { + return nil, err + } + + return NewSignatureValidatorFromJSONWebKeySet(&jwks) +} diff --git a/pkg/jwt/forwarding_signature_validator.go b/pkg/jwt/forwarding_signature_validator.go new file mode 100644 index 00000000..85132b70 --- /dev/null +++ b/pkg/jwt/forwarding_signature_validator.go @@ -0,0 +1,33 @@ +package jwt + +import ( + "sync/atomic" +) + +// ForwardingSignatureValidator wraps another SignatureValidator. It is +// used when the underlying SignatureValidator needs to be replaced at +// runtime. +type ForwardingSignatureValidator struct { + validator atomic.Pointer[SignatureValidator] +} + +// NewForwardingSignatureValidator creates a SignatureValidator that simply forwards +// requests to another SignatureValidator. +// This returns a pointer to the new ForwardingSignatureValidator, so as not to +// copy the atomic.Pointer. +func NewForwardingSignatureValidator(validator SignatureValidator) *ForwardingSignatureValidator { + sv := ForwardingSignatureValidator{} + sv.validator.Store(&validator) + + return &sv +} + +// Replace replaces the registered SignatureValidator +func (sv *ForwardingSignatureValidator) Replace(validator SignatureValidator) { + sv.validator.Store(&validator) +} + +// ValidateSignature validates a signature using the registered SignatureValidator +func (sv *ForwardingSignatureValidator) ValidateSignature(algorithm string, keyID *string, headerAndPayload string, signature []byte) bool { + return (*sv.validator.Load()).ValidateSignature(algorithm, keyID, headerAndPayload, signature) +} diff --git a/pkg/proto/configuration/jwt/BUILD.bazel b/pkg/proto/configuration/jwt/BUILD.bazel index 08c0a5a8..691b652f 100644 --- a/pkg/proto/configuration/jwt/BUILD.bazel +++ b/pkg/proto/configuration/jwt/BUILD.bazel @@ -8,6 +8,7 @@ proto_library( visibility = ["//visibility:public"], deps = [ "//pkg/proto/configuration/eviction:eviction_proto", + "@com_google_protobuf//:duration_proto", "@com_google_protobuf//:struct_proto", ], ) diff --git a/pkg/proto/configuration/jwt/jwt.pb.go b/pkg/proto/configuration/jwt/jwt.pb.go index f770ccb0..ff41335e 100644 --- a/pkg/proto/configuration/jwt/jwt.pb.go +++ b/pkg/proto/configuration/jwt/jwt.pb.go @@ -10,6 +10,7 @@ import ( eviction "github.com/buildbarn/bb-storage/pkg/proto/configuration/eviction" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + _ "google.golang.org/protobuf/types/known/durationpb" structpb "google.golang.org/protobuf/types/known/structpb" reflect "reflect" sync "sync" @@ -27,11 +28,15 @@ type AuthorizationHeaderParserConfiguration struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - JwksInline *structpb.Struct `protobuf:"bytes,7,opt,name=jwks_inline,json=jwksInline,proto3" json:"jwks_inline,omitempty"` - MaximumCacheSize int32 `protobuf:"varint,3,opt,name=maximum_cache_size,json=maximumCacheSize,proto3" json:"maximum_cache_size,omitempty"` - CacheReplacementPolicy eviction.CacheReplacementPolicy `protobuf:"varint,4,opt,name=cache_replacement_policy,json=cacheReplacementPolicy,proto3,enum=buildbarn.configuration.eviction.CacheReplacementPolicy" json:"cache_replacement_policy,omitempty"` - ClaimsValidationJmespathExpression string `protobuf:"bytes,5,opt,name=claims_validation_jmespath_expression,json=claimsValidationJmespathExpression,proto3" json:"claims_validation_jmespath_expression,omitempty"` - MetadataExtractionJmespathExpression string `protobuf:"bytes,6,opt,name=metadata_extraction_jmespath_expression,json=metadataExtractionJmespathExpression,proto3" json:"metadata_extraction_jmespath_expression,omitempty"` + // Types that are assignable to Jwks: + // + // *AuthorizationHeaderParserConfiguration_JwksInline + // *AuthorizationHeaderParserConfiguration_JwksFile + Jwks isAuthorizationHeaderParserConfiguration_Jwks `protobuf_oneof:"jwks"` + MaximumCacheSize int32 `protobuf:"varint,3,opt,name=maximum_cache_size,json=maximumCacheSize,proto3" json:"maximum_cache_size,omitempty"` + CacheReplacementPolicy eviction.CacheReplacementPolicy `protobuf:"varint,4,opt,name=cache_replacement_policy,json=cacheReplacementPolicy,proto3,enum=buildbarn.configuration.eviction.CacheReplacementPolicy" json:"cache_replacement_policy,omitempty"` + ClaimsValidationJmespathExpression string `protobuf:"bytes,5,opt,name=claims_validation_jmespath_expression,json=claimsValidationJmespathExpression,proto3" json:"claims_validation_jmespath_expression,omitempty"` + MetadataExtractionJmespathExpression string `protobuf:"bytes,6,opt,name=metadata_extraction_jmespath_expression,json=metadataExtractionJmespathExpression,proto3" json:"metadata_extraction_jmespath_expression,omitempty"` } func (x *AuthorizationHeaderParserConfiguration) Reset() { @@ -66,13 +71,27 @@ func (*AuthorizationHeaderParserConfiguration) Descriptor() ([]byte, []int) { return file_pkg_proto_configuration_jwt_jwt_proto_rawDescGZIP(), []int{0} } +func (m *AuthorizationHeaderParserConfiguration) GetJwks() isAuthorizationHeaderParserConfiguration_Jwks { + if m != nil { + return m.Jwks + } + return nil +} + func (x *AuthorizationHeaderParserConfiguration) GetJwksInline() *structpb.Struct { - if x != nil { + if x, ok := x.GetJwks().(*AuthorizationHeaderParserConfiguration_JwksInline); ok { return x.JwksInline } return nil } +func (x *AuthorizationHeaderParserConfiguration) GetJwksFile() string { + if x, ok := x.GetJwks().(*AuthorizationHeaderParserConfiguration_JwksFile); ok { + return x.JwksFile + } + return "" +} + func (x *AuthorizationHeaderParserConfiguration) GetMaximumCacheSize() int32 { if x != nil { return x.MaximumCacheSize @@ -101,6 +120,24 @@ func (x *AuthorizationHeaderParserConfiguration) GetMetadataExtractionJmespathEx return "" } +type isAuthorizationHeaderParserConfiguration_Jwks interface { + isAuthorizationHeaderParserConfiguration_Jwks() +} + +type AuthorizationHeaderParserConfiguration_JwksInline struct { + JwksInline *structpb.Struct `protobuf:"bytes,7,opt,name=jwks_inline,json=jwksInline,proto3,oneof"` +} + +type AuthorizationHeaderParserConfiguration_JwksFile struct { + JwksFile string `protobuf:"bytes,8,opt,name=jwks_file,json=jwksFile,proto3,oneof"` +} + +func (*AuthorizationHeaderParserConfiguration_JwksInline) isAuthorizationHeaderParserConfiguration_Jwks() { +} + +func (*AuthorizationHeaderParserConfiguration_JwksFile) isAuthorizationHeaderParserConfiguration_Jwks() { +} + var File_pkg_proto_configuration_jwt_jwt_proto protoreflect.FileDescriptor var file_pkg_proto_configuration_jwt_jwt_proto_rawDesc = []byte{ @@ -108,44 +145,48 @@ var file_pkg_proto_configuration_jwt_jwt_proto_rawDesc = []byte{ 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x77, 0x74, 0x2f, 0x6a, 0x77, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1b, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x62, 0x61, 0x72, 0x6e, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x2e, 0x6a, 0x77, 0x74, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, + 0x2e, 0x6a, 0x77, 0x74, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x65, 0x76, 0x69, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x65, 0x76, 0x69, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x22, 0xba, 0x03, 0x0a, 0x26, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, + 0x6f, 0x74, 0x6f, 0x22, 0xe3, 0x03, 0x0a, 0x26, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x50, 0x61, 0x72, 0x73, 0x65, - 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x38, + 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3a, 0x0a, 0x0b, 0x6a, 0x77, 0x6b, 0x73, 0x5f, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x0a, 0x6a, 0x77, - 0x6b, 0x73, 0x49, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x6d, 0x61, 0x78, 0x69, - 0x6d, 0x75, 0x6d, 0x5f, 0x63, 0x61, 0x63, 0x68, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x43, 0x61, 0x63, - 0x68, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x72, 0x0a, 0x18, 0x63, 0x61, 0x63, 0x68, 0x65, 0x5f, - 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x6f, 0x6c, 0x69, - 0x63, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x38, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, - 0x62, 0x61, 0x72, 0x6e, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x2e, 0x65, 0x76, 0x69, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x61, 0x63, 0x68, - 0x65, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x6f, 0x6c, 0x69, - 0x63, 0x79, 0x52, 0x16, 0x63, 0x61, 0x63, 0x68, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, - 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x51, 0x0a, 0x25, 0x63, 0x6c, - 0x61, 0x69, 0x6d, 0x73, 0x5f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, - 0x6a, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x74, 0x68, 0x5f, 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x22, 0x63, 0x6c, 0x61, 0x69, 0x6d, - 0x73, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x6d, 0x65, 0x73, 0x70, - 0x61, 0x74, 0x68, 0x45, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x55, 0x0a, - 0x27, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x65, 0x78, 0x74, 0x72, 0x61, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6a, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x74, 0x68, 0x5f, 0x65, 0x78, - 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x24, - 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x78, 0x74, 0x72, 0x61, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x4a, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x74, 0x68, 0x45, 0x78, 0x70, 0x72, 0x65, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, - 0x42, 0x3d, 0x5a, 0x3b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, - 0x75, 0x69, 0x6c, 0x64, 0x62, 0x61, 0x72, 0x6e, 0x2f, 0x62, 0x62, 0x2d, 0x73, 0x74, 0x6f, 0x72, - 0x61, 0x67, 0x65, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x77, 0x74, 0x62, - 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x48, 0x00, 0x52, 0x0a, + 0x6a, 0x77, 0x6b, 0x73, 0x49, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x1d, 0x0a, 0x09, 0x6a, 0x77, + 0x6b, 0x73, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, + 0x08, 0x6a, 0x77, 0x6b, 0x73, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x6d, 0x61, 0x78, + 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x63, 0x61, 0x63, 0x68, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x43, 0x61, + 0x63, 0x68, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x72, 0x0a, 0x18, 0x63, 0x61, 0x63, 0x68, 0x65, + 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x6f, 0x6c, + 0x69, 0x63, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x38, 0x2e, 0x62, 0x75, 0x69, 0x6c, + 0x64, 0x62, 0x61, 0x72, 0x6e, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x2e, 0x65, 0x76, 0x69, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x61, 0x63, + 0x68, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x6f, 0x6c, + 0x69, 0x63, 0x79, 0x52, 0x16, 0x63, 0x61, 0x63, 0x68, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, + 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x51, 0x0a, 0x25, 0x63, + 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x5f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x6a, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x74, 0x68, 0x5f, 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x22, 0x63, 0x6c, 0x61, 0x69, + 0x6d, 0x73, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x6d, 0x65, 0x73, + 0x70, 0x61, 0x74, 0x68, 0x45, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x55, + 0x0a, 0x27, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x65, 0x78, 0x74, 0x72, 0x61, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6a, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x74, 0x68, 0x5f, 0x65, + 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x24, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x78, 0x74, 0x72, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x4a, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x74, 0x68, 0x45, 0x78, 0x70, 0x72, 0x65, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x06, 0x0a, 0x04, 0x6a, 0x77, 0x6b, 0x73, 0x4a, 0x04, 0x08, + 0x01, 0x10, 0x02, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x42, 0x3d, 0x5a, 0x3b, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x62, 0x61, 0x72, + 0x6e, 0x2f, 0x62, 0x62, 0x2d, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2f, 0x70, 0x6b, 0x67, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x77, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -195,6 +236,10 @@ func file_pkg_proto_configuration_jwt_jwt_proto_init() { } } } + file_pkg_proto_configuration_jwt_jwt_proto_msgTypes[0].OneofWrappers = []interface{}{ + (*AuthorizationHeaderParserConfiguration_JwksInline)(nil), + (*AuthorizationHeaderParserConfiguration_JwksFile)(nil), + } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/pkg/proto/configuration/jwt/jwt.proto b/pkg/proto/configuration/jwt/jwt.proto index 5792267e..be6c4d26 100644 --- a/pkg/proto/configuration/jwt/jwt.proto +++ b/pkg/proto/configuration/jwt/jwt.proto @@ -2,6 +2,7 @@ syntax = "proto3"; package buildbarn.configuration.jwt; +import "google/protobuf/duration.proto"; import "google/protobuf/struct.proto"; import "pkg/proto/configuration/eviction/eviction.proto"; @@ -24,7 +25,14 @@ message AuthorizationHeaderParserConfiguration { // - https://smallstep.com/docs/step-cli/reference/crypto/key/format/ // - https://irrte.ch/jwt-js-decode/pem2jwk.html // - https://russelldavies.github.io/jwk-creator/ - google.protobuf.Struct jwks_inline = 7; + oneof jwks { + // JWKS stored inline in the configuration. + google.protobuf.Struct jwks_inline = 7; + + // JWKS stored in a separate file. + // The file will be automatically reloaded every 300 seconds. + string jwks_file = 8; + } // Maximum number of validated tokens to cache in memory. This speeds // up successive requests made with the same token.