Skip to content

Commit 448c86d

Browse files
nacxwbpcode
andauthored
sdk: bump to the latest version of the dynamic modules sdk (#219)
Bumps the SDK to the latest version. This brings some goodies: * Adds the `GetAttributeBool` so we can get the `mtls` attribute and use it in OPA and Cedar policies (implemented in this PR). * Introduces the `UnsafeEnvoyBuffer` to clearly set when allocations and copies are used. * This PR refactors all uses to use the `Unsafe` version where no copies were used before, and to use the `safe` version where `strings.Clone` or other copies were used. The last commit c2c35c2 fixes #254 --------- Signed-off-by: Ignasi Barrera <ignasi@tetrate.io> Co-authored-by: code <wbphub@gmail.com>
1 parent a6c06ed commit 448c86d

35 files changed

Lines changed: 490 additions & 369 deletions

File tree

.github/workflows/cli.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ jobs:
9797
run:
9898
working-directory: ./cli
9999
env:
100+
GO_TEST_ARGS: -v
100101
TEST_BOE_REGISTRY: ghcr.io/tetratelabs/built-on-envoy
101102
TEST_BOE_REGISTRY_USERNAME: ${{ github.actor }}
102103
TEST_BOE_REGISTRY_PASSWORD: ${{ secrets.GITHUB_TOKEN }}

cli/cmd/templates/create/go.mod.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ module {{ .Name }}
33
go 1.26.1
44

55
require (
6-
github.com/envoyproxy/envoy/source/extensions/dynamic_modules v0.0.0-20260224023207-910d516c7b4e
6+
github.com/envoyproxy/envoy/source/extensions/dynamic_modules v0.0.0-20260311012303-5ef4e4cea57f
77
)

cli/cmd/templates/create/rust/Cargo.toml.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ repository = "https://github.com/tetratelabs/built-on-envoy"
77

88
[dependencies]
99
# The SDK version must match the Envoy version due to the strict compatibility requirements.
10-
envoy-proxy-dynamic-modules-rust-sdk = { git = "https://github.com/envoyproxy/envoy", rev = "07bece5476a5e3c95c8b0a3df4e41d3dd0769df4" }
10+
envoy-proxy-dynamic-modules-rust-sdk = { git = "https://github.com/envoyproxy/envoy", rev = "5ef4e4cea57f63e7e2970b9c1ad696278db927d6" }
1111
serde = { version = "1.0", features = ["derive"] }
1212
serde_json = "1.0"
1313

cli/e2e/run_test.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ func TestDockerRemoteExtension(t *testing.T) {
8686
"--listen-port", "11000",
8787
"--dev",
8888
"--log-level", "dynamic_modules:debug",
89-
"--extension", "example-go")
89+
"--extension", "example-go:0.3.0")
9090

9191
t.Cleanup(func() {
9292
_ = proc.Signal(syscall.SIGTERM)
@@ -168,8 +168,16 @@ func TestRustLocalExtension(t *testing.T) {
168168
}
169169

170170
func TestLocalGoExtension(t *testing.T) {
171-
// Configure the test env vars, as composer src will be downloaded from the registry
172-
internaltesting.SkipIfTestRegistryNotConfigured(t)
171+
// Local builds for Go will pull libcomposer from the remote registry. However, when we're doing changes to versions, etc, we don't want it to
172+
// pull an obsolete version, so we'll just push the current composer source to the local registry and use that for the test.
173+
t.Setenv("BOE_REGISTRY", registryAddr)
174+
t.Setenv("BOE_REGISTRY_INSECURE", "true")
175+
makeCmd := exec.CommandContext(t.Context(), "make", "push_code")
176+
makeCmd.Dir = "../../extensions/composer"
177+
output, err := makeCmd.CombinedOutput()
178+
t.Logf("make push_code output: %s", string(output))
179+
require.NoError(t, err)
180+
173181
dataDir := t.TempDir()
174182

175183
// Create a brand new extension

extensions/composer/bedrock-guardrails/callback.go

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ type applyGuardrailCallback struct {
5050
index int
5151
}
5252

53-
func (a *applyGuardrailCallback) OnHttpCalloutDone(_ uint64, result shared.HttpCalloutResult, headers [][2]string, body [][]byte) { //nolint:revive
53+
func (a *applyGuardrailCallback) OnHttpCalloutDone(_ uint64, result shared.HttpCalloutResult, headers [][2]shared.UnsafeEnvoyBuffer, body []shared.UnsafeEnvoyBuffer) { //nolint:revive
5454
a.handle.Log(shared.LogLevelDebug, "bedrock-guardrails: Started Callout callback")
5555

5656
fullbody := joinBody(body)
@@ -205,11 +205,15 @@ func (a *applyGuardrailCallback) OnHttpCalloutDone(_ uint64, result shared.HttpC
205205

206206
// joinBody returns the body as a single byte slice, avoiding to call bytes.Join
207207
// when there is only one chunk which always returns a copy.
208-
func joinBody(body [][]byte) []byte {
208+
func joinBody(body []shared.UnsafeEnvoyBuffer) []byte {
209209
if len(body) == 1 {
210-
return body[0]
210+
return body[0].ToUnsafeBytes()
211211
}
212-
return bytes.Join(body, nil)
212+
chunks := make([][]byte, len(body))
213+
for i, b := range body {
214+
chunks[i] = b.ToUnsafeBytes()
215+
}
216+
return bytes.Join(chunks, nil)
213217
}
214218

215219
// sendLocalRespError logs the message (with optional raw body), sends
@@ -225,10 +229,10 @@ func sendLocalRespError(handle shared.HttpFilterHandle, level shared.LogLevel, s
225229
}
226230

227231
// headerValue returns the first value for a key in a [][2]string header list.
228-
func headerValue(headers [][2]string, key string) string {
232+
func headerValue(headers [][2]shared.UnsafeEnvoyBuffer, key string) string {
229233
for _, h := range headers {
230-
if h[0] == key {
231-
return h[1]
234+
if h[0].ToUnsafeString() == key {
235+
return h[1].ToUnsafeString()
232236
}
233237
}
234238
return ""

extensions/composer/bedrock-guardrails/callback_test.go

Lines changed: 42 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -15,59 +15,61 @@ import (
1515
"github.com/envoyproxy/envoy/source/extensions/dynamic_modules/sdk/go/shared/mocks"
1616
"github.com/stretchr/testify/require"
1717
"go.uber.org/mock/gomock"
18+
19+
"github.com/tetratelabs/built-on-envoy/extensions/composer/pkg"
1820
)
1921

2022
// --- Tests for joinBody ---
2123

2224
func TestJoinBody_SingleChunk(t *testing.T) {
2325
chunk := []byte("hello world")
24-
result := joinBody([][]byte{chunk})
26+
result := joinBody([]shared.UnsafeEnvoyBuffer{pkg.UnsafeBufferFromBytes(chunk)})
2527
require.Equal(t, chunk, result)
2628
}
2729

2830
func TestJoinBody_MultipleChunks(t *testing.T) {
29-
result := joinBody([][]byte{[]byte("foo"), []byte("bar"), []byte("baz")})
31+
result := joinBody([]shared.UnsafeEnvoyBuffer{pkg.UnsafeBufferFromBytes([]byte("foo")), pkg.UnsafeBufferFromBytes([]byte("bar")), pkg.UnsafeBufferFromBytes([]byte("baz"))})
3032
require.Equal(t, []byte("foobarbaz"), result)
3133
}
3234

3335
func TestJoinBody_EmptySlice(t *testing.T) {
34-
result := joinBody([][]byte{})
36+
result := joinBody([]shared.UnsafeEnvoyBuffer{})
3537
require.Empty(t, result)
3638
}
3739

3840
func TestJoinBody_EmptyChunks(t *testing.T) {
39-
result := joinBody([][]byte{{}, []byte("data"), {}})
41+
result := joinBody([]shared.UnsafeEnvoyBuffer{pkg.UnsafeBufferFromBytes([]byte{}), pkg.UnsafeBufferFromBytes([]byte("data")), pkg.UnsafeBufferFromBytes([]byte{})})
4042
require.Equal(t, []byte("data"), result)
4143
}
4244

4345
// --- Tests for headerValue ---
4446

4547
func TestHeaderValue_Found(t *testing.T) {
46-
headers := [][2]string{
47-
{":method", "POST"},
48-
{":path", "/api/v1"},
49-
{":status", "200"},
48+
headers := [][2]shared.UnsafeEnvoyBuffer{
49+
{pkg.UnsafeBufferFromBytes([]byte(":method")), pkg.UnsafeBufferFromBytes([]byte("POST"))},
50+
{pkg.UnsafeBufferFromBytes([]byte(":path")), pkg.UnsafeBufferFromBytes([]byte("/api/v1"))},
51+
{pkg.UnsafeBufferFromBytes([]byte(":status")), pkg.UnsafeBufferFromBytes([]byte("200"))},
5052
}
5153
require.Equal(t, "POST", headerValue(headers, ":method"))
5254
require.Equal(t, "/api/v1", headerValue(headers, ":path"))
5355
require.Equal(t, "200", headerValue(headers, ":status"))
5456
}
5557

5658
func TestHeaderValue_NotFound(t *testing.T) {
57-
headers := [][2]string{
58-
{":method", "POST"},
59+
headers := [][2]shared.UnsafeEnvoyBuffer{
60+
{pkg.UnsafeBufferFromBytes([]byte(":method")), pkg.UnsafeBufferFromBytes([]byte("POST"))},
5961
}
6062
require.Empty(t, headerValue(headers, ":status"))
6163
}
6264

6365
func TestHeaderValue_EmptyHeaders(t *testing.T) {
64-
require.Empty(t, headerValue([][2]string{}, ":method"))
66+
require.Empty(t, headerValue([][2]shared.UnsafeEnvoyBuffer{}, ":method"))
6567
}
6668

6769
func TestHeaderValue_ReturnsFirstMatch(t *testing.T) {
68-
headers := [][2]string{
69-
{"x-header", "first"},
70-
{"x-header", "second"},
70+
headers := [][2]shared.UnsafeEnvoyBuffer{
71+
{pkg.UnsafeBufferFromBytes([]byte("x-header")), pkg.UnsafeBufferFromBytes([]byte("first"))},
72+
{pkg.UnsafeBufferFromBytes([]byte("x-header")), pkg.UnsafeBufferFromBytes([]byte("second"))},
7173
}
7274
require.Equal(t, "first", headerValue(headers, "x-header"))
7375
}
@@ -123,11 +125,13 @@ func TestGetCalloutHeaders_ValidBody(t *testing.T) {
123125
APIKey: "my-secret-key",
124126
}
125127

126-
headers, calloutBody, err := getCalloutHeaders(args)
128+
rawHeaders, calloutBody, err := getCalloutHeaders(args)
127129
require.NoError(t, err)
128-
require.NotNil(t, headers)
130+
require.NotNil(t, rawHeaders)
129131
require.NotNil(t, calloutBody)
130132

133+
headers := toUnsafeEnvoyBufferHeaders(rawHeaders)
134+
131135
// Verify path contains guardrail ID and version
132136
require.Equal(t, "/guardrail/my-guardrail/version/DRAFT/apply", headerValue(headers, ":path"))
133137
// Verify required HTTP/2 pseudo-headers
@@ -158,7 +162,7 @@ func TestGetCalloutHeaders_MultipleUserMessages(t *testing.T) {
158162

159163
headers, calloutBody, err := getCalloutHeaders(args)
160164
require.NoError(t, err)
161-
require.Equal(t, "/guardrail/g1/version/1/apply", headerValue(headers, ":path"))
165+
require.Equal(t, "/guardrail/g1/version/1/apply", headerValue(toUnsafeEnvoyBufferHeaders(headers), ":path"))
162166

163167
var req ApplyGuardrailRequest
164168
require.NoError(t, json.Unmarshal(calloutBody, &req))
@@ -382,7 +386,7 @@ func TestOnHttpCalloutDone_CalloutFailure(t *testing.T) {
382386
},
383387
}
384388

385-
a.OnHttpCalloutDone(1, shared.HttpCalloutReset, [][2]string{}, [][]byte{})
389+
a.OnHttpCalloutDone(1, shared.HttpCalloutReset, [][2]shared.UnsafeEnvoyBuffer{}, []shared.UnsafeEnvoyBuffer{})
386390
}
387391

388392
func TestOnHttpCalloutDone_NonSuccessHTTPStatus(t *testing.T) {
@@ -402,8 +406,8 @@ func TestOnHttpCalloutDone_NonSuccessHTTPStatus(t *testing.T) {
402406
}
403407

404408
a.OnHttpCalloutDone(1, shared.HttpCalloutSuccess,
405-
[][2]string{{":status", "503"}},
406-
[][]byte{[]byte(`{"error":"service unavailable"}`)},
409+
[][2]shared.UnsafeEnvoyBuffer{{pkg.UnsafeBufferFromBytes([]byte(":status")), pkg.UnsafeBufferFromBytes([]byte("503"))}},
410+
[]shared.UnsafeEnvoyBuffer{pkg.UnsafeBufferFromBytes([]byte(`{"error":"service unavailable"}`))},
407411
)
408412
}
409413

@@ -431,8 +435,8 @@ func TestOnHttpCalloutDone_NoInterventionLastGuardrail(t *testing.T) {
431435
}
432436

433437
a.OnHttpCalloutDone(1, shared.HttpCalloutSuccess,
434-
[][2]string{{":status", "200"}},
435-
[][]byte{respBody},
438+
[][2]shared.UnsafeEnvoyBuffer{{pkg.UnsafeBufferFromBytes([]byte(":status")), pkg.UnsafeBufferFromBytes([]byte("200"))}},
439+
[]shared.UnsafeEnvoyBuffer{pkg.UnsafeBufferFromBytes(respBody)},
436440
)
437441

438442
// The original body should have been appended to the buffered body
@@ -456,8 +460,8 @@ func TestOnHttpCalloutDone_BlockedByContentPolicy(t *testing.T) {
456460
}
457461

458462
a.OnHttpCalloutDone(1, shared.HttpCalloutSuccess,
459-
[][2]string{{":status", "200"}},
460-
[][]byte{blockedContentPolicyResponse("g1", "1")},
463+
[][2]shared.UnsafeEnvoyBuffer{{pkg.UnsafeBufferFromBytes([]byte(":status")), pkg.UnsafeBufferFromBytes([]byte("200"))}},
464+
[]shared.UnsafeEnvoyBuffer{pkg.UnsafeBufferFromBytes(blockedContentPolicyResponse("g1", "1"))},
461465
)
462466
}
463467

@@ -478,8 +482,8 @@ func TestOnHttpCalloutDone_BlockedByTopicPolicy(t *testing.T) {
478482
}
479483

480484
a.OnHttpCalloutDone(1, shared.HttpCalloutSuccess,
481-
[][2]string{{":status", "200"}},
482-
[][]byte{blockedTopicPolicyResponse("g1", "1")},
485+
[][2]shared.UnsafeEnvoyBuffer{{pkg.UnsafeBufferFromBytes([]byte(":status")), pkg.UnsafeBufferFromBytes([]byte("200"))}},
486+
[]shared.UnsafeEnvoyBuffer{pkg.UnsafeBufferFromBytes(blockedTopicPolicyResponse("g1", "1"))},
483487
)
484488
}
485489

@@ -500,8 +504,8 @@ func TestOnHttpCalloutDone_BlockedByPIIPolicy(t *testing.T) {
500504
}
501505

502506
a.OnHttpCalloutDone(1, shared.HttpCalloutSuccess,
503-
[][2]string{{":status", "200"}},
504-
[][]byte{blockedPIIResponse("g1", "1")},
507+
[][2]shared.UnsafeEnvoyBuffer{{pkg.UnsafeBufferFromBytes([]byte(":status")), pkg.UnsafeBufferFromBytes([]byte("200"))}},
508+
[]shared.UnsafeEnvoyBuffer{pkg.UnsafeBufferFromBytes(blockedPIIResponse("g1", "1"))},
505509
)
506510
}
507511

@@ -530,8 +534,8 @@ func TestOnHttpCalloutDone_MaskedLastGuardrail(t *testing.T) {
530534
}
531535

532536
a.OnHttpCalloutDone(1, shared.HttpCalloutSuccess,
533-
[][2]string{{":status", "200"}},
534-
[][]byte{respBody},
537+
[][2]shared.UnsafeEnvoyBuffer{{pkg.UnsafeBufferFromBytes([]byte(":status")), pkg.UnsafeBufferFromBytes([]byte("200"))}},
538+
[]shared.UnsafeEnvoyBuffer{pkg.UnsafeBufferFromBytes(respBody)},
535539
)
536540

537541
// The masked body should have been appended (not the original)
@@ -577,8 +581,8 @@ func TestOnHttpCalloutDone_NoInterventionTriggersNextGuardrail(t *testing.T) {
577581
}
578582

579583
a.OnHttpCalloutDone(1, shared.HttpCalloutSuccess,
580-
[][2]string{{":status", "200"}},
581-
[][]byte{respBody},
584+
[][2]shared.UnsafeEnvoyBuffer{{pkg.UnsafeBufferFromBytes([]byte(":status")), pkg.UnsafeBufferFromBytes([]byte("200"))}},
585+
[]shared.UnsafeEnvoyBuffer{pkg.UnsafeBufferFromBytes(respBody)},
582586
)
583587
}
584588

@@ -616,8 +620,8 @@ func TestOnHttpCalloutDone_MaskedTriggersNextGuardrail(t *testing.T) {
616620
}
617621

618622
a.OnHttpCalloutDone(1, shared.HttpCalloutSuccess,
619-
[][2]string{{":status", "200"}},
620-
[][]byte{respBody},
623+
[][2]shared.UnsafeEnvoyBuffer{{pkg.UnsafeBufferFromBytes([]byte(":status")), pkg.UnsafeBufferFromBytes([]byte("200"))}},
624+
[]shared.UnsafeEnvoyBuffer{pkg.UnsafeBufferFromBytes(respBody)},
621625
)
622626
}
623627

@@ -657,8 +661,8 @@ func TestOnHttpCalloutDone_NextGuardrailCalloutFailure(t *testing.T) {
657661
}
658662

659663
a.OnHttpCalloutDone(1, shared.HttpCalloutSuccess,
660-
[][2]string{{":status", "200"}},
661-
[][]byte{respBody},
664+
[][2]shared.UnsafeEnvoyBuffer{{pkg.UnsafeBufferFromBytes([]byte(":status")), pkg.UnsafeBufferFromBytes([]byte("200"))}},
665+
[]shared.UnsafeEnvoyBuffer{pkg.UnsafeBufferFromBytes(respBody)},
662666
)
663667
}
664668

@@ -689,8 +693,8 @@ func TestOnHttpCalloutDone_BodyMultipleChunks(t *testing.T) {
689693
}
690694

691695
a.OnHttpCalloutDone(1, shared.HttpCalloutSuccess,
692-
[][2]string{{":status", "200"}},
693-
[][]byte{chunk1, chunk2},
696+
[][2]shared.UnsafeEnvoyBuffer{{pkg.UnsafeBufferFromBytes([]byte(":status")), pkg.UnsafeBufferFromBytes([]byte("200"))}},
697+
[]shared.UnsafeEnvoyBuffer{pkg.UnsafeBufferFromBytes(chunk1), pkg.UnsafeBufferFromBytes(chunk2)},
694698
)
695699

696700
require.Equal(t, originalBody, fakeBuffer.Body)

extensions/composer/bedrock-guardrails/plugin_test.go

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import (
1414
"github.com/envoyproxy/envoy/source/extensions/dynamic_modules/sdk/go/shared/mocks"
1515
"github.com/stretchr/testify/require"
1616
"go.uber.org/mock/gomock"
17+
18+
"github.com/tetratelabs/built-on-envoy/extensions/composer/pkg"
1719
)
1820

1921
// testBodyBuffer is a correct implementation of shared.BodyBuffer for tests,
@@ -28,8 +30,10 @@ func newTestBodyBuffer(data []byte) *testBodyBuffer {
2830
return &testBodyBuffer{body: b}
2931
}
3032

31-
func (b *testBodyBuffer) GetChunks() [][]byte { return [][]byte{b.body} }
32-
func (b *testBodyBuffer) GetSize() uint64 { return uint64(len(b.body)) }
33+
func (b *testBodyBuffer) GetChunks() []shared.UnsafeEnvoyBuffer {
34+
return []shared.UnsafeEnvoyBuffer{pkg.UnsafeBufferFromBytes(b.body)}
35+
}
36+
func (b *testBodyBuffer) GetSize() uint64 { return uint64(len(b.body)) }
3337
func (b *testBodyBuffer) Drain(size uint64) {
3438
if size >= uint64(len(b.body)) {
3539
b.body = nil
@@ -532,15 +536,15 @@ func TestOnRequestBody_MultipleGuardrails_FirstTriggered(t *testing.T) {
532536
fakeHeaders := fake.NewFakeHeaderMap(map[string][]string{})
533537
mockHandle.EXPECT().RequestHeaders().Return(fakeHeaders).AnyTimes()
534538

535-
var capturedHeaders [][2]string
539+
var capturedHeaders [][2]shared.UnsafeEnvoyBuffer
536540
mockHandle.EXPECT().HttpCallout(
537541
"bedrock-cluster",
538542
gomock.Any(),
539543
gomock.Any(),
540544
gomock.Any(),
541545
gomock.Any(),
542546
).DoAndReturn(func(_ string, headers [][2]string, _ []byte, _ uint64, _ shared.HttpCalloutCallback) (shared.HttpCalloutInitResult, uint64) {
543-
capturedHeaders = headers
547+
capturedHeaders = toUnsafeEnvoyBufferHeaders(headers)
544548
return shared.HttpCalloutInitSuccess, uint64(1)
545549
}).Times(1)
546550

@@ -565,3 +569,12 @@ func TestOnRequestBody_MultipleGuardrails_FirstTriggered(t *testing.T) {
565569
// Only the first guardrail should have been called
566570
require.Equal(t, "/guardrail/first-guardrail/version/1/apply", headerValue(capturedHeaders, ":path"))
567571
}
572+
573+
func toUnsafeEnvoyBufferHeaders(headers [][2]string) [][2]shared.UnsafeEnvoyBuffer {
574+
unsafeHeaders := make([][2]shared.UnsafeEnvoyBuffer, len(headers))
575+
for i, h := range headers {
576+
unsafeHeaders[i][0] = pkg.UnsafeBufferFromBytes([]byte(h[0]))
577+
unsafeHeaders[i][1] = pkg.UnsafeBufferFromBytes([]byte(h[1]))
578+
}
579+
return unsafeHeaders
580+
}

extensions/composer/cedar/manifest.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ longDescription: |
7575
"address": "10.0.0.2:443"
7676
},
7777
"connection": {
78+
"mtls": true,
7879
"tls_version": "TLSv1.3"
7980
},
8081
"parsed_path": ["api", "v1", "resource"],

0 commit comments

Comments
 (0)