Skip to content

Commit

Permalink
Remote authenticator and authorizer (#234)
Browse files Browse the repository at this point in the history
* Move authorizer_factory out to pkg/auth/configuration

Prepare for remote authentication and authorization to avoid future
curcular dependency.

* Add grpcClientFactory to grpc.NewAuthenticatorFromConfiguration

* Add grpcClientFactory to http.NewAuthenticatorFromConfiguration

* Add grpcClientFactory to NewAuthorizerFromConfiguration

* Add remote auth .proto definitions

* Implement remote authenticator and authorizer

The authenticate and authorize tasks can now be sent remotely over gRPC
to an external service. This way, custom authentication and
authorization does not require a modified builds of the Buildbarn
components.

To avoid spamming the remote service with calls for every REv2 request
and keep the latency low, the verdicts, both allow and deny, are cached
for a duration specified in the response from the remote service.

* Deduplicate JWT header authenticator code
  • Loading branch information
moroten authored Feb 20, 2025
1 parent c93a48e commit 214cfae
Show file tree
Hide file tree
Showing 39 changed files with 3,316 additions and 616 deletions.
3 changes: 2 additions & 1 deletion cmd/bb_replicator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,12 @@ func main() {
replicator_pb.RegisterReplicatorServer(s, replication.NewReplicatorServer(replicator))
},
siblingsGroup,
grpcClientFactory,
); err != nil {
return util.StatusWrap(err, "gRPC server failure")
}

lifecycleState.MarkReadyAndWait(siblingsGroup)
lifecycleState.MarkReadyAndWait(siblingsGroup, grpcClientFactory)
return nil
})
}
1 change: 1 addition & 0 deletions cmd/bb_storage/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ go_library(
visibility = ["//visibility:private"],
deps = [
"//pkg/auth",
"//pkg/auth/configuration",
"//pkg/blobstore",
"//pkg/blobstore/configuration",
"//pkg/blobstore/grpcservers",
Expand Down
35 changes: 21 additions & 14 deletions cmd/bb_storage/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

remoteexecution "github.com/bazelbuild/remote-apis/build/bazel/remote/execution/v2"
"github.com/buildbarn/bb-storage/pkg/auth"
auth_configuration "github.com/buildbarn/bb-storage/pkg/auth/configuration"
"github.com/buildbarn/bb-storage/pkg/blobstore"
blobstore_configuration "github.com/buildbarn/bb-storage/pkg/blobstore/configuration"
"github.com/buildbarn/bb-storage/pkg/blobstore/grpcservers"
Expand Down Expand Up @@ -56,7 +57,8 @@ func main() {
configuration.ContentAddressableStorage,
blobstore_configuration.NewCASBlobAccessCreator(
grpcClientFactory,
int(configuration.MaximumMessageSizeBytes)))
int(configuration.MaximumMessageSizeBytes)),
grpcClientFactory)
if err != nil {
return util.StatusWrap(err, "Failed to create Content Addressable Storage")
}
Expand All @@ -75,7 +77,8 @@ func main() {
blobstore_configuration.NewACBlobAccessCreator(
contentAddressableStorageInfo,
grpcClientFactory,
int(configuration.MaximumMessageSizeBytes)))
int(configuration.MaximumMessageSizeBytes)),
grpcClientFactory)
if err != nil {
return util.StatusWrap(err, "Failed to create Action Cache")
}
Expand All @@ -94,7 +97,8 @@ func main() {
configuration.IndirectContentAddressableStorage,
blobstore_configuration.NewICASBlobAccessCreator(
grpcClientFactory,
int(configuration.MaximumMessageSizeBytes)))
int(configuration.MaximumMessageSizeBytes)),
grpcClientFactory)
if err != nil {
return util.StatusWrap(err, "Failed to create Indirect Content Addressable Storage")
}
Expand All @@ -109,7 +113,8 @@ func main() {
configuration.InitialSizeClassCache,
blobstore_configuration.NewISCCBlobAccessCreator(
grpcClientFactory,
int(configuration.MaximumMessageSizeBytes)))
int(configuration.MaximumMessageSizeBytes)),
grpcClientFactory)
if err != nil {
return util.StatusWrap(err, "Failed to create Initial Size Class Cache")
}
Expand All @@ -124,7 +129,8 @@ func main() {
configuration.FileSystemAccessCache,
blobstore_configuration.NewFSACBlobAccessCreator(
grpcClientFactory,
int(configuration.MaximumMessageSizeBytes)))
int(configuration.MaximumMessageSizeBytes)),
grpcClientFactory)
if err != nil {
return util.StatusWrap(err, "Failed to create File System Access Cache")
}
Expand All @@ -148,7 +154,7 @@ func main() {
if err != nil {
return err
}
executeAuthorizer, err := auth.DefaultAuthorizerFactory.NewAuthorizerFromConfiguration(configuration.GetExecuteAuthorizer())
executeAuthorizer, err := auth_configuration.DefaultAuthorizerFactory.NewAuthorizerFromConfiguration(configuration.GetExecuteAuthorizer(), grpcClientFactory)
if err != nil {
return util.StatusWrap(err, "Failed to create execute authorizer")
}
Expand Down Expand Up @@ -210,26 +216,27 @@ func main() {
}
},
siblingsGroup,
grpcClientFactory,
); err != nil {
return util.StatusWrap(err, "gRPC server failure")
}

lifecycleState.MarkReadyAndWait(siblingsGroup)
lifecycleState.MarkReadyAndWait(siblingsGroup, grpcClientFactory)
return nil
})
}

func newNonScannableBlobAccess(dependenciesGroup program.Group, configuration *bb_storage.NonScannableBlobAccessConfiguration, creator blobstore_configuration.BlobAccessCreator) (blobstore_configuration.BlobAccessInfo, blobstore.BlobAccess, []auth.Authorizer, auth.Authorizer, error) {
func newNonScannableBlobAccess(dependenciesGroup program.Group, configuration *bb_storage.NonScannableBlobAccessConfiguration, creator blobstore_configuration.BlobAccessCreator, grpcClientFactory bb_grpc.ClientFactory) (blobstore_configuration.BlobAccessInfo, blobstore.BlobAccess, []auth.Authorizer, auth.Authorizer, error) {
info, err := blobstore_configuration.NewBlobAccessFromConfiguration(dependenciesGroup, configuration.Backend, creator)
if err != nil {
return blobstore_configuration.BlobAccessInfo{}, nil, nil, nil, err
}

getAuthorizer, err := auth.DefaultAuthorizerFactory.NewAuthorizerFromConfiguration(configuration.GetAuthorizer)
getAuthorizer, err := auth_configuration.DefaultAuthorizerFactory.NewAuthorizerFromConfiguration(configuration.GetAuthorizer, grpcClientFactory)
if err != nil {
return blobstore_configuration.BlobAccessInfo{}, nil, nil, nil, util.StatusWrap(err, "Failed to create Get() authorizer")
}
putAuthorizer, err := auth.DefaultAuthorizerFactory.NewAuthorizerFromConfiguration(configuration.PutAuthorizer)
putAuthorizer, err := auth_configuration.DefaultAuthorizerFactory.NewAuthorizerFromConfiguration(configuration.PutAuthorizer, grpcClientFactory)
if err != nil {
return blobstore_configuration.BlobAccessInfo{}, nil, nil, nil, util.StatusWrap(err, "Failed to create Put() authorizer")
}
Expand All @@ -241,21 +248,21 @@ func newNonScannableBlobAccess(dependenciesGroup program.Group, configuration *b
nil
}

func newScannableBlobAccess(dependenciesGroup program.Group, configuration *bb_storage.ScannableBlobAccessConfiguration, creator blobstore_configuration.BlobAccessCreator) (blobstore_configuration.BlobAccessInfo, blobstore.BlobAccess, []auth.Authorizer, error) {
func newScannableBlobAccess(dependenciesGroup program.Group, configuration *bb_storage.ScannableBlobAccessConfiguration, creator blobstore_configuration.BlobAccessCreator, grpcClientFactory bb_grpc.ClientFactory) (blobstore_configuration.BlobAccessInfo, blobstore.BlobAccess, []auth.Authorizer, error) {
info, err := blobstore_configuration.NewBlobAccessFromConfiguration(dependenciesGroup, configuration.Backend, creator)
if err != nil {
return blobstore_configuration.BlobAccessInfo{}, nil, nil, err
}

getAuthorizer, err := auth.DefaultAuthorizerFactory.NewAuthorizerFromConfiguration(configuration.GetAuthorizer)
getAuthorizer, err := auth_configuration.DefaultAuthorizerFactory.NewAuthorizerFromConfiguration(configuration.GetAuthorizer, grpcClientFactory)
if err != nil {
return blobstore_configuration.BlobAccessInfo{}, nil, nil, util.StatusWrap(err, "Failed to create Get() authorizer")
}
putAuthorizer, err := auth.DefaultAuthorizerFactory.NewAuthorizerFromConfiguration(configuration.PutAuthorizer)
putAuthorizer, err := auth_configuration.DefaultAuthorizerFactory.NewAuthorizerFromConfiguration(configuration.PutAuthorizer, grpcClientFactory)
if err != nil {
return blobstore_configuration.BlobAccessInfo{}, nil, nil, util.StatusWrap(err, "Failed to create Put() authorizer")
}
findMissingAuthorizer, err := auth.DefaultAuthorizerFactory.NewAuthorizerFromConfiguration(configuration.FindMissingAuthorizer)
findMissingAuthorizer, err := auth_configuration.DefaultAuthorizerFactory.NewAuthorizerFromConfiguration(configuration.FindMissingAuthorizer, grpcClientFactory)
if err != nil {
return blobstore_configuration.BlobAccessInfo{}, nil, nil, util.StatusWrap(err, "Failed to create FindMissing() authorizer")
}
Expand Down
1 change: 1 addition & 0 deletions internal/mock/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ gomock(
out = "auth.go",
interfaces = [
"Authorizer",
"RequestHeadersAuthenticator",
],
library = "//pkg/auth",
mockgen_model_library = "@org_uber_go_mock//mockgen/model",
Expand Down
16 changes: 14 additions & 2 deletions pkg/auth/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,29 @@ go_library(
"any_authorizer.go",
"authentication_metadata.go",
"authorizer.go",
"authorizer_factory.go",
"jmespath_expression_authorizer.go",
"remote_authorizer.go",
"remote_request_headers_authenticator.go",
"request_headers_authenticator.go",
"static_authorizer.go",
],
importpath = "github.com/buildbarn/bb-storage/pkg/auth",
visibility = ["//visibility:public"],
deps = [
"//pkg/clock",
"//pkg/digest",
"//pkg/eviction",
"//pkg/otel",
"//pkg/proto/auth",
"//pkg/proto/configuration/auth",
"//pkg/util",
"@com_github_jmespath_go_jmespath//:go-jmespath",
"@io_opentelemetry_go_otel//attribute",
"@org_golang_google_grpc//:grpc",
"@org_golang_google_grpc//codes",
"@org_golang_google_grpc//status",
"@org_golang_google_protobuf//encoding/protojson",
"@org_golang_google_protobuf//proto",
"@org_golang_google_protobuf//types/known/structpb",
],
)

Expand All @@ -33,21 +38,28 @@ go_test(
"any_authorizer_test.go",
"authentication_metadata_test.go",
"jmespath_expression_authorizer_test.go",
"remote_authorizer_test.go",
"remote_request_headers_authenticator_test.go",
"static_authorizer_test.go",
],
deps = [
":auth",
"//internal/mock",
"//pkg/digest",
"//pkg/eviction",
"//pkg/proto/auth",
"//pkg/testutil",
"@com_github_jmespath_go_jmespath//:go-jmespath",
"@com_github_stretchr_testify//require",
"@io_opentelemetry_go_otel//attribute",
"@io_opentelemetry_go_proto_otlp//common/v1:common",
"@org_golang_google_grpc//:grpc",
"@org_golang_google_grpc//codes",
"@org_golang_google_grpc//status",
"@org_golang_google_protobuf//proto",
"@org_golang_google_protobuf//types/known/emptypb",
"@org_golang_google_protobuf//types/known/structpb",
"@org_golang_google_protobuf//types/known/timestamppb",
"@org_uber_go_mock//gomock",
],
)
21 changes: 21 additions & 0 deletions pkg/auth/configuration/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
load("@rules_go//go:def.bzl", "go_library")

go_library(
name = "configuration",
srcs = ["authorizer_factory.go"],
importpath = "github.com/buildbarn/bb-storage/pkg/auth/configuration",
visibility = ["//visibility:public"],
deps = [
"//pkg/auth",
"//pkg/clock",
"//pkg/digest",
"//pkg/eviction",
"//pkg/grpc",
"//pkg/proto/configuration/auth",
"//pkg/util",
"@com_github_jmespath_go_jmespath//:go-jmespath",
"@org_golang_google_grpc//codes",
"@org_golang_google_grpc//status",
"@org_golang_google_protobuf//encoding/protojson",
],
)
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package auth
package configuration

import (
"github.com/buildbarn/bb-storage/pkg/auth"
"github.com/buildbarn/bb-storage/pkg/clock"
"github.com/buildbarn/bb-storage/pkg/digest"
"github.com/buildbarn/bb-storage/pkg/eviction"
"github.com/buildbarn/bb-storage/pkg/grpc"
pb "github.com/buildbarn/bb-storage/pkg/proto/configuration/auth"
"github.com/buildbarn/bb-storage/pkg/util"
"github.com/jmespath/go-jmespath"
Expand All @@ -16,7 +20,7 @@ import (
type AuthorizerFactory interface {
// NewAuthorizerFromConfiguration constructs an authorizer based on
// options specified in a configuration message.
NewAuthorizerFromConfiguration(configuration *pb.AuthorizerConfiguration) (Authorizer, error)
NewAuthorizerFromConfiguration(configuration *pb.AuthorizerConfiguration, grpcClientFactory grpc.ClientFactory) (auth.Authorizer, error)
}

// DefaultAuthorizerFactory constructs deduplicated authorizers based on
Expand All @@ -29,15 +33,15 @@ type BaseAuthorizerFactory struct{}

// NewAuthorizerFromConfiguration constructs an authorizer based on
// options specified in a configuration message.
func (f BaseAuthorizerFactory) NewAuthorizerFromConfiguration(config *pb.AuthorizerConfiguration) (Authorizer, error) {
func (f BaseAuthorizerFactory) NewAuthorizerFromConfiguration(config *pb.AuthorizerConfiguration, grpcClientFactory grpc.ClientFactory) (auth.Authorizer, error) {
if config == nil {
return nil, status.Error(codes.InvalidArgument, "Authorizer configuration not specified")
}
switch policy := config.Policy.(type) {
case *pb.AuthorizerConfiguration_Allow:
return NewStaticAuthorizer(func(in digest.InstanceName) bool { return true }), nil
return auth.NewStaticAuthorizer(func(in digest.InstanceName) bool { return true }), nil
case *pb.AuthorizerConfiguration_Deny:
return NewStaticAuthorizer(func(in digest.InstanceName) bool { return false }), nil
return auth.NewStaticAuthorizer(func(in digest.InstanceName) bool { return false }), nil
case *pb.AuthorizerConfiguration_InstanceNamePrefix:
trie := digest.NewInstanceNameTrie()
for _, i := range policy.InstanceNamePrefix.AllowedInstanceNamePrefixes {
Expand All @@ -47,13 +51,29 @@ func (f BaseAuthorizerFactory) NewAuthorizerFromConfiguration(config *pb.Authori
}
trie.Set(instanceNamePrefix, 0)
}
return NewStaticAuthorizer(trie.ContainsPrefix), nil
return auth.NewStaticAuthorizer(trie.ContainsPrefix), nil
case *pb.AuthorizerConfiguration_JmespathExpression:
expression, err := jmespath.Compile(policy.JmespathExpression)
if err != nil {
return nil, util.StatusWrapWithCode(err, codes.InvalidArgument, "Failed to compile JMESPath expression")
}
return NewJMESPathExpressionAuthorizer(expression), nil
return auth.NewJMESPathExpressionAuthorizer(expression), nil
case *pb.AuthorizerConfiguration_Remote:
grpcClient, err := grpcClientFactory.NewClientFromConfiguration(policy.Remote.Endpoint)
if err != nil {
return nil, util.StatusWrap(err, "Failed to create authorizer RPC client")
}
evictionSet, err := eviction.NewSetFromConfiguration[auth.RemoteAuthorizerCacheKey](policy.Remote.CacheReplacementPolicy)
if err != nil {
return nil, util.StatusWrap(err, "Cache replacement policy for remote authorization")
}
return auth.NewRemoteAuthorizer(
grpcClient,
policy.Remote.Scope,
clock.SystemClock,
eviction.NewMetricsSet(evictionSet, "remote_authorizer"),
int(policy.Remote.MaximumCacheSize),
), nil
default:
return nil, status.Error(codes.InvalidArgument, "Unknown authorizer configuration")
}
Expand All @@ -62,7 +82,7 @@ func (f BaseAuthorizerFactory) NewAuthorizerFromConfiguration(config *pb.Authori
type deduplicatingAuthorizerFactory struct {
base AuthorizerFactory
// Keys are protojson-encoded pb.AuthorizerConfigurations
known map[string]Authorizer
known map[string]auth.Authorizer
}

// NewDeduplicatingAuthorizerFactory creates a new AuthorizerFactory
Expand All @@ -71,19 +91,19 @@ type deduplicatingAuthorizerFactory struct {
func NewDeduplicatingAuthorizerFactory(base AuthorizerFactory) AuthorizerFactory {
return &deduplicatingAuthorizerFactory{
base: base,
known: make(map[string]Authorizer),
known: make(map[string]auth.Authorizer),
}
}

// NewAuthorizerFromConfiguration creates an Authorizer based on the passed configuration.
func (af *deduplicatingAuthorizerFactory) NewAuthorizerFromConfiguration(config *pb.AuthorizerConfiguration) (Authorizer, error) {
func (af *deduplicatingAuthorizerFactory) NewAuthorizerFromConfiguration(config *pb.AuthorizerConfiguration, grpcClientFactory grpc.ClientFactory) (auth.Authorizer, error) {
keyBytes, err := protojson.Marshal(config)
key := string(keyBytes)
if err != nil {
return nil, err
}
if _, ok := af.known[key]; !ok {
a, err := af.base.NewAuthorizerFromConfiguration(config)
a, err := af.base.NewAuthorizerFromConfiguration(config, grpcClientFactory)
if err != nil {
return nil, err
}
Expand Down
Loading

0 comments on commit 214cfae

Please sign in to comment.