mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-05-06 20:03:05 +08:00
✨ feat(security): 新增密钥存储基础架构
This commit is contained in:
8
go.mod
8
go.mod
@@ -28,11 +28,14 @@ require (
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
|
||||
github.com/99designs/keyring v1.2.2
|
||||
github.com/ClickHouse/ch-go v0.71.0 // indirect
|
||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||
github.com/apache/arrow-go/v18 v18.5.1 // indirect
|
||||
github.com/bep/debounce v1.2.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/danieljoos/wincred v1.1.2 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/duckdb/duckdb-go-bindings v0.3.3 // indirect
|
||||
github.com/duckdb/duckdb-go-bindings/lib/darwin-amd64 v0.3.3 // indirect
|
||||
@@ -41,17 +44,20 @@ require (
|
||||
github.com/duckdb/duckdb-go-bindings/lib/linux-arm64 v0.3.3 // indirect
|
||||
github.com/duckdb/duckdb-go-bindings/lib/windows-amd64 v0.3.3 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/dvsekhvalnov/jose2go v1.5.0 // indirect
|
||||
github.com/go-faster/city v1.0.1 // indirect
|
||||
github.com/go-faster/errors v0.7.1 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
|
||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
||||
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
||||
github.com/golang/snappy v1.0.0 // indirect
|
||||
github.com/google/flatbuffers v25.12.19+incompatible // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
|
||||
github.com/hashicorp/go-version v1.8.0 // indirect
|
||||
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
@@ -68,6 +74,7 @@ require (
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||
github.com/mtibben/percent v0.2.1 // indirect
|
||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||
github.com/paulmach/orb v0.12.0 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.25 // indirect
|
||||
@@ -100,6 +107,7 @@ require (
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.40.0 // indirect
|
||||
golang.org/x/telemetry v0.0.0-20260116145544-c6413dc483f5 // indirect
|
||||
golang.org/x/term v0.39.0 // indirect
|
||||
golang.org/x/tools v0.41.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
|
||||
modernc.org/libc v1.67.6 // indirect
|
||||
|
||||
17
go.sum
17
go.sum
@@ -4,6 +4,10 @@ gitea.com/kingbase/gokb v0.0.0-20201021123113-29bd62a876c3 h1:QjslQNaH5Nuap5i4ni
|
||||
gitea.com/kingbase/gokb v0.0.0-20201021123113-29bd62a876c3/go.mod h1:7lH5A1jzCXD9Nl16DzaBUOfDAT8NPrDmZwKu1p5wf94=
|
||||
gitee.com/chunanyong/dm v1.8.22 h1:H7fsrnUIvEA0jlDWew7vwELry1ff+tLMIu2Fk2cIBSg=
|
||||
gitee.com/chunanyong/dm v1.8.22/go.mod h1:EPRJnuPFgbyOFgJ0TRYCTGzhq+ZT4wdyaj/GW/LLcNg=
|
||||
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs=
|
||||
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4=
|
||||
github.com/99designs/keyring v1.2.2 h1:pZd3neh/EmUzWONb35LxQfvuY7kiSXAq3HQd97+XBn0=
|
||||
github.com/99designs/keyring v1.2.2/go.mod h1:wes/FrByc8j7lFOAGLGSNEg8f/PaI3cgTBqhFkHUrPk=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4=
|
||||
@@ -34,6 +38,8 @@ github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0=
|
||||
github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
@@ -56,6 +62,8 @@ github.com/duckdb/duckdb-go/v2 v2.5.5 h1:TlK8ipnzoKW2aNrjGqRkFWLCDpJDxR/VwH8ezEc
|
||||
github.com/duckdb/duckdb-go/v2 v2.5.5/go.mod h1:6uIbC3gz36NCEygECzboygOo/Z9TeVwox/puG+ohWV0=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/dvsekhvalnov/jose2go v1.5.0 h1:3j8ya4Z4kMCwT5nXIKFSV84YS+HdqSSO0VsTQxaLAeM=
|
||||
github.com/dvsekhvalnov/jose2go v1.5.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU=
|
||||
github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw=
|
||||
github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw=
|
||||
github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg=
|
||||
@@ -68,6 +76,8 @@ github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPE
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
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/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0=
|
||||
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
|
||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
@@ -95,6 +105,8 @@ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
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/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU=
|
||||
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0=
|
||||
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
|
||||
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
@@ -158,6 +170,8 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
|
||||
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
||||
github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs=
|
||||
github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns=
|
||||
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
@@ -201,6 +215,7 @@ github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
@@ -300,6 +315,7 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -342,6 +358,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
104
internal/secretstore/keyring_store.go
Normal file
104
internal/secretstore/keyring_store.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package secretstore
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"github.com/99designs/keyring"
|
||||
)
|
||||
|
||||
type keyringClient interface {
|
||||
Get(key string) (keyring.Item, error)
|
||||
Set(item keyring.Item) error
|
||||
Remove(key string) error
|
||||
}
|
||||
|
||||
type keyringStore struct {
|
||||
ring keyringClient
|
||||
}
|
||||
|
||||
type keyringOpener func(cfg keyring.Config) (keyring.Keyring, error)
|
||||
|
||||
func NewKeyringStore() SecretStore {
|
||||
return newKeyringStoreWithOpener(runtime.GOOS, keyring.Open)
|
||||
}
|
||||
|
||||
func newKeyringStoreWithOpener(goos string, open keyringOpener) SecretStore {
|
||||
cfg, err := keyringConfigFor(goos)
|
||||
if err != nil {
|
||||
return NewUnavailableStore(err.Error())
|
||||
}
|
||||
|
||||
ring, err := open(cfg)
|
||||
if err != nil {
|
||||
return NewUnavailableStore(err.Error())
|
||||
}
|
||||
|
||||
return &keyringStore{ring: ring}
|
||||
}
|
||||
|
||||
func (s *keyringStore) Put(ref string, payload []byte) error {
|
||||
return wrapKeyringError(s.ring.Set(keyring.Item{Key: ref, Data: payload}))
|
||||
}
|
||||
|
||||
func (s *keyringStore) Get(ref string) ([]byte, error) {
|
||||
item, err := s.ring.Get(ref)
|
||||
if err != nil {
|
||||
return nil, wrapKeyringError(err)
|
||||
}
|
||||
return item.Data, nil
|
||||
}
|
||||
|
||||
func (s *keyringStore) Delete(ref string) error {
|
||||
return wrapKeyringError(s.ring.Remove(ref))
|
||||
}
|
||||
|
||||
func (s *keyringStore) HealthCheck() error {
|
||||
_, err := s.ring.Get(healthCheckRef)
|
||||
if err == nil || errors.Is(err, keyring.ErrKeyNotFound) {
|
||||
return nil
|
||||
}
|
||||
return wrapKeyringError(err)
|
||||
}
|
||||
|
||||
func wrapKeyringError(err error) error {
|
||||
if err == nil || errors.Is(err, keyring.ErrKeyNotFound) || IsUnavailable(err) {
|
||||
return err
|
||||
}
|
||||
return &UnavailableError{Reason: err.Error()}
|
||||
}
|
||||
|
||||
func keyringConfigFor(goos string) (keyring.Config, error) {
|
||||
backends := allowedBackendsFor(goos)
|
||||
if len(backends) == 0 {
|
||||
return keyring.Config{}, fmt.Errorf("unsupported keyring platform: %s", goos)
|
||||
}
|
||||
|
||||
return keyring.Config{
|
||||
ServiceName: serviceName,
|
||||
AllowedBackends: backends,
|
||||
KeychainTrustApplication: true,
|
||||
KeychainAccessibleWhenUnlocked: true,
|
||||
LibSecretCollectionName: "default",
|
||||
KeyCtlScope: "user",
|
||||
WinCredPrefix: serviceName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func allowedBackendsFor(goos string) []keyring.BackendType {
|
||||
switch goos {
|
||||
case "windows":
|
||||
return []keyring.BackendType{keyring.WinCredBackend}
|
||||
case "darwin":
|
||||
return []keyring.BackendType{keyring.KeychainBackend}
|
||||
case "linux":
|
||||
return []keyring.BackendType{
|
||||
keyring.SecretServiceBackend,
|
||||
keyring.KWalletBackend,
|
||||
keyring.KeyCtlBackend,
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
102
internal/secretstore/keyring_store_test.go
Normal file
102
internal/secretstore/keyring_store_test.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package secretstore
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/99designs/keyring"
|
||||
)
|
||||
|
||||
func TestBuildRefRejectsEmptyKind(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if _, err := BuildRef("", "secret-id"); err == nil {
|
||||
t.Fatal("BuildRef should reject an empty kind")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildRefRejectsEmptyID(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if _, err := BuildRef("database", ""); err == nil {
|
||||
t.Fatal("BuildRef should reject an empty id")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnavailableStoreHealthCheckReturnsUnavailableError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
store := NewUnavailableStore("keyring backend disabled")
|
||||
|
||||
err := store.HealthCheck()
|
||||
if err == nil {
|
||||
t.Fatal("HealthCheck should return an unavailable error")
|
||||
}
|
||||
|
||||
if !IsUnavailable(err) {
|
||||
t.Fatalf("HealthCheck error should be detected by IsUnavailable, got %T", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyringStoreHealthCheckTreatsMissingProbeItemAsHealthy(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
store := &keyringStore{ring: fakeKeyringClient{getErr: keyring.ErrKeyNotFound}}
|
||||
if err := store.HealthCheck(); err != nil {
|
||||
t.Fatalf("HealthCheck should accept ErrKeyNotFound, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyringStoreHealthCheckReturnsUnavailableErrorOnBackendFailure(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
store := &keyringStore{ring: fakeKeyringClient{getErr: errors.New("backend offline")}}
|
||||
if err := store.HealthCheck(); err == nil || !IsUnavailable(err) {
|
||||
t.Fatalf("HealthCheck should wrap backend failures as unavailable, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewKeyringStoreReturnsUnavailableStoreWhenOpenFails(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
store := newKeyringStoreWithOpener("windows", func(cfg keyring.Config) (keyring.Keyring, error) {
|
||||
if len(cfg.AllowedBackends) != 1 || cfg.AllowedBackends[0] != keyring.WinCredBackend {
|
||||
t.Fatalf("unexpected backend config: %#v", cfg.AllowedBackends)
|
||||
}
|
||||
return nil, errors.New("no backend")
|
||||
})
|
||||
|
||||
if err := store.HealthCheck(); err == nil || !IsUnavailable(err) {
|
||||
t.Fatalf("expected unavailable store when open fails, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
type fakeKeyringClient struct {
|
||||
getErr error
|
||||
item keyring.Item
|
||||
removeErr error
|
||||
}
|
||||
|
||||
func (f fakeKeyringClient) Get(string) (keyring.Item, error) {
|
||||
if f.getErr != nil {
|
||||
return keyring.Item{}, f.getErr
|
||||
}
|
||||
return f.item, nil
|
||||
}
|
||||
|
||||
func (f fakeKeyringClient) Set(item keyring.Item) error {
|
||||
_ = item
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f fakeKeyringClient) Remove(string) error {
|
||||
return f.removeErr
|
||||
}
|
||||
|
||||
func (f fakeKeyringClient) GetMetadata(string) (keyring.Metadata, error) {
|
||||
return keyring.Metadata{}, nil
|
||||
}
|
||||
|
||||
func (f fakeKeyringClient) Keys() ([]string, error) {
|
||||
return nil, nil
|
||||
}
|
||||
69
internal/secretstore/store.go
Normal file
69
internal/secretstore/store.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package secretstore
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
serviceName = "gonavi"
|
||||
healthCheckRef = "oskeyring://gonavi/healthcheck/ping"
|
||||
)
|
||||
|
||||
type SecretStore interface {
|
||||
Put(ref string, payload []byte) error
|
||||
Get(ref string) ([]byte, error)
|
||||
Delete(ref string) error
|
||||
HealthCheck() error
|
||||
}
|
||||
|
||||
type UnavailableError struct {
|
||||
Reason string
|
||||
}
|
||||
|
||||
func (e *UnavailableError) Error() string {
|
||||
reason := strings.TrimSpace(e.Reason)
|
||||
if reason == "" {
|
||||
return "secret store unavailable"
|
||||
}
|
||||
return fmt.Sprintf("secret store unavailable: %s", reason)
|
||||
}
|
||||
|
||||
func IsUnavailable(err error) bool {
|
||||
var target *UnavailableError
|
||||
return errors.As(err, &target)
|
||||
}
|
||||
|
||||
type unavailableStore struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func NewUnavailableStore(reason string) SecretStore {
|
||||
return unavailableStore{err: &UnavailableError{Reason: strings.TrimSpace(reason)}}
|
||||
}
|
||||
|
||||
func (s unavailableStore) Put(string, []byte) error {
|
||||
return s.err
|
||||
}
|
||||
|
||||
func (s unavailableStore) Get(string) ([]byte, error) {
|
||||
return nil, s.err
|
||||
}
|
||||
|
||||
func (s unavailableStore) Delete(string) error {
|
||||
return s.err
|
||||
}
|
||||
|
||||
func (s unavailableStore) HealthCheck() error {
|
||||
return s.err
|
||||
}
|
||||
|
||||
func BuildRef(kind, id string) (string, error) {
|
||||
kind = strings.TrimSpace(kind)
|
||||
id = strings.TrimSpace(id)
|
||||
if kind == "" || id == "" {
|
||||
return "", fmt.Errorf("invalid secret ref")
|
||||
}
|
||||
return fmt.Sprintf("oskeyring://%s/%s/%s", serviceName, kind, id), nil
|
||||
}
|
||||
Reference in New Issue
Block a user