Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions _examples/shielding/fastly.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# This file describes a Fastly Compute package. To learn more visit:
# https://developer.fastly.com/reference/fastly-toml/

authors = ["[email protected]"]
description = ""
language = "go"
manifest_version = 2
name = "shielding"

[local_server.shielding_sites]
"pdx-or-us" = "Local"
"bfi-wa-us".unencrypted = "http://localhost"
"bfi-wa-us".encrypted = "https://localhost"
25 changes: 25 additions & 0 deletions _examples/shielding/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright 2022 Fastly, Inc.

package main

import (
"context"
"fmt"

"github.com/fastly/compute-sdk-go/fsthttp"
"github.com/fastly/compute-sdk-go/shielding"
)

func main() {
fsthttp.ServeFunc(func(ctx context.Context, w fsthttp.ResponseWriter, r *fsthttp.Request) {
name := r.URL.Query().Get("shield")

shield, err := shielding.ShieldFromName(name)
if err != nil {
fsthttp.Error(w, err.Error(), fsthttp.StatusInternalServerError)
return
}

fmt.Fprintf(w, "Shield Name=%v, RunningOn=%v\n", shield.Name(), shield.IsRunningOn())
})
}
20 changes: 20 additions & 0 deletions integration_tests/shielding/fastly.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# This file describes a Fastly Compute package. To learn more visit:
# https://developer.fastly.com/reference/fastly-toml/

authors = ["[email protected]"]
description = ""
language = "other"
manifest_version = 2
name = "hello_world"
service_id = ""

[local_server]

[local_server.shielding_sites]
"pdx-or-us" = "Local"
"bfi-wa-us".unencrypted = "http://localhost"
"bfi-wa-us".encrypted = "https://localhost"

[local_server.backends]
[local_server.backends.TheOrigin]
url = "https://compute-sdk-test-backend.edgecompute.app/"
56 changes: 56 additions & 0 deletions integration_tests/shielding/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//go:build ((tinygo.wasm && wasi) || wasip1) && !nofastlyhostcalls

// Copyright 2022 Fastly, Inc.

package main

import (
"context"
"fmt"
"testing"

"github.com/fastly/compute-sdk-go/fsthttp"
"github.com/fastly/compute-sdk-go/fsttest"
"github.com/fastly/compute-sdk-go/shielding"
)

func TestShielding(t *testing.T) {
handler := func(_ context.Context, w fsthttp.ResponseWriter, r *fsthttp.Request) {
name := r.URL.Query().Get("shield")

shield, err := shielding.ShieldFromName(name)
if err != nil {
fsthttp.Error(w, err.Error(), fsthttp.StatusInternalServerError)
return
}

fmt.Fprintf(w, "Name=%v RunningOn=%v", shield.Name(), shield.IsRunningOn())
}

var tests = []struct {
shield, want string
}{
{"bfi-wa-us", "Name=bfi-wa-us RunningOn=false"},
{"pdx-or-us", "Name=pdx-or-us RunningOn=true"},
}

for _, tt := range tests {

r, err := fsthttp.NewRequest("GET", "/?shield="+tt.shield, nil)
if err != nil {
t.Fatalf("NewRequest: %v", err)
}
w := fsttest.NewRecorder()

handler(context.Background(), w, r)

if got, want := w.Code, fsthttp.StatusOK; got != want {
t.Errorf("Code = %d, want %d", got, want)
}

if got, want := w.Body.String(), tt.want; got != want {
t.Errorf("Body = %q, want %q", got, want)
}

}
}
8 changes: 8 additions & 0 deletions internal/abi/fastly/hostcalls_noguest.go
Original file line number Diff line number Diff line change
Expand Up @@ -727,3 +727,11 @@ func HTTPCacheGetSurrogateKeys(h *HTTPCacheHandle) (string, error) {
func HTTPCacheGetVaryRule(h *HTTPCacheHandle) (string, error) {
return "", fmt.Errorf("not implemented")
}

func ShieldingShieldInfo(name string) (*ShieldInfo, error) {
return nil, fmt.Errorf("not implemented")
}

func ShieldingBackendForShield(name string, opts *ShieldingBackendOptions) (string, error) {
return "", fmt.Errorf("not implemented")
}
116 changes: 116 additions & 0 deletions internal/abi/fastly/shielding_guest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
//go:build ((tinygo.wasm && wasi) || wasip1) && !nofastlyhostcalls

package fastly

import (
"bytes"

"github.com/fastly/compute-sdk-go/internal/abi/prim"
)

// (module $fastly_shielding

// witx:
//
// (@interface func (export "shield_info")
// (param $name string)
// (param $info_block (@witx pointer (@witx char8)))
// (param $info_block_max_len (@witx usize))
// (result $err (expected $num_bytes (error $fastly_status)))
// )
//
//go:wasmimport fastly_shielding shield_info
//go:noescape
func fastlyShieldingShieldingInfo(
name prim.Pointer[prim.U8], nameLen prim.Usize,
bufPtr prim.Pointer[prim.Char8], bufLen prim.Usize,
bufLenOut prim.Pointer[prim.Usize],
) FastlyStatus

func ShieldingShieldInfo(name string) (*ShieldInfo, error) {

n := prim.NewReadBufferFromString(name).Wstring()
buf := prim.NewWriteBuffer(DefaultMediumBufLen)

if err := fastlyShieldingShieldingInfo(
n.Data, n.Len,
prim.ToPointer(buf.Char8Pointer()), buf.Cap(),
prim.ToPointer(buf.NPointer()),
).toError(); err != nil {
return nil, err
}

bufb := buf.AsBytes()

if len(bufb) == 0 {
return nil, FastlyStatusBadAlign.toError()
}

// block format:
// 0x1 (running on shield)
// 0x0 0x0 // no targets, but still have field separators)
// OR
// 0x0 (not running on shield)
// <unencrypted target> 0x0
// <encrypted target> 0x0

var info ShieldInfo
info.me = bufb[0] == 1

if !info.me {
// strip first null byte and extract targets
vals := bytes.Split(bufb[1:], []byte{0})

// ensure we got what we expected
if len(vals) != 3 {
return nil, FastlyStatusBadAlign.toError()
}

info.target = string(vals[0])
info.sslTarget = string(vals[1])
}

return &info, nil
}

// witx:
//
// (@interface func (export "backend_for_shield")
//
// (param $shield_name string)
// (param $backend_config_mask $shield_backend_options)
// (param $backend_configuration (@witx pointer $shield_backend_config))
// (param $backend_name_out (@witx pointer (@witx char8)))
// (param $backend_name_max_len (@witx usize))
// (result $err (expected $num_bytes (error $fastly_status)))
// )
//
//go:wasmimport fastly_shielding backend_for_shield
//go:noescape
func fastlyShieldingBackendForShield(
name prim.Pointer[prim.Char8], nameLen prim.Usize,
mask shieldingBackendOptionsMask,
opts prim.Pointer[shieldingBackendOptions],
bufPtr prim.Pointer[prim.Char8], bufLen prim.Usize,
bufLenOut prim.Pointer[prim.Usize],
) FastlyStatus

func ShieldingBackendForShield(name string, opts *ShieldingBackendOptions) (backend string, err error) {

n := prim.NewReadBufferFromString(name)

buf := prim.NewWriteBuffer(DefaultMediumBufLen)

if err := fastlyShieldingBackendForShield(
prim.ToPointer(n.Char8Pointer()), n.Len(),
opts.mask, prim.ToPointer(&opts.opts),
prim.ToPointer(buf.Char8Pointer()),
buf.Cap(),
prim.ToPointer(buf.NPointer()),
).toError(); err != nil {
return "", err

}

return buf.ToString(), nil
}
46 changes: 46 additions & 0 deletions internal/abi/fastly/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -1623,3 +1623,49 @@ const (
httpCacheWriteOptionsFlagLength httpCacheWriteOptionsMask = 1 << 5
httpCacheWriteOptionsFlagSensitiveData httpCacheWriteOptionsMask = 1 << 6
)

// shielding.witx

type shieldingBackendOptionsMask prim.U32

const (
shieldingBackendOptionsFlagReserved shieldingBackendOptionsMask = 1 << 0
shieldingBackendOptionsFlagUseCacheKey shieldingBackendOptionsMask = 1 << 1
)

type shieldingBackendOptions struct {
// A list of surrogate keys that may be used to purge this response.
//
// The format is a string containing [valid surrogate
// keys](https://www.fastly.com/documentation/reference/http/http-headers/Surrogate-Key/)
// separated by spaces.
//
// If this field is not set, no surrogate keys will be associated with the response. This
// means that the response cannot be purged except via a purge-all operation.
cacheKeyPtr prim.Pointer[prim.Char8]
cacheKeyLen prim.Usize
}

type ShieldingBackendOptions struct {
mask shieldingBackendOptionsMask
opts shieldingBackendOptions
}

func (s *ShieldingBackendOptions) CacheKey(key string) {
s.mask |= shieldingBackendOptionsFlagUseCacheKey
buf := prim.NewReadBufferFromString(key)
s.opts.cacheKeyPtr = prim.ToPointer(buf.Char8Pointer())
s.opts.cacheKeyLen = buf.Len()
}

type ShieldInfo struct {
me bool
target string
sslTarget string
}

func (s *ShieldInfo) RunningOn() bool { return s.me }

func (s *ShieldInfo) Target() string { return s.target }

func (s *ShieldInfo) SSLTarget() string { return s.sslTarget }
4 changes: 4 additions & 0 deletions shielding/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Package shielding provides support for shielding for Compute services. Refer to
// https://www.fastly.com/documentation/guides/concepts/shielding/ for more information.

package shielding
40 changes: 40 additions & 0 deletions shielding/shielding.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package shielding

import (
"github.com/fastly/compute-sdk-go/internal/abi/fastly"
)

// Shield is a shielding site withing Fastly.
type Shield struct {
name string
runningOn bool
}

// ShieldFromName returns information about a particular shield site.
func ShieldFromName(n string) (*Shield, error) {
info, err := fastly.ShieldingShieldInfo(n)
if err != nil {
return nil, err
}

return &Shield{
name: n,
runningOn: info.RunningOn(),
}, nil
}

// Name returns the name of the shield site.
func (s *Shield) Name() string { return s.name }

// IsRunningOn returns whether the Compute node is currently in the shielding site.
func (s *Shield) IsRunningOn() bool { return s.runningOn }

// BackendOptions
type BackendOptions struct {
}

// Backend returns a named backend for use with the fsthttp package.
func (s *Shield) Backend(opts *BackendOptions) (string, error) {
Comment on lines +32 to +37
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is reserving the ability to add options later and not break the public API?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. There was an (unexported) option in the Rust SDK that I copied over, but have since removed.

var abiOpts fastly.ShieldingBackendOptions
return fastly.ShieldingBackendForShield(s.name, &abiOpts)
}