Skip to content

Commit ec9d39b

Browse files
authored
feat: add upload service client (#3) (#4) (#5) (#6) (#8) (#9) (#10) (#11) (#12)
Adds a new Go client for interacting with the upload service via UCAN RPC invocations, along with structured logging helpers to make invocation arguments/metadata easier to inspect in logs. **Changes:** - Introduce `pkg/client.UploadClient` with `RegisterCustomer` (`/customer/add`) and `ProvisionSpace` (`/provider/add`) operations. - Add `pkg/lib/zapucan` helpers for logging UCAN invocations and decoding CBOR-encoded IPLD maps into zap fields. - Bump `github.com/fil-forge/libforge` dependency version. --- Adds initial scaffolding for running hilt as a service: a Cobra-based `hilt serve` command that loads config via Viper and starts an Echo HTTP server wired together with Uber Fx. **Changes:** - Introduce Fx modules for config surfacing, zap logging, and Echo server lifecycle management. - Add Viper-based config loading with defaults, env var support, and pflag binding. - Add `cmd/main.go` CLI entrypoint plus basic Makefile/.gitignore and new dependencies (Echo, Cobra, Viper, Fx). --- Adds initial Tenant + Access Key management API scaffolding and wires store backends (memory/postgres) into the Fx app based on configuration, including new config types/flags and Postgres migration hooks. **Changes:** - Introduces API route stubs and request/response types for tenant + access key endpoints, and auto-registers routes onto the Echo server via an Fx group. - Adds storage backend selection (memory vs postgres) in `AppModule`, plus Fx modules to provide the corresponding store implementations. - Expands configuration to include `storage.*` and `storage.postgres.*` settings and flag bindings; standardizes PG unique-violation handling using `pgerrcode`. --- Adds a configurable “partner key” authentication mechanism to gate access to the Tenant API routes, wiring the key through config/flags and enforcing it via Echo middleware. **Changes:** - Introduces `AuthConfig` (`auth.partner_key`) and exposes it through the fx config module. - Wraps all Tenant API routes in partner-key bearer auth middleware while keeping `/` and `/health` open. - Adds new Echo middleware + unit tests for partner-key bearer authentication. --- Adds a KMS-agnostic `vault.Vault` interface plus in-memory and HashiCorp Vault (KV v2) implementations, and wires vault selection/config into the fx app and CLI so Hilt can persist private key material in a configurable backend. **Changes:** - Introduce `pkg/vault.Vault` interface with `ErrNotFound`, plus an in-memory implementation and a HashiCorp Vault (KV v2) implementation (including AppRole auth helper). - Add config schema + viper defaults + CLI flags for selecting/configuring the vault backend, and wire backend selection into the fx app graph. - Add tests for the vault interface contract and HashiCorp AppRole login using a testcontainers Vault dev container. --- Implements tenant provisioning via `PUT /tenants/{tenantId}` by introducing an external tenant identifier, wiring a did:plc directory client, generating/storing tenant keys in Vault, and persisting tenant records. **Changes:** - Added `external_id` to tenant storage model and introduced `GetByExternalID` for idempotent provisioning. - Added PLC directory configuration + fx wiring to provide a `plc.DirectoryClient`. - Implemented `ProvisionTenant` API handler with accompanying tests (memory stores + httptest PLC server). --- Implements the remaining tenant management endpoints by wiring real store-backed handlers and adding the store capabilities required to support tenant deletion cascades (buckets, access keys, delegations, vault secrets, and did:plc deactivation). **Changes:** - Implement `GET /tenants/:tenantId`, `POST /tenants/:tenantId/status`, and `DELETE /tenants/:tenantId` handlers (with did:plc deactivation + cascade cleanup). - Extend tenant/bucket/access-key stores with `Delete` and “list by tenant” capabilities, including pagination for buckets. - Add/extend unit tests for the new store methods and HTTP handlers; bump dependencies to newer `ucantone` and `dag-json-gen`. --- This PR implements the Management API access-key endpoints, including key material storage in Vault and issuing tenant→access-key UCAN delegations based on requested S3 permissions. It also extends the store layer to support access-key expiry and efficient bucket lookups by ID for response rendering. **Changes:** - Implement Create/List/Get/Delete access-key handlers, including Vault secret storage and delegation issuance/revocation. - Add `expires_at` support for access keys across schema, store implementations, and tests. - Extend bucket listing to support filtering by explicit bucket IDs via new `bucket.ListOption`/`bucket.ListConfig`. --- Adds initial scaffolding for Hilt’s UCAN-based RPC surface (used by Ingot for S3 tenant management), including an identity-backed UCAN server mounted on the existing Echo HTTP server. **Changes:** - Introduces UCAN RPC handler stubs and wires them into an `fx` module that builds a `ucantone` HTTP server. - Adds a configurable service identity (PEM-backed or ephemeral) and exposes a public `did:web` DID document endpoint. - Extends configuration/CLI flags to support identity settings.
1 parent 3225c62 commit ec9d39b

61 files changed

Lines changed: 4691 additions & 77 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
hilt

Makefile

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
BINARY := hilt
2+
BIN_DIR := .
3+
CMD := ./cmd
4+
5+
.PHONY: build test vet clean
6+
7+
build:
8+
go build -o $(BIN_DIR)/$(BINARY) $(CMD)
9+
10+
test:
11+
go test ./...
12+
13+
vet:
14+
go vet ./...
15+
16+
clean:
17+
rm $(BIN_DIR)/$(BINARY)

cmd/main.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"github.com/spf13/cobra"
8+
"go.uber.org/fx"
9+
"go.uber.org/fx/fxevent"
10+
"go.uber.org/zap"
11+
12+
"github.com/fil-forge/hilt/pkg/config"
13+
appfx "github.com/fil-forge/hilt/pkg/fx"
14+
)
15+
16+
var cfgFile string
17+
18+
func main() {
19+
rootCmd := &cobra.Command{
20+
Use: "hilt",
21+
Short: "Hilt tenant management service",
22+
Long: "Hilt manages tenants of Ingot and their secret keys.",
23+
}
24+
25+
serveCmd := &cobra.Command{
26+
Use: "serve",
27+
Short: "Start the hilt service",
28+
RunE: runServe,
29+
}
30+
31+
// identity config (UCAN RPC service identity)
32+
serveCmd.Flags().String("identity-key-file", "", "path to a PEM-encoded Ed25519 private key for the Hilt service identity (an ephemeral key is generated if unset)")
33+
serveCmd.Flags().String("identity-service-id", "", "optional did:web service identity to wrap the key with, e.g. did:web:hilt.example.com")
34+
35+
// http server config
36+
serveCmd.Flags().String("host", "127.0.0.1", "host to bind the server to")
37+
serveCmd.Flags().Int("port", 8080, "port to bind the server to")
38+
39+
// storage config
40+
serveCmd.Flags().String("storage", "postgres", "storage backend (memory or postgres)")
41+
serveCmd.Flags().String("postgres-dsn", "", "postgres connection string (used when storage=postgres)")
42+
serveCmd.Flags().Bool("skip-migrations", false, "skip running postgres migrations on startup")
43+
44+
// vault config
45+
serveCmd.Flags().String("vault", "hashicorp", "vault backend for private keys (hashicorp or memory)")
46+
serveCmd.Flags().String("hashicorp-address", "http://127.0.0.1:8200", "hashicorp vault server address")
47+
serveCmd.Flags().String("hashicorp-mount", "secret", "hashicorp vault KV v2 secrets engine mount path")
48+
serveCmd.Flags().String("hashicorp-auth-method", "approle", "hashicorp vault auth method (approle or token)")
49+
serveCmd.Flags().String("hashicorp-token", "", "hashicorp vault token (auth-method=token; prefer HILT_VAULT_HASHICORP_TOKEN env var or config file to avoid exposing via process args)")
50+
serveCmd.Flags().String("hashicorp-approle-role-id", "", "hashicorp vault AppRole role ID (auth-method=approle; prefer HILT_VAULT_HASHICORP_APPROLE_ROLE_ID env var or config file)")
51+
serveCmd.Flags().String("hashicorp-approle-secret-id", "", "hashicorp vault AppRole secret ID (auth-method=approle; prefer HILT_VAULT_HASHICORP_APPROLE_SECRET_ID env var or config file)")
52+
serveCmd.Flags().String("hashicorp-approle-mount", "approle", "hashicorp vault AppRole auth mount path")
53+
54+
// plc config
55+
serveCmd.Flags().String("plc-directory", "https://plc.directory", "did:plc directory endpoint")
56+
57+
// auth config
58+
serveCmd.Flags().String("partner-key", "", "CSV partner bearer key(s) required on Tenant API requests (prefer HILT_AUTH_PARTNER_KEY env var or config file to avoid exposing via process args)")
59+
60+
rootCmd.AddCommand(serveCmd)
61+
62+
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file path (default: looks for config.yaml in current dir)")
63+
64+
if err := rootCmd.Execute(); err != nil {
65+
fmt.Fprintln(os.Stderr, err)
66+
os.Exit(1)
67+
}
68+
}
69+
70+
func runServe(cmd *cobra.Command, args []string) error {
71+
cfg, err := config.Load(cfgFile, config.WithFlagSet(cmd.Flags()))
72+
if err != nil {
73+
return err
74+
}
75+
app := fx.New(
76+
appfx.AppModule(cfg),
77+
// Suppress fx's default logging and use our own zap logger.
78+
fx.WithLogger(func(log *zap.Logger) fxevent.Logger {
79+
return &fxevent.ZapLogger{Logger: log}
80+
}),
81+
)
82+
app.Run()
83+
84+
return nil
85+
}

go.mod

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,30 @@ go 1.26.4
44

55
require (
66
github.com/docker/docker v28.5.2+incompatible
7-
github.com/fil-forge/libforge v0.0.0-20260619083649-eb26d871cda1
8-
github.com/fil-forge/ucantone v0.0.0-20260619013642-7985ec010b88
7+
github.com/fil-forge/libforge v0.0.0-20260701162346-f0706e1641a3
8+
github.com/fil-forge/ucantone v0.0.0-20260630103048-a8f24fe31eb6
9+
github.com/hashicorp/vault-client-go v0.4.3
910
github.com/ipfs/go-cid v0.6.1
11+
github.com/jackc/pgerrcode v0.0.0-20250907135507-afb5586c32a6
1012
github.com/jackc/pgx/v5 v5.8.0
13+
github.com/labstack/echo/v4 v4.15.0
14+
github.com/multiformats/go-multibase v0.3.0
1115
github.com/pressly/goose/v3 v3.27.0
16+
github.com/spf13/cobra v1.10.2
17+
github.com/spf13/pflag v1.0.10
18+
github.com/spf13/viper v1.21.0
1219
github.com/stretchr/testify v1.11.1
1320
github.com/testcontainers/testcontainers-go v0.42.0
1421
github.com/testcontainers/testcontainers-go/modules/postgres v0.42.0
22+
go.uber.org/fx v1.24.0
1523
go.uber.org/zap v1.28.0
1624
)
1725

1826
require (
1927
dario.cat/mergo v1.0.2 // indirect
2028
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
2129
github.com/Microsoft/go-winio v0.6.2 // indirect
22-
github.com/alanshaw/dag-json-gen v0.0.6 // indirect
30+
github.com/alanshaw/dag-json-gen v0.0.8 // indirect
2331
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
2432
github.com/cespare/xxhash/v2 v2.3.0 // indirect
2533
github.com/containerd/errdefs v1.0.0 // indirect
@@ -33,20 +41,31 @@ require (
3341
github.com/docker/go-units v0.5.0 // indirect
3442
github.com/ebitengine/purego v0.10.0 // indirect
3543
github.com/felixge/httpsnoop v1.0.4 // indirect
44+
github.com/fsnotify/fsnotify v1.9.0 // indirect
3645
github.com/go-logr/logr v1.4.3 // indirect
3746
github.com/go-logr/stdr v1.2.2 // indirect
3847
github.com/go-ole/go-ole v1.2.6 // indirect
48+
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
3949
github.com/gobwas/glob v0.2.3 // indirect
4050
github.com/google/uuid v1.6.0 // indirect
51+
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
52+
github.com/hashicorp/go-retryablehttp v0.7.1 // indirect
53+
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
54+
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
55+
github.com/inconshreveable/mousetrap v1.1.0 // indirect
4156
github.com/jackc/pgpassfile v1.0.0 // indirect
4257
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
4358
github.com/jackc/puddle/v2 v2.2.2 // indirect
4459
github.com/klauspost/compress v1.18.5 // indirect
4560
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
61+
github.com/labstack/gommon v0.4.2 // indirect
4662
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
4763
github.com/magiconair/properties v1.8.10 // indirect
64+
github.com/mattn/go-colorable v0.1.14 // indirect
65+
github.com/mattn/go-isatty v0.0.20 // indirect
4866
github.com/mfridman/interpolate v0.0.2 // indirect
4967
github.com/minio/sha256-simd v1.0.1 // indirect
68+
github.com/mitchellh/go-homedir v1.1.0 // indirect
5069
github.com/moby/docker-image-spec v1.3.1 // indirect
5170
github.com/moby/go-archive v0.2.0 // indirect
5271
github.com/moby/moby/api v1.54.1 // indirect
@@ -61,23 +80,33 @@ require (
6180
github.com/mr-tron/base58 v1.3.0 // indirect
6281
github.com/multiformats/go-base32 v0.1.0 // indirect
6382
github.com/multiformats/go-base36 v0.2.0 // indirect
64-
github.com/multiformats/go-multibase v0.3.0 // indirect
6583
github.com/multiformats/go-multicodec v0.10.0 // indirect
6684
github.com/multiformats/go-multihash v0.2.3 // indirect
6785
github.com/multiformats/go-varint v0.1.0 // indirect
6886
github.com/opencontainers/go-digest v1.0.0 // indirect
6987
github.com/opencontainers/image-spec v1.1.1 // indirect
88+
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
7089
github.com/pkg/errors v0.9.1 // indirect
7190
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
7291
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
92+
github.com/ryanuber/go-glob v1.0.0 // indirect
93+
github.com/sagikazarmark/locafero v0.11.0 // indirect
7394
github.com/sethvargo/go-retry v0.3.0 // indirect
7495
github.com/shirou/gopsutil/v4 v4.26.3 // indirect
7596
github.com/sirupsen/logrus v1.9.4 // indirect
97+
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
7698
github.com/spaolacci/murmur3 v1.1.0 // indirect
99+
github.com/spf13/afero v1.15.0 // indirect
100+
github.com/spf13/cast v1.10.0 // indirect
101+
github.com/subosito/gotenv v1.6.0 // indirect
77102
github.com/tklauser/go-sysconf v0.3.16 // indirect
78103
github.com/tklauser/numcpus v0.11.0 // indirect
104+
github.com/valyala/bytebufferpool v1.0.0 // indirect
105+
github.com/valyala/fasttemplate v1.2.2 // indirect
79106
github.com/whyrusleeping/cbor-gen v0.3.1 // indirect
80107
github.com/yusufpapurcu/wmi v1.2.4 // indirect
108+
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect
109+
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect
81110
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
82111
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect
83112
go.opentelemetry.io/otel v1.44.0 // indirect
@@ -86,13 +115,17 @@ require (
86115
go.opentelemetry.io/otel/sdk v1.44.0 // indirect
87116
go.opentelemetry.io/otel/sdk/metric v1.44.0 // indirect
88117
go.opentelemetry.io/otel/trace v1.44.0 // indirect
118+
go.uber.org/dig v1.19.0 // indirect
89119
go.uber.org/multierr v1.11.0 // indirect
90-
golang.org/x/crypto v0.50.0 // indirect
120+
go.yaml.in/yaml/v3 v3.0.4 // indirect
121+
golang.org/x/crypto v0.51.0 // indirect
122+
golang.org/x/net v0.55.0 // indirect
91123
golang.org/x/sync v0.20.0 // indirect
92124
golang.org/x/sys v0.45.0 // indirect
93125
golang.org/x/text v0.37.0 // indirect
126+
golang.org/x/time v0.14.0 // indirect
94127
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
95128
gopkg.in/yaml.v3 v3.0.1 // indirect
96129
lukechampine.com/blake3 v1.4.1 // indirect
97-
pitr.ca/jsontokenizer v0.3.0 // indirect
130+
pitr.ca/jsontokenizer v0.3.2 // indirect
98131
)

0 commit comments

Comments
 (0)