1
0
mirror of https://github.com/getsops/sops.git synced 2026-02-05 12:45:21 +01:00
Files
sops/gcpkms/keysource.go
2018-03-07 17:18:45 +01:00

154 lines
4.7 KiB
Go

package gcpkms //import "go.mozilla.org/sops/gcpkms"
import (
"encoding/base64"
"fmt"
"regexp"
"strings"
"time"
"go.mozilla.org/sops/logging"
"golang.org/x/net/context"
"golang.org/x/oauth2/google"
"github.com/sirupsen/logrus"
cloudkms "google.golang.org/api/cloudkms/v1"
)
var log *logrus.Logger
func init() {
log = logging.NewLogger("GCPKMS")
}
// MasterKey is a GCP KMS key used to encrypt and decrypt sops' data key.
type MasterKey struct {
ResourceID string
EncryptedKey string
CreationDate time.Time
}
// EncryptedDataKey returns the encrypted data key this master key holds
func (key *MasterKey) EncryptedDataKey() []byte {
return []byte(key.EncryptedKey)
}
// SetEncryptedDataKey sets the encrypted data key for this master key
func (key *MasterKey) SetEncryptedDataKey(enc []byte) {
key.EncryptedKey = string(enc)
}
// Encrypt takes a sops data key, encrypts it with GCP KMS and stores the result in the EncryptedKey field
func (key *MasterKey) Encrypt(dataKey []byte) error {
cloudkmsService, err := key.createCloudKMSService()
if err != nil {
log.WithField("resourceID", key.ResourceID).Info("Encryption failed")
return fmt.Errorf("Cannot create GCP KMS service: %v", err)
}
req := &cloudkms.EncryptRequest{
Plaintext: base64.StdEncoding.EncodeToString(dataKey),
}
resp, err := cloudkmsService.Projects.Locations.KeyRings.CryptoKeys.Encrypt(key.ResourceID, req).Do()
if err != nil {
log.WithField("resourceID", key.ResourceID).Info("Encryption failed")
return fmt.Errorf("Failed to call GCP KMS encryption service: %v", err)
}
log.WithField("resourceID", key.ResourceID).Info("Encryption succeeded")
key.EncryptedKey = resp.Ciphertext
return nil
}
// EncryptIfNeeded encrypts the provided sops' data key and encrypts it if it hasn't been encrypted yet
func (key *MasterKey) EncryptIfNeeded(dataKey []byte) error {
if key.EncryptedKey == "" {
return key.Encrypt(dataKey)
}
return nil
}
// Decrypt decrypts the EncryptedKey field with CGP KMS and returns the result.
func (key *MasterKey) Decrypt() ([]byte, error) {
cloudkmsService, err := key.createCloudKMSService()
if err != nil {
log.WithField("resourceID", key.ResourceID).Info("Decryption failed")
return nil, fmt.Errorf("Cannot create GCP KMS service: %v", err)
}
req := &cloudkms.DecryptRequest{
Ciphertext: key.EncryptedKey,
}
resp, err := cloudkmsService.Projects.Locations.KeyRings.CryptoKeys.Decrypt(key.ResourceID, req).Do()
if err != nil {
log.WithField("resourceID", key.ResourceID).Info("Decryption failed")
return nil, fmt.Errorf("Error decrypting key: %v", err)
}
encryptedKey, err := base64.StdEncoding.DecodeString(resp.Plaintext)
if err != nil {
log.WithField("resourceID", key.ResourceID).Info("Decryption failed")
return nil, err
}
log.WithField("resourceID", key.ResourceID).Info("Decryption succeeded")
return encryptedKey, nil
}
// NeedsRotation returns whether the data key needs to be rotated or not.
func (key *MasterKey) NeedsRotation() bool {
return time.Since(key.CreationDate) > (time.Hour * 24 * 30 * 6)
}
// ToString converts the key to a string representation
func (key *MasterKey) ToString() string {
return key.ResourceID
}
// NewMasterKeyFromResourceID takes a GCP KMS resource ID string and returns a new MasterKey for that
func NewMasterKeyFromResourceID(resourceID string) *MasterKey {
k := &MasterKey{}
resourceID = strings.Replace(resourceID, " ", "", -1)
k.ResourceID = resourceID
k.CreationDate = time.Now().UTC()
return k
}
// MasterKeysFromResourceIDString takes a comma separated list of GCP KMS resource IDs and returns a slice of new MasterKeys for them
func MasterKeysFromResourceIDString(resourceID string) []*MasterKey {
var keys []*MasterKey
if resourceID == "" {
return keys
}
for _, s := range strings.Split(resourceID, ",") {
keys = append(keys, NewMasterKeyFromResourceID(s))
}
return keys
}
func (key MasterKey) createCloudKMSService() (*cloudkms.Service, error) {
re := regexp.MustCompile(`^projects/[^/]+/locations/[^/]+/keyRings/[^/]+/cryptoKeys/[^/]+$`)
matches := re.FindStringSubmatch(key.ResourceID)
if matches == nil {
return nil, fmt.Errorf("No valid resourceId found in %q", key.ResourceID)
}
ctx := context.Background()
client, err := google.DefaultClient(ctx, cloudkms.CloudPlatformScope)
if err != nil {
return nil, err
}
cloudkmsService, err := cloudkms.New(client)
if err != nil {
return nil, err
}
return cloudkmsService, nil
}
// ToMap converts the MasterKey to a map for serialization purposes
func (key MasterKey) ToMap() map[string]interface{} {
out := make(map[string]interface{})
out["resource_id"] = key.ResourceID
out["enc"] = key.EncryptedKey
out["created_at"] = key.CreationDate.UTC().Format(time.RFC3339)
return out
}