spindle/secrets: add openbao manager #385

merged
opened by anirudh.fi targeting master from push-mzsvpkottnnt
+31 -13
go.mod
···
module tangled.sh/tangled.sh/core
-
go 1.24.0
-
-
toolchain go1.24.3
+
go 1.24.4
require (
github.com/Blank-Xu/sql-adapter v1.1.1
+
github.com/alecthomas/assert/v2 v2.11.0
github.com/alecthomas/chroma/v2 v2.15.0
github.com/avast/retry-go/v4 v4.6.1
github.com/bluekeyes/go-gitdiff v0.8.1
···
github.com/go-git/go-git/v5 v5.14.0
github.com/google/uuid v1.6.0
github.com/gorilla/sessions v1.4.0
-
github.com/gorilla/websocket v1.5.3
+
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674
github.com/hiddeco/sshsig v0.2.0
github.com/hpcloud/tail v1.0.0
github.com/ipfs/go-cid v0.5.0
github.com/lestrrat-go/jwx/v2 v2.1.6
github.com/mattn/go-sqlite3 v1.14.24
github.com/microcosm-cc/bluemonday v1.0.27
+
github.com/openbao/openbao/api/v2 v2.3.0
github.com/posthog/posthog-go v1.5.5
-
github.com/redis/go-redis/v9 v9.3.0
+
github.com/redis/go-redis/v9 v9.7.3
github.com/resend/resend-go/v2 v2.15.0
github.com/sethvargo/go-envconfig v1.1.0
github.com/stretchr/testify v1.10.0
···
github.com/whyrusleeping/cbor-gen v0.3.1
github.com/yuin/goldmark v1.4.13
golang.org/x/crypto v0.40.0
-
golang.org/x/net v0.41.0
+
golang.org/x/net v0.42.0
+
golang.org/x/sync v0.16.0
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da
gopkg.in/yaml.v3 v3.0.1
tangled.sh/icyphox.sh/atproto-oauth v0.0.0-20250724194903-28e660378cb1
···
require (
dario.cat/mergo v1.0.1 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
-
github.com/ProtonMail/go-crypto v1.2.0 // indirect
+
github.com/ProtonMail/go-crypto v1.3.0 // indirect
+
github.com/alecthomas/repr v0.4.0 // indirect
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bmatcuk/doublestar/v4 v4.7.1 // indirect
github.com/casbin/govaluate v1.3.0 // indirect
+
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
-
github.com/cloudflare/circl v1.6.0 // indirect
+
github.com/cloudflare/circl v1.6.2-0.20250618153321-aa837fd1539d // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/log v0.1.0 // indirect
···
github.com/docker/go-units v0.5.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
+
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-enry/go-oniguruma v1.2.1 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.6.2 // indirect
+
github.com/go-jose/go-jose/v3 v3.0.4 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-redis/cache/v9 v9.0.0 // indirect
+
github.com/go-test/deep v1.1.1 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.3 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
+
github.com/golang/mock v1.6.0 // indirect
github.com/gorilla/css v1.0.1 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
+
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
+
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
+
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 // indirect
+
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
+
github.com/hashicorp/go-sockaddr v1.0.7 // indirect
github.com/hashicorp/golang-lru v1.0.2 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
+
github.com/hashicorp/hcl v1.0.1-vault-7 // indirect
+
github.com/hexops/gotextdiff v1.0.3 // indirect
github.com/ipfs/bbloom v0.0.4 // indirect
github.com/ipfs/boxo v0.33.0 // indirect
github.com/ipfs/go-block-format v0.2.2 // indirect
···
github.com/lestrrat-go/option v1.0.1 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/minio/sha256-simd v1.0.1 // indirect
+
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/sys/atomicwriter v0.1.0 // indirect
github.com/moby/term v0.5.2 // indirect
···
github.com/multiformats/go-multihash v0.2.3 // indirect
github.com/multiformats/go-varint v0.0.7 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
+
github.com/onsi/gomega v1.37.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
-
github.com/opentracing/opentracing-go v1.2.0 // indirect
+
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
github.com/pjbgf/sha1cd v0.3.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
···
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.64.0 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
+
github.com/ryanuber/go-glob v1.0.0 // indirect
github.com/segmentio/asm v1.2.0 // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
···
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect
go.opentelemetry.io/otel v1.37.0 // indirect
+
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 // indirect
go.opentelemetry.io/otel/metric v1.37.0 // indirect
go.opentelemetry.io/otel/trace v1.37.0 // indirect
go.opentelemetry.io/proto/otlp v1.6.0 // indirect
···
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
-
golang.org/x/sync v0.15.0 // indirect
golang.org/x/sys v0.34.0 // indirect
+
golang.org/x/text v0.27.0 // indirect
golang.org/x/time v0.12.0 // indirect
-
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect
-
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect
-
google.golang.org/grpc v1.72.1 // indirect
+
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect
+
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
+
google.golang.org/grpc v1.73.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/fsnotify.v1 v1.4.7 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
+80 -96
go.sum
···
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
-
github.com/ProtonMail/go-crypto v1.2.0 h1:+PhXXn4SPGd+qk76TlEePBfOfivE0zkWFenhGhFLzWs=
-
github.com/ProtonMail/go-crypto v1.2.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
+
github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
+
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
···
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
-
github.com/bluesky-social/indigo v0.0.0-20250520232546-236dd575c91e h1:DVD+HxQsDCVJtAkjfIKZVaBNc3kayHaU+A2TJZkFdp4=
-
github.com/bluesky-social/indigo v0.0.0-20250520232546-236dd575c91e/go.mod h1:ovyxp8AMO1Hoe838vMJUbqHTZaAR8ABM3g3TXu+A5Ng=
github.com/bluesky-social/indigo v0.0.0-20250724221105-5827c8fb61bb h1:BqMNDZMfXwiRTJ6NvQotJ0qInn37JH5U8E+TF01CFHQ=
github.com/bluesky-social/indigo v0.0.0-20250724221105-5827c8fb61bb/go.mod h1:0XUyOCRtL4/OiyeqMTmr6RlVHQMDgw3LS7CfibuZR5Q=
github.com/bluesky-social/jetstream v0.0.0-20241210005130-ea96859b93d1 h1:CFvRtYNSnWRAi/98M3O466t9dYuwtesNbu6FVPymRrA=
···
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
-
github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk=
-
github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
+
github.com/cloudflare/circl v1.6.2-0.20250618153321-aa837fd1539d h1:IiIprFGH6SqstblP0Y9NIo3eaUJGkI/YDOFVSL64Uq4=
+
github.com/cloudflare/circl v1.6.2-0.20250618153321-aa837fd1539d/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
···
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
-
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
-
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
+
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
+
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
+
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-chi/chi/v5 v5.2.0 h1:Aj1EtB0qR2Rdo2dG4O94RIU35w2lvQSj6BRA4+qwFL0=
···
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git-fixtures/v5 v5.0.0-20241203230421-0753e18f8f03 h1:LumE+tQdnYW24a9RoO08w64LHTzkNkdUqBD/0QPtlEY=
github.com/go-git/go-git-fixtures/v5 v5.0.0-20241203230421-0753e18f8f03/go.mod h1:hMKrMnUE4W0SJ7bFyM00dyz/HoknZoptGWzrj6M+dEM=
+
github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY=
+
github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
-
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
-
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
···
github.com/go-redis/cache/v9 v9.0.0 h1:0thdtFo0xJi0/WXbRVu8B066z8OvVymXTJGaXrVWnN0=
github.com/go-redis/cache/v9 v9.0.0/go.mod h1:cMwi1N8ASBOufbIvk7cdXe2PbPjK/WMRL95FFHWsSgI=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
+
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
+
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
-
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
-
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
-
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0=
github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
-
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
+
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
+
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
···
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
-
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
-
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
+
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=
+
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
+
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
-
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
-
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
+
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
+
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48=
github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=
+
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 h1:U+kC2dOhMFQctRfhK0gRctKAPTloZdMU5ZJxaesJ/VM=
+
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0/go.mod h1:Ll013mhdmsVDuoIXVfBtvgGJsXDYkTw1kooNcoCXuE0=
+
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts=
+
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4=
+
github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw=
+
github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw=
github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
+
github.com/hashicorp/hcl v1.0.1-vault-7 h1:ag5OxFVy3QYTFTJODRzTKVZ6xvdfLLCA1cy/Y6xGI0I=
+
github.com/hashicorp/hcl v1.0.1-vault-7/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/hiddeco/sshsig v0.2.0 h1:gMWllgKCITXdydVkDL+Zro0PU96QI55LwUwebSwNTSw=
···
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs=
github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0=
-
github.com/ipfs/boxo v0.30.0 h1:7afsoxPGGqfoH7Dum/wOTGUB9M5fb8HyKPMlLfBvIEQ=
-
github.com/ipfs/boxo v0.30.0/go.mod h1:BPqgGGyHB9rZZcPSzah2Dc9C+5Or3U1aQe7EH1H7370=
github.com/ipfs/boxo v0.33.0 h1:9ow3chwkDzMj0Deq4AWRUEI7WnIIV7SZhPTzzG2mmfw=
github.com/ipfs/boxo v0.33.0/go.mod h1:3IPh7YFcCIcKp6o02mCHovrPntoT5Pctj/7j4syh/RM=
-
github.com/ipfs/go-block-format v0.2.1 h1:96kW71XGNNa+mZw/MTzJrCpMhBWCrd9kBLoKm9Iip/Q=
-
github.com/ipfs/go-block-format v0.2.1/go.mod h1:frtvXHMQhM6zn7HvEQu+Qz5wSTj+04oEH/I+NjDgEjk=
github.com/ipfs/go-block-format v0.2.2 h1:uecCTgRwDIXyZPgYspaLXoMiMmxQpSx2aq34eNc4YvQ=
github.com/ipfs/go-block-format v0.2.2/go.mod h1:vmuefuWU6b+9kIU0vZJgpiJt1yicQz9baHXE8qR+KB8=
github.com/ipfs/go-cid v0.5.0 h1:goEKKhaGm0ul11IHA7I6p1GmKz8kEYniqFopaB5Otwg=
···
github.com/ipfs/go-ipfs-ds-help v1.1.1/go.mod h1:75vrVCkSdSFidJscs8n4W+77AtTpCIAdDGAwjitJMIo=
github.com/ipfs/go-ipfs-util v0.0.3 h1:2RFdGez6bu2ZlZdI+rWfIdbQb1KudQp3VGwPtdNCmE0=
github.com/ipfs/go-ipfs-util v0.0.3/go.mod h1:LHzG1a0Ig4G+iZ26UUOMjHd+lfM84LZCrn17xAKWBvs=
-
github.com/ipfs/go-ipld-cbor v0.2.0 h1:VHIW3HVIjcMd8m4ZLZbrYpwjzqlVUfjLM7oK4T5/YF0=
-
github.com/ipfs/go-ipld-cbor v0.2.0/go.mod h1:Cp8T7w1NKcu4AQJLqK0tWpd1nkgTxEVB5C6kVpLW6/0=
github.com/ipfs/go-ipld-cbor v0.2.1 h1:H05yEJbK/hxg0uf2AJhyerBDbjOuHX4yi+1U/ogRa7E=
github.com/ipfs/go-ipld-cbor v0.2.1/go.mod h1:x9Zbeq8CoE5R2WicYgBMcr/9mnkQ0lHddYWJP2sMV3A=
-
github.com/ipfs/go-ipld-format v0.6.1 h1:lQLmBM/HHbrXvjIkrydRXkn+gc0DE5xO5fqelsCKYOQ=
-
github.com/ipfs/go-ipld-format v0.6.1/go.mod h1:8TOH1Hj+LFyqM2PjSqI2/ZnyO0KlfhHbJLkbxFa61hs=
github.com/ipfs/go-ipld-format v0.6.2 h1:bPZQ+A05ol0b3lsJSl0bLvwbuQ+HQbSsdGTy4xtYUkU=
github.com/ipfs/go-ipld-format v0.6.2/go.mod h1:nni2xFdHKx5lxvXJ6brt/pndtGxKAE+FPR1rg4jTkyk=
github.com/ipfs/go-log v1.0.5 h1:2dOuUCB1Z7uoczMWgAyDck5JLb72zHzrMnGnCNNbvY8=
···
github.com/ipfs/go-log/v2 v2.6.0/go.mod h1:p+Efr3qaY5YXpx9TX7MoLCSEZX5boSWj9wh86P5HJa8=
github.com/ipfs/go-metrics-interface v0.3.0 h1:YwG7/Cy4R94mYDUuwsBfeziJCVm9pBMJ6q/JR9V40TU=
github.com/ipfs/go-metrics-interface v0.3.0/go.mod h1:OxxQjZDGocXVdyTPocns6cOLwHieqej/jos7H4POwoY=
-
github.com/ipfs/go-test v0.2.1 h1:/D/a8xZ2JzkYqcVcV/7HYlCnc7bv/pKHQiX5TdClkPE=
-
github.com/ipfs/go-test v0.2.1/go.mod h1:dzu+KB9cmWjuJnXFDYJwC25T3j1GcN57byN+ixmK39M=
-
github.com/ipfs/go-test v0.2.2 h1:1yjYyfbdt1w93lVzde6JZ2einh3DIV40at4rVoyEcE8=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
···
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
-
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
-
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
···
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-
github.com/lestrrat-go/blackmagic v1.0.3 h1:94HXkVLxkZO9vJI/w2u1T0DAoprShFd13xtnSINtDWs=
-
github.com/lestrrat-go/blackmagic v1.0.3/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw=
github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA=
github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw=
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
···
github.com/lestrrat-go/jwx/v2 v2.1.6/go.mod h1:Y722kU5r/8mV7fYDifjug0r8FK8mZdw0K0GpJw/l8pU=
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
-
github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8=
-
github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg=
-
github.com/libp2p/go-libp2p v0.41.1 h1:8ecNQVT5ev/jqALTvisSJeVNvXYJyK4NhQx1nNRXQZE=
-
github.com/libp2p/go-libp2p v0.41.1/go.mod h1:DcGTovJzQl/I7HMrby5ZRjeD0kQkGiy+9w6aEkSZpRI=
-
github.com/libp2p/go-libp2p v0.42.0 h1:A8foZk+ZEhZTv0Jb++7xUFlrFhBDv4j2Vh/uq4YX+KE=
-
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
-
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
+
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
+
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
···
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
+
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
+
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
···
github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI=
github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0=
github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4=
-
github.com/multiformats/go-multiaddr v0.15.0 h1:zB/HeaI/apcZiTDwhY5YqMvNVl/oQYvs3XySU+qeAVo=
-
github.com/multiformats/go-multiaddr v0.15.0/go.mod h1:JSVUmXDjsVFiW7RjIFMP7+Ev+h1DTbiJgVeTV/tcmP0=
-
github.com/multiformats/go-multiaddr v0.16.0 h1:oGWEVKioVQcdIOBlYM8BH1rZDWOGJSqr9/BKl6zQ4qc=
github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g=
github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk=
-
github.com/multiformats/go-multicodec v0.9.0 h1:pb/dlPnzee/Sxv/j4PmkDRxCOi3hXTz3IbPKOXWJkmg=
-
github.com/multiformats/go-multicodec v0.9.0/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k=
-
github.com/multiformats/go-multicodec v0.9.2 h1:YrlXCuqxjqm3bXl+vBq5LKz5pz4mvAsugdqy78k0pXQ=
github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U=
github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM=
github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8=
···
github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg=
github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM=
github.com/onsi/gomega v1.25.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM=
-
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
-
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
+
github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y=
+
github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
+
github.com/openbao/openbao/api/v2 v2.3.0 h1:61FO3ILtpKoxbD9kTWeGaCq8pz1sdt4dv2cmTXsiaAc=
+
github.com/openbao/openbao/api/v2 v2.3.0/go.mod h1:T47WKHb7DqHa3Ms3xicQtl5EiPE+U8diKjb9888okWs=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
-
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
+
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A=
+
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU=
github.com/oppiliappan/chroma/v2 v2.19.0 h1:PN7/pb+6JRKCva30NPTtRJMlrOyzgpPpIroNzy4ekHU=
github.com/oppiliappan/chroma/v2 v2.19.0/go.mod h1:RVX6AvYm4VfYe/zsk7mjHueLDZor3aWCNE14TFlepBk=
github.com/oppiliappan/go-git/v5 v5.17.0 h1:CuJnpcIDxr0oiNaSHMconovSWnowHznVDG+AhjGuSEo=
···
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
-
github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k=
-
github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18=
github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4=
github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/redis/go-redis/v9 v9.0.0-rc.4/go.mod h1:Vo3EsyWnicKnSKCA7HhgnvnyA74wOA69Cd2Meli5mmA=
-
github.com/redis/go-redis/v9 v9.3.0 h1:RiVDjmig62jIWp7Kk4XVLs0hzV6pI3PyTnnL0cnn0u0=
-
github.com/redis/go-redis/v9 v9.3.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
+
github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM=
+
github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA=
github.com/resend/resend-go/v2 v2.15.0 h1:B6oMEPf8IEQwn2Ovx/9yymkESLDSeNfLFaNMw+mzHhE=
github.com/resend/resend-go/v2 v2.15.0/go.mod h1:3YCb8c8+pLiqhtRFXTyFwlLvfjQtluxOr9HEh2BwCkQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
···
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
+
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
···
github.com/whyrusleeping/cbor-gen v0.3.1/go.mod h1:pM99HXyEbSQHcosHc0iW7YFmwnscr+t9Te4ibko05so=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
···
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02/go.mod h1:JTnUj0mpYiAsuZLmKjTx/ex3AtMowcCgnE7YNyCEP0I=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
-
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
-
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY=
-
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
-
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
-
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 h1:K0XaT3DwHAcV4nKLzcQvwAgSyisUghWoY20I7huthMk=
-
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0/go.mod h1:B5Ki776z/MBnVha1Nzwp5arlzBbE3+1jk+pGmaP5HME=
+
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 h1:Vh5HayB/0HHfOQA7Ctx69E/Y/DcQSMPpKANYVMQ7fBA=
+
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0/go.mod h1:cpgtDBaqD/6ok/UG0jT15/uKjAY8mRA53diogHBg3UI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 h1:lUsI2TYsQw2r1IASwoROaCnjdj2cvC2+Jbxvk6nHnWU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0/go.mod h1:2HpZxxQurfGxJlJDblybejHB6RX6pmExPNe517hREw4=
-
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
-
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
-
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
-
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
-
go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
-
go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
+
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
-
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
-
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
+
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
go.opentelemetry.io/proto/otlp v1.6.0 h1:jQjP+AQyTf+Fe7OKj/MfkDrmK4MNVtw2NpXsf9fefDI=
···
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
-
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
-
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
+
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
-
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
-
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
···
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
···
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
-
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
-
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
-
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
-
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
+
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
+
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
+
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
···
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
-
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
-
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
-
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
+
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
+
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
···
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
···
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
-
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
···
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
-
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
-
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
+
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
+
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
+
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
···
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
-
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
+
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
-
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
-
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
+
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
···
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
+
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY=
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
-
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 h1:Kog3KlB4xevJlAcbbbzPfRG0+X9fdoGM+UBRKVz6Wr0=
-
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237/go.mod h1:ezi0AVyMKDWy5xAncvjLWH7UcLBB5n7y2fQ8MzjJcto=
-
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 h1:cJfm9zPbe1e873mHJzmQ1nwVEeRDU/T1wXDK2kUSU34=
-
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
-
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
-
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
+
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
+
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc=
+
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
+
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
+
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
+
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
···
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=
lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo=
-
tangled.sh/icyphox.sh/atproto-oauth v0.0.0-20250526154904-3906c5336421 h1:ZQNKE1HKWjfQBizxDb2XLhrJcgZqE0MLFIU1iggaF90=
-
tangled.sh/icyphox.sh/atproto-oauth v0.0.0-20250526154904-3906c5336421/go.mod h1:Wad0H70uyyY4qZryU/1Ic+QZyw41YxN5QfzNAEBaXkQ=
tangled.sh/icyphox.sh/atproto-oauth v0.0.0-20250724194903-28e660378cb1 h1:z1os1aRIqeo5e8d0Tx7hk+LH8OdZZeIOY0zw9VB/ZoU=
tangled.sh/icyphox.sh/atproto-oauth v0.0.0-20250724194903-28e660378cb1/go.mod h1:+oQi9S6IIDll0nxLZVhuzOPX8WKLCYEnE6M5kUKupDg=
tangled.sh/oppi.li/go-gitdiff v0.8.2 h1:pASJJNWaFn6EmEIUNNjHZQ3stRu6BqTO2YyjKvTcxIc=
+9 -1
spindle/config/config.go
···
}
type Secrets struct {
-
Provider string `env:"PROVIDER, default=sqlite"`
+
Provider string `env:"PROVIDER, default=sqlite"`
+
OpenBao OpenBaoConfig `env:",prefix=OPENBAO_"`
+
}
+
+
type OpenBaoConfig struct {
+
Addr string `env:"ADDR"`
+
RoleID string `env:"ROLE_ID"`
+
SecretID string `env:"SECRET_ID"`
+
Mount string `env:"MOUNT, default=spindle"`
}
type Pipelines struct {
+1 -1
spindle/engine/engine.go
···
// extract secrets
var allSecrets []secrets.UnlockedSecret
if didSlashRepo, err := securejoin.SecureJoin(pipeline.RepoOwner, pipeline.RepoName); err == nil {
-
if res, err := e.vault.GetSecretsUnlocked(secrets.DidSlashRepo(didSlashRepo)); err == nil {
+
if res, err := e.vault.GetSecretsUnlocked(ctx, secrets.DidSlashRepo(didSlashRepo)); err == nil {
allSecrets = res
}
}
+11 -4
spindle/secrets/manager.go
···
package secrets
import (
+
"context"
"errors"
"regexp"
"time"
···
type UnlockedSecret = Secret[string]
type Manager interface {
-
AddSecret(secret UnlockedSecret) error
-
RemoveSecret(secret Secret[any]) error
-
GetSecretsLocked(repo DidSlashRepo) ([]LockedSecret, error)
-
GetSecretsUnlocked(repo DidSlashRepo) ([]UnlockedSecret, error)
+
AddSecret(ctx context.Context, secret UnlockedSecret) error
+
RemoveSecret(ctx context.Context, secret Secret[any]) error
+
GetSecretsLocked(ctx context.Context, repo DidSlashRepo) ([]LockedSecret, error)
+
GetSecretsUnlocked(ctx context.Context, repo DidSlashRepo) ([]UnlockedSecret, error)
+
}
+
+
// stopper interface for managers that need cleanup
+
type Stopper interface {
+
Stop()
}
var ErrKeyAlreadyPresent = errors.New("key already present")
···
var (
_ = []Manager{
&SqliteManager{},
+
&OpenBaoManager{},
}
)
+407
spindle/secrets/openbao.go
···
+
package secrets
+
+
import (
+
"context"
+
"fmt"
+
"log/slog"
+
"path"
+
"strings"
+
"sync"
+
"time"
+
+
"github.com/bluesky-social/indigo/atproto/syntax"
+
vault "github.com/openbao/openbao/api/v2"
+
)
+
+
type OpenBaoManager struct {
+
client *vault.Client
+
mountPath string
+
roleID string
+
secretID string
+
stopCh chan struct{}
+
tokenMu sync.RWMutex
+
logger *slog.Logger
+
}
+
+
type OpenBaoManagerOpt func(*OpenBaoManager)
+
+
func WithMountPath(mountPath string) OpenBaoManagerOpt {
+
return func(v *OpenBaoManager) {
+
v.mountPath = mountPath
+
}
+
}
+
+
func NewOpenBaoManager(address, roleID, secretID string, logger *slog.Logger, opts ...OpenBaoManagerOpt) (*OpenBaoManager, error) {
+
if address == "" {
+
return nil, fmt.Errorf("address cannot be empty")
+
}
+
if roleID == "" {
+
return nil, fmt.Errorf("role_id cannot be empty")
+
}
+
if secretID == "" {
+
return nil, fmt.Errorf("secret_id cannot be empty")
+
}
+
+
config := vault.DefaultConfig()
+
config.Address = address
+
+
client, err := vault.NewClient(config)
+
if err != nil {
+
return nil, fmt.Errorf("failed to create openbao client: %w", err)
+
}
+
+
// Authenticate using AppRole
+
err = authenticateAppRole(client, roleID, secretID)
+
if err != nil {
+
return nil, fmt.Errorf("failed to authenticate with AppRole: %w", err)
+
}
+
+
manager := &OpenBaoManager{
+
client: client,
+
mountPath: "spindle", // default KV v2 mount path
+
roleID: roleID,
+
secretID: secretID,
+
stopCh: make(chan struct{}),
+
logger: logger,
+
}
+
+
for _, opt := range opts {
+
opt(manager)
+
}
+
+
go manager.tokenRenewalLoop()
+
+
return manager, nil
+
}
+
+
// authenticateAppRole authenticates the client using AppRole method
+
func authenticateAppRole(client *vault.Client, roleID, secretID string) error {
+
authData := map[string]interface{}{
+
"role_id": roleID,
+
"secret_id": secretID,
+
}
+
+
resp, err := client.Logical().Write("auth/approle/login", authData)
+
if err != nil {
+
return fmt.Errorf("failed to login with AppRole: %w", err)
+
}
+
+
if resp == nil || resp.Auth == nil {
+
return fmt.Errorf("no auth info returned from AppRole login")
+
}
+
+
client.SetToken(resp.Auth.ClientToken)
+
return nil
+
}
+
+
// stop stops the token renewal goroutine
+
func (v *OpenBaoManager) Stop() {
+
close(v.stopCh)
+
}
+
+
// tokenRenewalLoop runs in a background goroutine to automatically renew or re-authenticate tokens
+
func (v *OpenBaoManager) tokenRenewalLoop() {
+
ticker := time.NewTicker(30 * time.Second) // Check every 30 seconds
+
defer ticker.Stop()
+
+
for {
+
select {
+
case <-v.stopCh:
+
return
+
case <-ticker.C:
+
ctx := context.Background()
+
if err := v.ensureValidToken(ctx); err != nil {
+
v.logger.Error("openbao token renewal failed", "error", err)
+
}
+
}
+
}
+
}
+
+
// ensureValidToken checks if the current token is valid and renews or re-authenticates if needed
+
func (v *OpenBaoManager) ensureValidToken(ctx context.Context) error {
+
v.tokenMu.Lock()
+
defer v.tokenMu.Unlock()
+
+
// check current token info
+
tokenInfo, err := v.client.Auth().Token().LookupSelf()
+
if err != nil {
+
// token is invalid, need to re-authenticate
+
v.logger.Warn("token lookup failed, re-authenticating", "error", err)
+
return v.reAuthenticate()
+
}
+
+
if tokenInfo == nil || tokenInfo.Data == nil {
+
return v.reAuthenticate()
+
}
+
+
// check TTL
+
ttlRaw, ok := tokenInfo.Data["ttl"]
+
if !ok {
+
return v.reAuthenticate()
+
}
+
+
var ttl int64
+
switch t := ttlRaw.(type) {
+
case int64:
+
ttl = t
+
case float64:
+
ttl = int64(t)
+
case int:
+
ttl = int64(t)
+
default:
+
return v.reAuthenticate()
+
}
+
+
// if TTL is less than 5 minutes, try to renew
+
if ttl < 300 {
+
v.logger.Info("token ttl low, attempting renewal", "ttl_seconds", ttl)
+
+
renewResp, err := v.client.Auth().Token().RenewSelf(3600) // 1h
+
if err != nil {
+
v.logger.Warn("token renewal failed, re-authenticating", "error", err)
+
return v.reAuthenticate()
+
}
+
+
if renewResp == nil || renewResp.Auth == nil {
+
v.logger.Warn("token renewal returned no auth info, re-authenticating")
+
return v.reAuthenticate()
+
}
+
+
v.logger.Info("token renewed successfully", "new_ttl_seconds", renewResp.Auth.LeaseDuration)
+
}
+
+
return nil
+
}
+
+
// reAuthenticate performs a fresh authentication using AppRole
+
func (v *OpenBaoManager) reAuthenticate() error {
+
v.logger.Info("re-authenticating with approle")
+
+
err := authenticateAppRole(v.client, v.roleID, v.secretID)
+
if err != nil {
+
return fmt.Errorf("re-authentication failed: %w", err)
+
}
+
+
v.logger.Info("re-authentication successful")
+
return nil
+
}
+
+
func (v *OpenBaoManager) AddSecret(ctx context.Context, secret UnlockedSecret) error {
+
v.tokenMu.RLock()
+
defer v.tokenMu.RUnlock()
+
if err := ValidateKey(secret.Key); err != nil {
+
return err
+
}
+
+
secretPath := v.buildSecretPath(secret.Repo, secret.Key)
+
+
fmt.Println(v.mountPath, secretPath)
+
+
existing, err := v.client.KVv2(v.mountPath).Get(ctx, secretPath)
+
if err == nil && existing != nil {
+
return ErrKeyAlreadyPresent
+
}
+
+
secretData := map[string]interface{}{
+
"value": secret.Value,
+
"repo": string(secret.Repo),
+
"key": secret.Key,
+
"created_at": secret.CreatedAt.Format(time.RFC3339),
+
"created_by": secret.CreatedBy.String(),
+
}
+
+
_, err = v.client.KVv2(v.mountPath).Put(ctx, secretPath, secretData)
+
if err != nil {
+
return fmt.Errorf("failed to store secret in openbao: %w", err)
+
}
+
+
return nil
+
}
+
+
func (v *OpenBaoManager) RemoveSecret(ctx context.Context, secret Secret[any]) error {
+
v.tokenMu.RLock()
+
defer v.tokenMu.RUnlock()
+
secretPath := v.buildSecretPath(secret.Repo, secret.Key)
+
+
existing, err := v.client.KVv2(v.mountPath).Get(ctx, secretPath)
+
if err != nil || existing == nil {
+
return ErrKeyNotFound
+
}
+
+
err = v.client.KVv2(v.mountPath).Delete(ctx, secretPath)
+
if err != nil {
+
return fmt.Errorf("failed to delete secret from openbao: %w", err)
+
}
+
+
return nil
+
}
+
+
func (v *OpenBaoManager) GetSecretsLocked(ctx context.Context, repo DidSlashRepo) ([]LockedSecret, error) {
+
v.tokenMu.RLock()
+
defer v.tokenMu.RUnlock()
+
repoPath := v.buildRepoPath(repo)
+
+
secretsList, err := v.client.Logical().List(fmt.Sprintf("%s/metadata/%s", v.mountPath, repoPath))
+
if err != nil {
+
if strings.Contains(err.Error(), "no secret found") || strings.Contains(err.Error(), "no handler for route") {
+
return []LockedSecret{}, nil
+
}
+
return nil, fmt.Errorf("failed to list secrets: %w", err)
+
}
+
+
if secretsList == nil || secretsList.Data == nil {
+
return []LockedSecret{}, nil
+
}
+
+
keys, ok := secretsList.Data["keys"].([]interface{})
+
if !ok {
+
return []LockedSecret{}, nil
+
}
+
+
var secrets []LockedSecret
+
+
for _, keyInterface := range keys {
+
key, ok := keyInterface.(string)
+
if !ok {
+
continue
+
}
+
+
secretPath := path.Join(repoPath, key)
+
secretData, err := v.client.KVv2(v.mountPath).Get(ctx, secretPath)
+
if err != nil {
+
continue // Skip secrets we can't read
+
}
+
+
if secretData == nil || secretData.Data == nil {
+
continue
+
}
+
+
data := secretData.Data
+
+
createdAtStr, ok := data["created_at"].(string)
+
if !ok {
+
createdAtStr = time.Now().Format(time.RFC3339)
+
}
+
+
createdAt, err := time.Parse(time.RFC3339, createdAtStr)
+
if err != nil {
+
createdAt = time.Now()
+
}
+
+
createdByStr, ok := data["created_by"].(string)
+
if !ok {
+
createdByStr = ""
+
}
+
+
keyStr, ok := data["key"].(string)
+
if !ok {
+
keyStr = key
+
}
+
+
secret := LockedSecret{
+
Key: keyStr,
+
Repo: repo,
+
CreatedAt: createdAt,
+
CreatedBy: syntax.DID(createdByStr),
+
}
+
+
secrets = append(secrets, secret)
+
}
+
+
return secrets, nil
+
}
+
+
func (v *OpenBaoManager) GetSecretsUnlocked(ctx context.Context, repo DidSlashRepo) ([]UnlockedSecret, error) {
+
v.tokenMu.RLock()
+
defer v.tokenMu.RUnlock()
+
repoPath := v.buildRepoPath(repo)
+
+
secretsList, err := v.client.Logical().List(fmt.Sprintf("%s/metadata/%s", v.mountPath, repoPath))
+
if err != nil {
+
if strings.Contains(err.Error(), "no secret found") || strings.Contains(err.Error(), "no handler for route") {
+
return []UnlockedSecret{}, nil
+
}
+
return nil, fmt.Errorf("failed to list secrets: %w", err)
+
}
+
+
if secretsList == nil || secretsList.Data == nil {
+
return []UnlockedSecret{}, nil
+
}
+
+
keys, ok := secretsList.Data["keys"].([]interface{})
+
if !ok {
+
return []UnlockedSecret{}, nil
+
}
+
+
var secrets []UnlockedSecret
+
+
for _, keyInterface := range keys {
+
key, ok := keyInterface.(string)
+
if !ok {
+
continue
+
}
+
+
secretPath := path.Join(repoPath, key)
+
secretData, err := v.client.KVv2(v.mountPath).Get(ctx, secretPath)
+
if err != nil {
+
continue
+
}
+
+
if secretData == nil || secretData.Data == nil {
+
continue
+
}
+
+
data := secretData.Data
+
+
valueStr, ok := data["value"].(string)
+
if !ok {
+
continue // skip secrets without values
+
}
+
+
createdAtStr, ok := data["created_at"].(string)
+
if !ok {
+
createdAtStr = time.Now().Format(time.RFC3339)
+
}
+
+
createdAt, err := time.Parse(time.RFC3339, createdAtStr)
+
if err != nil {
+
createdAt = time.Now()
+
}
+
+
createdByStr, ok := data["created_by"].(string)
+
if !ok {
+
createdByStr = ""
+
}
+
+
keyStr, ok := data["key"].(string)
+
if !ok {
+
keyStr = key
+
}
+
+
secret := UnlockedSecret{
+
Key: keyStr,
+
Value: valueStr,
+
Repo: repo,
+
CreatedAt: createdAt,
+
CreatedBy: syntax.DID(createdByStr),
+
}
+
+
secrets = append(secrets, secret)
+
}
+
+
return secrets, nil
+
}
+
+
// buildRepoPath creates an OpenBao path for a repository
+
func (v *OpenBaoManager) buildRepoPath(repo DidSlashRepo) string {
+
// convert DidSlashRepo to a safe path by replacing special characters
+
repoPath := strings.ReplaceAll(string(repo), "/", "_")
+
repoPath = strings.ReplaceAll(repoPath, ":", "_")
+
repoPath = strings.ReplaceAll(repoPath, ".", "_")
+
return fmt.Sprintf("repos/%s", repoPath)
+
}
+
+
// buildSecretPath creates an OpenBao path for a specific secret
+
func (v *OpenBaoManager) buildSecretPath(repo DidSlashRepo, key string) string {
+
return path.Join(v.buildRepoPath(repo), key)
+
}
+630
spindle/secrets/openbao_test.go
···
+
package secrets
+
+
import (
+
"context"
+
"log/slog"
+
"os"
+
"testing"
+
"time"
+
+
"github.com/bluesky-social/indigo/atproto/syntax"
+
"github.com/stretchr/testify/assert"
+
)
+
+
// MockOpenBaoManager is a mock implementation of Manager interface for testing
+
type MockOpenBaoManager struct {
+
secrets map[string]UnlockedSecret // key: repo_key format
+
shouldError bool
+
errorToReturn error
+
stopped bool
+
}
+
+
func NewMockOpenBaoManager() *MockOpenBaoManager {
+
return &MockOpenBaoManager{secrets: make(map[string]UnlockedSecret)}
+
}
+
+
func (m *MockOpenBaoManager) SetError(err error) {
+
m.shouldError = true
+
m.errorToReturn = err
+
}
+
+
func (m *MockOpenBaoManager) ClearError() {
+
m.shouldError = false
+
m.errorToReturn = nil
+
}
+
+
func (m *MockOpenBaoManager) Stop() {
+
m.stopped = true
+
}
+
+
func (m *MockOpenBaoManager) IsStopped() bool {
+
return m.stopped
+
}
+
+
func (m *MockOpenBaoManager) buildKey(repo DidSlashRepo, key string) string {
+
return string(repo) + "_" + key
+
}
+
+
func (m *MockOpenBaoManager) AddSecret(ctx context.Context, secret UnlockedSecret) error {
+
if m.shouldError {
+
return m.errorToReturn
+
}
+
+
key := m.buildKey(secret.Repo, secret.Key)
+
if _, exists := m.secrets[key]; exists {
+
return ErrKeyAlreadyPresent
+
}
+
+
m.secrets[key] = secret
+
return nil
+
}
+
+
func (m *MockOpenBaoManager) RemoveSecret(ctx context.Context, secret Secret[any]) error {
+
if m.shouldError {
+
return m.errorToReturn
+
}
+
+
key := m.buildKey(secret.Repo, secret.Key)
+
if _, exists := m.secrets[key]; !exists {
+
return ErrKeyNotFound
+
}
+
+
delete(m.secrets, key)
+
return nil
+
}
+
+
func (m *MockOpenBaoManager) GetSecretsLocked(ctx context.Context, repo DidSlashRepo) ([]LockedSecret, error) {
+
if m.shouldError {
+
return nil, m.errorToReturn
+
}
+
+
var result []LockedSecret
+
for _, secret := range m.secrets {
+
if secret.Repo == repo {
+
result = append(result, LockedSecret{
+
Key: secret.Key,
+
Repo: secret.Repo,
+
CreatedAt: secret.CreatedAt,
+
CreatedBy: secret.CreatedBy,
+
})
+
}
+
}
+
+
return result, nil
+
}
+
+
func (m *MockOpenBaoManager) GetSecretsUnlocked(ctx context.Context, repo DidSlashRepo) ([]UnlockedSecret, error) {
+
if m.shouldError {
+
return nil, m.errorToReturn
+
}
+
+
var result []UnlockedSecret
+
for _, secret := range m.secrets {
+
if secret.Repo == repo {
+
result = append(result, secret)
+
}
+
}
+
+
return result, nil
+
}
+
+
func createTestSecretForOpenBao(repo, key, value, createdBy string) UnlockedSecret {
+
return UnlockedSecret{
+
Key: key,
+
Value: value,
+
Repo: DidSlashRepo(repo),
+
CreatedAt: time.Now(),
+
CreatedBy: syntax.DID(createdBy),
+
}
+
}
+
+
func TestOpenBaoManagerInterface(t *testing.T) {
+
var _ Manager = (*OpenBaoManager)(nil)
+
}
+
+
func TestNewOpenBaoManager(t *testing.T) {
+
tests := []struct {
+
name string
+
address string
+
roleID string
+
secretID string
+
opts []OpenBaoManagerOpt
+
expectError bool
+
errorContains string
+
}{
+
{
+
name: "empty address",
+
address: "",
+
roleID: "test-role-id",
+
secretID: "test-secret-id",
+
opts: nil,
+
expectError: true,
+
errorContains: "address cannot be empty",
+
},
+
{
+
name: "empty role_id",
+
address: "http://localhost:8200",
+
roleID: "",
+
secretID: "test-secret-id",
+
opts: nil,
+
expectError: true,
+
errorContains: "role_id cannot be empty",
+
},
+
{
+
name: "empty secret_id",
+
address: "http://localhost:8200",
+
roleID: "test-role-id",
+
secretID: "",
+
opts: nil,
+
expectError: true,
+
errorContains: "secret_id cannot be empty",
+
},
+
}
+
+
for _, tt := range tests {
+
t.Run(tt.name, func(t *testing.T) {
+
logger := slog.New(slog.NewTextHandler(os.Stderr, nil))
+
manager, err := NewOpenBaoManager(tt.address, tt.roleID, tt.secretID, logger, tt.opts...)
+
+
if tt.expectError {
+
assert.Error(t, err)
+
assert.Nil(t, manager)
+
assert.Contains(t, err.Error(), tt.errorContains)
+
} else {
+
// For valid configurations, we expect an error during authentication
+
// since we're not connecting to a real OpenBao server
+
assert.Error(t, err)
+
assert.Nil(t, manager)
+
}
+
})
+
}
+
}
+
+
func TestOpenBaoManager_PathBuilding(t *testing.T) {
+
manager := &OpenBaoManager{mountPath: "secret"}
+
+
tests := []struct {
+
name string
+
repo DidSlashRepo
+
key string
+
expected string
+
}{
+
{
+
name: "simple repo path",
+
repo: DidSlashRepo("did:plc:foo/repo"),
+
key: "api_key",
+
expected: "repos/did_plc_foo_repo/api_key",
+
},
+
{
+
name: "complex repo path with dots",
+
repo: DidSlashRepo("did:web:example.com/my-repo"),
+
key: "secret_key",
+
expected: "repos/did_web_example_com_my-repo/secret_key",
+
},
+
}
+
+
for _, tt := range tests {
+
t.Run(tt.name, func(t *testing.T) {
+
result := manager.buildSecretPath(tt.repo, tt.key)
+
assert.Equal(t, tt.expected, result)
+
})
+
}
+
}
+
+
func TestOpenBaoManager_buildRepoPath(t *testing.T) {
+
manager := &OpenBaoManager{mountPath: "test"}
+
+
tests := []struct {
+
name string
+
repo DidSlashRepo
+
expected string
+
}{
+
{
+
name: "simple repo",
+
repo: "did:plc:test/myrepo",
+
expected: "repos/did_plc_test_myrepo",
+
},
+
{
+
name: "repo with dots",
+
repo: "did:plc:example.com/my.repo",
+
expected: "repos/did_plc_example_com_my_repo",
+
},
+
{
+
name: "complex repo",
+
repo: "did:web:example.com:8080/path/to/repo",
+
expected: "repos/did_web_example_com_8080_path_to_repo",
+
},
+
}
+
+
for _, tt := range tests {
+
t.Run(tt.name, func(t *testing.T) {
+
result := manager.buildRepoPath(tt.repo)
+
assert.Equal(t, tt.expected, result)
+
})
+
}
+
}
+
+
func TestWithMountPath(t *testing.T) {
+
manager := &OpenBaoManager{mountPath: "default"}
+
+
opt := WithMountPath("custom-mount")
+
opt(manager)
+
+
assert.Equal(t, "custom-mount", manager.mountPath)
+
}
+
+
func TestOpenBaoManager_Stop(t *testing.T) {
+
// Create a manager with minimal setup
+
manager := &OpenBaoManager{
+
mountPath: "test",
+
stopCh: make(chan struct{}),
+
}
+
+
// Verify the manager implements Stopper interface
+
var stopper Stopper = manager
+
assert.NotNil(t, stopper)
+
+
// Call Stop and verify it doesn't panic
+
assert.NotPanics(t, func() {
+
manager.Stop()
+
})
+
+
// Verify the channel was closed
+
select {
+
case <-manager.stopCh:
+
// Channel was closed as expected
+
default:
+
t.Error("Expected stop channel to be closed after Stop()")
+
}
+
}
+
+
func TestOpenBaoManager_StopperInterface(t *testing.T) {
+
manager := &OpenBaoManager{}
+
+
// Verify that OpenBaoManager implements the Stopper interface
+
_, ok := interface{}(manager).(Stopper)
+
assert.True(t, ok, "OpenBaoManager should implement Stopper interface")
+
}
+
+
// Test MockOpenBaoManager interface compliance
+
func TestMockOpenBaoManagerInterface(t *testing.T) {
+
var _ Manager = (*MockOpenBaoManager)(nil)
+
var _ Stopper = (*MockOpenBaoManager)(nil)
+
}
+
+
func TestMockOpenBaoManager_AddSecret(t *testing.T) {
+
tests := []struct {
+
name string
+
secrets []UnlockedSecret
+
expectError bool
+
}{
+
{
+
name: "add single secret",
+
secrets: []UnlockedSecret{
+
createTestSecretForOpenBao("did:plc:test/repo1", "API_KEY", "secret123", "did:plc:creator"),
+
},
+
expectError: false,
+
},
+
{
+
name: "add multiple secrets",
+
secrets: []UnlockedSecret{
+
createTestSecretForOpenBao("did:plc:test/repo1", "API_KEY", "secret123", "did:plc:creator"),
+
createTestSecretForOpenBao("did:plc:test/repo1", "DB_PASSWORD", "dbpass456", "did:plc:creator"),
+
},
+
expectError: false,
+
},
+
{
+
name: "add duplicate secret",
+
secrets: []UnlockedSecret{
+
createTestSecretForOpenBao("did:plc:test/repo1", "API_KEY", "secret123", "did:plc:creator"),
+
createTestSecretForOpenBao("did:plc:test/repo1", "API_KEY", "newsecret", "did:plc:creator"),
+
},
+
expectError: true,
+
},
+
}
+
+
for _, tt := range tests {
+
t.Run(tt.name, func(t *testing.T) {
+
mock := NewMockOpenBaoManager()
+
ctx := context.Background()
+
var err error
+
+
for i, secret := range tt.secrets {
+
err = mock.AddSecret(ctx, secret)
+
if tt.expectError && i == 1 { // Second secret should fail for duplicate test
+
assert.Equal(t, ErrKeyAlreadyPresent, err)
+
return
+
}
+
if !tt.expectError {
+
assert.NoError(t, err)
+
}
+
}
+
+
if !tt.expectError {
+
assert.NoError(t, err)
+
}
+
})
+
}
+
}
+
+
func TestMockOpenBaoManager_RemoveSecret(t *testing.T) {
+
tests := []struct {
+
name string
+
setupSecrets []UnlockedSecret
+
removeSecret Secret[any]
+
expectError bool
+
}{
+
{
+
name: "remove existing secret",
+
setupSecrets: []UnlockedSecret{
+
createTestSecretForOpenBao("did:plc:test/repo1", "API_KEY", "secret123", "did:plc:creator"),
+
},
+
removeSecret: Secret[any]{
+
Key: "API_KEY",
+
Repo: DidSlashRepo("did:plc:test/repo1"),
+
},
+
expectError: false,
+
},
+
{
+
name: "remove non-existent secret",
+
setupSecrets: []UnlockedSecret{},
+
removeSecret: Secret[any]{
+
Key: "API_KEY",
+
Repo: DidSlashRepo("did:plc:test/repo1"),
+
},
+
expectError: true,
+
},
+
}
+
+
for _, tt := range tests {
+
t.Run(tt.name, func(t *testing.T) {
+
mock := NewMockOpenBaoManager()
+
ctx := context.Background()
+
+
// Setup secrets
+
for _, secret := range tt.setupSecrets {
+
err := mock.AddSecret(ctx, secret)
+
assert.NoError(t, err)
+
}
+
+
// Remove secret
+
err := mock.RemoveSecret(ctx, tt.removeSecret)
+
+
if tt.expectError {
+
assert.Equal(t, ErrKeyNotFound, err)
+
} else {
+
assert.NoError(t, err)
+
}
+
})
+
}
+
}
+
+
func TestMockOpenBaoManager_GetSecretsLocked(t *testing.T) {
+
tests := []struct {
+
name string
+
setupSecrets []UnlockedSecret
+
queryRepo DidSlashRepo
+
expectedCount int
+
expectedKeys []string
+
expectError bool
+
}{
+
{
+
name: "get secrets from repo with secrets",
+
setupSecrets: []UnlockedSecret{
+
createTestSecretForOpenBao("did:plc:test/repo1", "API_KEY", "secret123", "did:plc:creator"),
+
createTestSecretForOpenBao("did:plc:test/repo1", "DB_PASSWORD", "dbpass456", "did:plc:creator"),
+
createTestSecretForOpenBao("did:plc:test/repo2", "OTHER_KEY", "other789", "did:plc:creator"),
+
},
+
queryRepo: DidSlashRepo("did:plc:test/repo1"),
+
expectedCount: 2,
+
expectedKeys: []string{"API_KEY", "DB_PASSWORD"},
+
expectError: false,
+
},
+
{
+
name: "get secrets from empty repo",
+
setupSecrets: []UnlockedSecret{},
+
queryRepo: DidSlashRepo("did:plc:test/empty"),
+
expectedCount: 0,
+
expectedKeys: []string{},
+
expectError: false,
+
},
+
}
+
+
for _, tt := range tests {
+
t.Run(tt.name, func(t *testing.T) {
+
mock := NewMockOpenBaoManager()
+
ctx := context.Background()
+
+
// Setup
+
for _, secret := range tt.setupSecrets {
+
err := mock.AddSecret(ctx, secret)
+
assert.NoError(t, err)
+
}
+
+
// Test
+
secrets, err := mock.GetSecretsLocked(ctx, tt.queryRepo)
+
+
if tt.expectError {
+
assert.Error(t, err)
+
} else {
+
assert.NoError(t, err)
+
assert.Len(t, secrets, tt.expectedCount)
+
+
// Check keys
+
actualKeys := make([]string, len(secrets))
+
for i, secret := range secrets {
+
actualKeys[i] = secret.Key
+
}
+
+
for _, expectedKey := range tt.expectedKeys {
+
assert.Contains(t, actualKeys, expectedKey)
+
}
+
}
+
})
+
}
+
}
+
+
func TestMockOpenBaoManager_GetSecretsUnlocked(t *testing.T) {
+
tests := []struct {
+
name string
+
setupSecrets []UnlockedSecret
+
queryRepo DidSlashRepo
+
expectedCount int
+
expectedSecrets map[string]string // key -> value
+
expectError bool
+
}{
+
{
+
name: "get unlocked secrets from repo",
+
setupSecrets: []UnlockedSecret{
+
createTestSecretForOpenBao("did:plc:test/repo1", "API_KEY", "secret123", "did:plc:creator"),
+
createTestSecretForOpenBao("did:plc:test/repo1", "DB_PASSWORD", "dbpass456", "did:plc:creator"),
+
createTestSecretForOpenBao("did:plc:test/repo2", "OTHER_KEY", "other789", "did:plc:creator"),
+
},
+
queryRepo: DidSlashRepo("did:plc:test/repo1"),
+
expectedCount: 2,
+
expectedSecrets: map[string]string{
+
"API_KEY": "secret123",
+
"DB_PASSWORD": "dbpass456",
+
},
+
expectError: false,
+
},
+
{
+
name: "get secrets from empty repo",
+
setupSecrets: []UnlockedSecret{},
+
queryRepo: DidSlashRepo("did:plc:test/empty"),
+
expectedCount: 0,
+
expectedSecrets: map[string]string{},
+
expectError: false,
+
},
+
}
+
+
for _, tt := range tests {
+
t.Run(tt.name, func(t *testing.T) {
+
mock := NewMockOpenBaoManager()
+
ctx := context.Background()
+
+
// Setup
+
for _, secret := range tt.setupSecrets {
+
err := mock.AddSecret(ctx, secret)
+
assert.NoError(t, err)
+
}
+
+
// Test
+
secrets, err := mock.GetSecretsUnlocked(ctx, tt.queryRepo)
+
+
if tt.expectError {
+
assert.Error(t, err)
+
} else {
+
assert.NoError(t, err)
+
assert.Len(t, secrets, tt.expectedCount)
+
+
// Check key-value pairs
+
actualSecrets := make(map[string]string)
+
for _, secret := range secrets {
+
actualSecrets[secret.Key] = secret.Value
+
}
+
+
for expectedKey, expectedValue := range tt.expectedSecrets {
+
actualValue, exists := actualSecrets[expectedKey]
+
assert.True(t, exists, "Expected key %s not found", expectedKey)
+
assert.Equal(t, expectedValue, actualValue)
+
}
+
}
+
})
+
}
+
}
+
+
func TestMockOpenBaoManager_ErrorHandling(t *testing.T) {
+
mock := NewMockOpenBaoManager()
+
ctx := context.Background()
+
testError := assert.AnError
+
+
// Test error injection
+
mock.SetError(testError)
+
+
secret := createTestSecretForOpenBao("did:plc:test/repo1", "API_KEY", "secret123", "did:plc:creator")
+
+
// All operations should return the injected error
+
err := mock.AddSecret(ctx, secret)
+
assert.Equal(t, testError, err)
+
+
_, err = mock.GetSecretsLocked(ctx, "did:plc:test/repo1")
+
assert.Equal(t, testError, err)
+
+
_, err = mock.GetSecretsUnlocked(ctx, "did:plc:test/repo1")
+
assert.Equal(t, testError, err)
+
+
err = mock.RemoveSecret(ctx, Secret[any]{Key: "API_KEY", Repo: "did:plc:test/repo1"})
+
assert.Equal(t, testError, err)
+
+
// Clear error and test normal operation
+
mock.ClearError()
+
err = mock.AddSecret(ctx, secret)
+
assert.NoError(t, err)
+
}
+
+
func TestMockOpenBaoManager_Stop(t *testing.T) {
+
mock := NewMockOpenBaoManager()
+
+
assert.False(t, mock.IsStopped())
+
+
mock.Stop()
+
+
assert.True(t, mock.IsStopped())
+
}
+
+
func TestMockOpenBaoManager_Integration(t *testing.T) {
+
tests := []struct {
+
name string
+
scenario func(t *testing.T, mock *MockOpenBaoManager)
+
}{
+
{
+
name: "complete workflow",
+
scenario: func(t *testing.T, mock *MockOpenBaoManager) {
+
ctx := context.Background()
+
repo := DidSlashRepo("did:plc:test/integration")
+
+
// Start with empty repo
+
secrets, err := mock.GetSecretsLocked(ctx, repo)
+
assert.NoError(t, err)
+
assert.Empty(t, secrets)
+
+
// Add some secrets
+
secret1 := createTestSecretForOpenBao(string(repo), "API_KEY", "secret123", "did:plc:creator")
+
secret2 := createTestSecretForOpenBao(string(repo), "DB_PASSWORD", "dbpass456", "did:plc:creator")
+
+
err = mock.AddSecret(ctx, secret1)
+
assert.NoError(t, err)
+
+
err = mock.AddSecret(ctx, secret2)
+
assert.NoError(t, err)
+
+
// Verify secrets exist
+
secrets, err = mock.GetSecretsLocked(ctx, repo)
+
assert.NoError(t, err)
+
assert.Len(t, secrets, 2)
+
+
unlockedSecrets, err := mock.GetSecretsUnlocked(ctx, repo)
+
assert.NoError(t, err)
+
assert.Len(t, unlockedSecrets, 2)
+
+
// Remove one secret
+
err = mock.RemoveSecret(ctx, Secret[any]{Key: "API_KEY", Repo: repo})
+
assert.NoError(t, err)
+
+
// Verify only one secret remains
+
secrets, err = mock.GetSecretsLocked(ctx, repo)
+
assert.NoError(t, err)
+
assert.Len(t, secrets, 1)
+
assert.Equal(t, "DB_PASSWORD", secrets[0].Key)
+
},
+
},
+
}
+
+
for _, tt := range tests {
+
t.Run(tt.name, func(t *testing.T) {
+
mock := NewMockOpenBaoManager()
+
tt.scenario(t, mock)
+
})
+
}
+
}
+15
spindle/secrets/policy.hcl
···
+
# KV v2 data operations
+
path "spindle/data/*" {
+
capabilities = ["create", "read", "update", "delete", "list"]
+
}
+
+
# KV v2 metadata operations (needed for listing)
+
path "spindle/metadata/*" {
+
capabilities = ["list", "read", "delete"]
+
}
+
+
# Root path access (needed for mount-level operations)
+
path "spindle/*" {
+
capabilities = ["list"]
+
}
+
+9 -8
spindle/secrets/sqlite.go
···
package secrets
import (
+
"context"
"database/sql"
"fmt"
"time"
···
return err
}
-
func (s *SqliteManager) AddSecret(secret UnlockedSecret) error {
+
func (s *SqliteManager) AddSecret(ctx context.Context, secret UnlockedSecret) error {
query := fmt.Sprintf(`
insert or ignore into %s (repo, key, value, created_by)
values (?, ?, ?, ?);
`, s.tableName)
-
res, err := s.db.Exec(query, secret.Repo, secret.Key, secret.Value, secret.CreatedBy)
+
res, err := s.db.ExecContext(ctx, query, secret.Repo, secret.Key, secret.Value, secret.CreatedBy)
if err != nil {
return err
}
···
return nil
}
-
func (s *SqliteManager) RemoveSecret(secret Secret[any]) error {
+
func (s *SqliteManager) RemoveSecret(ctx context.Context, secret Secret[any]) error {
query := fmt.Sprintf(`
delete from %s where repo = ? and key = ?;
`, s.tableName)
-
res, err := s.db.Exec(query, secret.Repo, secret.Key)
+
res, err := s.db.ExecContext(ctx, query, secret.Repo, secret.Key)
if err != nil {
return err
}
···
return nil
}
-
func (s *SqliteManager) GetSecretsLocked(didSlashRepo DidSlashRepo) ([]LockedSecret, error) {
+
func (s *SqliteManager) GetSecretsLocked(ctx context.Context, didSlashRepo DidSlashRepo) ([]LockedSecret, error) {
query := fmt.Sprintf(`
select repo, key, created_at, created_by from %s where repo = ?;
`, s.tableName)
-
rows, err := s.db.Query(query, didSlashRepo)
+
rows, err := s.db.QueryContext(ctx, query, didSlashRepo)
if err != nil {
return nil, err
}
···
return ls, nil
}
-
func (s *SqliteManager) GetSecretsUnlocked(didSlashRepo DidSlashRepo) ([]UnlockedSecret, error) {
+
func (s *SqliteManager) GetSecretsUnlocked(ctx context.Context, didSlashRepo DidSlashRepo) ([]UnlockedSecret, error) {
query := fmt.Sprintf(`
select repo, key, value, created_at, created_by from %s where repo = ?;
`, s.tableName)
-
rows, err := s.db.Query(query, didSlashRepo)
+
rows, err := s.db.QueryContext(ctx, query, didSlashRepo)
if err != nil {
return nil, err
}
+31 -21
spindle/secrets/sqlite_test.go
···
package secrets
import (
+
"context"
"testing"
"time"
+
"github.com/alecthomas/assert/v2"
"github.com/bluesky-social/indigo/atproto/syntax"
)
···
defer manager.db.Close()
for i, secret := range tt.secrets {
-
err := manager.AddSecret(secret)
+
err := manager.AddSecret(context.Background(), secret)
if err != tt.expectError[i] {
t.Errorf("Secret %d: expected error %v, got %v", i, tt.expectError[i], err)
}
···
// Setup secrets
for _, secret := range tt.setupSecrets {
-
if err := manager.AddSecret(secret); err != nil {
+
if err := manager.AddSecret(context.Background(), secret); err != nil {
t.Fatalf("Failed to setup secret: %v", err)
}
}
// Test removal
-
err := manager.RemoveSecret(tt.removeSecret)
+
err := manager.RemoveSecret(context.Background(), tt.removeSecret)
if err != tt.expectError {
t.Errorf("Expected error %v, got %v", tt.expectError, err)
}
···
// Setup secrets
for _, secret := range tt.setupSecrets {
-
if err := manager.AddSecret(secret); err != nil {
+
if err := manager.AddSecret(context.Background(), secret); err != nil {
t.Fatalf("Failed to setup secret: %v", err)
}
}
// Test getting locked secrets
-
lockedSecrets, err := manager.GetSecretsLocked(tt.queryRepo)
+
lockedSecrets, err := manager.GetSecretsLocked(context.Background(), tt.queryRepo)
if tt.expectError && err == nil {
t.Error("Expected error but got none")
return
···
// Setup secrets
for _, secret := range tt.setupSecrets {
-
if err := manager.AddSecret(secret); err != nil {
+
if err := manager.AddSecret(context.Background(), secret); err != nil {
t.Fatalf("Failed to setup secret: %v", err)
}
}
// Test getting unlocked secrets
-
unlockedSecrets, err := manager.GetSecretsUnlocked(tt.queryRepo)
+
unlockedSecrets, err := manager.GetSecretsUnlocked(context.Background(), tt.queryRepo)
if tt.expectError && err == nil {
t.Error("Expected error but got none")
return
···
operations: []func(Manager) error{
func(m Manager) error {
secret := createTestSecret("interface.test/repo", "test_key", "test_value", "did:plc:user")
-
return m.AddSecret(secret)
+
return m.AddSecret(context.Background(), secret)
},
func(m Manager) error {
-
_, err := m.GetSecretsLocked(DidSlashRepo("interface.test/repo"))
+
_, err := m.GetSecretsLocked(context.Background(), DidSlashRepo("interface.test/repo"))
return err
},
func(m Manager) error {
-
_, err := m.GetSecretsUnlocked(DidSlashRepo("interface.test/repo"))
+
_, err := m.GetSecretsUnlocked(context.Background(), DidSlashRepo("interface.test/repo"))
return err
},
func(m Manager) error {
···
Key: "test_key",
Repo: DidSlashRepo("interface.test/repo"),
}
-
return m.RemoveSecret(secret)
+
return m.RemoveSecret(context.Background(), secret)
},
},
expectError: false,
···
operations: []func(Manager) error{
func(m Manager) error {
secret := createTestSecret("interface.test/repo", "dup_key", "value1", "did:plc:user")
-
return m.AddSecret(secret)
+
return m.AddSecret(context.Background(), secret)
},
func(m Manager) error {
secret := createTestSecret("interface.test/repo", "dup_key", "value2", "did:plc:user")
-
return m.AddSecret(secret) // Should return ErrKeyAlreadyPresent
+
return m.AddSecret(context.Background(), secret) // Should return ErrKeyAlreadyPresent
},
},
expectError: true,
···
// Add all secrets
for _, secret := range secrets {
-
if err := manager.AddSecret(secret); err != nil {
+
if err := manager.AddSecret(context.Background(), secret); err != nil {
t.Fatalf("Failed to add secret %s: %v", secret.Key, err)
}
}
// Verify counts
-
locked1, _ := manager.GetSecretsLocked(repo1)
-
locked2, _ := manager.GetSecretsLocked(repo2)
+
locked1, _ := manager.GetSecretsLocked(context.Background(), repo1)
+
locked2, _ := manager.GetSecretsLocked(context.Background(), repo2)
if len(locked1) != 2 {
t.Errorf("Expected 2 secrets for repo1, got %d", len(locked1))
···
// Remove and verify
secretToRemove := Secret[any]{Key: "db_password", Repo: repo1}
-
if err := manager.RemoveSecret(secretToRemove); err != nil {
+
if err := manager.RemoveSecret(context.Background(), secretToRemove); err != nil {
t.Fatalf("Failed to remove secret: %v", err)
}
-
locked1After, _ := manager.GetSecretsLocked(repo1)
+
locked1After, _ := manager.GetSecretsLocked(context.Background(), repo1)
if len(locked1After) != 1 {
t.Errorf("Expected 1 secret for repo1 after removal, got %d", len(locked1After))
}
···
repo := DidSlashRepo("empty.test/repo")
// Operations on empty database should not error
-
locked, err := manager.GetSecretsLocked(repo)
+
locked, err := manager.GetSecretsLocked(context.Background(), repo)
if err != nil {
t.Errorf("GetSecretsLocked on empty DB failed: %v", err)
}
···
t.Errorf("Expected 0 secrets, got %d", len(locked))
}
-
unlocked, err := manager.GetSecretsUnlocked(repo)
+
unlocked, err := manager.GetSecretsUnlocked(context.Background(), repo)
if err != nil {
t.Errorf("GetSecretsUnlocked on empty DB failed: %v", err)
}
···
// Remove from empty should return ErrKeyNotFound
nonExistent := Secret[any]{Key: "none", Repo: repo}
-
err = manager.RemoveSecret(nonExistent)
+
err = manager.RemoveSecret(context.Background(), nonExistent)
if err != ErrKeyNotFound {
t.Errorf("Expected ErrKeyNotFound, got %v", err)
}
···
})
}
}
+
+
func TestSqliteManager_StopperInterface(t *testing.T) {
+
manager := &SqliteManager{}
+
+
// Verify that SqliteManager does NOT implement the Stopper interface
+
_, ok := interface{}(manager).(Stopper)
+
assert.False(t, ok, "SqliteManager should NOT implement Stopper interface")
+
}
+36 -4
spindle/server.go
···
n := notifier.New()
-
// TODO: add hashicorp vault provider and choose here
-
vault, err := secrets.NewSQLiteManager(cfg.Server.DBPath, secrets.WithTableName("secrets"))
-
if err != nil {
-
return fmt.Errorf("failed to setup secrets provider: %w", err)
+
var vault secrets.Manager
+
switch cfg.Server.Secrets.Provider {
+
case "openbao":
+
if cfg.Server.Secrets.OpenBao.Addr == "" {
+
return fmt.Errorf("openbao address is required when using openbao secrets provider")
+
}
+
if cfg.Server.Secrets.OpenBao.RoleID == "" {
+
return fmt.Errorf("openbao role_id is required when using openbao secrets provider")
+
}
+
if cfg.Server.Secrets.OpenBao.SecretID == "" {
+
return fmt.Errorf("openbao secret_id is required when using openbao secrets provider")
+
}
+
vault, err = secrets.NewOpenBaoManager(
+
cfg.Server.Secrets.OpenBao.Addr,
+
cfg.Server.Secrets.OpenBao.RoleID,
+
cfg.Server.Secrets.OpenBao.SecretID,
+
logger,
+
secrets.WithMountPath(cfg.Server.Secrets.OpenBao.Mount),
+
)
+
if err != nil {
+
return fmt.Errorf("failed to setup openbao secrets provider: %w", err)
+
}
+
logger.Info("using openbao secrets provider", "address", cfg.Server.Secrets.OpenBao.Addr, "mount", cfg.Server.Secrets.OpenBao.Mount)
+
case "sqlite", "":
+
vault, err = secrets.NewSQLiteManager(cfg.Server.DBPath, secrets.WithTableName("secrets"))
+
if err != nil {
+
return fmt.Errorf("failed to setup sqlite secrets provider: %w", err)
+
}
+
logger.Info("using sqlite secrets provider", "path", cfg.Server.DBPath)
+
default:
+
return fmt.Errorf("unknown secrets provider: %s", cfg.Server.Secrets.Provider)
}
eng, err := engine.New(ctx, cfg, d, &n, vault)
···
jq.Start()
defer jq.Stop()
+
// Stop vault token renewal if it implements Stopper
+
if stopper, ok := vault.(secrets.Stopper); ok {
+
defer stopper.Stop()
+
}
+
cursorStore, err := cursor.NewSQLiteStore(cfg.Server.DBPath)
if err != nil {
return fmt.Errorf("failed to setup sqlite3 cursor store: %w", err)