Skip to content

Commit 214cfae

Browse files
authored
Remote authenticator and authorizer (#234)
* 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
1 parent c93a48e commit 214cfae

39 files changed

+3316
-616
lines changed

cmd/bb_replicator/main.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,12 @@ func main() {
6565
replicator_pb.RegisterReplicatorServer(s, replication.NewReplicatorServer(replicator))
6666
},
6767
siblingsGroup,
68+
grpcClientFactory,
6869
); err != nil {
6970
return util.StatusWrap(err, "gRPC server failure")
7071
}
7172

72-
lifecycleState.MarkReadyAndWait(siblingsGroup)
73+
lifecycleState.MarkReadyAndWait(siblingsGroup, grpcClientFactory)
7374
return nil
7475
})
7576
}

cmd/bb_storage/BUILD.bazel

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ go_library(
88
visibility = ["//visibility:private"],
99
deps = [
1010
"//pkg/auth",
11+
"//pkg/auth/configuration",
1112
"//pkg/blobstore",
1213
"//pkg/blobstore/configuration",
1314
"//pkg/blobstore/grpcservers",

cmd/bb_storage/main.go

+21-14
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66

77
remoteexecution "github.com/bazelbuild/remote-apis/build/bazel/remote/execution/v2"
88
"github.com/buildbarn/bb-storage/pkg/auth"
9+
auth_configuration "github.com/buildbarn/bb-storage/pkg/auth/configuration"
910
"github.com/buildbarn/bb-storage/pkg/blobstore"
1011
blobstore_configuration "github.com/buildbarn/bb-storage/pkg/blobstore/configuration"
1112
"github.com/buildbarn/bb-storage/pkg/blobstore/grpcservers"
@@ -56,7 +57,8 @@ func main() {
5657
configuration.ContentAddressableStorage,
5758
blobstore_configuration.NewCASBlobAccessCreator(
5859
grpcClientFactory,
59-
int(configuration.MaximumMessageSizeBytes)))
60+
int(configuration.MaximumMessageSizeBytes)),
61+
grpcClientFactory)
6062
if err != nil {
6163
return util.StatusWrap(err, "Failed to create Content Addressable Storage")
6264
}
@@ -75,7 +77,8 @@ func main() {
7577
blobstore_configuration.NewACBlobAccessCreator(
7678
contentAddressableStorageInfo,
7779
grpcClientFactory,
78-
int(configuration.MaximumMessageSizeBytes)))
80+
int(configuration.MaximumMessageSizeBytes)),
81+
grpcClientFactory)
7982
if err != nil {
8083
return util.StatusWrap(err, "Failed to create Action Cache")
8184
}
@@ -94,7 +97,8 @@ func main() {
9497
configuration.IndirectContentAddressableStorage,
9598
blobstore_configuration.NewICASBlobAccessCreator(
9699
grpcClientFactory,
97-
int(configuration.MaximumMessageSizeBytes)))
100+
int(configuration.MaximumMessageSizeBytes)),
101+
grpcClientFactory)
98102
if err != nil {
99103
return util.StatusWrap(err, "Failed to create Indirect Content Addressable Storage")
100104
}
@@ -109,7 +113,8 @@ func main() {
109113
configuration.InitialSizeClassCache,
110114
blobstore_configuration.NewISCCBlobAccessCreator(
111115
grpcClientFactory,
112-
int(configuration.MaximumMessageSizeBytes)))
116+
int(configuration.MaximumMessageSizeBytes)),
117+
grpcClientFactory)
113118
if err != nil {
114119
return util.StatusWrap(err, "Failed to create Initial Size Class Cache")
115120
}
@@ -124,7 +129,8 @@ func main() {
124129
configuration.FileSystemAccessCache,
125130
blobstore_configuration.NewFSACBlobAccessCreator(
126131
grpcClientFactory,
127-
int(configuration.MaximumMessageSizeBytes)))
132+
int(configuration.MaximumMessageSizeBytes)),
133+
grpcClientFactory)
128134
if err != nil {
129135
return util.StatusWrap(err, "Failed to create File System Access Cache")
130136
}
@@ -148,7 +154,7 @@ func main() {
148154
if err != nil {
149155
return err
150156
}
151-
executeAuthorizer, err := auth.DefaultAuthorizerFactory.NewAuthorizerFromConfiguration(configuration.GetExecuteAuthorizer())
157+
executeAuthorizer, err := auth_configuration.DefaultAuthorizerFactory.NewAuthorizerFromConfiguration(configuration.GetExecuteAuthorizer(), grpcClientFactory)
152158
if err != nil {
153159
return util.StatusWrap(err, "Failed to create execute authorizer")
154160
}
@@ -210,26 +216,27 @@ func main() {
210216
}
211217
},
212218
siblingsGroup,
219+
grpcClientFactory,
213220
); err != nil {
214221
return util.StatusWrap(err, "gRPC server failure")
215222
}
216223

217-
lifecycleState.MarkReadyAndWait(siblingsGroup)
224+
lifecycleState.MarkReadyAndWait(siblingsGroup, grpcClientFactory)
218225
return nil
219226
})
220227
}
221228

222-
func newNonScannableBlobAccess(dependenciesGroup program.Group, configuration *bb_storage.NonScannableBlobAccessConfiguration, creator blobstore_configuration.BlobAccessCreator) (blobstore_configuration.BlobAccessInfo, blobstore.BlobAccess, []auth.Authorizer, auth.Authorizer, error) {
229+
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) {
223230
info, err := blobstore_configuration.NewBlobAccessFromConfiguration(dependenciesGroup, configuration.Backend, creator)
224231
if err != nil {
225232
return blobstore_configuration.BlobAccessInfo{}, nil, nil, nil, err
226233
}
227234

228-
getAuthorizer, err := auth.DefaultAuthorizerFactory.NewAuthorizerFromConfiguration(configuration.GetAuthorizer)
235+
getAuthorizer, err := auth_configuration.DefaultAuthorizerFactory.NewAuthorizerFromConfiguration(configuration.GetAuthorizer, grpcClientFactory)
229236
if err != nil {
230237
return blobstore_configuration.BlobAccessInfo{}, nil, nil, nil, util.StatusWrap(err, "Failed to create Get() authorizer")
231238
}
232-
putAuthorizer, err := auth.DefaultAuthorizerFactory.NewAuthorizerFromConfiguration(configuration.PutAuthorizer)
239+
putAuthorizer, err := auth_configuration.DefaultAuthorizerFactory.NewAuthorizerFromConfiguration(configuration.PutAuthorizer, grpcClientFactory)
233240
if err != nil {
234241
return blobstore_configuration.BlobAccessInfo{}, nil, nil, nil, util.StatusWrap(err, "Failed to create Put() authorizer")
235242
}
@@ -241,21 +248,21 @@ func newNonScannableBlobAccess(dependenciesGroup program.Group, configuration *b
241248
nil
242249
}
243250

244-
func newScannableBlobAccess(dependenciesGroup program.Group, configuration *bb_storage.ScannableBlobAccessConfiguration, creator blobstore_configuration.BlobAccessCreator) (blobstore_configuration.BlobAccessInfo, blobstore.BlobAccess, []auth.Authorizer, error) {
251+
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) {
245252
info, err := blobstore_configuration.NewBlobAccessFromConfiguration(dependenciesGroup, configuration.Backend, creator)
246253
if err != nil {
247254
return blobstore_configuration.BlobAccessInfo{}, nil, nil, err
248255
}
249256

250-
getAuthorizer, err := auth.DefaultAuthorizerFactory.NewAuthorizerFromConfiguration(configuration.GetAuthorizer)
257+
getAuthorizer, err := auth_configuration.DefaultAuthorizerFactory.NewAuthorizerFromConfiguration(configuration.GetAuthorizer, grpcClientFactory)
251258
if err != nil {
252259
return blobstore_configuration.BlobAccessInfo{}, nil, nil, util.StatusWrap(err, "Failed to create Get() authorizer")
253260
}
254-
putAuthorizer, err := auth.DefaultAuthorizerFactory.NewAuthorizerFromConfiguration(configuration.PutAuthorizer)
261+
putAuthorizer, err := auth_configuration.DefaultAuthorizerFactory.NewAuthorizerFromConfiguration(configuration.PutAuthorizer, grpcClientFactory)
255262
if err != nil {
256263
return blobstore_configuration.BlobAccessInfo{}, nil, nil, util.StatusWrap(err, "Failed to create Put() authorizer")
257264
}
258-
findMissingAuthorizer, err := auth.DefaultAuthorizerFactory.NewAuthorizerFromConfiguration(configuration.FindMissingAuthorizer)
265+
findMissingAuthorizer, err := auth_configuration.DefaultAuthorizerFactory.NewAuthorizerFromConfiguration(configuration.FindMissingAuthorizer, grpcClientFactory)
259266
if err != nil {
260267
return blobstore_configuration.BlobAccessInfo{}, nil, nil, util.StatusWrap(err, "Failed to create FindMissing() authorizer")
261268
}

internal/mock/BUILD.bazel

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ gomock(
2323
out = "auth.go",
2424
interfaces = [
2525
"Authorizer",
26+
"RequestHeadersAuthenticator",
2627
],
2728
library = "//pkg/auth",
2829
mockgen_model_library = "@org_uber_go_mock//mockgen/model",

pkg/auth/BUILD.bazel

+14-2
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,29 @@ go_library(
66
"any_authorizer.go",
77
"authentication_metadata.go",
88
"authorizer.go",
9-
"authorizer_factory.go",
109
"jmespath_expression_authorizer.go",
10+
"remote_authorizer.go",
11+
"remote_request_headers_authenticator.go",
12+
"request_headers_authenticator.go",
1113
"static_authorizer.go",
1214
],
1315
importpath = "github.com/buildbarn/bb-storage/pkg/auth",
1416
visibility = ["//visibility:public"],
1517
deps = [
18+
"//pkg/clock",
1619
"//pkg/digest",
20+
"//pkg/eviction",
1721
"//pkg/otel",
1822
"//pkg/proto/auth",
19-
"//pkg/proto/configuration/auth",
2023
"//pkg/util",
2124
"@com_github_jmespath_go_jmespath//:go-jmespath",
2225
"@io_opentelemetry_go_otel//attribute",
26+
"@org_golang_google_grpc//:grpc",
2327
"@org_golang_google_grpc//codes",
2428
"@org_golang_google_grpc//status",
2529
"@org_golang_google_protobuf//encoding/protojson",
2630
"@org_golang_google_protobuf//proto",
31+
"@org_golang_google_protobuf//types/known/structpb",
2732
],
2833
)
2934

@@ -33,21 +38,28 @@ go_test(
3338
"any_authorizer_test.go",
3439
"authentication_metadata_test.go",
3540
"jmespath_expression_authorizer_test.go",
41+
"remote_authorizer_test.go",
42+
"remote_request_headers_authenticator_test.go",
3643
"static_authorizer_test.go",
3744
],
3845
deps = [
3946
":auth",
4047
"//internal/mock",
4148
"//pkg/digest",
49+
"//pkg/eviction",
4250
"//pkg/proto/auth",
4351
"//pkg/testutil",
4452
"@com_github_jmespath_go_jmespath//:go-jmespath",
4553
"@com_github_stretchr_testify//require",
4654
"@io_opentelemetry_go_otel//attribute",
4755
"@io_opentelemetry_go_proto_otlp//common/v1:common",
56+
"@org_golang_google_grpc//:grpc",
4857
"@org_golang_google_grpc//codes",
4958
"@org_golang_google_grpc//status",
59+
"@org_golang_google_protobuf//proto",
60+
"@org_golang_google_protobuf//types/known/emptypb",
5061
"@org_golang_google_protobuf//types/known/structpb",
62+
"@org_golang_google_protobuf//types/known/timestamppb",
5163
"@org_uber_go_mock//gomock",
5264
],
5365
)

pkg/auth/configuration/BUILD.bazel

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
load("@rules_go//go:def.bzl", "go_library")
2+
3+
go_library(
4+
name = "configuration",
5+
srcs = ["authorizer_factory.go"],
6+
importpath = "github.com/buildbarn/bb-storage/pkg/auth/configuration",
7+
visibility = ["//visibility:public"],
8+
deps = [
9+
"//pkg/auth",
10+
"//pkg/clock",
11+
"//pkg/digest",
12+
"//pkg/eviction",
13+
"//pkg/grpc",
14+
"//pkg/proto/configuration/auth",
15+
"//pkg/util",
16+
"@com_github_jmespath_go_jmespath//:go-jmespath",
17+
"@org_golang_google_grpc//codes",
18+
"@org_golang_google_grpc//status",
19+
"@org_golang_google_protobuf//encoding/protojson",
20+
],
21+
)

pkg/auth/authorizer_factory.go pkg/auth/configuration/authorizer_factory.go

+31-11
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1-
package auth
1+
package configuration
22

33
import (
4+
"github.com/buildbarn/bb-storage/pkg/auth"
5+
"github.com/buildbarn/bb-storage/pkg/clock"
46
"github.com/buildbarn/bb-storage/pkg/digest"
7+
"github.com/buildbarn/bb-storage/pkg/eviction"
8+
"github.com/buildbarn/bb-storage/pkg/grpc"
59
pb "github.com/buildbarn/bb-storage/pkg/proto/configuration/auth"
610
"github.com/buildbarn/bb-storage/pkg/util"
711
"github.com/jmespath/go-jmespath"
@@ -16,7 +20,7 @@ import (
1620
type AuthorizerFactory interface {
1721
// NewAuthorizerFromConfiguration constructs an authorizer based on
1822
// options specified in a configuration message.
19-
NewAuthorizerFromConfiguration(configuration *pb.AuthorizerConfiguration) (Authorizer, error)
23+
NewAuthorizerFromConfiguration(configuration *pb.AuthorizerConfiguration, grpcClientFactory grpc.ClientFactory) (auth.Authorizer, error)
2024
}
2125

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

3034
// NewAuthorizerFromConfiguration constructs an authorizer based on
3135
// options specified in a configuration message.
32-
func (f BaseAuthorizerFactory) NewAuthorizerFromConfiguration(config *pb.AuthorizerConfiguration) (Authorizer, error) {
36+
func (f BaseAuthorizerFactory) NewAuthorizerFromConfiguration(config *pb.AuthorizerConfiguration, grpcClientFactory grpc.ClientFactory) (auth.Authorizer, error) {
3337
if config == nil {
3438
return nil, status.Error(codes.InvalidArgument, "Authorizer configuration not specified")
3539
}
3640
switch policy := config.Policy.(type) {
3741
case *pb.AuthorizerConfiguration_Allow:
38-
return NewStaticAuthorizer(func(in digest.InstanceName) bool { return true }), nil
42+
return auth.NewStaticAuthorizer(func(in digest.InstanceName) bool { return true }), nil
3943
case *pb.AuthorizerConfiguration_Deny:
40-
return NewStaticAuthorizer(func(in digest.InstanceName) bool { return false }), nil
44+
return auth.NewStaticAuthorizer(func(in digest.InstanceName) bool { return false }), nil
4145
case *pb.AuthorizerConfiguration_InstanceNamePrefix:
4246
trie := digest.NewInstanceNameTrie()
4347
for _, i := range policy.InstanceNamePrefix.AllowedInstanceNamePrefixes {
@@ -47,13 +51,29 @@ func (f BaseAuthorizerFactory) NewAuthorizerFromConfiguration(config *pb.Authori
4751
}
4852
trie.Set(instanceNamePrefix, 0)
4953
}
50-
return NewStaticAuthorizer(trie.ContainsPrefix), nil
54+
return auth.NewStaticAuthorizer(trie.ContainsPrefix), nil
5155
case *pb.AuthorizerConfiguration_JmespathExpression:
5256
expression, err := jmespath.Compile(policy.JmespathExpression)
5357
if err != nil {
5458
return nil, util.StatusWrapWithCode(err, codes.InvalidArgument, "Failed to compile JMESPath expression")
5559
}
56-
return NewJMESPathExpressionAuthorizer(expression), nil
60+
return auth.NewJMESPathExpressionAuthorizer(expression), nil
61+
case *pb.AuthorizerConfiguration_Remote:
62+
grpcClient, err := grpcClientFactory.NewClientFromConfiguration(policy.Remote.Endpoint)
63+
if err != nil {
64+
return nil, util.StatusWrap(err, "Failed to create authorizer RPC client")
65+
}
66+
evictionSet, err := eviction.NewSetFromConfiguration[auth.RemoteAuthorizerCacheKey](policy.Remote.CacheReplacementPolicy)
67+
if err != nil {
68+
return nil, util.StatusWrap(err, "Cache replacement policy for remote authorization")
69+
}
70+
return auth.NewRemoteAuthorizer(
71+
grpcClient,
72+
policy.Remote.Scope,
73+
clock.SystemClock,
74+
eviction.NewMetricsSet(evictionSet, "remote_authorizer"),
75+
int(policy.Remote.MaximumCacheSize),
76+
), nil
5777
default:
5878
return nil, status.Error(codes.InvalidArgument, "Unknown authorizer configuration")
5979
}
@@ -62,7 +82,7 @@ func (f BaseAuthorizerFactory) NewAuthorizerFromConfiguration(config *pb.Authori
6282
type deduplicatingAuthorizerFactory struct {
6383
base AuthorizerFactory
6484
// Keys are protojson-encoded pb.AuthorizerConfigurations
65-
known map[string]Authorizer
85+
known map[string]auth.Authorizer
6686
}
6787

6888
// NewDeduplicatingAuthorizerFactory creates a new AuthorizerFactory
@@ -71,19 +91,19 @@ type deduplicatingAuthorizerFactory struct {
7191
func NewDeduplicatingAuthorizerFactory(base AuthorizerFactory) AuthorizerFactory {
7292
return &deduplicatingAuthorizerFactory{
7393
base: base,
74-
known: make(map[string]Authorizer),
94+
known: make(map[string]auth.Authorizer),
7595
}
7696
}
7797

7898
// NewAuthorizerFromConfiguration creates an Authorizer based on the passed configuration.
79-
func (af *deduplicatingAuthorizerFactory) NewAuthorizerFromConfiguration(config *pb.AuthorizerConfiguration) (Authorizer, error) {
99+
func (af *deduplicatingAuthorizerFactory) NewAuthorizerFromConfiguration(config *pb.AuthorizerConfiguration, grpcClientFactory grpc.ClientFactory) (auth.Authorizer, error) {
80100
keyBytes, err := protojson.Marshal(config)
81101
key := string(keyBytes)
82102
if err != nil {
83103
return nil, err
84104
}
85105
if _, ok := af.known[key]; !ok {
86-
a, err := af.base.NewAuthorizerFromConfiguration(config)
106+
a, err := af.base.NewAuthorizerFromConfiguration(config, grpcClientFactory)
87107
if err != nil {
88108
return nil, err
89109
}

0 commit comments

Comments
 (0)