Skip to content

Commit 4b6a1a5

Browse files
committed
all: add support for shielding hostcalls
1 parent d4c6571 commit 4b6a1a5

File tree

9 files changed

+328
-0
lines changed

9 files changed

+328
-0
lines changed

_examples/shielding/fastly.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# This file describes a Fastly Compute package. To learn more visit:
2+
# https://developer.fastly.com/reference/fastly-toml/
3+
4+
authors = ["[email protected]"]
5+
description = ""
6+
language = "go"
7+
manifest_version = 2
8+
name = "shielding"
9+
10+
[local_server.shielding_sites]
11+
"pdx-or-us" = "Local"
12+
"bfi-wa-us".unencrypted = "http://localhost"
13+
"bfi-wa-us".encrypted = "https://localhost"

_examples/shielding/main.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright 2022 Fastly, Inc.
2+
3+
package main
4+
5+
import (
6+
"context"
7+
"fmt"
8+
9+
"github.com/fastly/compute-sdk-go/fsthttp"
10+
"github.com/fastly/compute-sdk-go/shielding"
11+
)
12+
13+
func main() {
14+
fsthttp.ServeFunc(func(ctx context.Context, w fsthttp.ResponseWriter, r *fsthttp.Request) {
15+
name := r.URL.Query().Get("shield")
16+
17+
shield, err := shielding.ShieldFromName(name)
18+
if err != nil {
19+
fsthttp.Error(w, err.Error(), fsthttp.StatusInternalServerError)
20+
return
21+
}
22+
23+
fmt.Fprintf(w, "Shield Name=%v, RunningOn=%v\n", shield.Name(), shield.IsRunningOn())
24+
})
25+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# This file describes a Fastly Compute package. To learn more visit:
2+
# https://developer.fastly.com/reference/fastly-toml/
3+
4+
authors = ["[email protected]"]
5+
description = ""
6+
language = "other"
7+
manifest_version = 2
8+
name = "hello_world"
9+
service_id = ""
10+
11+
[local_server]
12+
13+
[local_server.shielding_sites]
14+
"pdx-or-us" = "Local"
15+
"bfi-wa-us".unencrypted = "http://localhost"
16+
"bfi-wa-us".encrypted = "https://localhost"
17+
18+
[local_server.backends]
19+
[local_server.backends.TheOrigin]
20+
url = "https://compute-sdk-test-backend.edgecompute.app/"
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
//go:build ((tinygo.wasm && wasi) || wasip1) && !nofastlyhostcalls
2+
3+
// Copyright 2022 Fastly, Inc.
4+
5+
package main
6+
7+
import (
8+
"context"
9+
"fmt"
10+
"testing"
11+
12+
"github.com/fastly/compute-sdk-go/fsthttp"
13+
"github.com/fastly/compute-sdk-go/fsttest"
14+
"github.com/fastly/compute-sdk-go/shielding"
15+
)
16+
17+
func TestShielding(t *testing.T) {
18+
handler := func(_ context.Context, w fsthttp.ResponseWriter, r *fsthttp.Request) {
19+
name := r.URL.Query().Get("shield")
20+
21+
shield, err := shielding.ShieldFromName(name)
22+
if err != nil {
23+
fsthttp.Error(w, err.Error(), fsthttp.StatusInternalServerError)
24+
return
25+
}
26+
27+
fmt.Fprintf(w, "Name=%v RunningOn=%v", shield.Name(), shield.IsRunningOn())
28+
}
29+
30+
var tests = []struct {
31+
shield, want string
32+
}{
33+
{"bfi-wa-us", "Name=bfi-wa-us RunningOn=false"},
34+
{"pdx-or-us", "Name=pdx-or-us RunningOn=true"},
35+
}
36+
37+
for _, tt := range tests {
38+
39+
r, err := fsthttp.NewRequest("GET", "/?shield="+tt.shield, nil)
40+
if err != nil {
41+
t.Fatalf("NewRequest: %v", err)
42+
}
43+
w := fsttest.NewRecorder()
44+
45+
handler(context.Background(), w, r)
46+
47+
if got, want := w.Code, fsthttp.StatusOK; got != want {
48+
t.Errorf("Code = %d, want %d", got, want)
49+
}
50+
51+
if got, want := w.Body.String(), tt.want; got != want {
52+
t.Errorf("Body = %q, want %q", got, want)
53+
}
54+
55+
}
56+
}

internal/abi/fastly/hostcalls_noguest.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -727,3 +727,11 @@ func HTTPCacheGetSurrogateKeys(h *HTTPCacheHandle) (string, error) {
727727
func HTTPCacheGetVaryRule(h *HTTPCacheHandle) (string, error) {
728728
return "", fmt.Errorf("not implemented")
729729
}
730+
731+
func ShieldingShieldInfo(name string) (*ShieldInfo, error) {
732+
return nil, fmt.Errorf("not implemented")
733+
}
734+
735+
func ShieldingBackendForShield(name string, opts *ShieldingBackendOptions) (string, error) {
736+
return "", fmt.Errorf("not implemented")
737+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
//go:build ((tinygo.wasm && wasi) || wasip1) && !nofastlyhostcalls
2+
3+
package fastly
4+
5+
import (
6+
"bytes"
7+
8+
"github.com/fastly/compute-sdk-go/internal/abi/prim"
9+
)
10+
11+
// (module $fastly_shielding
12+
13+
// witx:
14+
//
15+
// (@interface func (export "shield_info")
16+
// (param $name string)
17+
// (param $info_block (@witx pointer (@witx char8)))
18+
// (param $info_block_max_len (@witx usize))
19+
// (result $err (expected $num_bytes (error $fastly_status)))
20+
// )
21+
//
22+
//go:wasmimport fastly_shielding shield_info
23+
//go:noescape
24+
func fastlyShieldingShieldingInfo(
25+
name prim.Pointer[prim.U8], nameLen prim.Usize,
26+
bufPtr prim.Pointer[prim.Char8], bufLen prim.Usize,
27+
bufLenOut prim.Pointer[prim.Usize],
28+
) FastlyStatus
29+
30+
func ShieldingShieldInfo(name string) (*ShieldInfo, error) {
31+
32+
n := prim.NewReadBufferFromString(name).Wstring()
33+
buf := prim.NewWriteBuffer(DefaultMediumBufLen)
34+
35+
if err := fastlyShieldingShieldingInfo(
36+
n.Data, n.Len,
37+
prim.ToPointer(buf.Char8Pointer()), buf.Cap(),
38+
prim.ToPointer(buf.NPointer()),
39+
).toError(); err != nil {
40+
return nil, err
41+
}
42+
43+
bufb := buf.AsBytes()
44+
45+
if len(bufb) == 0 {
46+
return nil, FastlyStatusBadAlign.toError()
47+
}
48+
49+
// block format:
50+
// 0x1 (running on shield)
51+
// 0x0 0x0 // no targets, but still have field separators)
52+
// OR
53+
// 0x0 (not running on shield)
54+
// <unencrypted target> 0x0
55+
// <encrypted target> 0x0
56+
57+
var info ShieldInfo
58+
info.me = bufb[0] == 1
59+
60+
if !info.me {
61+
// strip first null byte and extract targets
62+
vals := bytes.Split(bufb[1:], []byte{0})
63+
64+
// ensure we got what we expected
65+
if len(vals) != 3 {
66+
return nil, FastlyStatusBadAlign.toError()
67+
}
68+
69+
info.target = string(vals[0])
70+
info.sslTarget = string(vals[1])
71+
}
72+
73+
return &info, nil
74+
}
75+
76+
// witx:
77+
//
78+
// (@interface func (export "backend_for_shield")
79+
//
80+
// (param $shield_name string)
81+
// (param $backend_config_mask $shield_backend_options)
82+
// (param $backend_configuration (@witx pointer $shield_backend_config))
83+
// (param $backend_name_out (@witx pointer (@witx char8)))
84+
// (param $backend_name_max_len (@witx usize))
85+
// (result $err (expected $num_bytes (error $fastly_status)))
86+
// )
87+
//
88+
//go:wasmimport fastly_shielding backend_for_shield
89+
//go:noescape
90+
func fastlyShieldingBackendForShield(
91+
name prim.Pointer[prim.Char8], nameLen prim.Usize,
92+
mask shieldingBackendOptionsMask,
93+
opts prim.Pointer[shieldingBackendOptions],
94+
bufPtr prim.Pointer[prim.Char8], bufLen prim.Usize,
95+
bufLenOut prim.Pointer[prim.Usize],
96+
) FastlyStatus
97+
98+
func ShieldingBackendForShield(name string, opts *ShieldingBackendOptions) (backend string, err error) {
99+
100+
n := prim.NewReadBufferFromString(name)
101+
102+
buf := prim.NewWriteBuffer(DefaultMediumBufLen)
103+
104+
if err := fastlyShieldingBackendForShield(
105+
prim.ToPointer(n.Char8Pointer()), n.Len(),
106+
opts.mask, prim.ToPointer(&opts.opts),
107+
prim.ToPointer(buf.Char8Pointer()),
108+
buf.Cap(),
109+
prim.ToPointer(buf.NPointer()),
110+
).toError(); err != nil {
111+
return "", err
112+
113+
}
114+
115+
return buf.ToString(), nil
116+
}

internal/abi/fastly/types.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1623,3 +1623,49 @@ const (
16231623
httpCacheWriteOptionsFlagLength httpCacheWriteOptionsMask = 1 << 5
16241624
httpCacheWriteOptionsFlagSensitiveData httpCacheWriteOptionsMask = 1 << 6
16251625
)
1626+
1627+
// shielding.witx
1628+
1629+
type shieldingBackendOptionsMask prim.U32
1630+
1631+
const (
1632+
shieldingBackendOptionsFlagReserved shieldingBackendOptionsMask = 1 << 0
1633+
shieldingBackendOptionsFlagUseCacheKey shieldingBackendOptionsMask = 1 << 1
1634+
)
1635+
1636+
type shieldingBackendOptions struct {
1637+
// A list of surrogate keys that may be used to purge this response.
1638+
//
1639+
// The format is a string containing [valid surrogate
1640+
// keys](https://www.fastly.com/documentation/reference/http/http-headers/Surrogate-Key/)
1641+
// separated by spaces.
1642+
//
1643+
// If this field is not set, no surrogate keys will be associated with the response. This
1644+
// means that the response cannot be purged except via a purge-all operation.
1645+
cacheKeyPtr prim.Pointer[prim.Char8]
1646+
cacheKeyLen prim.Usize
1647+
}
1648+
1649+
type ShieldingBackendOptions struct {
1650+
mask shieldingBackendOptionsMask
1651+
opts shieldingBackendOptions
1652+
}
1653+
1654+
func (s *ShieldingBackendOptions) CacheKey(key string) {
1655+
s.mask |= shieldingBackendOptionsFlagUseCacheKey
1656+
buf := prim.NewReadBufferFromString(key)
1657+
s.opts.cacheKeyPtr = prim.ToPointer(buf.Char8Pointer())
1658+
s.opts.cacheKeyLen = buf.Len()
1659+
}
1660+
1661+
type ShieldInfo struct {
1662+
me bool
1663+
target string
1664+
sslTarget string
1665+
}
1666+
1667+
func (s *ShieldInfo) RunningOn() bool { return s.me }
1668+
1669+
func (s *ShieldInfo) Target() string { return s.target }
1670+
1671+
func (s *ShieldInfo) SSLTarget() string { return s.sslTarget }

shielding/doc.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// Package shielding provides support for shielding for Compute services. Refer to
2+
// https://www.fastly.com/documentation/guides/concepts/shielding/ for more information.
3+
4+
package shielding

shielding/shielding.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package shielding
2+
3+
import (
4+
"github.com/fastly/compute-sdk-go/internal/abi/fastly"
5+
)
6+
7+
// Shield is a shielding site withing Fastly.
8+
type Shield struct {
9+
name string
10+
runningOn bool
11+
}
12+
13+
// ShieldFromName returns information about a particular shield site.
14+
func ShieldFromName(n string) (*Shield, error) {
15+
info, err := fastly.ShieldingShieldInfo(n)
16+
if err != nil {
17+
return nil, err
18+
}
19+
20+
return &Shield{
21+
name: n,
22+
runningOn: info.RunningOn(),
23+
}, nil
24+
}
25+
26+
// Name returns the name of the shield site.
27+
func (s *Shield) Name() string { return s.name }
28+
29+
// IsRunningOn returns whether the Compute node is currently in the shielding site.
30+
func (s *Shield) IsRunningOn() bool { return s.runningOn }
31+
32+
// BackendOptions
33+
type BackendOptions struct {
34+
}
35+
36+
// Backend returns a named backend for use with the fsthttp package.
37+
func (s *Shield) Backend(opts *BackendOptions) (string, error) {
38+
var abiOpts fastly.ShieldingBackendOptions
39+
return fastly.ShieldingBackendForShield(s.name, &abiOpts)
40+
}

0 commit comments

Comments
 (0)