mirror of
https://github.com/grafana/grafana.git
synced 2025-02-09 23:16:16 -06:00
Auth: Add SigningKeys Service (#64343)
* Add key service Co-authored-by: Misi <mgyongyosi@users.noreply.github.com> * Wire the service * Rename Service * Implement GetJWKS * Slipt interface and implementation Co-authored-by: Misi <mgyongyosi@users.noreply.github.com> * Change implementation, add tests * Align to the expected package hierarchy * Update CODEOWNERS * Align names and fix wire.go * Update pkg/services/signingkeys/signingkeysimpl/service.go Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com> * Update pkg/services/signingkeys/signingkeysimpl/service_test.go Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com> * Update pkg/services/signingkeys/signingkeysimpl/service_test.go Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com> * Update pkg/services/signingkeys/signingkeysimpl/service_test.go Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com> * Add AddPrivateKey method to SigningKeysService * Align tests to the guidelines * Add test for GetJWKS() method * Add comments to the interface * Add FakeSigningKeysService --------- Co-authored-by: Misi <mgyongyosi@users.noreply.github.com> Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com>
This commit is contained in:
parent
3c2a69c82c
commit
4027254b87
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@ -522,6 +522,7 @@ lerna.json @grafana/frontend-ops
|
||||
/pkg/services/anonymous/ @grafana/grafana-authnz-team
|
||||
/pkg/services/auth/ @grafana/grafana-authnz-team
|
||||
/pkg/services/authn/ @grafana/grafana-authnz-team
|
||||
/pkg/services/signingkeys/ @grafana/grafana-authnz-team
|
||||
/pkg/services/dashboards/accesscontrol.go @grafana/grafana-authnz-team
|
||||
/pkg/services/datasources/permissions/ @grafana/grafana-authnz-team
|
||||
/pkg/services/guardian/ @grafana/grafana-authnz-team
|
||||
|
@ -115,6 +115,8 @@ import (
|
||||
serviceaccountsretriever "github.com/grafana/grafana/pkg/services/serviceaccounts/retriever"
|
||||
"github.com/grafana/grafana/pkg/services/shorturls"
|
||||
"github.com/grafana/grafana/pkg/services/shorturls/shorturlimpl"
|
||||
"github.com/grafana/grafana/pkg/services/signingkeys"
|
||||
"github.com/grafana/grafana/pkg/services/signingkeys/signingkeysimpl"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
starApi "github.com/grafana/grafana/pkg/services/star/api"
|
||||
"github.com/grafana/grafana/pkg/services/star/starimpl"
|
||||
@ -359,6 +361,8 @@ var wireBasicSet = wire.NewSet(
|
||||
supportbundlesimpl.ProvideService,
|
||||
loggermw.Provide,
|
||||
modules.WireSet,
|
||||
signingkeysimpl.ProvideEmbeddedSigningKeysService,
|
||||
wire.Bind(new(signingkeys.Service), new(*signingkeysimpl.Service)),
|
||||
)
|
||||
|
||||
var wireSet = wire.NewSet(
|
||||
|
9
pkg/services/signingkeys/error.go
Normal file
9
pkg/services/signingkeys/error.go
Normal file
@ -0,0 +1,9 @@
|
||||
package signingkeys
|
||||
|
||||
import "github.com/grafana/grafana/pkg/util/errutil"
|
||||
|
||||
var (
|
||||
ErrSigningKeyNotFound = errutil.NewBase(errutil.StatusNotFound, "signingkeys.keyNotFound")
|
||||
ErrSigningKeyAlreadyExists = errutil.NewBase(errutil.StatusBadRequest, "signingkeys.keyAlreadyExists")
|
||||
ErrKeyGenerationFailed = errutil.NewBase(errutil.StatusInternal, "signingkeys.keyGenerationFailed")
|
||||
)
|
32
pkg/services/signingkeys/signingkeys.go
Normal file
32
pkg/services/signingkeys/signingkeys.go
Normal file
@ -0,0 +1,32 @@
|
||||
// Package signingkeys implements the SigningKeys service which is responsible for managing
|
||||
// the signing keys used to sign and verify JWT tokens.
|
||||
//
|
||||
// The service is under active development and is not yet ready for production use.
|
||||
//
|
||||
// Currently, it only supports RSA keys and the keys are stored in memory.
|
||||
|
||||
package signingkeys
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
|
||||
"github.com/go-jose/go-jose/v3"
|
||||
)
|
||||
|
||||
// Service provides functionality for managing signing keys used to sign and verify JWT tokens.
|
||||
//
|
||||
// The service is under active development and is not yet ready for production use.
|
||||
type Service interface {
|
||||
// GetJWKS returns the JSON Web Key Set (JWKS) with all the keys that can be used to verify tokens (public keys)
|
||||
GetJWKS() jose.JSONWebKeySet
|
||||
// GetJWK returns the JSON Web Key (JWK) with the specified key ID which can be used to verify tokens (public key)
|
||||
GetJWK(keyID string) (jose.JSONWebKey, error)
|
||||
// GetPublicKey returns the public key with the specified key ID
|
||||
GetPublicKey(keyID string) (crypto.PublicKey, error)
|
||||
// GetPrivateKey returns the private key with the specified key ID
|
||||
GetPrivateKey(keyID string) (crypto.PrivateKey, error)
|
||||
// GetServerPrivateKey returns the private key used to sign tokens
|
||||
GetServerPrivateKey() (crypto.PrivateKey, error)
|
||||
// AddPrivateKey adds a private key to the service
|
||||
AddPrivateKey(keyID string, privateKey crypto.PrivateKey) error
|
||||
}
|
113
pkg/services/signingkeys/signingkeysimpl/service.go
Normal file
113
pkg/services/signingkeys/signingkeysimpl/service.go
Normal file
@ -0,0 +1,113 @@
|
||||
package signingkeysimpl
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
|
||||
"github.com/go-jose/go-jose/v3"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/signingkeys"
|
||||
)
|
||||
|
||||
const (
|
||||
serverPrivateKeyID = "default"
|
||||
)
|
||||
|
||||
var _ signingkeys.Service = new(Service)
|
||||
|
||||
func ProvideEmbeddedSigningKeysService(features *featuremgmt.FeatureManager) (*Service, error) {
|
||||
s := &Service{
|
||||
log: log.New("auth.key_service"),
|
||||
keys: map[string]crypto.Signer{},
|
||||
}
|
||||
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, 4096)
|
||||
if err != nil {
|
||||
s.log.Error("Error generating private key", "err", err)
|
||||
return nil, signingkeys.ErrKeyGenerationFailed.Errorf("Error generating private key: %v", err)
|
||||
}
|
||||
|
||||
if err := s.AddPrivateKey(serverPrivateKeyID, privateKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Service provides functionality for managing signing keys used to sign and verify JWT tokens for
|
||||
// the OSS version of Grafana.
|
||||
//
|
||||
// The service is under active development and is not yet ready for production use.
|
||||
type Service struct {
|
||||
log log.Logger
|
||||
keys map[string]crypto.Signer
|
||||
}
|
||||
|
||||
// GetJWKS returns the JSON Web Key Set (JWKS) with all the keys that can be used to verify tokens (public keys)
|
||||
func (s *Service) GetJWKS() jose.JSONWebKeySet {
|
||||
result := jose.JSONWebKeySet{}
|
||||
|
||||
for keyID := range s.keys {
|
||||
// Skip error check because keyID must be a valid key ID
|
||||
jwk, _ := s.GetJWK(keyID)
|
||||
result.Keys = append(result.Keys, jwk)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// GetJWK returns the JSON Web Key (JWK) with the specified key ID which can be used to verify tokens (public key)
|
||||
func (s *Service) GetJWK(keyID string) (jose.JSONWebKey, error) {
|
||||
privateKey, ok := s.keys[keyID]
|
||||
if !ok {
|
||||
s.log.Error("The specified key was not found", "keyID", keyID)
|
||||
return jose.JSONWebKey{}, signingkeys.ErrSigningKeyNotFound.Errorf("The specified key was not found: %s", keyID)
|
||||
}
|
||||
|
||||
result := jose.JSONWebKey{
|
||||
Key: privateKey.Public(),
|
||||
Use: "sig",
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetPublicKey returns the public key with the specified key ID
|
||||
func (s *Service) GetPublicKey(keyID string) (crypto.PublicKey, error) {
|
||||
privateKey, ok := s.keys[keyID]
|
||||
if !ok {
|
||||
s.log.Error("The specified key was not found", "keyID", keyID)
|
||||
return nil, signingkeys.ErrSigningKeyNotFound.Errorf("The specified key was not found: %s", keyID)
|
||||
}
|
||||
|
||||
return privateKey.Public(), nil
|
||||
}
|
||||
|
||||
// GetPrivateKey returns the private key with the specified key ID
|
||||
func (s *Service) GetPrivateKey(keyID string) (crypto.PrivateKey, error) {
|
||||
privateKey, ok := s.keys[keyID]
|
||||
if !ok {
|
||||
s.log.Error("The specified key was not found", "keyID", keyID)
|
||||
return nil, signingkeys.ErrSigningKeyNotFound.Errorf("The specified key was not found: %s", keyID)
|
||||
}
|
||||
|
||||
return privateKey, nil
|
||||
}
|
||||
|
||||
// AddPrivateKey adds a private key to the service
|
||||
func (s *Service) AddPrivateKey(keyID string, privateKey crypto.PrivateKey) error {
|
||||
if _, ok := s.keys[keyID]; ok {
|
||||
s.log.Error("The specified key ID is already in use", "keyID", keyID)
|
||||
return signingkeys.ErrSigningKeyAlreadyExists.Errorf("The specified key ID is already in use: %s", keyID)
|
||||
}
|
||||
s.keys[keyID] = privateKey.(crypto.Signer)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetServerPrivateKey returns the private key used to sign tokens
|
||||
func (s *Service) GetServerPrivateKey() (crypto.PrivateKey, error) {
|
||||
return s.GetPrivateKey(serverPrivateKeyID)
|
||||
}
|
283
pkg/services/signingkeys/signingkeysimpl/service_test.go
Normal file
283
pkg/services/signingkeys/signingkeysimpl/service_test.go
Normal file
@ -0,0 +1,283 @@
|
||||
package signingkeysimpl
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/go-jose/go-jose/v3"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
privateKeyPem = `-----BEGIN RSA PRIVATE KEY-----
|
||||
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
|
||||
-----END RSA PRIVATE KEY-----`
|
||||
)
|
||||
|
||||
func getPrivateKey(t *testing.T) *rsa.PrivateKey {
|
||||
pemBlock, _ := pem.Decode([]byte(privateKeyPem))
|
||||
privateKey, err := x509.ParsePKCS1PrivateKey(pemBlock.Bytes)
|
||||
require.NoError(t, err)
|
||||
return privateKey
|
||||
}
|
||||
|
||||
func setupTestService(t *testing.T) *Service {
|
||||
svc := &Service{
|
||||
log: log.NewNopLogger(),
|
||||
keys: map[string]crypto.Signer{serverPrivateKeyID: getPrivateKey(t)},
|
||||
}
|
||||
return svc
|
||||
}
|
||||
|
||||
func TestEmbeddedKeyService_GetJWK(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
keyID string
|
||||
want jose.JSONWebKey
|
||||
wantErr bool
|
||||
}{
|
||||
{name: "creates a JSON Web Key successfully",
|
||||
keyID: "default",
|
||||
want: jose.JSONWebKey{
|
||||
Key: getPrivateKey(t).Public(),
|
||||
Use: "sig",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{name: "returns error when the specified key was not found",
|
||||
keyID: "not-existing-key-id",
|
||||
want: jose.JSONWebKey{},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
svc := setupTestService(t)
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := svc.GetJWK(tt.keyID)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, got, tt.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmbeddedKeyService_GetJWK_OnlyPublicKeyShared(t *testing.T) {
|
||||
svc := setupTestService(t)
|
||||
jwk, err := svc.GetJWK("default")
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
jwkJson, err := jwk.MarshalJSON()
|
||||
require.NoError(t, err)
|
||||
|
||||
kvs := make(map[string]interface{})
|
||||
err = json.Unmarshal(jwkJson, &kvs)
|
||||
require.NoError(t, err)
|
||||
|
||||
// check that the private key is not shared
|
||||
require.NotContains(t, kvs, "d")
|
||||
require.NotContains(t, kvs, "p")
|
||||
require.NotContains(t, kvs, "q")
|
||||
}
|
||||
|
||||
func TestEmbeddedKeyService_GetJWKS(t *testing.T) {
|
||||
svc := &Service{
|
||||
log: log.NewNopLogger(),
|
||||
keys: map[string]crypto.Signer{
|
||||
serverPrivateKeyID: getPrivateKey(t),
|
||||
"other": getPrivateKey(t),
|
||||
},
|
||||
}
|
||||
jwk := svc.GetJWKS()
|
||||
|
||||
require.Equal(t, 2, len(jwk.Keys))
|
||||
}
|
||||
|
||||
func TestEmbeddedKeyService_GetJWKS_OnlyPublicKeyShared(t *testing.T) {
|
||||
svc := setupTestService(t)
|
||||
jwks := svc.GetJWKS()
|
||||
|
||||
jwksJson, err := json.Marshal(jwks)
|
||||
require.NoError(t, err)
|
||||
|
||||
type keys struct {
|
||||
Keys []map[string]interface{} `json:"keys"`
|
||||
}
|
||||
|
||||
var kvs keys
|
||||
err = json.Unmarshal(jwksJson, &kvs)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, kv := range kvs.Keys {
|
||||
// check that the private key is not shared
|
||||
require.NotContains(t, kv, "d")
|
||||
require.NotContains(t, kv, "p")
|
||||
require.NotContains(t, kv, "q")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmbeddedKeyService_GetPublicKey(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
keyID string
|
||||
want crypto.PublicKey
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "returns the public key successfully",
|
||||
keyID: "default",
|
||||
want: getPrivateKey(t).Public(),
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "returns error when the specified key was not found",
|
||||
keyID: "not-existent-key-id",
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
svc := setupTestService(t)
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := svc.GetPublicKey(tt.keyID)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, got, tt.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmbeddedKeyService_GetPrivateKey(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
keyID string
|
||||
want crypto.PrivateKey
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "returns the private key successfully",
|
||||
keyID: "default",
|
||||
want: getPrivateKey(t),
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "returns error when the specified key was not found",
|
||||
keyID: "not-existent-key-id",
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
svc := setupTestService(t)
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := svc.GetPrivateKey(tt.keyID)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, got, tt.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmbeddedKeyService_AddPrivateKey(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
keyID string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "adds the private key successfully",
|
||||
keyID: "new-key-id",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "returns error when the specified key is already in the store",
|
||||
keyID: serverPrivateKeyID,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
svc := setupTestService(t)
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := svc.AddPrivateKey(tt.keyID, &dummyPrivateKey{})
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type dummyPrivateKey struct {
|
||||
}
|
||||
|
||||
func (d dummyPrivateKey) Public() crypto.PublicKey {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (d dummyPrivateKey) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
|
||||
return nil, nil
|
||||
}
|
48
pkg/services/signingkeys/signingkeystest/fake.go
Normal file
48
pkg/services/signingkeys/signingkeystest/fake.go
Normal file
@ -0,0 +1,48 @@
|
||||
package signingkeystest
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
|
||||
"github.com/go-jose/go-jose/v3"
|
||||
)
|
||||
|
||||
type FakeSigningKeysService struct {
|
||||
ExpectedJSONWebKeySet jose.JSONWebKeySet
|
||||
ExpectedJSONWebKey jose.JSONWebKey
|
||||
ExpectedKeys map[string]crypto.Signer
|
||||
ExpectedServerPrivateKey crypto.PrivateKey
|
||||
ExpectedError error
|
||||
}
|
||||
|
||||
func (s *FakeSigningKeysService) GetJWKS() jose.JSONWebKeySet {
|
||||
return s.ExpectedJSONWebKeySet
|
||||
}
|
||||
|
||||
// GetJWK returns the JSON Web Key (JWK) with the specified key ID which can be used to verify tokens (public key)
|
||||
func (s *FakeSigningKeysService) GetJWK(keyID string) (jose.JSONWebKey, error) {
|
||||
return s.ExpectedJSONWebKey, s.ExpectedError
|
||||
}
|
||||
|
||||
// GetPublicKey returns the public key with the specified key ID
|
||||
func (s *FakeSigningKeysService) GetPublicKey(keyID string) (crypto.PublicKey, error) {
|
||||
return s.ExpectedKeys[keyID].Public(), s.ExpectedError
|
||||
}
|
||||
|
||||
// GetPrivateKey returns the private key with the specified key ID
|
||||
func (s *FakeSigningKeysService) GetPrivateKey(keyID string) (crypto.PrivateKey, error) {
|
||||
return s.ExpectedKeys[keyID], s.ExpectedError
|
||||
}
|
||||
|
||||
// GetServerPrivateKey returns the private key used to sign tokens
|
||||
func (s *FakeSigningKeysService) GetServerPrivateKey() (crypto.PrivateKey, error) {
|
||||
return s.ExpectedServerPrivateKey, s.ExpectedError
|
||||
}
|
||||
|
||||
// AddPrivateKey adds a private key to the service
|
||||
func (s *FakeSigningKeysService) AddPrivateKey(keyID string, privateKey crypto.PrivateKey) error {
|
||||
if s.ExpectedError != nil {
|
||||
return s.ExpectedError
|
||||
}
|
||||
s.ExpectedKeys[keyID] = privateKey.(crypto.Signer)
|
||||
return nil
|
||||
}
|
Loading…
Reference in New Issue
Block a user