2023-04-17 11:42:37 +02:00
|
|
|
package signingkeysimpl
|
|
|
|
|
|
|
|
|
|
import (
|
2023-10-04 10:37:27 +02:00
|
|
|
"context"
|
2023-04-17 11:42:37 +02:00
|
|
|
"crypto"
|
2023-05-25 15:38:30 +02:00
|
|
|
"crypto/ecdsa"
|
|
|
|
|
"crypto/elliptic"
|
2023-04-17 11:42:37 +02:00
|
|
|
"crypto/rand"
|
2023-10-04 10:37:27 +02:00
|
|
|
"errors"
|
|
|
|
|
"strings"
|
|
|
|
|
"time"
|
2023-04-17 11:42:37 +02:00
|
|
|
|
|
|
|
|
"github.com/go-jose/go-jose/v3"
|
|
|
|
|
|
2023-10-04 10:37:27 +02:00
|
|
|
"github.com/grafana/grafana/pkg/infra/db"
|
2023-04-17 11:42:37 +02:00
|
|
|
"github.com/grafana/grafana/pkg/infra/log"
|
2023-10-04 10:37:27 +02:00
|
|
|
"github.com/grafana/grafana/pkg/infra/remotecache"
|
|
|
|
|
"github.com/grafana/grafana/pkg/services/secrets"
|
2023-04-17 11:42:37 +02:00
|
|
|
"github.com/grafana/grafana/pkg/services/signingkeys"
|
2023-10-04 10:37:27 +02:00
|
|
|
"github.com/grafana/grafana/pkg/services/signingkeys/signingkeystore"
|
2023-04-17 11:42:37 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var _ signingkeys.Service = new(Service)
|
|
|
|
|
|
2023-10-04 10:37:27 +02:00
|
|
|
func ProvideEmbeddedSigningKeysService(dbStore db.DB, secretsService secrets.Service,
|
|
|
|
|
remoteCache remotecache.CacheStorage,
|
|
|
|
|
) (*Service, error) {
|
2023-04-17 11:42:37 +02:00
|
|
|
s := &Service{
|
2023-10-04 10:37:27 +02:00
|
|
|
log: log.New("auth.key_service"),
|
|
|
|
|
store: signingkeystore.NewSigningKeyStore(dbStore, secretsService),
|
|
|
|
|
remoteCache: remoteCache,
|
2023-04-17 11:42:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 {
|
2023-10-04 10:37:27 +02:00
|
|
|
log log.Logger
|
|
|
|
|
store signingkeystore.SigningStore
|
|
|
|
|
remoteCache remotecache.CacheStorage
|
2023-04-17 11:42:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GetJWKS returns the JSON Web Key Set (JWKS) with all the keys that can be used to verify tokens (public keys)
|
2023-10-04 10:37:27 +02:00
|
|
|
func (s *Service) GetJWKS(ctx context.Context) (jose.JSONWebKeySet, error) {
|
|
|
|
|
jwks, err := s.store.GetJWKS(ctx)
|
|
|
|
|
return jwks, err
|
2023-04-17 11:42:37 +02:00
|
|
|
}
|
|
|
|
|
|
2023-10-04 10:37:27 +02:00
|
|
|
// GetOrCreatePrivateKey returns the private key with the specified key ID. If the key does not exist, it will be
|
|
|
|
|
// created with the specified algorithm.
|
|
|
|
|
// The key will be automatically rotated at the beginning of each month. The previous key will be kept for 30 days.
|
|
|
|
|
func (s *Service) GetOrCreatePrivateKey(ctx context.Context,
|
|
|
|
|
keyPrefix string, alg jose.SignatureAlgorithm) (string, crypto.Signer, error) {
|
|
|
|
|
if alg != jose.ES256 {
|
|
|
|
|
s.log.Error("Only ES256 is supported", "alg", alg)
|
|
|
|
|
return "", nil, signingkeys.ErrKeyGenerationFailed.Errorf("Only ES256 is supported: %v", alg)
|
2023-04-17 11:42:37 +02:00
|
|
|
}
|
|
|
|
|
|
2023-10-04 10:37:27 +02:00
|
|
|
keyID := keyMonthScopedID(keyPrefix, alg)
|
|
|
|
|
signer, err := s.store.GetPrivateKey(ctx, keyID)
|
|
|
|
|
if err == nil {
|
|
|
|
|
return keyID, signer, nil
|
2023-04-17 11:42:37 +02:00
|
|
|
}
|
2023-10-04 10:37:27 +02:00
|
|
|
s.log.Debug("Private key not found, generating new key", "keyID", keyID, "err", err)
|
2023-04-17 11:42:37 +02:00
|
|
|
|
2023-10-04 10:37:27 +02:00
|
|
|
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
|
|
|
if err != nil {
|
|
|
|
|
s.log.Error("Error generating private key", "err", err)
|
|
|
|
|
return "", nil, signingkeys.ErrKeyGenerationFailed.Errorf("Error generating private key: %v", err)
|
2023-04-17 11:42:37 +02:00
|
|
|
}
|
|
|
|
|
|
2023-10-04 10:37:27 +02:00
|
|
|
expiry := time.Now().Add(30 * 24 * time.Hour)
|
|
|
|
|
if signer, err = s.store.AddPrivateKey(ctx, keyID, alg, privateKey, &expiry, false); err != nil && !errors.Is(err, signingkeys.ErrSigningKeyAlreadyExists) {
|
|
|
|
|
return "", nil, err
|
2023-04-17 11:42:37 +02:00
|
|
|
}
|
|
|
|
|
|
2023-10-04 10:37:27 +02:00
|
|
|
return keyID, signer, nil
|
2023-05-11 15:12:53 +02:00
|
|
|
}
|
|
|
|
|
|
2023-10-04 10:37:27 +02:00
|
|
|
func keyMonthScopedID(keyPrefix string, alg jose.SignatureAlgorithm) string {
|
|
|
|
|
keyID := keyPrefix + "-" + time.Now().UTC().Format("2006-01") + "-" + strings.ToLower(string(alg))
|
|
|
|
|
return keyID
|
2023-04-17 11:42:37 +02:00
|
|
|
}
|