first
This commit is contained in:
commit
b5d425e806
5 changed files with 294 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
/zig-cache
|
||||||
|
/zig-out
|
||||||
|
|
21
build.zig
Normal file
21
build.zig
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn build(b: *std.Build) void {
|
||||||
|
const target = b.standardTargetOptions(.{});
|
||||||
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
|
|
||||||
|
_ = b.addModule("vault", .{
|
||||||
|
.root_source_file = .{ .path = "src/root.zig" },
|
||||||
|
});
|
||||||
|
|
||||||
|
const unit_tests = b.addTest(.{
|
||||||
|
.root_source_file = .{ .path = "src/root.zig" },
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
|
||||||
|
const run_unit_tests = b.addRunArtifact(unit_tests);
|
||||||
|
|
||||||
|
const test_step = b.step("test", "Run unit tests");
|
||||||
|
test_step.dependOn(&run_unit_tests.step);
|
||||||
|
}
|
15
build.zig.zon
Normal file
15
build.zig.zon
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
.{
|
||||||
|
.name = "zig-vault",
|
||||||
|
.version = "0.0.0",
|
||||||
|
.dependencies = .{},
|
||||||
|
|
||||||
|
.paths = .{
|
||||||
|
"",
|
||||||
|
// For example...
|
||||||
|
//"build.zig",
|
||||||
|
//"build.zig.zon",
|
||||||
|
//"src",
|
||||||
|
//"LICENSE",
|
||||||
|
//"README.md",
|
||||||
|
},
|
||||||
|
}
|
24
src/main.zig
Normal file
24
src/main.zig
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn main() !void {
|
||||||
|
// Prints to stderr (it's a shortcut based on `std.io.getStdErr()`)
|
||||||
|
std.debug.print("All your {s} are belong to us.\n", .{"codebase"});
|
||||||
|
|
||||||
|
// stdout is for the actual output of your application, for example if you
|
||||||
|
// are implementing gzip, then only the compressed bytes should be sent to
|
||||||
|
// stdout, not any debugging messages.
|
||||||
|
const stdout_file = std.io.getStdOut().writer();
|
||||||
|
var bw = std.io.bufferedWriter(stdout_file);
|
||||||
|
const stdout = bw.writer();
|
||||||
|
|
||||||
|
try stdout.print("Run `zig build test` to run the tests.\n", .{});
|
||||||
|
|
||||||
|
try bw.flush(); // don't forget to flush!
|
||||||
|
}
|
||||||
|
|
||||||
|
test "simple test" {
|
||||||
|
var list = std.ArrayList(i32).init(std.testing.allocator);
|
||||||
|
defer list.deinit(); // try commenting this out and see if zig detects the memory leak!
|
||||||
|
try list.append(42);
|
||||||
|
try std.testing.expectEqual(@as(i32, 42), list.pop());
|
||||||
|
}
|
231
src/root.zig
Normal file
231
src/root.zig
Normal file
|
@ -0,0 +1,231 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
Loading…
Reference in a new issue