Skip to content

Commit 47c54f0

Browse files
committed
#3663: add Prometheus metrics to crypto storage engine
1 parent c98a4f6 commit 47c54f0

File tree

4 files changed

+185
-57
lines changed

4 files changed

+185
-57
lines changed

crypto/crypto.go

Lines changed: 40 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"github.com/nuts-foundation/nuts-node/crypto/storage/azure"
2828
"github.com/nuts-foundation/nuts-node/storage"
2929
"github.com/nuts-foundation/nuts-node/storage/orm"
30+
"github.com/prometheus/client_golang/prometheus"
3031
"gorm.io/gorm"
3132
"path"
3233
"time"
@@ -65,6 +66,7 @@ func DefaultCryptoConfig() Config {
6566
}
6667

6768
var _ KeyStore = (*Crypto)(nil)
69+
var _ core.Runnable = (*Crypto)(nil)
6870

6971
// Crypto holds references to storage and needed config
7072
type Crypto struct {
@@ -74,6 +76,22 @@ type Crypto struct {
7476
storage storage.Engine
7577
}
7678

79+
func (client *Crypto) Start() error {
80+
for _, collector := range client.backend.(*spi.PrometheusWrapper).Collectors() {
81+
if err := prometheus.Register(collector); err != nil {
82+
return fmt.Errorf("register metric: %w", err)
83+
}
84+
}
85+
return nil
86+
}
87+
88+
func (client *Crypto) Shutdown() error {
89+
for _, collector := range client.backend.(*spi.PrometheusWrapper).Collectors() {
90+
_ = prometheus.Unregister(collector)
91+
}
92+
return nil
93+
}
94+
7795
func (client *Crypto) CheckHealth() map[string]core.Health {
7896
return client.backend.CheckHealth()
7997
}
@@ -94,49 +112,28 @@ func (client *Crypto) Config() interface{} {
94112
return &client.config
95113
}
96114

97-
func (client *Crypto) setupFSBackend(config core.ServerConfig) error {
115+
func (client *Crypto) setupFSBackend(config core.ServerConfig) (spi.Storage, error) {
98116
log.Logger().Info("Setting up FileSystem backend for storage of private key material. " +
99117
"Discouraged for production use unless backups and encryption is properly set up. Consider using the Hashicorp Vault backend.")
100118
fsPath := path.Join(config.Datadir, "crypto")
101-
fsBackend, err := fs.NewFileSystemBackend(fsPath)
102-
if err != nil {
103-
return err
104-
}
105-
client.backend = spi.NewValidatedKIDBackendWrapper(fsBackend, spi.KidPattern)
106-
return nil
119+
return fs.NewFileSystemBackend(fsPath)
107120
}
108121

109-
func (client *Crypto) setupStorageAPIBackend() error {
122+
func (client *Crypto) setupStorageAPIBackend() (spi.Storage, error) {
110123
log.Logger().Debug("Setting up StorageAPI backend for storage of private key material.")
111124
log.Logger().Warn("External key storage backend is deprecated and will be removed in the future.")
112-
apiBackend, err := external.NewAPIClient(client.config.External)
113-
if err != nil {
114-
return fmt.Errorf("unable to set up external crypto API client: %w", err)
115-
}
116-
client.backend = spi.NewValidatedKIDBackendWrapper(apiBackend, spi.KidPattern)
117-
return nil
125+
return external.NewAPIClient(client.config.External)
118126
}
119127

120-
func (client *Crypto) setupVaultBackend(_ core.ServerConfig) error {
128+
func (client *Crypto) setupVaultBackend(_ core.ServerConfig) (spi.Storage, error) {
121129
log.Logger().Debug("Setting up Vault backend for storage of private key material. " +
122130
"This feature is experimental and may change in the future.")
123-
vaultBackend, err := vault.NewVaultKVStorage(client.config.Vault)
124-
if err != nil {
125-
return err
126-
}
127-
128-
client.backend = spi.NewValidatedKIDBackendWrapper(vaultBackend, spi.KidPattern)
129-
return nil
131+
return vault.NewVaultKVStorage(client.config.Vault)
130132
}
131133

132-
func (client *Crypto) setupAzureKeyVaultBackend(_ core.ServerConfig) error {
134+
func (client *Crypto) setupAzureKeyVaultBackend(_ core.ServerConfig) (spi.Storage, error) {
133135
log.Logger().Debug("Setting up Azure Key Vault backend for storage of private key material.")
134-
azureBackend, err := azure.New(client.config.AzureKeyVault)
135-
if err != nil {
136-
return err
137-
}
138-
client.backend = spi.NewValidatedKIDBackendWrapper(azureBackend, spi.KidPattern)
139-
return nil
136+
return azure.New(client.config.AzureKeyVault)
140137
}
141138

142139
// List returns the KIDs of the private keys that are present in the key store.
@@ -163,24 +160,33 @@ func (client *Crypto) List(ctx context.Context) []string {
163160
func (client *Crypto) Configure(config core.ServerConfig) error {
164161
client.db = client.storage.GetSQLDatabase()
165162

163+
var backend spi.Storage
164+
var err error
166165
switch client.config.Storage {
167166
case fs.StorageType:
168-
return client.setupFSBackend(config)
167+
backend, err = client.setupFSBackend(config)
169168
case vault.StorageType:
170-
return client.setupVaultBackend(config)
169+
backend, err = client.setupVaultBackend(config)
171170
case azure.StorageType:
172-
return client.setupAzureKeyVaultBackend(config)
171+
backend, err = client.setupAzureKeyVaultBackend(config)
173172
case external.StorageType:
174-
return client.setupStorageAPIBackend()
173+
backend, err = client.setupStorageAPIBackend()
175174
case "":
176175
if config.Strictmode {
177176
return errors.New("backend must be explicitly set in strict mode")
178177
}
179178
// default to file system and run this setup again
180-
return client.setupFSBackend(config)
179+
backend, err = client.setupFSBackend(config)
181180
default:
182181
return fmt.Errorf("invalid config for crypto.storage. Available options are: vaultkv, fs, %s(experimental)", external.StorageType)
183182
}
183+
if err != nil {
184+
return fmt.Errorf("could not setup crypto backend (type=%s): %w", client.config.Storage, err)
185+
}
186+
187+
metricsWrapper := spi.NewPrometheusWrapper(spi.NewValidatedKIDBackendWrapper(backend, spi.KidPattern))
188+
client.backend = metricsWrapper
189+
return nil
184190
}
185191

186192
func (client *Crypto) Migrate() error {

crypto/crypto_test.go

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,19 @@ package crypto
2020

2121
import (
2222
"context"
23+
"crypto"
2324
"github.com/nuts-foundation/nuts-node/audit"
2425
"github.com/nuts-foundation/nuts-node/crypto/storage/fs"
2526
"github.com/nuts-foundation/nuts-node/crypto/storage/spi"
2627
"github.com/nuts-foundation/nuts-node/storage"
2728
"github.com/nuts-foundation/nuts-node/storage/orm"
29+
"github.com/prometheus/client_golang/prometheus"
2830
"github.com/sirupsen/logrus"
2931
"github.com/stretchr/testify/require"
3032
"net/http"
3133
"net/http/httptest"
34+
"os"
35+
"path"
3236
"reflect"
3337
"testing"
3438

@@ -191,16 +195,20 @@ func TestCrypto_Resolve(t *testing.T) {
191195
func TestCrypto_setupBackend(t *testing.T) {
192196
directory := io.TestDirectory(t)
193197
cfg := *core.NewServerConfig()
198+
cfg.Strictmode = false
194199
cfg.Datadir = directory
195200

196201
t.Run("backends should be wrapped", func(t *testing.T) {
197-
202+
client := createCrypto(t)
203+
err := client.Configure(cfg)
204+
require.NoError(t, err)
198205
t.Run("ok - fs backend is wrapped", func(t *testing.T) {
199-
client := createCrypto(t)
200-
err := client.setupFSBackend(cfg)
201-
require.NoError(t, err)
202206
storageType := reflect.TypeOf(client.backend).String()
203-
assert.Equal(t, "spi.wrapper", storageType)
207+
assert.Equal(t, "*spi.PrometheusWrapper", storageType)
208+
})
209+
t.Run("backend is wrapped in validating wrapper", func(t *testing.T) {
210+
err := client.backend.SavePrivateKey(context.Background(), "../not-allowed", nil)
211+
assert.EqualError(t, err, "invalid key ID: ../not-allowed")
204212
})
205213

206214
t.Run("ok - vault backend is wrapped", func(t *testing.T) {
@@ -211,10 +219,10 @@ func TestCrypto_setupBackend(t *testing.T) {
211219
defer s.Close()
212220
client := createCrypto(t)
213221
client.config.Vault.Address = s.URL
214-
err := client.setupVaultBackend(cfg)
222+
err := client.Configure(cfg)
215223
require.NoError(t, err)
216224
storageType := reflect.TypeOf(client.backend).String()
217-
assert.Equal(t, "spi.wrapper", storageType)
225+
assert.Equal(t, "*spi.PrometheusWrapper", storageType)
218226
})
219227
})
220228
}
@@ -266,3 +274,37 @@ func createCrypto(t *testing.T) *Crypto {
266274
}
267275
return &c
268276
}
277+
278+
func TestCrypto_StartAndShutdown(t *testing.T) {
279+
t.Run("metrics", func(t *testing.T) {
280+
storageEngine := storage.NewTestStorageEngine(t)
281+
instance := NewCryptoInstance(storageEngine)
282+
err := instance.Configure(core.TestServerConfig(func(config *core.ServerConfig) {
283+
config.Strictmode = false
284+
}))
285+
require.NoError(t, err)
286+
err = instance.Start()
287+
require.NoError(t, err)
288+
289+
// Generate some metrics
290+
_, _, err = instance.New(audit.TestContext(), func(key crypto.PublicKey) (string, error) {
291+
return "hello", nil
292+
})
293+
require.NoError(t, err)
294+
295+
stats := getPrometheusStats(t)
296+
assert.Contains(t, stats, "crypto_storage_op_duration_seconds_count{op=\"new_private_key\"} 1")
297+
298+
err = instance.Shutdown()
299+
assert.NoError(t, err)
300+
})
301+
}
302+
303+
func getPrometheusStats(t *testing.T) string {
304+
fileName := path.Join(t.TempDir(), "prometheus.txt")
305+
err := prometheus.WriteToTextfile(fileName, prometheus.Gatherers{prometheus.DefaultGatherer})
306+
require.NoError(t, err)
307+
data, err := os.ReadFile(fileName)
308+
require.NoError(t, err)
309+
return string(data)
310+
}

0 commit comments

Comments
 (0)