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
50 changes: 50 additions & 0 deletions documentation/docs/shielding/Shield/Shield.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
hide_title: false
hide_table_of_contents: false
pagination_next: null
pagination_prev: null
---
# `Shield()`

Load information about the given shield.

Returns an object representing the shield if it is active, or throws an exception if the string is malformed or the shield doesn’t exist.

Shield names are defined on [this webpage](https://www.fastly.com/documentation/guides/concepts/shielding/#shield-locations), in the “shield code” column. For example, the string “pdx-or-us” will look up our Portland, OR, USA shield site, while “paris-fr” will look up our Paris site.

If you are using a major cloud provider for your primary origin site, consider looking at the “Recommended for” column, to find the Fastly POP most closely located to the given cloud provider.

## Syntax

```js
new Shield(name)
```

> **Note:** `Shield()` can only be constructed with `new`. Attempting to call it without `new` throws a [`TypeError`](../../globals/TypeError/TypeError.mdx).

### Exceptions

- `TypeError`
- Thrown if no Shield exists with the provided name

## Examples

In this example, we create a Shield instance for the Sydney, Australia shield POP. If the code is running on that shield POP, it fetches directly from origin. Otherwise, it routes the request through the shield using an encrypted connection.

```js
/// <reference types="@fastly/js-compute" />

import { Shield } from "fastly:shielding";

async function app(event) {
const shield = new Shield('wsi-australia-au');
// If running on the shield POP, fetch from the origin directly
if (shield.runningOn()) {
return await fetch('https://http-me.fastly.com/anything', { backend: 'httpme' });
}
// Otherwise, route the request through the shield using an encrypted connection
return await fetch(event.request, { backend: shield.encryptedBackend() });
}

addEventListener("fetch", (event) => event.respondWith(app(event)))
```
13 changes: 13 additions & 0 deletions documentation/docs/shielding/Shield/prototype/encryptedBackend.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
hide_title: false
hide_table_of_contents: false
pagination_next: null
pagination_prev: null
---
# Shield.prototype.encryptedBackend

**encryptedBackend**(): `Backend`

Returns a `Backend` representing an encrypted connection to the POP.

For reference, this is almost always the backend that you want to use. Only use [`Shield::unencryptedBackend`](./unencryptedBackend.mdx) in situations in which you are 100% sure that all the data you will send and receive over the backend is already encrypted.
15 changes: 15 additions & 0 deletions documentation/docs/shielding/Shield/prototype/runningOn.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
hide_title: false
hide_table_of_contents: false
pagination_next: null
pagination_prev: null
---
# Shield.prototype.runningOn

▸ **runningOn**(): `boolean`

Returns whether we are currently operating on the given shield.

Technically, this may also return true in very isolated incidents in which Fastly is routing traffic from the target shield POP to the POP that this code is running on, but in these situations the results should be approximately identical.

(For example, it may be the case that you are asking to shield to ‘pdx-or-us’. But, for load balancing, performance, or other reasons, Fastly is temporarily shifting shielding traffic from Portland to Seattle. In that case, this function may return true for hosts running on ‘bfi-wa-us’, our Seattle site, because effectively the shield has moved to that location. This should give you a slightly faster experience than the alternative, in which this function would return false, you would try to forward your traffic to the Portland site, and then that traffic would be caught and redirected back to Seattle.)
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
hide_title: false
hide_table_of_contents: false
pagination_next: null
pagination_prev: null
---
# Shield.prototype.unencryptedBackend

▸ **unencryptedBackend**(): `Backend`

Returns a `Backend` representing an unencrypted connection to the POP.

Generally speaking, we encourage users to use [`Shield::encryptedBackend`](./encryptedBackend.mdx) instead of this function. Data sent over this backend – the unencrypted version – will be sent over the open internet, with no protections. In most cases, this is not what you want. However, in some cases – such as when you want to ship large data blobs that you know are already encrypted — using these backends can prevent a double-encryption performance penalty.

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions integration-tests/js-compute/fixtures/app/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import './response-redirect.js';
import './response.js';
import './secret-store.js';
import './server.js';
import './shielding.js';
import './tee.js';
import './timers.js';
import './urlsearchparams.js';
Expand Down
7 changes: 7 additions & 0 deletions integration-tests/js-compute/fixtures/app/src/shielding.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { assert, assertThrows } from './assertions.js';
import { routes } from './routes.js';
import { Shield } from 'fastly:shielding';

routes.set('/shielding/invalid-shield', () => {
assertThrows(() => new Shield('i-am-not-a-real-shield'));
});
3 changes: 3 additions & 0 deletions integration-tests/js-compute/fixtures/app/tests.json
Original file line number Diff line number Diff line change
Expand Up @@ -3136,5 +3136,8 @@
["link", "</style2.css>; rel=preload; as=style"]
]
}
},
"GET /shielding/invalid-shield": {
"environments": ["compute"]
}
}
1 change: 1 addition & 0 deletions runtime/fastly/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ add_builtin(fastly::edge_rate_limiter SRC builtins/edge-rate-limiter.cpp)
add_builtin(fastly::config_store SRC builtins/config-store.cpp)
add_builtin(fastly::secret_store SRC builtins/secret-store.cpp)
add_builtin(fastly::image_optimizer SRC builtins/image-optimizer.cpp)
add_builtin(fastly::shielding SRC builtins/shielding.cpp)

add_builtin(fastly::fetch
SRC
Expand Down
133 changes: 133 additions & 0 deletions runtime/fastly/builtins/shielding.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
#include "shielding.h"
#include "../../../StarlingMonkey/runtime/encode.h"
#include "../common/validations.h"
#include "../host-api/host_api_fastly.h"
#include "backend.h"
#include "fastly.h"

namespace fastly::shielding {
const JSFunctionSpec Shield::static_methods[] = {
JS_FS_END,
};

const JSPropertySpec Shield::static_properties[] = {
JS_PS_END,
};

const JSFunctionSpec Shield::methods[] = {
JS_FN("runningOn", runningOn, 0, JSPROP_ENUMERATE),
JS_FN("unencryptedBackend", unencryptedBackend, 0, JSPROP_ENUMERATE),
JS_FN("encryptedBackend", encryptedBackend, 0, JSPROP_ENUMERATE), JS_FS_END};

const JSPropertySpec Shield::properties[] = {JS_PS_END};

bool Shield::runningOn(JSContext *cx, unsigned argc, JS::Value *vp) {
METHOD_HEADER(0);
bool is_me = JS::GetReservedSlot(self, static_cast<uint32_t>(Slots::IsMe)).toBoolean();
args.rval().setBoolean(is_me);
return true;
}
bool Shield::unencryptedBackend(JSContext *cx, unsigned argc, JS::Value *vp) {
METHOD_HEADER(0)
JS::RootedString target(
cx, JS::GetReservedSlot(self, static_cast<uint32_t>(Slots::PlainTarget)).toString());
return backend_for_shield(cx, target, args.rval());
}
bool Shield::encryptedBackend(JSContext *cx, unsigned argc, JS::Value *vp) {
METHOD_HEADER(0)
JS::RootedString target(
cx, JS::GetReservedSlot(self, static_cast<uint32_t>(Slots::SSLTarget)).toString());
return backend_for_shield(cx, target, args.rval());
}
bool Shield::backend_for_shield(JSContext *cx, JS::HandleString target,
JS::MutableHandleValue rval) {
auto name = core::encode(cx, target);
fastly_shielding_shield_backend_config config{nullptr, 0, 0};
auto options_mask = 0;
std::uint32_t backend_name_size_out = 0;
constexpr std::size_t max_backend_name_size = 1024;
std::string backend_name_out(max_backend_name_size, 0);
auto status = fastly_shielding_backend_for_shield(name.ptr.get(), name.len, options_mask, &config,
backend_name_out.data(), max_backend_name_size,
&backend_name_size_out);
if (status != 0) {
HANDLE_ERROR(cx, status);
return false;
}
backend_name_out.resize(backend_name_size_out);
host_api::HostString backend_name(backend_name_out);
return backend::Backend::get_from_valid_name(cx, std::move(backend_name), rval);
}

bool Shield::constructor(JSContext *cx, unsigned argc, JS::Value *vp) {
REQUEST_HANDLER_ONLY("The Shield builtin");
CTOR_HEADER("Shield", 1);

JS::HandleValue name_arg = args.get(0);
auto name = core::encode(cx, name_arg);

// Keep calling fastly_shielding_shield_info with a increasingly large buffer until it returns OK
std::uint32_t buf_size = 1024;
std::vector<char> out_buf(buf_size);
while (true) {
std::uint32_t used_amount = 0;
auto status = fastly_shielding_shield_info(name.ptr.get(), name.len, out_buf.data(), buf_size,
&used_amount);
if (status == 0) {
out_buf.resize(used_amount);
break;
} else if (status == FASTLY_HOST_ERROR_BUFFER_LEN) {
Copy link
Contributor

Choose a reason for hiding this comment

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

doesn't FASTLY_HOST_ERROR_BUFFER_LEN usually set buf_size to the expected buf_size, so you don't have to keep retrying like this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Usually, yes, not in the case of this host call it seems. I'm following the Rust SDK behaviour fwiw

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

apparently it doesn't. Absolutely rude.

buf_size *= 2;
out_buf = std::vector<char>(1024);
} else {
HANDLE_ERROR(cx, status);
return false;
}
}

if (out_buf.size() < 3) {
return false;
}

JS::RootedObject self(cx, JS_NewObjectWithGivenProto(cx, &class_, proto_obj));

bool is_me = out_buf[0] != 0;
JS_SetReservedSlot(self, static_cast<uint32_t>(Slots::IsMe), JS::BooleanValue(is_me));
JS_SetReservedSlot(self, static_cast<uint32_t>(Slots::PlainTarget),
JS::StringValue(JS_NewStringCopyZ(cx, out_buf.data() + 1)));
auto plain_bytes_end = std::find(begin(out_buf) + 1, end(out_buf), 0);
JS_SetReservedSlot(self, static_cast<uint32_t>(Slots::SSLTarget),
JS::StringValue(JS_NewStringCopyZ(cx, &*plain_bytes_end + 1)));

args.rval().setObject(*self);
return true;
}

bool install(api::Engine *engine) {
RootedObject shielding_ns(engine->cx(), JS_NewObject(engine->cx(), nullptr));
if (!Shield::init_class_impl(engine->cx(), shielding_ns)) {
return false;
}
RootedObject shield_obj(engine->cx(), JS_GetConstructor(engine->cx(), Shield::proto_obj));
RootedValue shield_val(engine->cx(), ObjectValue(*shield_obj));
if (!JS_SetProperty(engine->cx(), shielding_ns, "Shield", shield_val)) {
return false;
}

RootedValue shielding_ns_val(engine->cx(), JS::ObjectValue(*shielding_ns));
if (!engine->define_builtin_module("fastly:shielding", shielding_ns_val)) {
return false;
}

RootedObject fastly(engine->cx());
if (!fastly::get_fastly_object(engine, &fastly)) {
return false;
}
if (!JS_SetProperty(engine->cx(), fastly, "shielding", shielding_ns_val)) {
return false;
}

return true;
}

} // namespace fastly::shielding
33 changes: 33 additions & 0 deletions runtime/fastly/builtins/shielding.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#ifndef FASTLY_SHIELDING_H
#define FASTLY_SHIELDING_H

#include "../host-api/host_api_fastly.h"
#include "builtin.h"
#include "extension-api.h"

namespace fastly::shielding {

class Shield : public builtins::BuiltinImpl<Shield> {
private:
static bool backend_for_shield(JSContext *cx, JS::HandleString target,
JS::MutableHandleValue rval);

public:
static constexpr const char *class_name = "Shield";
static const int ctor_length = 0;
enum Slots { IsMe, PlainTarget, SSLTarget, Count };
static const JSFunctionSpec static_methods[];
static const JSPropertySpec static_properties[];
static const JSFunctionSpec methods[];
static const JSPropertySpec properties[];

static bool runningOn(JSContext *cx, unsigned argc, JS::Value *vp);
static bool unencryptedBackend(JSContext *cx, unsigned argc, JS::Value *vp);
static bool encryptedBackend(JSContext *cx, unsigned argc, JS::Value *vp);

static bool constructor(JSContext *cx, unsigned argc, JS::Value *vp);
};

} // namespace fastly::shielding

#endif
20 changes: 20 additions & 0 deletions runtime/fastly/host-api/fastly.h
Original file line number Diff line number Diff line change
Expand Up @@ -1138,6 +1138,26 @@ int image_optimizer_transform_image_optimizer_request(
fastly_image_optimizer_error_detail *io_error_detail, uint32_t *resp_handle_out,
uint32_t *resp_body_handle_out);

#define FASTLY_SHIELDING_SHIELD_BACKEND_OPTIONS_RESERVED (1 << 0)
#define FASTLY_SHIELDING_SHIELD_BACKEND_OPTIONS_CACHE_KEY (1 << 1)
#define FASTLY_SHIELDING_SHIELD_BACKEND_OPTIONS_FIRST_BYTE_TIMEOUT (1 << 2)

struct fastly_shielding_shield_backend_config {
const char *cache_key;
uint32_t cache_key_len;
uint32_t first_byte_timeout_ms;
};

WASM_IMPORT("fastly_shielding", "shield_info")
int fastly_shielding_shield_info(const char *name, size_t name_len, char *info_block,
size_t info_block_len, uint32_t *nwritten_out);

WASM_IMPORT("fastly_shielding", "backend_for_shield")
int fastly_shielding_backend_for_shield(
const char *name, size_t name_len, uint32_t options_mask,
const fastly_shielding_shield_backend_config *backend_config, char *backend_name,
size_t backend_name_len, uint32_t *nwritten_out);

#ifdef __cplusplus
} // namespace fastly
} // extern C
Expand Down
7 changes: 7 additions & 0 deletions src/bundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,13 @@ export const TransactionCacheEntry = globalThis.TransactionCacheEntry;
} = globalThis.fastly.imageOptimizer;`,
};
}
case 'shielding': {
return {
contents: `export const {
Shield
} = globalThis.fastly.shielding;`,
};
}
}
});
},
Expand Down
1 change: 1 addition & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@
/// <reference path="secret-store.d.ts" />
/// <reference path="html-rewriter.d.ts" />
/// <reference path="image-optimizer.d.ts" />
/// <reference path="shielding.d.ts" />
10 changes: 10 additions & 0 deletions types/shielding.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Backend } from 'fastly:backend';

declare module 'fastly:shielding' {
export class Shield {
constructor(name: string);
runningOn(): boolean;
unencryptedBackend(): Backend;
encryptedBackend(): Backend;
}
}
Loading