Skip to content

Commit 449234d

Browse files
committed
feat(grpc): allow files to be read by jmespath expressions
This allows allow jmespath expressions to refer to the contents of certain pre-declared files which are reloaded on a user-specified schedule. For example, if you want to use k8s bound service account tokens you could add the following to your Pod: volumes: - name: token-vol projected: sources: - serviceAccountToken: audience: my-buildbarn-instance expirationSeconds: 3600 path: buildbarn Assuming this is mounted at '/tokens', you could then specify in your buildbarn config: addMetadataJmespathExpression: { expression: ||| { "authorization": [std.format('bearer %s', files.token)] } |||, files: [ { key: "token", path: "/tokens/buildbarn", refreshInterval: "1800s", } ] }, This is quite useful for k8s service account tokens, as the maximum validity is often capped. Likewise this can also be used for Google service account id tokens, which also have a relatively short maximum validity.
1 parent 89b9202 commit 449234d

40 files changed

Lines changed: 1100 additions & 186 deletions

pkg/auth/BUILD.bazel

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ go_library(
1818
"//pkg/clock",
1919
"//pkg/digest",
2020
"//pkg/eviction",
21+
"//pkg/jmespath",
2122
"//pkg/otel",
2223
"//pkg/proto/auth",
2324
"//pkg/util",
24-
"@com_github_jmespath_go_jmespath//:go-jmespath",
2525
"@io_opentelemetry_go_otel//attribute",
2626
"@org_golang_google_grpc//:grpc",
2727
"@org_golang_google_grpc//codes",
@@ -47,10 +47,10 @@ go_test(
4747
"//internal/mock",
4848
"//pkg/digest",
4949
"//pkg/eviction",
50+
"//pkg/jmespath",
5051
"//pkg/proto/auth",
5152
"//pkg/testutil",
5253
"//pkg/util",
53-
"@com_github_jmespath_go_jmespath//:go-jmespath",
5454
"@com_github_stretchr_testify//require",
5555
"@io_opentelemetry_go_otel//attribute",
5656
"@io_opentelemetry_go_proto_otlp//common/v1:common",

pkg/auth/configuration/BUILD.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ go_library(
1111
"//pkg/digest",
1212
"//pkg/eviction",
1313
"//pkg/grpc",
14+
"//pkg/jmespath",
1415
"//pkg/proto/configuration/auth",
1516
"//pkg/util",
16-
"@com_github_jmespath_go_jmespath//:go-jmespath",
1717
"@org_golang_google_grpc//codes",
1818
"@org_golang_google_grpc//status",
1919
"@org_golang_google_protobuf//encoding/protojson",

pkg/auth/configuration/authorizer_factory.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ import (
66
"github.com/buildbarn/bb-storage/pkg/digest"
77
"github.com/buildbarn/bb-storage/pkg/eviction"
88
"github.com/buildbarn/bb-storage/pkg/grpc"
9+
"github.com/buildbarn/bb-storage/pkg/jmespath"
910
pb "github.com/buildbarn/bb-storage/pkg/proto/configuration/auth"
1011
"github.com/buildbarn/bb-storage/pkg/util"
11-
"github.com/jmespath/go-jmespath"
1212

1313
"google.golang.org/grpc/codes"
1414
"google.golang.org/grpc/status"
@@ -53,7 +53,7 @@ func (f BaseAuthorizerFactory) NewAuthorizerFromConfiguration(config *pb.Authori
5353
}
5454
return auth.NewStaticAuthorizer(trie.ContainsPrefix), nil
5555
case *pb.AuthorizerConfiguration_JmespathExpression:
56-
expression, err := jmespath.Compile(policy.JmespathExpression)
56+
expression, err := jmespath.NewExpressionFromConfiguration(policy.JmespathExpression, nil)
5757
if err != nil {
5858
return nil, util.StatusWrapWithCode(err, codes.InvalidArgument, "Failed to compile JMESPath expression")
5959
}

pkg/auth/jmespath_expression_authorizer.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,18 @@ import (
44
"context"
55

66
"github.com/buildbarn/bb-storage/pkg/digest"
7-
"github.com/jmespath/go-jmespath"
7+
"github.com/buildbarn/bb-storage/pkg/jmespath"
88
)
99

1010
type jmespathExpressionAuthorizer struct {
11-
expression *jmespath.JMESPath
11+
expression *jmespath.Expression
1212
}
1313

1414
// NewJMESPathExpressionAuthorizer creates an Authorizer that evaluates
1515
// a JMESPath expression to make an authorization decision. The JMESpath
1616
// expression is called with a JSON object that includes both the REv2
1717
// instance name and authentication metadata.
18-
func NewJMESPathExpressionAuthorizer(expression *jmespath.JMESPath) Authorizer {
18+
func NewJMESPathExpressionAuthorizer(expression *jmespath.Expression) Authorizer {
1919
return &jmespathExpressionAuthorizer{
2020
expression: expression,
2121
}

pkg/auth/jmespath_expression_authorizer_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ import (
66

77
"github.com/buildbarn/bb-storage/pkg/auth"
88
"github.com/buildbarn/bb-storage/pkg/digest"
9+
"github.com/buildbarn/bb-storage/pkg/jmespath"
910
auth_pb "github.com/buildbarn/bb-storage/pkg/proto/auth"
1011
"github.com/buildbarn/bb-storage/pkg/testutil"
1112
"github.com/buildbarn/bb-storage/pkg/util"
12-
"github.com/jmespath/go-jmespath"
1313
"github.com/stretchr/testify/require"
1414

1515
"google.golang.org/grpc/codes"

pkg/grpc/BUILD.bazel

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ go_library(
3737
"//pkg/auth",
3838
"//pkg/clock",
3939
"//pkg/eviction",
40+
"//pkg/jmespath",
4041
"//pkg/jwt",
4142
"//pkg/program",
4243
"//pkg/proto/auth",
@@ -45,7 +46,6 @@ go_library(
4546
"@bazel_remote_apis//build/bazel/remote/execution/v2:remote_execution_go_proto",
4647
"@com_github_grpc_ecosystem_go_grpc_middleware//:go-grpc-middleware",
4748
"@com_github_grpc_ecosystem_go_grpc_prometheus//:go-grpc-prometheus",
48-
"@com_github_jmespath_go_jmespath//:go-jmespath",
4949
"@io_opentelemetry_go_contrib_instrumentation_google_golang_org_grpc_otelgrpc//:otelgrpc",
5050
"@io_opentelemetry_go_otel//attribute",
5151
"@io_opentelemetry_go_otel_trace//:trace",
@@ -126,12 +126,14 @@ go_test(
126126
":grpc",
127127
"//internal/mock",
128128
"//pkg/auth",
129+
"//pkg/jmespath",
130+
"//pkg/program",
129131
"//pkg/proto/auth",
130132
"//pkg/proto/configuration/grpc",
133+
"//pkg/proto/jmespath",
131134
"//pkg/testutil",
132135
"//pkg/util",
133136
"@bazel_remote_apis//build/bazel/remote/execution/v2:remote_execution_go_proto",
134-
"@com_github_jmespath_go_jmespath//:go-jmespath",
135137
"@com_github_stretchr_testify//require",
136138
"@io_opentelemetry_go_otel//attribute",
137139
"@io_opentelemetry_go_otel_trace//:trace",
@@ -143,6 +145,7 @@ go_test(
143145
"@org_golang_google_grpc//peer",
144146
"@org_golang_google_grpc//status",
145147
"@org_golang_google_protobuf//proto",
148+
"@org_golang_google_protobuf//types/known/durationpb",
146149
"@org_golang_google_protobuf//types/known/emptypb",
147150
"@org_golang_google_protobuf//types/known/structpb",
148151
"@org_uber_go_mock//gomock",

pkg/grpc/authenticator.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ import (
77
"github.com/buildbarn/bb-storage/pkg/auth"
88
"github.com/buildbarn/bb-storage/pkg/clock"
99
"github.com/buildbarn/bb-storage/pkg/eviction"
10+
"github.com/buildbarn/bb-storage/pkg/jmespath"
1011
"github.com/buildbarn/bb-storage/pkg/jwt"
1112
"github.com/buildbarn/bb-storage/pkg/program"
1213
configuration "github.com/buildbarn/bb-storage/pkg/proto/configuration/grpc"
1314
"github.com/buildbarn/bb-storage/pkg/util"
14-
"github.com/jmespath/go-jmespath"
1515

1616
"google.golang.org/grpc/codes"
1717
"google.golang.org/grpc/status"
@@ -72,11 +72,11 @@ func NewAuthenticatorFromConfiguration(policy *configuration.AuthenticationPolic
7272
if !clientCAs.AppendCertsFromPEM([]byte(policyKind.TlsClientCertificate.ClientCertificateAuthorities)) {
7373
return nil, false, false, status.Error(codes.InvalidArgument, "Failed to parse client certificate authorities")
7474
}
75-
validator, err := jmespath.Compile(policyKind.TlsClientCertificate.ValidationJmespathExpression)
75+
validator, err := jmespath.NewExpressionFromConfiguration(policyKind.TlsClientCertificate.ValidationJmespathExpression, group)
7676
if err != nil {
7777
return nil, false, false, util.StatusWrap(err, "Failed to compile validation JMESPath expression")
7878
}
79-
metadataExtractor, err := jmespath.Compile(policyKind.TlsClientCertificate.MetadataExtractionJmespathExpression)
79+
metadataExtractor, err := jmespath.NewExpressionFromConfiguration(policyKind.TlsClientCertificate.MetadataExtractionJmespathExpression, group)
8080
if err != nil {
8181
return nil, false, false, util.StatusWrap(err, "Failed to compile metadata extraction JMESPath expression")
8282
}
@@ -93,7 +93,7 @@ func NewAuthenticatorFromConfiguration(policy *configuration.AuthenticationPolic
9393
}
9494
return NewRequestHeadersAuthenticator(authorizationHeaderParser, []string{jwt.AuthorizationHeaderName}), false, false, nil
9595
case *configuration.AuthenticationPolicy_PeerCredentialsJmespathExpression:
96-
metadataExtractor, err := jmespath.Compile(policyKind.PeerCredentialsJmespathExpression)
96+
metadataExtractor, err := jmespath.NewExpressionFromConfiguration(policyKind.PeerCredentialsJmespathExpression, group)
9797
if err != nil {
9898
return nil, false, false, util.StatusWrap(err, "Failed to compile peer credentials metadata extraction JMESPath expression")
9999
}

pkg/grpc/base_client_factory.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ import (
77
"net/http"
88
"net/url"
99

10+
"github.com/buildbarn/bb-storage/pkg/jmespath"
1011
configuration "github.com/buildbarn/bb-storage/pkg/proto/configuration/grpc"
1112
"github.com/buildbarn/bb-storage/pkg/util"
12-
"github.com/jmespath/go-jmespath"
1313

1414
"google.golang.org/grpc"
1515
"google.golang.org/grpc/codes"
@@ -178,8 +178,8 @@ func (cf baseClientFactory) NewClientFromConfiguration(config *configuration.Cli
178178
}
179179

180180
// Optional: metadata extraction.
181-
if jmesExpression := config.AddMetadataJmespathExpression; jmesExpression != "" {
182-
expr, err := jmespath.Compile(jmesExpression)
181+
if jmesExpression := config.AddMetadataJmespathExpression; jmesExpression != nil {
182+
expr, err := jmespath.NewExpressionFromConfiguration(jmesExpression, nil)
183183
if err != nil {
184184
return nil, util.StatusWrap(err, "Failed to compile JMESPath expression")
185185
}

pkg/grpc/jmespath_extractor.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import (
44
"context"
55

66
"github.com/buildbarn/bb-storage/pkg/auth"
7+
"github.com/buildbarn/bb-storage/pkg/jmespath"
78
"github.com/buildbarn/bb-storage/pkg/util"
8-
"github.com/jmespath/go-jmespath"
99

1010
"google.golang.org/grpc/codes"
1111
"google.golang.org/grpc/metadata"
@@ -21,22 +21,23 @@ import (
2121
// structure:
2222
//
2323
// {
24-
// "authenticationMetadata": value,
25-
// "incomingGRPCMetadata": map<string, repeated string>
24+
// "authenticationMetadata": value,
25+
// "files": map<string, any>,
26+
// "incomingGRPCMetadata": map<string, repeated string>
2627
// }
27-
func NewJMESPathMetadataExtractor(expression *jmespath.JMESPath) (MetadataExtractor, error) {
28+
func NewJMESPathMetadataExtractor(expression *jmespath.Expression) (MetadataExtractor, error) {
2829
return func(ctx context.Context) (MetadataHeaderValues, error) {
29-
searchContext := make(map[string]interface{}, 2)
30+
searchContext := make(map[string]interface{}, 3)
3031
if authenticationMetadata := auth.AuthenticationMetadataFromContext(ctx); authenticationMetadata != nil {
3132
searchContext["authenticationMetadata"] = authenticationMetadata.GetRaw()
3233
}
3334

3435
if md, ok := metadata.FromIncomingContext(ctx); ok {
3536
// JMESPath only treats map[string]interface{}, struct, or *struct as map types,
3637
// so we need to copy from the map[string][]string.
37-
incomingGRPCMetadata := make(map[string]interface{}, len(md))
38+
incomingGRPCMetadata := make(map[string]any, len(md))
3839
for k, rawVs := range md {
39-
vs := make([]interface{}, 0, len(rawVs))
40+
vs := make([]any, 0, len(rawVs))
4041
for _, rawV := range rawVs {
4142
vs = append(vs, rawV)
4243
}

pkg/grpc/jmespath_extractor_test.go

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,25 @@ package grpc_test
22

33
import (
44
"context"
5+
"os"
6+
"path/filepath"
57
"testing"
8+
"time"
69

710
"github.com/buildbarn/bb-storage/pkg/auth"
811
"github.com/buildbarn/bb-storage/pkg/grpc"
12+
"github.com/buildbarn/bb-storage/pkg/jmespath"
13+
"github.com/buildbarn/bb-storage/pkg/program"
914
auth_pb "github.com/buildbarn/bb-storage/pkg/proto/auth"
15+
jmespath_pb "github.com/buildbarn/bb-storage/pkg/proto/jmespath"
1016
"github.com/buildbarn/bb-storage/pkg/testutil"
1117
"github.com/buildbarn/bb-storage/pkg/util"
12-
"github.com/jmespath/go-jmespath"
1318
"github.com/stretchr/testify/require"
1419

1520
"google.golang.org/grpc/codes"
1621
"google.golang.org/grpc/metadata"
1722
"google.golang.org/grpc/status"
23+
"google.golang.org/protobuf/types/known/durationpb"
1824
"google.golang.org/protobuf/types/known/structpb"
1925
)
2026

@@ -83,3 +89,71 @@ func TestJMESPathMetadataExtractorAuthMatchToHeterogenousSlice(t *testing.T) {
8389
_, err = extractor(ctx)
8490
testutil.RequireEqualStatus(t, status.Errorf(codes.InvalidArgument, "Failed to extract JMESPath result: Non-string metadata value"), err)
8591
}
92+
93+
func TestNewJMESPathMetadataFileProvider(t *testing.T) {
94+
// Build the extractor.
95+
ctx, cancel := context.WithCancel(context.Background())
96+
program.RunLocal(ctx, func(ctx context.Context, siblingsGroup, dependenciesGroup program.Group) error {
97+
// Create a temporary file with test content.
98+
tempDir := t.TempDir()
99+
filePath := filepath.Join(tempDir, "test-token")
100+
err := os.WriteFile(filePath, []byte("token-value1"), 0o644)
101+
require.NoError(t, err)
102+
103+
expr, err := jmespath.NewExpressionFromConfiguration(
104+
&jmespath_pb.Expression{
105+
Expression: `{"authorization": [files.token]}`,
106+
Files: []*jmespath_pb.File{
107+
{
108+
Key: "token",
109+
Path: filePath,
110+
RefreshInterval: durationpb.New(time.Millisecond),
111+
},
112+
},
113+
TestVectors: []*jmespath_pb.TestVector{
114+
{
115+
Input: util.Must(structpb.NewStruct(map[string]any{
116+
"files": map[string]any{
117+
"token": "tv-token-value",
118+
},
119+
})),
120+
ExpectedOutput: util.Must(structpb.NewValue(map[string]any{
121+
"authorization": []any{"tv-token-value"},
122+
})),
123+
},
124+
},
125+
},
126+
siblingsGroup,
127+
)
128+
require.NoError(t, err)
129+
extractor, err := grpc.NewJMESPathMetadataExtractor(expr)
130+
require.NoError(t, err)
131+
132+
// Validate the initial contents are correct.
133+
headers, err := extractor(ctx)
134+
require.NoError(t, err)
135+
want := grpc.MetadataHeaderValues([]string{
136+
"authorization", "token-value1",
137+
})
138+
require.Equal(t, want, headers)
139+
140+
// Modify the file.
141+
err = os.WriteFile(filePath, []byte("token-value2"), 0o644)
142+
require.NoError(t, err)
143+
144+
// Wait for the file to be reloaded. This is potentially fragile.
145+
time.Sleep(time.Second)
146+
147+
// Validate the updated contents are correct.
148+
headers, err = extractor(ctx)
149+
require.NoError(t, err)
150+
want = grpc.MetadataHeaderValues([]string{
151+
"authorization", "token-value2",
152+
})
153+
require.Equal(t, want, headers)
154+
155+
// Cancel the context to stop the file reloading.
156+
cancel()
157+
return nil
158+
})
159+
}

0 commit comments

Comments
 (0)