Skip to content

Commit 380fb00

Browse files
committed
all: add support for shielding hostcalls
1 parent d4c6571 commit 380fb00

File tree

9 files changed

+317
-0
lines changed

9 files changed

+317
-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, Target=%v, SSL Target=%v", shield.Name(), shield.Target(), shield.SSLTarget())
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: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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 Target=%v SSL=%v", shield.Name(), shield.Target(), shield.SSLTarget())
28+
}
29+
30+
r, err := fsthttp.NewRequest("GET", "/?shield=bfi-wa-us", nil)
31+
if err != nil {
32+
t.Fatalf("NewRequest: %v", err)
33+
}
34+
w := fsttest.NewRecorder()
35+
36+
handler(context.Background(), w, r)
37+
38+
if got, want := w.Code, fsthttp.StatusOK; got != want {
39+
t.Errorf("Code = %d, want %d", got, want)
40+
}
41+
42+
if got, want := w.Body.String(), "Name=bfi-wa-us Target=http://localhost/ SSL=https://localhost/"; got != want {
43+
t.Errorf("Body = %q, want %q", got, want)
44+
}
45+
}

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: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
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+
// split on null bytes
44+
vals := bytes.Split(buf.AsBytes(), []byte{0})
45+
46+
if len(vals) == 0 {
47+
return nil, FastlyStatusBadAlign.toError()
48+
}
49+
50+
var info ShieldInfo
51+
52+
if len(vals[0]) == 0 {
53+
info.me = true
54+
}
55+
56+
info.target = string(vals[1])
57+
info.sslTarget = string(vals[2])
58+
59+
return &info, nil
60+
}
61+
62+
// witx:
63+
//
64+
// (@interface func (export "backend_for_shield")
65+
//
66+
// (param $shield_name string)
67+
// (param $backend_config_mask $shield_backend_options)
68+
// (param $backend_configuration (@witx pointer $shield_backend_config))
69+
// (param $backend_name_out (@witx pointer (@witx char8)))
70+
// (param $backend_name_max_len (@witx usize))
71+
// (result $err (expected $num_bytes (error $fastly_status)))
72+
// )
73+
//
74+
//go:wasmimport fastly_shielding backend_for_shield
75+
//go:noescape
76+
func fastlyShieldingBackendForShield(
77+
name prim.Pointer[prim.Char8], nameLen prim.Usize,
78+
mask shieldingBackendOptionsMask,
79+
opts prim.Pointer[shieldingBackendOptions],
80+
bufPtr prim.Pointer[prim.Char8], bufLen prim.Usize,
81+
bufLenOut prim.Pointer[prim.Usize],
82+
) FastlyStatus
83+
84+
func ShieldingBackendForShield(name string, opts ShieldingBackendOptions) (backend string, err error) {
85+
86+
n := prim.NewReadBufferFromString(name)
87+
88+
buf := prim.NewWriteBuffer(DefaultMediumBufLen)
89+
90+
if err := fastlyShieldingBackendForShield(
91+
prim.ToPointer(n.Char8Pointer()), n.Len(),
92+
opts.mask, prim.ToPointer(&opts.opts),
93+
prim.ToPointer(buf.Char8Pointer()),
94+
buf.Cap(),
95+
prim.ToPointer(buf.NPointer()),
96+
).toError(); err != nil {
97+
return "", err
98+
99+
}
100+
101+
return buf.ToString(), nil
102+
}

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) Me() 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: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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+
me bool
11+
target string
12+
sslTarget string
13+
}
14+
15+
// ShieldFromName returns information about a particular shield site.
16+
func ShieldFromName(n string) (*Shield, error) {
17+
info, err := fastly.ShieldingShieldInfo(n)
18+
if err != nil {
19+
return nil, err
20+
}
21+
22+
return &Shield{
23+
name: n,
24+
me: info.Me(),
25+
target: info.Target(),
26+
sslTarget: info.SSLTarget(),
27+
}, nil
28+
}
29+
30+
// Name returns the name of the shield site.
31+
func (s *Shield) Name() string { return s.name }
32+
33+
// Me returns whether the Compute node is currently in the shielding site.
34+
func (s *Shield) Me() bool { return s.me }
35+
36+
// Target returns the target for unecrypted data.
37+
func (s *Shield) Target() string { return s.target }
38+
39+
// SSLTarget returns the target for encrypted traffic.
40+
func (s *Shield) SSLTarget() string { return s.sslTarget }
41+
42+
// BackendOptions
43+
type BackendOptions struct {
44+
cacheKey string
45+
}
46+
47+
// Backend returns a named backend for use with the fsthttp package.
48+
func (s *Shield) Backend(opts *BackendOptions) (string, error) {
49+
var abiOpts fastly.ShieldingBackendOptions
50+
if opts.cacheKey != "" {
51+
abiOpts.CacheKey(opts.cacheKey)
52+
}
53+
return fastly.ShieldingBackendForShield(s.name, abiOpts)
54+
}

0 commit comments

Comments
 (0)