zig-vault/src/root.zig
2024-02-29 21:39:33 -06:00

232 lines
7.4 KiB
Zig

const std = @import("std");
/// KeyValue secret engine, version 2
pub const KV2 = struct {
base_url: std.Uri,
namespace: ?[]const u8 = null,
mount_path: []const u8,
token: []const u8,
fn do(
self: @This(),
alloc: std.mem.Allocator,
comptime T: type,
method: std.http.Method,
path: []const u8,
payload: ?[]const u8,
) !std.json.Parsed(T) {
var client = std.http.Client{ .allocator = alloc };
defer client.deinit();
var authoriziation_buf: [128]u8 = undefined;
const authoriziation = try std.fmt.bufPrint(&authoriziation_buf, "Bearer {s}", .{self.token});
var namespace_buf: [128]u8 = undefined;
const extra_headers = h: {
if (self.namespace) |namespace| {
break :h &[_]std.http.Header{
.{
.name = "X-Vault-Namespace",
.value = try std.fmt.bufPrint(&namespace_buf, "{s}/", .{namespace}),
},
.{
.name = "Accept",
.value = "application/json",
},
};
} else {
break :h &[_]std.http.Header{
.{
.name = "Accept",
.value = "application/json",
},
};
}
};
var aux_buf: [1024]u8 = undefined;
const url = try self.base_url.resolve_inplace(path, &aux_buf);
var server_header_buf: [2048]u8 = undefined;
var req = try client.open(
method,
url,
.{
.server_header_buffer = &server_header_buf,
.headers = .{
.authorization = .{
.override = authoriziation,
},
.content_type = .{
.override = "application/json",
},
},
.extra_headers = extra_headers,
},
);
defer req.deinit();
if (payload) |p| req.transfer_encoding = .{ .content_length = p.len };
try req.send(.{});
if (payload) |p| try req.writeAll(p);
try req.finish();
try req.wait();
var result_buf = std.ArrayList(u8).init(alloc);
errdefer result_buf.deinit();
const reader = req.reader();
while (true) {
var buf: [4096]u8 = undefined;
const len = try reader.read(&buf);
if (len > 0) try result_buf.appendSlice(buf[0..len]);
if (len == 0) break;
}
return try std.json.parseFromSlice(
T,
alloc,
try result_buf.toOwnedSlice(),
.{},
);
}
pub fn ReadResult(comptime T: type) type {
return struct {
request_id: []const u8,
lease_id: []const u8,
renewable: bool,
lease_duration: u64,
data: struct {
data: T,
metadata: struct {
created_time: []const u8,
custom_metadata: ?std.json.Value = null,
deletion_time: []const u8,
destroyed: bool,
version: u64,
},
},
wrap_info: ?std.json.Value = null,
warnings: ?std.json.Value = null,
auth: ?std.json.Value = null,
};
}
pub fn read(self: @This(), alloc: std.mem.Allocator, comptime T: type, secret_path: []const u8, version: ?u64) !std.json.Parsed(ReadResult(T)) {
var query_buf: [1024]u8 = undefined;
const query = q: {
if (version) |v| break :q try std.fmt.bufPrint(&query_buf, "?version={d}", .{v});
break :q "";
};
var path_buf: [1024]u8 = undefined;
const path = try std.fmt.bufPrint(&path_buf, "/v1/{s}/data/{s}{s}", .{ self.mount_path, secret_path, query });
return try self.do(alloc, ReadResult(T), .GET, path, null);
}
pub fn ReadSubkeyResult(comptime T: type) type {
return struct {
subkeys: T,
metadata: struct {
created_time: []const u8,
custom_metadata: ?std.json.Value = null,
deletion_time: []const u8,
destroyed: bool,
version: u64,
},
};
}
pub fn read_subkeys(
self: @This(),
alloc: std.mem.Allocator,
comptime T: type,
secret_path: []const u8,
version: ?u64,
depth: ?u64,
) !std.json.Parsed(ReadSubkeyResult(T)) {
var query_buf: [1024]u8 = undefined;
var query_fbs = std.io.fixedBufferStream(&query_buf);
const query_writer = query_fbs.writer();
if (version) |v| try query_writer.print("?version={d}", .{v});
if (version == null and depth != null) try query_writer.writeAll("?");
if (version != null and depth != null) try query_writer.writeAll("&");
if (depth) |d| try query_writer.print("depth={d}", .{d});
var path_buf: [1024]u8 = undefined;
const path = std.fmt.bufPrint(&path_buf, "/v1/{s}/subkeys/{s}{s}", .{ self.mount_path, secret_path, query_fbs.getWritten() });
return try self.do(alloc, ReadResult(T), .GET, path, null);
}
pub const CreateOrUpdateResult = struct {
data: struct {
created_time: []const u8,
custom_metadata: ?std.json.Value = null,
deletion_time: []const u8,
destroyed: bool,
version: u64,
},
};
pub fn create_or_update(self: @This(), alloc: std.mem.Allocator, comptime T: type, secret_path: []const u8, data: T, cas: ?u64) !std.json.Parsed(CreateOrUpdateResult) {
var path_buf: [1024]u8 = undefined;
const path = std.fmt.bufPrint(&path_buf, "/v1/{s}/data/{s}", .{ self.mount_path, secret_path });
var payload_buf: [1024]u8 = undefined;
var payload_fbs = std.io.fixedBufferStream(&payload_buf);
const payload_writer = payload_fbs.writer();
const payload = p: {
if (cas) |c| {
break :p struct {
options: struct {
cas: u64,
},
data: T,
}{
.options = .{ .cas = c },
.data = data,
};
} else {
break :p struct {
data: T,
}{
.data = data,
};
}
};
try std.json.stringify(payload, .{}, payload_writer);
return try self.do(alloc, CreateOrUpdateResult, .POST, path, payload_fbs.getWritten());
}
// pub const DeleteResult = struct {
// };
// pub fn delete(self: @This(), alloc: std.mem.Allocator, mount_path: []const u8, secret_path: []const u8) !std.json.Parsed(DeleteResult) {
// }
pub const ListResult = struct {
data: struct {
keys: [][]const u8,
},
};
pub fn list(self: @This(), alloc: std.mem.Allocator, secret_path: []const u8) !std.json.Parsed(ListResult) {
var path_buf: [1024]u8 = undefined;
const path = std.fmt.bufPrint(&path_buf, "/v1/{s}/metadata/{s}", .{ self.mount_path, secret_path });
return try self.do(alloc, ListResult, .LIST, path, null);
}
};