Skip to content

Commit 28da40c

Browse files
committed
feat: add deadline and trace context to iamv1.Caller
As a workaround for CEL-Go currently not supporting threading the user context through expression and function evaluation, this patch adds support for the caller to thread request deadline and trace context through to the CEL functions, so that downstream gRPC calls can inherit the request deadline and trace context. This might all be removable if/when CEL attains built-in support for "async functions", as per google/cel-go#368
1 parent d807e57 commit 28da40c

File tree

10 files changed

+199
-52
lines changed

10 files changed

+199
-52
lines changed

cmd/iamctl/go.sum

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+
3838
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
3939
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
4040
cloud.google.com/go/spanner v1.17.0/go.mod h1:+17t2ixFwRG4lWRwE+5kipDR9Ef07Jkmc8z0IbMDKUs=
41-
cloud.google.com/go/spanner v1.20.0/go.mod h1:ajR/W06cMHQu7nqQ4irRGplPNoWgejGJlEhlB8xBTKk=
4241
cloud.google.com/go/spanner v1.21.0 h1:NWLJnTTPwKu5OB/3SwL/VkJ9rIpvNPjalWz0p6vywnk=
4342
cloud.google.com/go/spanner v1.21.0/go.mod h1:P1Pl0zyIIdhovaFueBrOjSQ6jKQDfl5bVemE+gdEJog=
4443
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=

cmd/iamctl/internal/examplecmd/exampleservercmd/server.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"google.golang.org/grpc"
2424
"google.golang.org/grpc/codes"
2525
"google.golang.org/grpc/status"
26+
"google.golang.org/protobuf/types/known/timestamppb"
2627
)
2728

2829
func newServer(spannerClient *spanner.Client) (*iamexample.Authorization, error) {
@@ -126,6 +127,11 @@ type googleIdentityTokenCallerResolver struct{}
126127
func (googleIdentityTokenCallerResolver) ResolveCaller(ctx context.Context) (*iamv1.Caller, error) {
127128
const authorizationKey = "authorization"
128129
var result iamv1.Caller
130+
if deadline, ok := ctx.Deadline(); ok {
131+
result.Context = &iamv1.Caller_Context{
132+
Deadline: timestamppb.New(deadline),
133+
}
134+
}
129135
token, ok := iamtoken.FromIncomingContext(ctx, authorizationKey)
130136
if !ok {
131137
return &result, nil

iamcaller/chain.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ func (c chainResolver) ResolveCaller(ctx context.Context) (*iamv1.Caller, error)
3636
for key, value := range nextCaller.Metadata {
3737
Add(&result, key, value)
3838
}
39+
// TODO: Remove this when CEL-Go supports async functions with context arguments.
40+
if result.Context == nil && nextCaller.Context != nil {
41+
result.Context = nextCaller.Context
42+
}
3943
}
4044
return &result, nil
4145
}

iamcaller/chain_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ import (
44
"context"
55
"errors"
66
"testing"
7+
"time"
78

89
iamv1 "go.einride.tech/iam/proto/gen/einride/iam/v1"
910
"google.golang.org/protobuf/testing/protocmp"
11+
"google.golang.org/protobuf/types/known/timestamppb"
1012
"gotest.tools/v3/assert"
1113
)
1214

@@ -83,6 +85,23 @@ func TestChainResolvers(t *testing.T) {
8385
assert.DeepEqual(t, expected, actual, protocmp.Transform())
8486
})
8587

88+
t.Run("context", func(t *testing.T) {
89+
expected := &iamv1.Caller{
90+
Context: &iamv1.Caller_Context{
91+
Deadline: timestamppb.New(time.Unix(1234, 0).UTC()),
92+
Trace: "mock-trace-context",
93+
},
94+
Members: []string{"test:bar", "test:foo"},
95+
Metadata: map[string]*iamv1.Caller_Metadata{
96+
"key1": {Members: []string{"test:foo"}},
97+
"key2": {Members: []string{"test:bar"}},
98+
},
99+
}
100+
actual, err := ChainResolvers(constant(expected)).ResolveCaller(context.Background())
101+
assert.NilError(t, err)
102+
assert.DeepEqual(t, expected, actual, protocmp.Transform())
103+
})
104+
86105
t.Run("error", func(t *testing.T) {
87106
actual, err := ChainResolvers(
88107
constant(&iamv1.Caller{

iamcel/test.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package iamcel
22

33
import (
44
"context"
5-
"time"
65

76
"github.com/google/cel-go/checker/decls"
87
"github.com/google/cel-go/common/types"
@@ -57,8 +56,12 @@ func NewTestFunctionImplementation(
5756
return types.NewErr("%s: no permission configured for resource '%s'", codes.PermissionDenied, resource)
5857
}
5958
// TODO: When cel-go supports async functions, use the caller context here.
60-
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
61-
defer cancel()
59+
ctx := context.Background()
60+
if caller.GetContext().GetDeadline() != nil {
61+
var cancel context.CancelFunc
62+
ctx, cancel = context.WithDeadline(ctx, caller.GetContext().GetDeadline().AsTime())
63+
defer cancel()
64+
}
6265
if result, err := tester.TestPermissions(ctx, caller, map[string]string{resource: permission}); err != nil {
6366
if s, ok := status.FromError(err); ok {
6467
return types.NewErr("%s: %s", s.Code(), s.Message())

iamcel/testall.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package iamcel
33
import (
44
"context"
55
"reflect"
6-
"time"
76

87
"github.com/google/cel-go/checker/decls"
98
"github.com/google/cel-go/common/types"
@@ -70,8 +69,12 @@ func NewTestAllFunctionImplementation(
7069
resourcePermissions[resource] = permission
7170
}
7271
// TODO: When cel-go supports async functions, use the caller context here.
73-
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
74-
defer cancel()
72+
ctx := context.Background()
73+
if caller.GetContext().GetDeadline() != nil {
74+
var cancel context.CancelFunc
75+
ctx, cancel = context.WithDeadline(ctx, caller.GetContext().GetDeadline().AsTime())
76+
defer cancel()
77+
}
7578
if result, err := tester.TestPermissions(ctx, caller, resourcePermissions); err != nil {
7679
if s, ok := status.FromError(err); ok {
7780
return types.NewErr("%s: %s", s.Code(), s.Message())

iamcel/testany.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package iamcel
33
import (
44
"context"
55
"reflect"
6-
"time"
76

87
"github.com/google/cel-go/checker/decls"
98
"github.com/google/cel-go/common/types"
@@ -70,8 +69,12 @@ func NewTestAnyFunctionImplementation(
7069
resourcePermissions[resource] = permission
7170
}
7271
// TODO: When cel-go supports async functions, use the caller context here.
73-
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
74-
defer cancel()
72+
ctx := context.Background()
73+
if caller.GetContext().GetDeadline() != nil {
74+
var cancel context.CancelFunc
75+
ctx, cancel = context.WithDeadline(ctx, caller.GetContext().GetDeadline().AsTime())
76+
defer cancel()
77+
}
7578
if result, err := tester.TestPermissions(ctx, caller, resourcePermissions); err != nil {
7679
if s, ok := status.FromError(err); ok {
7780
return types.NewErr("%s: %s", s.Code(), s.Message())

iamexample/caller.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"go.einride.tech/iam/iamcaller"
77
iamv1 "go.einride.tech/iam/proto/gen/einride/iam/v1"
88
"google.golang.org/grpc/metadata"
9+
"google.golang.org/protobuf/types/known/timestamppb"
910
)
1011

1112
// MemberHeader is the gRPC header used by the example server to determine IAM members of the caller.
@@ -23,13 +24,16 @@ type memberHeaderResolver struct{}
2324
// ResolveCaller implements iamcaller.Resolver.
2425
func (m *memberHeaderResolver) ResolveCaller(ctx context.Context) (*iamv1.Caller, error) {
2526
var result iamv1.Caller
26-
md, ok := metadata.FromIncomingContext(ctx)
27-
if !ok {
28-
return &result, nil
27+
if md, ok := metadata.FromIncomingContext(ctx); ok {
28+
iamcaller.Add(&result, MemberHeader, &iamv1.Caller_Metadata{
29+
Members: md.Get(MemberHeader),
30+
})
31+
}
32+
if deadline, ok := ctx.Deadline(); ok {
33+
result.Context = &iamv1.Caller_Context{
34+
Deadline: timestamppb.New(deadline),
35+
}
2936
}
30-
iamcaller.Add(&result, MemberHeader, &iamv1.Caller_Metadata{
31-
Members: md.Get(MemberHeader),
32-
})
3337
return &result, nil
3438
}
3539

0 commit comments

Comments
 (0)