Encryption: Add Prometheus metrics (#48603)
This commit is contained in:
@@ -12,7 +12,6 @@ import (
|
|||||||
const ExporterName = "grafana"
|
const ExporterName = "grafana"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
||||||
// MInstanceStart is a metric counter for started instances
|
// MInstanceStart is a metric counter for started instances
|
||||||
MInstanceStart prometheus.Counter
|
MInstanceStart prometheus.Counter
|
||||||
|
|
||||||
@@ -191,6 +190,9 @@ var (
|
|||||||
|
|
||||||
// StatsTotalLibraryVariables is a metric of total number of library variables stored in Grafana.
|
// StatsTotalLibraryVariables is a metric of total number of library variables stored in Grafana.
|
||||||
StatsTotalLibraryVariables prometheus.Gauge
|
StatsTotalLibraryVariables prometheus.Gauge
|
||||||
|
|
||||||
|
// StatsTotalDataKeys is a metric of total number of data keys stored in Grafana.
|
||||||
|
StatsTotalDataKeys prometheus.Gauge
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -565,6 +567,12 @@ func init() {
|
|||||||
Help: "total amount of library variables in the database",
|
Help: "total amount of library variables in the database",
|
||||||
Namespace: ExporterName,
|
Namespace: ExporterName,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
StatsTotalDataKeys = prometheus.NewGauge(prometheus.GaugeOpts{
|
||||||
|
Name: "stat_totals_data_keys",
|
||||||
|
Help: "total amount of data keys in the database",
|
||||||
|
Namespace: ExporterName,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetBuildInformation sets the build information for this binary
|
// SetBuildInformation sets the build information for this binary
|
||||||
@@ -660,6 +668,7 @@ func initMetricVars() {
|
|||||||
MAccessEvaluationCount,
|
MAccessEvaluationCount,
|
||||||
StatsTotalLibraryPanels,
|
StatsTotalLibraryPanels,
|
||||||
StatsTotalLibraryVariables,
|
StatsTotalLibraryVariables,
|
||||||
|
StatsTotalDataKeys,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -138,6 +138,7 @@ func (s *Service) collect(ctx context.Context) (map[string]interface{}, error) {
|
|||||||
m["stats.folders_viewers_can_edit.count"] = statsQuery.Result.FoldersViewersCanEdit
|
m["stats.folders_viewers_can_edit.count"] = statsQuery.Result.FoldersViewersCanEdit
|
||||||
m["stats.folders_viewers_can_admin.count"] = statsQuery.Result.FoldersViewersCanAdmin
|
m["stats.folders_viewers_can_admin.count"] = statsQuery.Result.FoldersViewersCanAdmin
|
||||||
m["stats.api_keys.count"] = statsQuery.Result.APIKeys
|
m["stats.api_keys.count"] = statsQuery.Result.APIKeys
|
||||||
|
m["stats.data_keys.count"] = statsQuery.Result.DataKeys
|
||||||
|
|
||||||
ossEditionCount := 1
|
ossEditionCount := 1
|
||||||
enterpriseEditionCount := 0
|
enterpriseEditionCount := 0
|
||||||
@@ -326,6 +327,7 @@ func (s *Service) updateTotalStats(ctx context.Context) bool {
|
|||||||
metrics.StatsTotalAlertRules.Set(float64(statsQuery.Result.AlertRules))
|
metrics.StatsTotalAlertRules.Set(float64(statsQuery.Result.AlertRules))
|
||||||
metrics.StatsTotalLibraryPanels.Set(float64(statsQuery.Result.LibraryPanels))
|
metrics.StatsTotalLibraryPanels.Set(float64(statsQuery.Result.LibraryPanels))
|
||||||
metrics.StatsTotalLibraryVariables.Set(float64(statsQuery.Result.LibraryVariables))
|
metrics.StatsTotalLibraryVariables.Set(float64(statsQuery.Result.LibraryVariables))
|
||||||
|
metrics.StatsTotalDataKeys.Set(float64(statsQuery.Result.DataKeys))
|
||||||
|
|
||||||
dsStats := models.GetDataSourceStatsQuery{}
|
dsStats := models.GetDataSourceStatsQuery{}
|
||||||
if err := s.sqlstore.GetDataSourceStats(ctx, &dsStats); err != nil {
|
if err := s.sqlstore.GetDataSourceStats(ctx, &dsStats); err != nil {
|
||||||
|
|||||||
@@ -8,18 +8,17 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
|
sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
|
||||||
"github.com/grafana/grafana/pkg/registry"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/httpclient"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||||
|
"github.com/grafana/grafana/pkg/infra/httpclient"
|
||||||
"github.com/grafana/grafana/pkg/infra/usagestats"
|
"github.com/grafana/grafana/pkg/infra/usagestats"
|
||||||
"github.com/grafana/grafana/pkg/login/social"
|
"github.com/grafana/grafana/pkg/login/social"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
"github.com/grafana/grafana/pkg/plugins"
|
"github.com/grafana/grafana/pkg/plugins"
|
||||||
|
"github.com/grafana/grafana/pkg/registry"
|
||||||
"github.com/grafana/grafana/pkg/services/datasources"
|
"github.com/grafana/grafana/pkg/services/datasources"
|
||||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||||
@@ -281,6 +280,8 @@ func TestCollectingUsageStats(t *testing.T) {
|
|||||||
assert.EqualValues(t, 1, metrics["stats.packaging.deb.count"])
|
assert.EqualValues(t, 1, metrics["stats.packaging.deb.count"])
|
||||||
assert.EqualValues(t, 1, metrics["stats.distributor.hosted-grafana.count"])
|
assert.EqualValues(t, 1, metrics["stats.distributor.hosted-grafana.count"])
|
||||||
|
|
||||||
|
assert.EqualValues(t, 11, metrics["stats.data_keys.count"])
|
||||||
|
|
||||||
assert.InDelta(t, int64(65), metrics["stats.uptime"], 6)
|
assert.InDelta(t, int64(65), metrics["stats.uptime"], 6)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -323,6 +324,7 @@ func mockSystemStats(sqlStore *mockstore.SQLStoreMock) {
|
|||||||
FoldersViewersCanAdmin: 1,
|
FoldersViewersCanAdmin: 1,
|
||||||
FoldersViewersCanEdit: 5,
|
FoldersViewersCanEdit: 5,
|
||||||
APIKeys: 2,
|
APIKeys: 2,
|
||||||
|
DataKeys: 11,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ type SystemStats struct {
|
|||||||
DailyActiveEditors int64
|
DailyActiveEditors int64
|
||||||
DailyActiveViewers int64
|
DailyActiveViewers int64
|
||||||
DailyActiveSessions int64
|
DailyActiveSessions int64
|
||||||
|
DataKeys int64
|
||||||
}
|
}
|
||||||
|
|
||||||
type DataSourceStats struct {
|
type DataSourceStats struct {
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
package manager
|
package manager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -36,6 +39,11 @@ func (c *dataKeyCache) get(id string) ([]byte, bool) {
|
|||||||
defer c.RUnlock()
|
defer c.RUnlock()
|
||||||
|
|
||||||
entry, exists := c.entries[id]
|
entry, exists := c.entries[id]
|
||||||
|
|
||||||
|
cacheReadsCounter.With(prometheus.Labels{
|
||||||
|
"hit": strconv.FormatBool(exists),
|
||||||
|
}).Inc()
|
||||||
|
|
||||||
if !exists || entry.expired() {
|
if !exists || entry.expired() {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
@@ -16,6 +17,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/kmsproviders"
|
"github.com/grafana/grafana/pkg/services/kmsproviders"
|
||||||
"github.com/grafana/grafana/pkg/services/secrets"
|
"github.com/grafana/grafana/pkg/services/secrets"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
)
|
)
|
||||||
@@ -130,11 +132,20 @@ func (s *SecretsService) EncryptWithDBSession(ctx context.Context, payload []byt
|
|||||||
return s.enc.Encrypt(ctx, payload, setting.SecretKey)
|
return s.enc.Encrypt(ctx, payload, setting.SecretKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
defer func() {
|
||||||
|
opsCounter.With(prometheus.Labels{
|
||||||
|
"success": strconv.FormatBool(err == nil),
|
||||||
|
"operation": OpEncrypt,
|
||||||
|
}).Inc()
|
||||||
|
}()
|
||||||
|
|
||||||
// If encryption featuremgmt.FlagEnvelopeEncryption toggle is on, use envelope encryption
|
// If encryption featuremgmt.FlagEnvelopeEncryption toggle is on, use envelope encryption
|
||||||
scope := opt()
|
scope := opt()
|
||||||
keyName := s.keyName(scope)
|
keyName := s.keyName(scope)
|
||||||
|
|
||||||
dataKey, err := s.dataKey(ctx, keyName)
|
var dataKey []byte
|
||||||
|
dataKey, err = s.dataKey(ctx, keyName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, secrets.ErrDataKeyNotFound) {
|
if errors.Is(err, secrets.ErrDataKeyNotFound) {
|
||||||
dataKey, err = s.newDataKey(ctx, keyName, scope, sess)
|
dataKey, err = s.newDataKey(ctx, keyName, scope, sess)
|
||||||
@@ -146,7 +157,8 @@ func (s *SecretsService) EncryptWithDBSession(ctx context.Context, payload []byt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
encrypted, err := s.enc.Encrypt(ctx, payload, string(dataKey))
|
var encrypted []byte
|
||||||
|
encrypted, err = s.enc.Encrypt(ctx, payload, string(dataKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -174,8 +186,17 @@ func (s *SecretsService) Decrypt(ctx context.Context, payload []byte) ([]byte, e
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If encryption featuremgmt.FlagEnvelopeEncryption toggle is on, use envelope encryption
|
// If encryption featuremgmt.FlagEnvelopeEncryption toggle is on, use envelope encryption
|
||||||
|
var err error
|
||||||
|
defer func() {
|
||||||
|
opsCounter.With(prometheus.Labels{
|
||||||
|
"success": strconv.FormatBool(err == nil),
|
||||||
|
"operation": OpDecrypt,
|
||||||
|
}).Inc()
|
||||||
|
}()
|
||||||
|
|
||||||
if len(payload) == 0 {
|
if len(payload) == 0 {
|
||||||
return nil, fmt.Errorf("unable to decrypt empty payload")
|
err = fmt.Errorf("unable to decrypt empty payload")
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var dataKey []byte
|
var dataKey []byte
|
||||||
@@ -187,12 +208,13 @@ func (s *SecretsService) Decrypt(ctx context.Context, payload []byte) ([]byte, e
|
|||||||
payload = payload[1:]
|
payload = payload[1:]
|
||||||
endOfKey := bytes.Index(payload, []byte{'#'})
|
endOfKey := bytes.Index(payload, []byte{'#'})
|
||||||
if endOfKey == -1 {
|
if endOfKey == -1 {
|
||||||
return nil, fmt.Errorf("could not find valid key in encrypted payload")
|
err = fmt.Errorf("could not find valid key in encrypted payload")
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
b64Key := payload[:endOfKey]
|
b64Key := payload[:endOfKey]
|
||||||
payload = payload[endOfKey+1:]
|
payload = payload[endOfKey+1:]
|
||||||
key := make([]byte, b64.DecodedLen(len(b64Key)))
|
key := make([]byte, b64.DecodedLen(len(b64Key)))
|
||||||
_, err := b64.Decode(key, b64Key)
|
_, err = b64.Decode(key, b64Key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -204,7 +226,10 @@ func (s *SecretsService) Decrypt(ctx context.Context, payload []byte) ([]byte, e
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.enc.Decrypt(ctx, payload, string(dataKey))
|
var decrypted []byte
|
||||||
|
decrypted, err = s.enc.Decrypt(ctx, payload, string(dataKey))
|
||||||
|
|
||||||
|
return decrypted, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SecretsService) EncryptJsonData(ctx context.Context, kv map[string]string, opt secrets.EncryptionOptions) (map[string][]byte, error) {
|
func (s *SecretsService) EncryptJsonData(ctx context.Context, kv map[string]string, opt secrets.EncryptionOptions) (map[string][]byte, error) {
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package manager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/grafana/grafana/pkg/infra/metrics"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
OpEncrypt = "encrypt"
|
||||||
|
OpDecrypt = "decrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
opsCounter = prometheus.NewCounterVec(
|
||||||
|
prometheus.CounterOpts{
|
||||||
|
Namespace: metrics.ExporterName,
|
||||||
|
Name: "encryption_ops_total",
|
||||||
|
Help: "A counter for encryption operations",
|
||||||
|
},
|
||||||
|
[]string{"success", "operation"},
|
||||||
|
)
|
||||||
|
cacheReadsCounter = prometheus.NewCounterVec(
|
||||||
|
prometheus.CounterOpts{
|
||||||
|
Namespace: metrics.ExporterName,
|
||||||
|
Name: "encryption_cache_reads_total",
|
||||||
|
Help: "A counter for encryption cache reads",
|
||||||
|
},
|
||||||
|
[]string{"hit"},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
prometheus.MustRegister(
|
||||||
|
opsCounter,
|
||||||
|
cacheReadsCounter,
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -102,6 +102,7 @@ func (ss *SQLStore) GetSystemStats(ctx context.Context, query *models.GetSystemS
|
|||||||
sb.Write(`(SELECT COUNT(id) FROM ` + dialect.Quote("api_key") + `WHERE service_account_id IS NULL) AS api_keys,`)
|
sb.Write(`(SELECT COUNT(id) FROM ` + dialect.Quote("api_key") + `WHERE service_account_id IS NULL) AS api_keys,`)
|
||||||
sb.Write(`(SELECT COUNT(id) FROM `+dialect.Quote("library_element")+` WHERE kind = ?) AS library_panels,`, models.PanelElement)
|
sb.Write(`(SELECT COUNT(id) FROM `+dialect.Quote("library_element")+` WHERE kind = ?) AS library_panels,`, models.PanelElement)
|
||||||
sb.Write(`(SELECT COUNT(id) FROM `+dialect.Quote("library_element")+` WHERE kind = ?) AS library_variables,`, models.VariableElement)
|
sb.Write(`(SELECT COUNT(id) FROM `+dialect.Quote("library_element")+` WHERE kind = ?) AS library_variables,`, models.VariableElement)
|
||||||
|
sb.Write(`(SELECT COUNT(*) FROM ` + dialect.Quote("data_keys") + `) AS data_keys,`)
|
||||||
|
|
||||||
sb.Write(ss.roleCounterSQL(ctx))
|
sb.Write(ss.roleCounterSQL(ctx))
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user