Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CA support for bun install #14416

Merged
merged 22 commits into from
Oct 11, 2024
Merged
Show file tree
Hide file tree
Changes from 18 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 docs/runtime/bunfig.md
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,19 @@ myorg = { username = "myusername", password = "$npm_password", url = "https://re
myorg = { token = "$npm_token", url = "https://registry.myorg.com/" }
```

### `install.ca` and `install.cafile`

To configure a CA certificate, use `install.ca` or `install.cafile` to specify a path to a CA certificate file.

```toml
[install]
# The CA certificate as a string
ca = "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----"

# A path to a CA certificate file. The file can contain multiple certificates.
cafile = "path/to/cafile"
```

### `install.cache`

To configure the cache behavior:
Expand Down
4 changes: 2 additions & 2 deletions packages/bun-usockets/src/context.c
Original file line number Diff line number Diff line change
Expand Up @@ -278,11 +278,11 @@ struct us_socket_context_t *us_create_socket_context(int ssl, struct us_loop_t *
return context;
}

struct us_socket_context_t *us_create_bun_socket_context(int ssl, struct us_loop_t *loop, int context_ext_size, struct us_bun_socket_context_options_t options) {
struct us_socket_context_t *us_create_bun_socket_context(int ssl, struct us_loop_t *loop, int context_ext_size, struct us_bun_socket_context_options_t options, enum create_bun_socket_error_t *err) {
#ifndef LIBUS_NO_SSL
if (ssl) {
/* This function will call us, again, with SSL = false and a bigger ext_size */
return (struct us_socket_context_t *) us_internal_bun_create_ssl_socket_context(loop, context_ext_size, options);
return (struct us_socket_context_t *) us_internal_bun_create_ssl_socket_context(loop, context_ext_size, options, err);
}
#endif

Expand Down
19 changes: 13 additions & 6 deletions packages/bun-usockets/src/crypto/openssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -1104,7 +1104,8 @@ int us_verify_callback(int preverify_ok, X509_STORE_CTX *ctx) {
}

SSL_CTX *create_ssl_context_from_bun_options(
struct us_bun_socket_context_options_t options) {
struct us_bun_socket_context_options_t options,
enum create_bun_socket_error_t *err) {
/* Create the context */
SSL_CTX *ssl_context = SSL_CTX_new(TLS_method());

Expand Down Expand Up @@ -1174,13 +1175,15 @@ SSL_CTX *create_ssl_context_from_bun_options(
STACK_OF(X509_NAME) * ca_list;
ca_list = SSL_load_client_CA_file(options.ca_file_name);
if (ca_list == NULL) {
*err = CREATE_BUN_SOCKET_ERROR_LOAD_CA_FILE;
free_ssl_context(ssl_context);
return NULL;
}

SSL_CTX_set_client_CA_list(ssl_context, ca_list);
if (SSL_CTX_load_verify_locations(ssl_context, options.ca_file_name,
NULL) != 1) {
*err = CREATE_BUN_SOCKET_ERROR_INVALID_CA_FILE;
free_ssl_context(ssl_context);
return NULL;
}
Expand All @@ -1203,6 +1206,7 @@ SSL_CTX *create_ssl_context_from_bun_options(
}

if (!add_ca_cert_to_ctx_store(ssl_context, options.ca[i], cert_store)) {
*err = CREATE_BUN_SOCKET_ERROR_INVALID_CA;
free_ssl_context(ssl_context);
return NULL;
}
Expand Down Expand Up @@ -1338,7 +1342,8 @@ void us_bun_internal_ssl_socket_context_add_server_name(
struct us_bun_socket_context_options_t options, void *user) {

/* Try and construct an SSL_CTX from options */
SSL_CTX *ssl_context = create_ssl_context_from_bun_options(options);
enum create_bun_socket_error_t err = CREATE_BUN_SOCKET_ERROR_NONE;
SSL_CTX *ssl_context = create_ssl_context_from_bun_options(options, &err);

/* Attach the user data to this context */
if (1 != SSL_CTX_set_ex_data(ssl_context, 0, user)) {
Expand Down Expand Up @@ -1468,14 +1473,15 @@ struct us_internal_ssl_socket_context_t *us_internal_create_ssl_socket_context(
struct us_internal_ssl_socket_context_t *
us_internal_bun_create_ssl_socket_context(
struct us_loop_t *loop, int context_ext_size,
struct us_bun_socket_context_options_t options) {
struct us_bun_socket_context_options_t options,
enum create_bun_socket_error_t *err) {
/* If we haven't initialized the loop data yet, do so .
* This is needed because loop data holds shared OpenSSL data and
* the function is also responsible for initializing OpenSSL */
us_internal_init_loop_ssl_data(loop);

/* First of all we try and create the SSL context from options */
SSL_CTX *ssl_context = create_ssl_context_from_bun_options(options);
SSL_CTX *ssl_context = create_ssl_context_from_bun_options(options, err);
if (!ssl_context) {
/* We simply fail early if we cannot even create the OpenSSL context */
return NULL;
Expand All @@ -1487,7 +1493,7 @@ us_internal_bun_create_ssl_socket_context(
(struct us_internal_ssl_socket_context_t *)us_create_bun_socket_context(
0, loop,
sizeof(struct us_internal_ssl_socket_context_t) + context_ext_size,
options);
options, err);

/* I guess this is the only optional callback */
context->on_server_name = NULL;
Expand Down Expand Up @@ -1983,9 +1989,10 @@ struct us_internal_ssl_socket_t *us_internal_ssl_socket_wrap_with_tls(
struct us_socket_context_t *old_context = us_socket_context(0, s);
us_socket_context_ref(0,old_context);

enum create_bun_socket_error_t err = CREATE_BUN_SOCKET_ERROR_NONE;
struct us_socket_context_t *context = us_create_bun_socket_context(
1, old_context->loop, sizeof(struct us_wrapped_socket_context_t),
options);
options, &err);

// Handle SSL context creation failure
if (UNLIKELY(!context)) {
Expand Down
3 changes: 2 additions & 1 deletion packages/bun-usockets/src/internal/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,8 @@ struct us_internal_ssl_socket_context_t *us_internal_create_ssl_socket_context(
struct us_internal_ssl_socket_context_t *
us_internal_bun_create_ssl_socket_context(
struct us_loop_t *loop, int context_ext_size,
struct us_bun_socket_context_options_t options);
struct us_bun_socket_context_options_t options,
enum create_bun_socket_error_t *err);

void us_internal_ssl_socket_context_free(
us_internal_ssl_socket_context_r context);
Expand Down
10 changes: 9 additions & 1 deletion packages/bun-usockets/src/libusockets.h
Original file line number Diff line number Diff line change
Expand Up @@ -246,8 +246,16 @@ void *us_socket_context_get_native_handle(int ssl, us_socket_context_r context);
/* A socket context holds shared callbacks and user data extension for associated sockets */
struct us_socket_context_t *us_create_socket_context(int ssl, us_loop_r loop,
int ext_size, struct us_socket_context_options_t options) nonnull_fn_decl;

enum create_bun_socket_error_t {
CREATE_BUN_SOCKET_ERROR_NONE = 0,
CREATE_BUN_SOCKET_ERROR_LOAD_CA_FILE,
CREATE_BUN_SOCKET_ERROR_INVALID_CA_FILE,
CREATE_BUN_SOCKET_ERROR_INVALID_CA,
};

struct us_socket_context_t *us_create_bun_socket_context(int ssl, struct us_loop_t *loop,
int ext_size, struct us_bun_socket_context_options_t options);
int ext_size, struct us_bun_socket_context_options_t options, enum create_bun_socket_error_t *err);

/* Delete resources allocated at creation time (will call unref now and only free when ref count == 0). */
void us_socket_context_free(int ssl, us_socket_context_r context) nonnull_fn_decl;
Expand Down
3 changes: 2 additions & 1 deletion packages/bun-uws/src/HttpContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,8 @@ struct HttpContext {
static HttpContext *create(Loop *loop, us_bun_socket_context_options_t options = {}) {
HttpContext *httpContext;

httpContext = (HttpContext *) us_create_bun_socket_context(SSL, (us_loop_t *) loop, sizeof(HttpContextData<SSL>), options);
enum create_bun_socket_error_t err = CREATE_BUN_SOCKET_ERROR_NONE;
httpContext = (HttpContext *) us_create_bun_socket_context(SSL, (us_loop_t *) loop, sizeof(HttpContextData<SSL>), options, &err);

if (!httpContext) {
return nullptr;
Expand Down
4 changes: 2 additions & 2 deletions src/Progress.zig
Original file line number Diff line number Diff line change
Expand Up @@ -380,11 +380,11 @@ pub fn lock_stderr(p: *Progress) void {
p.terminal = null;
};
}
std.debug.getStderrMutex().lock();
std.debug.lockStdErr();
}

pub fn unlock_stderr(p: *Progress) void {
std.debug.getStderrMutex().unlock();
std.debug.unlockStdErr();
p.update_mutex.unlock();
}

Expand Down
7 changes: 7 additions & 0 deletions src/api/schema.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2974,6 +2974,13 @@ pub const Api = struct {
/// concurrent_scripts
concurrent_scripts: ?u32 = null,

cafile: ?[]const u8 = null,

ca: ?union(enum) {
str: []const u8,
list: []const []const u8,
} = null,

pub fn decode(reader: anytype) anyerror!BunInstall {
var this = std.mem.zeroes(BunInstall);

Expand Down
15 changes: 12 additions & 3 deletions src/bun.js/api/bun/socket.zig
Original file line number Diff line number Diff line change
Expand Up @@ -642,15 +642,20 @@ pub const Listener = struct {
}
}
}
const ctx_opts: uws.us_bun_socket_context_options_t = JSC.API.ServerConfig.SSLConfig.asUSockets(ssl);
const ctx_opts: uws.us_bun_socket_context_options_t = if (ssl != null)
JSC.API.ServerConfig.SSLConfig.asUSockets(ssl.?)
else
.{};

vm.eventLoop().ensureWaker();

var create_err: uws.create_bun_socket_error_t = .none;
const socket_context = uws.us_create_bun_socket_context(
@intFromBool(ssl_enabled),
uws.Loop.get(),
@sizeOf(usize),
ctx_opts,
&create_err,
) orelse {
var err = globalObject.createErrorInstance("Failed to listen on {s}:{d}", .{ hostname_or_unix.slice(), port orelse 0 });
defer {
Expand Down Expand Up @@ -1172,9 +1177,13 @@ pub const Listener = struct {
}
}

const ctx_opts: uws.us_bun_socket_context_options_t = JSC.API.ServerConfig.SSLConfig.asUSockets(socket_config.ssl);
const ctx_opts: uws.us_bun_socket_context_options_t = if (ssl != null)
JSC.API.ServerConfig.SSLConfig.asUSockets(ssl.?)
else
.{};

const socket_context = uws.us_create_bun_socket_context(@intFromBool(ssl_enabled), uws.Loop.get(), @sizeOf(usize), ctx_opts) orelse {
var create_err: uws.create_bun_socket_error_t = .none;
const socket_context = uws.us_create_bun_socket_context(@intFromBool(ssl_enabled), uws.Loop.get(), @sizeOf(usize), ctx_opts, &create_err) orelse {
const err = JSC.SystemError{
.message = bun.String.static("Failed to connect"),
.syscall = bun.String.static("connect"),
Expand Down
58 changes: 28 additions & 30 deletions src/bun.js/api/server.zig
Original file line number Diff line number Diff line change
Expand Up @@ -583,41 +583,39 @@ pub const ServerConfig = struct {

const log = Output.scoped(.SSLConfig, false);

pub fn asUSockets(this_: ?SSLConfig) uws.us_bun_socket_context_options_t {
pub fn asUSockets(this: SSLConfig) uws.us_bun_socket_context_options_t {
var ctx_opts: uws.us_bun_socket_context_options_t = .{};

if (this_) |ssl_config| {
if (ssl_config.key_file_name != null)
ctx_opts.key_file_name = ssl_config.key_file_name;
if (ssl_config.cert_file_name != null)
ctx_opts.cert_file_name = ssl_config.cert_file_name;
if (ssl_config.ca_file_name != null)
ctx_opts.ca_file_name = ssl_config.ca_file_name;
if (ssl_config.dh_params_file_name != null)
ctx_opts.dh_params_file_name = ssl_config.dh_params_file_name;
if (ssl_config.passphrase != null)
ctx_opts.passphrase = ssl_config.passphrase;
ctx_opts.ssl_prefer_low_memory_usage = @intFromBool(ssl_config.low_memory_mode);
if (this.key_file_name != null)
ctx_opts.key_file_name = this.key_file_name;
if (this.cert_file_name != null)
ctx_opts.cert_file_name = this.cert_file_name;
if (this.ca_file_name != null)
ctx_opts.ca_file_name = this.ca_file_name;
if (this.dh_params_file_name != null)
ctx_opts.dh_params_file_name = this.dh_params_file_name;
if (this.passphrase != null)
ctx_opts.passphrase = this.passphrase;
ctx_opts.ssl_prefer_low_memory_usage = @intFromBool(this.low_memory_mode);

if (ssl_config.key) |key| {
ctx_opts.key = key.ptr;
ctx_opts.key_count = ssl_config.key_count;
}
if (ssl_config.cert) |cert| {
ctx_opts.cert = cert.ptr;
ctx_opts.cert_count = ssl_config.cert_count;
}
if (ssl_config.ca) |ca| {
ctx_opts.ca = ca.ptr;
ctx_opts.ca_count = ssl_config.ca_count;
}
if (this.key) |key| {
ctx_opts.key = key.ptr;
ctx_opts.key_count = this.key_count;
}
if (this.cert) |cert| {
ctx_opts.cert = cert.ptr;
ctx_opts.cert_count = this.cert_count;
}
if (this.ca) |ca| {
ctx_opts.ca = ca.ptr;
ctx_opts.ca_count = this.ca_count;
}

if (ssl_config.ssl_ciphers != null) {
ctx_opts.ssl_ciphers = ssl_config.ssl_ciphers;
}
ctx_opts.request_cert = ssl_config.request_cert;
ctx_opts.reject_unauthorized = ssl_config.reject_unauthorized;
if (this.ssl_ciphers != null) {
ctx_opts.ssl_ciphers = this.ssl_ciphers;
}
ctx_opts.request_cert = this.request_cert;
ctx_opts.reject_unauthorized = this.reject_unauthorized;

return ctx_opts;
}
Expand Down
3 changes: 2 additions & 1 deletion src/bun.js/bindings/ScriptExecutionContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ us_socket_context_t* ScriptExecutionContext::webSocketContextSSL()
opts.request_cert = true;
// but do not reject unauthorized
opts.reject_unauthorized = false;
this->m_ssl_client_websockets_ctx = us_create_bun_socket_context(1, loop, sizeof(size_t), opts);
enum create_bun_socket_error_t err = CREATE_BUN_SOCKET_ERROR_NONE;
this->m_ssl_client_websockets_ctx = us_create_bun_socket_context(1, loop, sizeof(size_t), opts, &err);
void** ptr = reinterpret_cast<void**>(us_socket_context_ext(1, m_ssl_client_websockets_ctx));
*ptr = this;
registerHTTPContextForWebSocket<true, false>(this, m_ssl_client_websockets_ctx, loop);
Expand Down
2 changes: 1 addition & 1 deletion src/bun.js/webcore/response.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1797,7 +1797,7 @@ pub const Fetch = struct {
fetch_options: FetchOptions,
promise: JSC.JSPromise.Strong,
) !*FetchTasklet {
http.HTTPThread.init();
http.HTTPThread.init(&.{});
var node = try get(
allocator,
global,
Expand Down
42 changes: 41 additions & 1 deletion src/bun.zig
Original file line number Diff line number Diff line change
Expand Up @@ -718,7 +718,7 @@ pub const Analytics = @import("./analytics/analytics_thread.zig");

pub usingnamespace @import("./tagged_pointer.zig");

pub fn once(comptime function: anytype, comptime ReturnType: type) ReturnType {
pub fn onceUnsafe(comptime function: anytype, comptime ReturnType: type) ReturnType {
const Result = struct {
var value: ReturnType = undefined;
var ran = false;
Expand Down Expand Up @@ -3938,3 +3938,43 @@ pub fn indexOfPointerInSlice(comptime T: type, slice: []const T, item: *const T)
const index = @divExact(offset, @sizeOf(T));
return index;
}

/// Copied from zig std. Modified to accept arguments.
pub fn once(comptime f: anytype) Once(f) {
return Once(f){};
}

/// Copied from zig std. Modified to accept arguments.
///
/// An object that executes the function `f` just once.
/// It is undefined behavior if `f` re-enters the same Once instance.
pub fn Once(comptime f: anytype) type {
return struct {
done: bool = false,
mutex: std.Thread.Mutex = std.Thread.Mutex{},

/// Call the function `f`.
/// If `call` is invoked multiple times `f` will be executed only the
/// first time.
/// The invocations are thread-safe.
pub fn call(self: *@This(), args: std.meta.ArgsTuple(@TypeOf(f))) void {
if (@atomicLoad(bool, &self.done, .acquire))
return;

return self.callSlow(args);
}

fn callSlow(self: *@This(), args: std.meta.ArgsTuple(@TypeOf(f))) void {
@setCold(true);

self.mutex.lock();
defer self.mutex.unlock();

// The first thread to acquire the mutex gets to run the initializer
if (!self.done) {
@call(.auto, f, args);
@atomicStore(bool, &self.done, true, .release);
}
}
};
}
2 changes: 1 addition & 1 deletion src/bun_js.zig
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ pub const Run = struct {

fn doPreconnect(preconnect: []const string) void {
if (preconnect.len == 0) return;
bun.HTTPThread.init();
bun.HTTPThread.init(&.{});

for (preconnect) |url_str| {
const url = bun.URL.parse(url_str);
Expand Down
Loading