mirror of https://github.com/grafana/grafana
Chore: Refactor manifest verifier (#67218)
parent
62587eee88
commit
aa9838bd25
@ -1,286 +0,0 @@ |
||||
package manifestverifier |
||||
|
||||
import ( |
||||
"bytes" |
||||
"context" |
||||
"encoding/json" |
||||
"errors" |
||||
"fmt" |
||||
"net" |
||||
"net/http" |
||||
"net/url" |
||||
"sync" |
||||
"time" |
||||
|
||||
"github.com/ProtonMail/go-crypto/openpgp" |
||||
"github.com/ProtonMail/go-crypto/openpgp/clearsign" |
||||
"github.com/ProtonMail/go-crypto/openpgp/packet" |
||||
"github.com/grafana/grafana/pkg/plugins" |
||||
"github.com/grafana/grafana/pkg/plugins/config" |
||||
"github.com/grafana/grafana/pkg/plugins/log" |
||||
|
||||
// Only used for getting the feature flag value
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt" |
||||
) |
||||
|
||||
const publicKeySyncInterval = 10 * 24 * time.Hour // 10 days
|
||||
|
||||
// ManifestKeys is the database representation of public keys
|
||||
// used to verify plugin manifests.
|
||||
type ManifestKeys struct { |
||||
KeyID string `json:"keyId"` |
||||
PublicKey string `json:"public"` |
||||
Since int64 `json:"since"` |
||||
} |
||||
|
||||
type ManifestVerifier struct { |
||||
cfg *config.Cfg |
||||
mlog log.Logger |
||||
|
||||
lock sync.Mutex |
||||
cli http.Client |
||||
kv plugins.KeyStore |
||||
hasKeys bool |
||||
} |
||||
|
||||
func New(cfg *config.Cfg, mlog log.Logger, kv plugins.KeyStore) *ManifestVerifier { |
||||
pmv := &ManifestVerifier{ |
||||
cfg: cfg, |
||||
mlog: mlog, |
||||
cli: makeHttpClient(), |
||||
kv: kv, |
||||
} |
||||
return pmv |
||||
} |
||||
|
||||
// IsDisabled disables dynamic retrieval of public keys from the API server.
|
||||
func (pmv *ManifestVerifier) IsDisabled() bool { |
||||
return pmv.cfg == nil || pmv.cfg.Features == nil || !pmv.cfg.Features.IsEnabled(featuremgmt.FlagPluginsAPIManifestKey) |
||||
} |
||||
|
||||
func (pmv *ManifestVerifier) Run(ctx context.Context) error { |
||||
// do an initial update if necessary
|
||||
err := pmv.updateKeys(ctx) |
||||
if err != nil { |
||||
pmv.mlog.Error("Error downloading plugin manifest keys", "error", err) |
||||
} |
||||
|
||||
// calculate initial send delay
|
||||
lastUpdated, err := pmv.kv.GetLastUpdated(ctx) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
nextSendInterval := time.Until(lastUpdated.Add(publicKeySyncInterval)) |
||||
if nextSendInterval < time.Minute { |
||||
nextSendInterval = time.Minute |
||||
} |
||||
|
||||
downloadKeysTicker := time.NewTicker(nextSendInterval) |
||||
defer downloadKeysTicker.Stop() |
||||
|
||||
select { |
||||
case <-downloadKeysTicker.C: |
||||
err = pmv.updateKeys(ctx) |
||||
if err != nil { |
||||
pmv.mlog.Error("Error downloading plugin manifest keys", "error", err) |
||||
} |
||||
|
||||
if nextSendInterval != publicKeySyncInterval { |
||||
nextSendInterval = publicKeySyncInterval |
||||
downloadKeysTicker.Reset(nextSendInterval) |
||||
} |
||||
case <-ctx.Done(): |
||||
return ctx.Err() |
||||
} |
||||
|
||||
return ctx.Err() |
||||
} |
||||
|
||||
func (pmv *ManifestVerifier) updateKeys(ctx context.Context) error { |
||||
pmv.lock.Lock() |
||||
defer pmv.lock.Unlock() |
||||
|
||||
lastUpdated, err := pmv.kv.GetLastUpdated(ctx) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if time.Since(*lastUpdated) < publicKeySyncInterval { |
||||
// Cache is still valid
|
||||
return nil |
||||
} |
||||
|
||||
return pmv.downloadKeys(ctx) |
||||
} |
||||
|
||||
const publicKeyID = "7e4d0c6a708866e7" |
||||
const publicKeyText = `-----BEGIN PGP PUBLIC KEY BLOCK----- |
||||
Version: OpenPGP.js v4.10.1 |
||||
Comment: https://openpgpjs.org
|
||||
|
||||
xpMEXpTXXxMFK4EEACMEIwQBiOUQhvGbDLvndE0fEXaR0908wXzPGFpf0P0Z |
||||
HJ06tsq+0higIYHp7WTNJVEZtcwoYLcPRGaa9OQqbUU63BEyZdgAkPTz3RFd |
||||
5+TkDWZizDcaVFhzbDd500yTwexrpIrdInwC/jrgs7Zy/15h8KA59XXUkdmT |
||||
YB6TR+OA9RKME+dCJozNGUdyYWZhbmEgPGVuZ0BncmFmYW5hLmNvbT7CvAQQ |
||||
EwoAIAUCXpTXXwYLCQcIAwIEFQgKAgQWAgEAAhkBAhsDAh4BAAoJEH5NDGpw |
||||
iGbnaWoCCQGQ3SQnCkRWrG6XrMkXOKfDTX2ow9fuoErN46BeKmLM4f1EkDZQ |
||||
Tpq3SE8+My8B5BIH3SOcBeKzi3S57JHGBdFA+wIJAYWMrJNIvw8GeXne+oUo |
||||
NzzACdvfqXAZEp/HFMQhCKfEoWGJE8d2YmwY2+3GufVRTI5lQnZOHLE8L/Vc |
||||
1S5MXESjzpcEXpTXXxIFK4EEACMEIwQBtHX/SD5Qm3v4V92qpaIZQgtTX0sT |
||||
cFPjYWAHqsQ1iENrYN/vg1wU3ADlYATvydOQYvkTyT/tbDvx2Fse8PL84MQA |
||||
YKKQ6AJ3gLVvmeouZdU03YoV4MYaT8KbnJUkZQZkqdz2riOlySNI9CG3oYmv |
||||
omjUAtzgAgnCcurfGLZkkMxlmY8DAQoJwqQEGBMKAAkFAl6U118CGwwACgkQ |
||||
fk0ManCIZuc0jAIJAVw2xdLr4ZQqPUhubrUyFcqlWoW8dQoQagwO8s8ubmby |
||||
KuLA9FWJkfuuRQr+O9gHkDVCez3aism7zmJBqIOi38aNAgjJ3bo6leSS2jR/ |
||||
x5NqiKVi83tiXDPncDQYPymOnMhW0l7CVA7wj75HrFvvlRI/4MArlbsZ2tBn |
||||
N1c5v9v/4h6qeA== |
||||
=DNbR |
||||
-----END PGP PUBLIC KEY BLOCK----- |
||||
` |
||||
|
||||
// Retrieve the key from the API and store it in the database
|
||||
func (pmv *ManifestVerifier) downloadKeys(ctx context.Context) error { |
||||
var data struct { |
||||
Items []ManifestKeys |
||||
} |
||||
|
||||
url, err := url.JoinPath(pmv.cfg.GrafanaComURL, "/api/plugins/ci/keys") // nolint:gosec URL is provided by config
|
||||
if err != nil { |
||||
return err |
||||
} |
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
resp, err := pmv.cli.Do(req) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer func() { |
||||
err := resp.Body.Close() |
||||
if err != nil { |
||||
pmv.mlog.Warn("error closing response body", "error", err) |
||||
} |
||||
}() |
||||
|
||||
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { |
||||
return err |
||||
} |
||||
|
||||
if len(data.Items) == 0 { |
||||
return errors.New("missing public key") |
||||
} |
||||
|
||||
cachedKeys, err := pmv.kv.ListKeys(ctx) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
shouldKeep := make(map[string]bool) |
||||
for _, key := range data.Items { |
||||
err = pmv.kv.Set(ctx, key.KeyID, key.PublicKey) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
shouldKeep[key.KeyID] = true |
||||
} |
||||
|
||||
// Delete keys that are no longer in the API
|
||||
for _, key := range cachedKeys { |
||||
if !shouldKeep[key] { |
||||
err = pmv.kv.Del(ctx, key) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Update the last updated timestamp
|
||||
return pmv.kv.SetLastUpdated(ctx) |
||||
} |
||||
|
||||
func (pmv *ManifestVerifier) ensureKeys(ctx context.Context) error { |
||||
if pmv.hasKeys { |
||||
return nil |
||||
} |
||||
keys, err := pmv.kv.ListKeys(ctx) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if len(keys) == 0 { |
||||
// Populate with the default key
|
||||
err := pmv.kv.Set(ctx, publicKeyID, publicKeyText) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
} |
||||
pmv.hasKeys = true |
||||
return nil |
||||
} |
||||
|
||||
// getPublicKey loads public keys from:
|
||||
// - The hard-coded value if the feature flag is not enabled.
|
||||
// - A cached value from kv storage if it has been already retrieved. This cache is populated from the grafana.com API.
|
||||
func (pmv *ManifestVerifier) getPublicKey(ctx context.Context, keyID string) (string, error) { |
||||
if pmv.IsDisabled() { |
||||
return publicKeyText, nil |
||||
} |
||||
|
||||
pmv.lock.Lock() |
||||
defer pmv.lock.Unlock() |
||||
|
||||
err := pmv.ensureKeys(ctx) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
|
||||
key, exist, err := pmv.kv.Get(ctx, keyID) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
if exist { |
||||
return key, nil |
||||
} |
||||
|
||||
return "", fmt.Errorf("missing public key for %s", keyID) |
||||
} |
||||
|
||||
func (pmv *ManifestVerifier) Verify(ctx context.Context, keyID string, block *clearsign.Block) error { |
||||
publicKey, err := pmv.getPublicKey(ctx, keyID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewBufferString(publicKey)) |
||||
if err != nil { |
||||
return fmt.Errorf("%v: %w", "failed to parse public key", err) |
||||
} |
||||
|
||||
if _, err = openpgp.CheckDetachedSignature(keyring, |
||||
bytes.NewBuffer(block.Bytes), |
||||
block.ArmoredSignature.Body, &packet.Config{}); err != nil { |
||||
return fmt.Errorf("%v: %w", "failed to check signature", err) |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
// Same configuration as pkg/plugins/repo/client.go
|
||||
func makeHttpClient() http.Client { |
||||
tr := &http.Transport{ |
||||
Proxy: http.ProxyFromEnvironment, |
||||
DialContext: (&net.Dialer{ |
||||
Timeout: 30 * time.Second, |
||||
KeepAlive: 30 * time.Second, |
||||
}).DialContext, |
||||
MaxIdleConns: 100, |
||||
IdleConnTimeout: 90 * time.Second, |
||||
TLSHandshakeTimeout: 10 * time.Second, |
||||
ExpectContinueTimeout: 1 * time.Second, |
||||
} |
||||
|
||||
return http.Client{ |
||||
Timeout: 10 * time.Second, |
||||
Transport: tr, |
||||
} |
||||
} |
@ -0,0 +1,56 @@ |
||||
package statickey |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
|
||||
"github.com/grafana/grafana/pkg/plugins" |
||||
) |
||||
|
||||
const publicKeyID = "7e4d0c6a708866e7" |
||||
const publicKeyText = `-----BEGIN PGP PUBLIC KEY BLOCK----- |
||||
Version: OpenPGP.js v4.10.1 |
||||
Comment: https://openpgpjs.org
|
||||
|
||||
xpMEXpTXXxMFK4EEACMEIwQBiOUQhvGbDLvndE0fEXaR0908wXzPGFpf0P0Z |
||||
HJ06tsq+0higIYHp7WTNJVEZtcwoYLcPRGaa9OQqbUU63BEyZdgAkPTz3RFd |
||||
5+TkDWZizDcaVFhzbDd500yTwexrpIrdInwC/jrgs7Zy/15h8KA59XXUkdmT |
||||
YB6TR+OA9RKME+dCJozNGUdyYWZhbmEgPGVuZ0BncmFmYW5hLmNvbT7CvAQQ |
||||
EwoAIAUCXpTXXwYLCQcIAwIEFQgKAgQWAgEAAhkBAhsDAh4BAAoJEH5NDGpw |
||||
iGbnaWoCCQGQ3SQnCkRWrG6XrMkXOKfDTX2ow9fuoErN46BeKmLM4f1EkDZQ |
||||
Tpq3SE8+My8B5BIH3SOcBeKzi3S57JHGBdFA+wIJAYWMrJNIvw8GeXne+oUo |
||||
NzzACdvfqXAZEp/HFMQhCKfEoWGJE8d2YmwY2+3GufVRTI5lQnZOHLE8L/Vc |
||||
1S5MXESjzpcEXpTXXxIFK4EEACMEIwQBtHX/SD5Qm3v4V92qpaIZQgtTX0sT |
||||
cFPjYWAHqsQ1iENrYN/vg1wU3ADlYATvydOQYvkTyT/tbDvx2Fse8PL84MQA |
||||
YKKQ6AJ3gLVvmeouZdU03YoV4MYaT8KbnJUkZQZkqdz2riOlySNI9CG3oYmv |
||||
omjUAtzgAgnCcurfGLZkkMxlmY8DAQoJwqQEGBMKAAkFAl6U118CGwwACgkQ |
||||
fk0ManCIZuc0jAIJAVw2xdLr4ZQqPUhubrUyFcqlWoW8dQoQagwO8s8ubmby |
||||
KuLA9FWJkfuuRQr+O9gHkDVCez3aism7zmJBqIOi38aNAgjJ3bo6leSS2jR/ |
||||
x5NqiKVi83tiXDPncDQYPymOnMhW0l7CVA7wj75HrFvvlRI/4MArlbsZ2tBn |
||||
N1c5v9v/4h6qeA== |
||||
=DNbR |
||||
-----END PGP PUBLIC KEY BLOCK----- |
||||
` |
||||
|
||||
type KeyRetriever struct{} |
||||
|
||||
var _ plugins.KeyRetriever = (*KeyRetriever)(nil) |
||||
|
||||
func New() *KeyRetriever { |
||||
return &KeyRetriever{} |
||||
} |
||||
|
||||
func (kr *KeyRetriever) GetPublicKey(ctx context.Context, keyID string) (string, error) { |
||||
if keyID == publicKeyID { |
||||
return publicKeyText, nil |
||||
} |
||||
return "", fmt.Errorf("missing public key for %s", keyID) |
||||
} |
||||
|
||||
func GetDefaultKey() string { |
||||
return publicKeyText |
||||
} |
||||
|
||||
func GetDefaultKeyID() string { |
||||
return publicKeyID |
||||
} |
@ -0,0 +1,234 @@ |
||||
package dynamic |
||||
|
||||
import ( |
||||
"context" |
||||
"encoding/json" |
||||
"errors" |
||||
"fmt" |
||||
"net" |
||||
"net/http" |
||||
"net/url" |
||||
"sync" |
||||
"time" |
||||
|
||||
"github.com/grafana/grafana/pkg/plugins" |
||||
"github.com/grafana/grafana/pkg/plugins/config" |
||||
"github.com/grafana/grafana/pkg/plugins/log" |
||||
"github.com/grafana/grafana/pkg/plugins/manager/signature/statickey" |
||||
"github.com/grafana/grafana/pkg/services/featuremgmt" |
||||
) |
||||
|
||||
const publicKeySyncInterval = 10 * 24 * time.Hour // 10 days
|
||||
|
||||
// ManifestKeys is the database representation of public keys
|
||||
// used to verify plugin manifests.
|
||||
type ManifestKeys struct { |
||||
KeyID string `json:"keyId"` |
||||
PublicKey string `json:"public"` |
||||
Since int64 `json:"since"` |
||||
} |
||||
|
||||
type KeyRetriever struct { |
||||
cfg *config.Cfg |
||||
log log.Logger |
||||
|
||||
lock sync.Mutex |
||||
cli http.Client |
||||
kv plugins.KeyStore |
||||
hasKeys bool |
||||
} |
||||
|
||||
var _ plugins.KeyRetriever = (*KeyRetriever)(nil) |
||||
|
||||
func ProvideService(cfg *config.Cfg, kv plugins.KeyStore) *KeyRetriever { |
||||
kr := &KeyRetriever{ |
||||
cfg: cfg, |
||||
log: log.New("plugin.signature.key_retriever"), |
||||
cli: makeHttpClient(), |
||||
kv: kv, |
||||
} |
||||
return kr |
||||
} |
||||
|
||||
// IsDisabled disables dynamic retrieval of public keys from the API server.
|
||||
func (kr *KeyRetriever) IsDisabled() bool { |
||||
return !kr.cfg.Features.IsEnabled(featuremgmt.FlagPluginsAPIManifestKey) |
||||
} |
||||
|
||||
func (kr *KeyRetriever) Run(ctx context.Context) error { |
||||
// do an initial update if necessary
|
||||
err := kr.updateKeys(ctx) |
||||
if err != nil { |
||||
kr.log.Error("Error downloading plugin manifest keys", "error", err) |
||||
} |
||||
|
||||
// calculate initial send delay
|
||||
lastUpdated, err := kr.kv.GetLastUpdated(ctx) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
nextSendInterval := time.Until(lastUpdated.Add(publicKeySyncInterval)) |
||||
if nextSendInterval < time.Minute { |
||||
nextSendInterval = time.Minute |
||||
} |
||||
|
||||
downloadKeysTicker := time.NewTicker(nextSendInterval) |
||||
defer downloadKeysTicker.Stop() |
||||
|
||||
select { |
||||
case <-downloadKeysTicker.C: |
||||
err = kr.updateKeys(ctx) |
||||
if err != nil { |
||||
kr.log.Error("Error downloading plugin manifest keys", "error", err) |
||||
} |
||||
|
||||
if nextSendInterval != publicKeySyncInterval { |
||||
nextSendInterval = publicKeySyncInterval |
||||
downloadKeysTicker.Reset(nextSendInterval) |
||||
} |
||||
case <-ctx.Done(): |
||||
return ctx.Err() |
||||
} |
||||
|
||||
return ctx.Err() |
||||
} |
||||
|
||||
func (kr *KeyRetriever) updateKeys(ctx context.Context) error { |
||||
kr.lock.Lock() |
||||
defer kr.lock.Unlock() |
||||
|
||||
lastUpdated, err := kr.kv.GetLastUpdated(ctx) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if time.Since(*lastUpdated) < publicKeySyncInterval { |
||||
// Cache is still valid
|
||||
return nil |
||||
} |
||||
|
||||
return kr.downloadKeys(ctx) |
||||
} |
||||
|
||||
// Retrieve the key from the API and store it in the database
|
||||
func (kr *KeyRetriever) downloadKeys(ctx context.Context) error { |
||||
var data struct { |
||||
Items []ManifestKeys |
||||
} |
||||
|
||||
url, err := url.JoinPath(kr.cfg.GrafanaComURL, "/api/plugins/ci/keys") // nolint:gosec URL is provided by config
|
||||
if err != nil { |
||||
return err |
||||
} |
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
resp, err := kr.cli.Do(req) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer func() { |
||||
err := resp.Body.Close() |
||||
if err != nil { |
||||
kr.log.Warn("error closing response body", "error", err) |
||||
} |
||||
}() |
||||
|
||||
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { |
||||
return err |
||||
} |
||||
|
||||
if len(data.Items) == 0 { |
||||
return errors.New("missing public key") |
||||
} |
||||
|
||||
cachedKeys, err := kr.kv.ListKeys(ctx) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
shouldKeep := make(map[string]bool) |
||||
for _, key := range data.Items { |
||||
err = kr.kv.Set(ctx, key.KeyID, key.PublicKey) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
shouldKeep[key.KeyID] = true |
||||
} |
||||
|
||||
// Delete keys that are no longer in the API
|
||||
for _, key := range cachedKeys { |
||||
if !shouldKeep[key] { |
||||
err = kr.kv.Del(ctx, key) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Update the last updated timestamp
|
||||
return kr.kv.SetLastUpdated(ctx) |
||||
} |
||||
|
||||
func (kr *KeyRetriever) ensureKeys(ctx context.Context) error { |
||||
if kr.hasKeys { |
||||
return nil |
||||
} |
||||
keys, err := kr.kv.ListKeys(ctx) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if len(keys) == 0 { |
||||
// Populate with the default key
|
||||
err := kr.kv.Set(ctx, statickey.GetDefaultKeyID(), statickey.GetDefaultKey()) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
} |
||||
kr.hasKeys = true |
||||
return nil |
||||
} |
||||
|
||||
// GetPublicKey loads public keys from:
|
||||
// - The hard-coded value if the feature flag is not enabled.
|
||||
// - A cached value from kv storage if it has been already retrieved. This cache is populated from the grafana.com API.
|
||||
func (kr *KeyRetriever) GetPublicKey(ctx context.Context, keyID string) (string, error) { |
||||
kr.lock.Lock() |
||||
defer kr.lock.Unlock() |
||||
|
||||
err := kr.ensureKeys(ctx) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
|
||||
key, exist, err := kr.kv.Get(ctx, keyID) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
if exist { |
||||
return key, nil |
||||
} |
||||
|
||||
return "", fmt.Errorf("missing public key for %s", keyID) |
||||
} |
||||
|
||||
// Same configuration as pkg/plugins/repo/client.go
|
||||
func makeHttpClient() http.Client { |
||||
tr := &http.Transport{ |
||||
Proxy: http.ProxyFromEnvironment, |
||||
DialContext: (&net.Dialer{ |
||||
Timeout: 30 * time.Second, |
||||
KeepAlive: 30 * time.Second, |
||||
}).DialContext, |
||||
MaxIdleConns: 100, |
||||
IdleConnTimeout: 90 * time.Second, |
||||
TLSHandshakeTimeout: 10 * time.Second, |
||||
ExpectContinueTimeout: 1 * time.Second, |
||||
} |
||||
|
||||
return http.Client{ |
||||
Timeout: 10 * time.Second, |
||||
Transport: tr, |
||||
} |
||||
} |
@ -0,0 +1,29 @@ |
||||
package keyretriever |
||||
|
||||
import ( |
||||
"context" |
||||
|
||||
"github.com/grafana/grafana/pkg/plugins" |
||||
"github.com/grafana/grafana/pkg/plugins/manager/signature/statickey" |
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/keyretriever/dynamic" |
||||
) |
||||
|
||||
var _ plugins.KeyRetriever = (*Service)(nil) |
||||
|
||||
type Service struct { |
||||
kr plugins.KeyRetriever |
||||
} |
||||
|
||||
func ProvideService(dkr *dynamic.KeyRetriever) *Service { |
||||
s := &Service{} |
||||
if !dkr.IsDisabled() { |
||||
s.kr = dkr |
||||
} else { |
||||
s.kr = statickey.New() |
||||
} |
||||
return s |
||||
} |
||||
|
||||
func (kr *Service) GetPublicKey(ctx context.Context, keyID string) (string, error) { |
||||
return kr.kr.GetPublicKey(ctx, keyID) |
||||
} |
@ -0,0 +1,26 @@ |
||||
package keyretriever |
||||
|
||||
import ( |
||||
"context" |
||||
"testing" |
||||
|
||||
"github.com/grafana/grafana/pkg/infra/kvstore" |
||||
"github.com/grafana/grafana/pkg/plugins/config" |
||||
"github.com/grafana/grafana/pkg/plugins/manager/signature/statickey" |
||||
"github.com/grafana/grafana/pkg/services/featuremgmt" |
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/keyretriever/dynamic" |
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/keystore" |
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
func Test_GetPublicKey(t *testing.T) { |
||||
t.Run("it should return a static key", func(t *testing.T) { |
||||
cfg := &config.Cfg{ |
||||
Features: featuremgmt.WithFeatures(), |
||||
} |
||||
kr := ProvideService(dynamic.ProvideService(cfg, keystore.ProvideService(kvstore.NewFakeKVStore()))) |
||||
key, err := kr.GetPublicKey(context.Background(), statickey.GetDefaultKeyID()) |
||||
require.NoError(t, err) |
||||
require.Equal(t, statickey.GetDefaultKey(), key) |
||||
}) |
||||
} |
Loading…
Reference in new issue