Skip to content

Commit

Permalink
Support NODE_TLS_REJECT_UNAUTHORIZED=0 at runtime and implement `BU…
Browse files Browse the repository at this point in the history
…N_CONFIG_VERBOSE_FETCH` (#11833)
  • Loading branch information
Jarred-Sumner authored Jun 13, 2024
1 parent 6c55ff6 commit c44d489
Show file tree
Hide file tree
Showing 14 changed files with 362 additions and 51 deletions.
10 changes: 10 additions & 0 deletions docs/runtime/env.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,16 @@ These environment variables are read by Bun and configure aspects of its behavio

---

- `NODE_TLS_REJECT_UNAUTHORIZED`
- `NODE_TLS_REJECT_UNAUTHORIZED=0` disables SSL certificate validation. This is useful for testing and debugging, but you should be very hesitant to use this in production. Note: This environment variable was originally introduced by Node.js and we kept the name for compatibility.

---

- `BUN_CONFIG_VERBOSE_FETCH`
- If `BUN_CONFIG_VERBOSE_FETCH=1`, then fetch requests will log the url, method, request headers and response headers to the console. This is useful for debugging network requests. This also works with `node:http`.

---

- `BUN_RUNTIME_TRANSPILER_CACHE_PATH`
- The runtime transpiler caches the transpiled output of source files larger than 50 kb. This makes CLIs using Bun load faster. If `BUN_RUNTIME_TRANSPILER_CACHE_PATH` is set, then the runtime transpiler will cache transpiled output to the specified directory. If `BUN_RUNTIME_TRANSPILER_CACHE_PATH` is set to an empty string or the string `"0"`, then the runtime transpiler will not cache transpiled output. If `BUN_RUNTIME_TRANSPILER_CACHE_PATH` is unset, then the runtime transpiler will cache transpiled output to the platform-specific cache directory.

Expand Down
18 changes: 11 additions & 7 deletions src/bun.js/api/bun/socket.zig
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,7 @@ pub const SocketConfig = struct {
exclusive: bool = false,

pub fn fromJS(
vm: *JSC.VirtualMachine,
opts: JSC.JSValue,
globalObject: *JSC.JSGlobalObject,
exception: JSC.C.ExceptionRef,
Expand All @@ -381,7 +382,7 @@ pub const SocketConfig = struct {
ssl = JSC.API.ServerConfig.SSLConfig.zero;
}
} else {
if (JSC.API.ServerConfig.SSLConfig.inJS(globalObject, tls, exception)) |ssl_config| {
if (JSC.API.ServerConfig.SSLConfig.inJS(vm, globalObject, tls, exception)) |ssl_config| {
ssl = ssl_config;
} else if (exception.* != null) {
return null;
Expand Down Expand Up @@ -616,7 +617,9 @@ pub const Listener = struct {
return .zero;
}

var socket_config = SocketConfig.fromJS(opts, globalObject, exception) orelse {
const vm = JSC.VirtualMachine.get();

var socket_config = SocketConfig.fromJS(vm, opts, globalObject, exception) orelse {
return .zero;
};

Expand All @@ -635,7 +638,7 @@ pub const Listener = struct {
const ctx_opts: uws.us_bun_socket_context_options_t = JSC.API.ServerConfig.SSLConfig.asUSockets(ssl);

defer if (ssl != null) ssl.?.deinit();
globalObject.bunVM().eventLoop().ensureWaker();
vm.eventLoop().ensureWaker();

const socket_context = uws.us_create_bun_socket_context(
@intFromBool(ssl_enabled),
Expand Down Expand Up @@ -858,7 +861,7 @@ pub const Listener = struct {
var exception_ref = [_]JSC.C.JSValueRef{null};
const exception: JSC.C.ExceptionRef = &exception_ref;

if (JSC.API.ServerConfig.SSLConfig.inJS(global, tls, exception)) |ssl_config| {
if (JSC.API.ServerConfig.SSLConfig.inJS(JSC.VirtualMachine.get(), global, tls, exception)) |ssl_config| {
// to keep nodejs compatibility, we allow to replace the server name
uws.us_socket_context_remove_server_name(1, this.socket_context, server_name);
uws.us_bun_socket_context_add_server_name(1, this.socket_context, server_name, ssl_config.asUSockets(), null);
Expand Down Expand Up @@ -994,8 +997,9 @@ pub const Listener = struct {
exception.* = JSC.toInvalidArguments("Expected options object", .{}, globalObject).asObjectRef();
return .zero;
}
const vm = globalObject.bunVM();

const socket_config = SocketConfig.fromJS(opts, globalObject, exception) orelse {
const socket_config = SocketConfig.fromJS(vm, opts, globalObject, exception) orelse {
return .zero;
};

Expand All @@ -1012,7 +1016,7 @@ pub const Listener = struct {

const ctx_opts: uws.us_bun_socket_context_options_t = JSC.API.ServerConfig.SSLConfig.asUSockets(socket_config.ssl);

globalObject.bunVM().eventLoop().ensureWaker();
vm.eventLoop().ensureWaker();

const socket_context = uws.us_create_bun_socket_context(@intFromBool(ssl_enabled), uws.Loop.get(), @sizeOf(usize), ctx_opts) orelse {
const err = JSC.SystemError{
Expand Down Expand Up @@ -2994,7 +2998,7 @@ fn NewSocket(comptime ssl: bool) type {
ssl_opts = JSC.API.ServerConfig.SSLConfig.zero;
}
} else {
if (JSC.API.ServerConfig.SSLConfig.inJS(globalObject, tls, &exception)) |ssl_config| {
if (JSC.API.ServerConfig.SSLConfig.inJS(JSC.VirtualMachine.get(), globalObject, tls, &exception)) |ssl_config| {
ssl_opts = ssl_config;
} else if (exception != null) {
return .zero;
Expand Down
12 changes: 8 additions & 4 deletions src/bun.js/api/server.zig
Original file line number Diff line number Diff line change
Expand Up @@ -408,8 +408,9 @@ pub const ServerConfig = struct {

pub const zero = SSLConfig{};

pub fn inJS(global: *JSC.JSGlobalObject, obj: JSC.JSValue, exception: JSC.C.ExceptionRef) ?SSLConfig {
pub fn inJS(vm: *JSC.VirtualMachine, global: *JSC.JSGlobalObject, obj: JSC.JSValue, exception: JSC.C.ExceptionRef) ?SSLConfig {
var result = zero;

var arena: bun.ArenaAllocator = bun.ArenaAllocator.init(bun.default_allocator);
defer arena.deinit();

Expand All @@ -420,6 +421,8 @@ pub const ServerConfig = struct {

var any = false;

result.reject_unauthorized = @intFromBool(vm.getTLSRejectUnauthorized());

// Required
if (obj.getTruthy(global, "keyFile")) |key_file_name| {
var sliced = key_file_name.toSlice(global, bun.default_allocator);
Expand Down Expand Up @@ -834,6 +837,7 @@ pub const ServerConfig = struct {

pub fn fromJS(global: *JSC.JSGlobalObject, arguments: *JSC.Node.ArgumentsSlice, exception: JSC.C.ExceptionRef) ServerConfig {
var env = arguments.vm.bundler.env;
const vm = JSC.VirtualMachine.get();

var args = ServerConfig{
.address = .{
Expand Down Expand Up @@ -1034,7 +1038,7 @@ pub const ServerConfig = struct {
return args;
}
while (value_iter.next()) |item| {
if (SSLConfig.inJS(global, item, exception)) |ssl_config| {
if (SSLConfig.inJS(vm, global, item, exception)) |ssl_config| {
if (args.ssl_config == null) {
args.ssl_config = ssl_config;
} else {
Expand All @@ -1061,7 +1065,7 @@ pub const ServerConfig = struct {
}
}
} else {
if (SSLConfig.inJS(global, tls, exception)) |ssl_config| {
if (SSLConfig.inJS(vm, global, tls, exception)) |ssl_config| {
args.ssl_config = ssl_config;
}

Expand All @@ -1078,7 +1082,7 @@ pub const ServerConfig = struct {
// @compatibility Bun v0.x - v0.2.1
// this used to be top-level, now it's "tls" object
if (args.ssl_config == null) {
if (SSLConfig.inJS(global, arg, exception)) |ssl_config| {
if (SSLConfig.inJS(vm, global, arg, exception)) |ssl_config| {
args.ssl_config = ssl_config;
}

Expand Down
160 changes: 160 additions & 0 deletions src/bun.js/bindings/JSEnvironmentVariableMap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
#include <JavaScriptCore/JSStringInlines.h>

#include "BunClientData.h"
#include "wtf/Compiler.h"
#include "wtf/Forward.h"

using namespace JSC;

Expand Down Expand Up @@ -121,6 +123,135 @@ JSC_DEFINE_CUSTOM_SETTER(jsTimeZoneEnvironmentVariableSetter, (JSGlobalObject *
return true;
}

extern "C" int Bun__getTLSRejectUnauthorizedValue();
extern "C" int Bun__setTLSRejectUnauthorizedValue(int value);
extern "C" int Bun__getVerboseFetchValue();
extern "C" int Bun__setVerboseFetchValue(int value);

ALWAYS_INLINE static Identifier NODE_TLS_REJECT_UNAUTHORIZED_PRIVATE_PROPERTY(VM& vm)
{
auto* clientData = WebCore::clientData(vm);
auto& builtinNames = clientData->builtinNames();
// We just pick one to reuse. This will never be exposed to a user. And we
// don't want to pay the cost of adding another one.
return builtinNames.textDecoderStreamDecoderPrivateName();
}

ALWAYS_INLINE static Identifier BUN_CONFIG_VERBOSE_FETCH_PRIVATE_PROPERTY(VM& vm)
{
auto* clientData = WebCore::clientData(vm);
auto& builtinNames = clientData->builtinNames();
// We just pick one to reuse. This will never be exposed to a user. And we
// don't want to pay the cost of adding another one.
return builtinNames.textEncoderStreamEncoderPrivateName();
}

JSC_DEFINE_CUSTOM_GETTER(jsNodeTLSRejectUnauthorizedGetter, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, PropertyName propertyName))
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);

auto* thisObject = jsDynamicCast<JSObject*>(JSValue::decode(thisValue));
if (UNLIKELY(!thisObject))
return JSValue::encode(jsUndefined());

const auto& privateName = NODE_TLS_REJECT_UNAUTHORIZED_PRIVATE_PROPERTY(vm);
JSValue result = thisObject->getDirect(vm, privateName);
if (UNLIKELY(result)) {
return JSValue::encode(result);
}

ZigString name = toZigString(propertyName.publicName());
ZigString value = { nullptr, 0 };

if (!Bun__getEnvValue(globalObject, &name, &value) || value.len == 0) {
return JSValue::encode(jsUndefined());
}

return JSValue::encode(jsString(vm, Zig::toStringCopy(value)));
}

JSC_DEFINE_CUSTOM_SETTER(jsNodeTLSRejectUnauthorizedSetter, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue value, PropertyName propertyName))
{
VM& vm = globalObject->vm();
JSC::JSObject* object = JSValue::decode(thisValue).getObject();
auto scope = DECLARE_THROW_SCOPE(vm);
if (!object)
return false;

JSValue decodedValue = JSValue::decode(value);
WTF::String str = decodedValue.toWTFString(globalObject);
RETURN_IF_EXCEPTION(scope, false);

// TODO: only check "0". Node doesn't check both. But we already did. So we
// should wait to do that until Bun v1.2.0.
if (str == "0"_s || str == "false"_s) {
Bun__setTLSRejectUnauthorizedValue(0);
} else {
Bun__setTLSRejectUnauthorizedValue(1);
}

const auto& privateName = NODE_TLS_REJECT_UNAUTHORIZED_PRIVATE_PROPERTY(vm);
object->putDirect(vm, privateName, JSValue::decode(value), 0);

// TODO: this is an assertion failure
// Recreate this because the property visibility needs to be set correctly
// object->putDirectWithoutTransition(vm, propertyName, JSC::CustomGetterSetter::create(vm, jsTimeZoneEnvironmentVariableGetter, jsTimeZoneEnvironmentVariableSetter), JSC::PropertyAttribute::CustomAccessor | 0);
return true;
}

JSC_DEFINE_CUSTOM_GETTER(jsBunConfigVerboseFetchGetter, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, PropertyName propertyName))
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);

auto* thisObject = jsDynamicCast<JSObject*>(JSValue::decode(thisValue));
if (UNLIKELY(!thisObject))
return JSValue::encode(jsUndefined());

const auto& privateName = BUN_CONFIG_VERBOSE_FETCH_PRIVATE_PROPERTY(vm);
JSValue result = thisObject->getDirect(vm, privateName);
if (UNLIKELY(result)) {
return JSValue::encode(result);
}

ZigString name = toZigString(propertyName.publicName());
ZigString value = { nullptr, 0 };

if (!Bun__getEnvValue(globalObject, &name, &value) || value.len == 0) {
return JSValue::encode(jsUndefined());
}

return JSValue::encode(jsString(vm, Zig::toStringCopy(value)));
}

JSC_DEFINE_CUSTOM_SETTER(jsBunConfigVerboseFetchSetter, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue value, PropertyName propertyName))
{
VM& vm = globalObject->vm();
JSC::JSObject* object = JSValue::decode(thisValue).getObject();
auto scope = DECLARE_THROW_SCOPE(vm);
if (!object)
return false;

JSValue decodedValue = JSValue::decode(value);
WTF::String str = decodedValue.toWTFString(globalObject);
RETURN_IF_EXCEPTION(scope, false);

if (str == "1"_s || str == "true"_s) {
Bun__setVerboseFetchValue(1);
} else {
Bun__setVerboseFetchValue(0);
}

const auto& privateName = BUN_CONFIG_VERBOSE_FETCH_PRIVATE_PROPERTY(vm);
object->putDirect(vm, privateName, JSValue::decode(value), 0);

// TODO: this is an assertion failure
// Recreate this because the property visibility needs to be set correctly
// object->putDirectWithoutTransition(vm, propertyName, JSC::CustomGetterSetter::create(vm, jsTimeZoneEnvironmentVariableGetter, jsTimeZoneEnvironmentVariableSetter), JSC::PropertyAttribute::CustomAccessor | 0);
return true;
}

JSValue createEnvironmentVariablesMap(Zig::GlobalObject* globalObject)
{
VM& vm = globalObject->vm();
Expand All @@ -140,7 +271,12 @@ JSValue createEnvironmentVariablesMap(Zig::GlobalObject* globalObject)
#endif

static NeverDestroyed<String> TZ = MAKE_STATIC_STRING_IMPL("TZ");
String NODE_TLS_REJECT_UNAUTHORIZED = String("NODE_TLS_REJECT_UNAUTHORIZED"_s);
String BUN_CONFIG_VERBOSE_FETCH = String("BUN_CONFIG_VERBOSE_FETCH"_s);
bool hasTZ = false;
bool hasNodeTLSRejectUnauthorized = false;
bool hasBunConfigVerboseFetch = false;

for (size_t i = 0; i < count; i++) {
unsigned char* chars;
size_t len = Bun__getEnvKey(list, i, &chars);
Expand All @@ -152,6 +288,14 @@ JSValue createEnvironmentVariablesMap(Zig::GlobalObject* globalObject)
hasTZ = true;
continue;
}
if (name == NODE_TLS_REJECT_UNAUTHORIZED) {
hasNodeTLSRejectUnauthorized = true;
continue;
}
if (name == BUN_CONFIG_VERBOSE_FETCH) {
hasBunConfigVerboseFetch = true;
continue;
}
ASSERT(len > 0);
#if OS(WINDOWS)
String idName = name.convertToASCIIUppercase();
Expand Down Expand Up @@ -185,6 +329,22 @@ JSValue createEnvironmentVariablesMap(Zig::GlobalObject* globalObject)
vm,
Identifier::fromString(vm, TZ), JSC::CustomGetterSetter::create(vm, jsTimeZoneEnvironmentVariableGetter, jsTimeZoneEnvironmentVariableSetter), TZAttrs);

unsigned int NODE_TLS_REJECT_UNAUTHORIZED_Attrs = JSC::PropertyAttribute::CustomAccessor | 0;
if (!hasNodeTLSRejectUnauthorized) {
NODE_TLS_REJECT_UNAUTHORIZED_Attrs |= JSC::PropertyAttribute::DontEnum;
}
object->putDirectCustomAccessor(
vm,
Identifier::fromString(vm, NODE_TLS_REJECT_UNAUTHORIZED), JSC::CustomGetterSetter::create(vm, jsNodeTLSRejectUnauthorizedGetter, jsNodeTLSRejectUnauthorizedSetter), NODE_TLS_REJECT_UNAUTHORIZED_Attrs);

unsigned int BUN_CONFIG_VERBOSE_FETCH_Attrs = JSC::PropertyAttribute::CustomAccessor | 0;
if (!hasBunConfigVerboseFetch) {
BUN_CONFIG_VERBOSE_FETCH_Attrs |= JSC::PropertyAttribute::DontEnum;
}
object->putDirectCustomAccessor(
vm,
Identifier::fromString(vm, BUN_CONFIG_VERBOSE_FETCH), JSC::CustomGetterSetter::create(vm, jsBunConfigVerboseFetchGetter, jsBunConfigVerboseFetchSetter), BUN_CONFIG_VERBOSE_FETCH_Attrs);

#if OS(WINDOWS)
JSC::JSFunction* getSourceEvent = JSC::JSFunction::create(vm, processObjectInternalsWindowsEnvCodeGenerator(vm), globalObject);
RETURN_IF_EXCEPTION(scope, {});
Expand Down
4 changes: 2 additions & 2 deletions src/bun.js/bindings/webcore/WebSocket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@

namespace WebCore {
WTF_MAKE_ISO_ALLOCATED_IMPL(WebSocket);
extern "C" int Bun__getTLSRejectUnauthorizedValue();

extern "C" bool Bun__defaultRejectUnauthorized(JSGlobalObject* lexicalGlobalObject);
static size_t getFramingOverhead(size_t payloadSize)
{
static const size_t hybiBaseFramingOverhead = 2; // Every frame has at least two-byte header.
Expand Down Expand Up @@ -164,7 +164,7 @@ WebSocket::WebSocket(ScriptExecutionContext& context)
{
m_state = CONNECTING;
m_hasPendingActivity.store(true);
m_rejectUnauthorized = Bun__defaultRejectUnauthorized(context.jsGlobalObject());
m_rejectUnauthorized = Bun__getTLSRejectUnauthorizedValue() != 0;
}

WebSocket::~WebSocket()
Expand Down
Loading

0 comments on commit c44d489

Please sign in to comment.