sops/keyservice: tidy and add tests
Signed-off-by: Hidde Beydals <hello@hidde.co>
This commit is contained in:
parent
ffdda3f3da
commit
9db141d9db
|
|
@ -118,7 +118,7 @@ func (key *MasterKey) Decrypt() ([]byte, error) {
|
|||
// with the latest.
|
||||
rawEncryptedKey, err := base64.RawURLEncoding.DecodeString(key.EncryptedKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode encrypted key: %w", err)
|
||||
return nil, fmt.Errorf("failed to base64 decode Azure Key Vault encrypted key: %w", err)
|
||||
}
|
||||
resp, err := c.Decrypt(context.Background(), crypto.EncryptionAlgorithmRSAOAEP256, rawEncryptedKey, nil)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@
|
|||
package keyservice
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"go.mozilla.org/sops/v3/keyservice"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"github.com/fluxcd/kustomize-controller/internal/sops/age"
|
||||
"github.com/fluxcd/kustomize-controller/internal/sops/azkv"
|
||||
|
|
@ -29,7 +29,7 @@ type Server struct {
|
|||
// keyring.
|
||||
gnuPGHome pgp.GnuPGHome
|
||||
|
||||
// ageIdentities holds the parsed age identities used for Decrypt
|
||||
// ageIdentities are the parsed age identities used for Decrypt
|
||||
// operations for age key types.
|
||||
ageIdentities age.ParsedIdentities
|
||||
|
||||
|
|
@ -86,6 +86,16 @@ func (ks Server) Encrypt(ctx context.Context, req *keyservice.EncryptRequest) (*
|
|||
return &keyservice.EncryptResponse{
|
||||
Ciphertext: ciphertext,
|
||||
}, nil
|
||||
case *keyservice.Key_VaultKey:
|
||||
if ks.vaultToken != "" {
|
||||
ciphertext, err := ks.encryptWithHCVault(k.VaultKey, req.Plaintext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &keyservice.EncryptResponse{
|
||||
Ciphertext: ciphertext,
|
||||
}, nil
|
||||
}
|
||||
case *keyservice.Key_AzureKeyvaultKey:
|
||||
if ks.azureToken != nil {
|
||||
ciphertext, err := ks.encryptWithAzureKeyVault(k.AzureKeyvaultKey, req.Plaintext)
|
||||
|
|
@ -97,7 +107,7 @@ func (ks Server) Encrypt(ctx context.Context, req *keyservice.EncryptRequest) (*
|
|||
}, nil
|
||||
}
|
||||
case nil:
|
||||
return nil, status.Errorf(codes.NotFound, "must provide a key")
|
||||
return nil, fmt.Errorf("must provide a key")
|
||||
}
|
||||
// Fallback to default server for any other request.
|
||||
return ks.defaultServer.Encrypt(ctx, req)
|
||||
|
|
@ -126,7 +136,7 @@ func (ks Server) Decrypt(ctx context.Context, req *keyservice.DecryptRequest) (*
|
|||
}, nil
|
||||
case *keyservice.Key_VaultKey:
|
||||
if ks.vaultToken != "" {
|
||||
plaintext, err := ks.decryptWithVault(k.VaultKey, req.Ciphertext)
|
||||
plaintext, err := ks.decryptWithHCVault(k.VaultKey, req.Ciphertext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -145,7 +155,7 @@ func (ks Server) Decrypt(ctx context.Context, req *keyservice.DecryptRequest) (*
|
|||
}, nil
|
||||
}
|
||||
case nil:
|
||||
return nil, status.Errorf(codes.NotFound, "must provide a key")
|
||||
return nil, fmt.Errorf("must provide a key")
|
||||
}
|
||||
// Fallback to default server for any other request.
|
||||
return ks.defaultServer.Decrypt(ctx, req)
|
||||
|
|
@ -197,11 +207,20 @@ func (ks *Server) decryptWithAge(key *keyservice.AgeKey, ciphertext []byte) ([]b
|
|||
return plaintext, err
|
||||
}
|
||||
|
||||
func (ks *Server) decryptWithVault(key *keyservice.VaultKey, ciphertext []byte) ([]byte, error) {
|
||||
if ks.vaultToken == "" {
|
||||
return nil, status.Errorf(codes.Unimplemented, "Hashicorp Vault decrypt service unavailable: no token found")
|
||||
func (ks *Server) encryptWithHCVault(key *keyservice.VaultKey, plaintext []byte) ([]byte, error) {
|
||||
vaultKey := hcvault.MasterKey{
|
||||
VaultAddress: key.VaultAddress,
|
||||
EnginePath: key.EnginePath,
|
||||
KeyName: key.KeyName,
|
||||
}
|
||||
ks.vaultToken.ApplyToMasterKey(&vaultKey)
|
||||
if err := vaultKey.Encrypt(plaintext); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []byte(vaultKey.EncryptedKey), nil
|
||||
}
|
||||
|
||||
func (ks *Server) decryptWithHCVault(key *keyservice.VaultKey, ciphertext []byte) ([]byte, error) {
|
||||
vaultKey := hcvault.MasterKey{
|
||||
VaultAddress: key.VaultAddress,
|
||||
EnginePath: key.EnginePath,
|
||||
|
|
@ -214,10 +233,6 @@ func (ks *Server) decryptWithVault(key *keyservice.VaultKey, ciphertext []byte)
|
|||
}
|
||||
|
||||
func (ks *Server) encryptWithAzureKeyVault(key *keyservice.AzureKeyVaultKey, plaintext []byte) ([]byte, error) {
|
||||
if ks.azureToken == nil {
|
||||
return nil, status.Errorf(codes.Unimplemented, "Azure Key Vault encrypt service unavailable: no authentication config present")
|
||||
}
|
||||
|
||||
azureKey := azkv.MasterKey{
|
||||
VaultURL: key.VaultUrl,
|
||||
Name: key.Name,
|
||||
|
|
@ -231,10 +246,6 @@ func (ks *Server) encryptWithAzureKeyVault(key *keyservice.AzureKeyVaultKey, pla
|
|||
}
|
||||
|
||||
func (ks *Server) decryptWithAzureKeyVault(key *keyservice.AzureKeyVaultKey, ciphertext []byte) ([]byte, error) {
|
||||
if ks.azureToken == nil {
|
||||
return nil, status.Errorf(codes.Unimplemented, "Azure Key Vault decrypt service unavailable: no authentication config present")
|
||||
}
|
||||
|
||||
azureKey := azkv.MasterKey{
|
||||
VaultURL: key.VaultUrl,
|
||||
Name: key.Name,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,214 @@
|
|||
/*
|
||||
Copyright 2022 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package keyservice
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
|
||||
. "github.com/onsi/gomega"
|
||||
"go.mozilla.org/sops/v3/keyservice"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/fluxcd/kustomize-controller/internal/sops/age"
|
||||
"github.com/fluxcd/kustomize-controller/internal/sops/azkv"
|
||||
"github.com/fluxcd/kustomize-controller/internal/sops/hcvault"
|
||||
"github.com/fluxcd/kustomize-controller/internal/sops/pgp"
|
||||
)
|
||||
|
||||
func TestServer_EncryptDecrypt_PGP(t *testing.T) {
|
||||
const (
|
||||
mockPublicKey = "../pgp/testdata/public.gpg"
|
||||
mockPrivateKey = "../pgp/testdata/private.gpg"
|
||||
mockFingerprint = "B59DAF469E8C948138901A649732075EA221A7EA"
|
||||
)
|
||||
|
||||
g := NewWithT(t)
|
||||
|
||||
gnuPGHome, err := pgp.NewGnuPGHome()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
t.Cleanup(func() {
|
||||
_ = os.RemoveAll(gnuPGHome.String())
|
||||
})
|
||||
g.Expect(gnuPGHome.ImportFile(mockPublicKey)).To(Succeed())
|
||||
|
||||
s := NewServer(WithGnuPGHome(gnuPGHome))
|
||||
key := KeyFromMasterKey(pgp.MasterKeyFromFingerprint(mockFingerprint))
|
||||
dataKey := []byte("some data key")
|
||||
encResp, err := s.Encrypt(context.TODO(), &keyservice.EncryptRequest{
|
||||
Key: &key,
|
||||
Plaintext: dataKey,
|
||||
})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Expect(encResp.Ciphertext).ToNot(BeEmpty())
|
||||
g.Expect(encResp.Ciphertext).ToNot(Equal(dataKey))
|
||||
|
||||
g.Expect(gnuPGHome.ImportFile(mockPrivateKey)).To(Succeed())
|
||||
decResp, err := s.Decrypt(context.TODO(), &keyservice.DecryptRequest{
|
||||
Key: &key,
|
||||
Ciphertext: encResp.Ciphertext,
|
||||
})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Expect(decResp.Plaintext).To(Equal(dataKey))
|
||||
}
|
||||
|
||||
func TestServer_EncryptDecrypt_age(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
const (
|
||||
mockRecipient string = "age1lzd99uklcjnc0e7d860axevet2cz99ce9pq6tzuzd05l5nr28ams36nvun"
|
||||
mockIdentity string = "AGE-SECRET-KEY-1G0Q5K9TV4REQ3ZSQRMTMG8NSWQGYT0T7TZ33RAZEE0GZYVZN0APSU24RK7"
|
||||
)
|
||||
|
||||
s := NewServer()
|
||||
key := KeyFromMasterKey(&age.MasterKey{Recipient: mockRecipient})
|
||||
dataKey := []byte("some data key")
|
||||
encResp, err := s.Encrypt(context.TODO(), &keyservice.EncryptRequest{
|
||||
Key: &key,
|
||||
Plaintext: dataKey,
|
||||
})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Expect(encResp.Ciphertext).ToNot(BeEmpty())
|
||||
g.Expect(encResp.Ciphertext).ToNot(Equal(dataKey))
|
||||
|
||||
i := make(age.ParsedIdentities, 0)
|
||||
g.Expect(i.Import(mockIdentity)).To(Succeed())
|
||||
|
||||
s = NewServer(WithAgeIdentities(i))
|
||||
decResp, err := s.Decrypt(context.TODO(), &keyservice.DecryptRequest{
|
||||
Key: &key,
|
||||
Ciphertext: encResp.Ciphertext,
|
||||
})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Expect(decResp.Plaintext).To(Equal(dataKey))
|
||||
}
|
||||
|
||||
func TestServer_EncryptDecrypt_HCVault(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
s := NewServer(WithVaultToken("token"))
|
||||
key := KeyFromMasterKey(hcvault.MasterKeyFromAddress("https://example.com", "engine-path", "key-name"))
|
||||
_, err := s.Encrypt(context.TODO(), &keyservice.EncryptRequest{
|
||||
Key: &key,
|
||||
})
|
||||
g.Expect(err).To(HaveOccurred())
|
||||
g.Expect(err.Error()).To(ContainSubstring("failed to encrypt sops data key to Vault transit backend"))
|
||||
|
||||
_, err = s.Decrypt(context.TODO(), &keyservice.DecryptRequest{
|
||||
Key: &key,
|
||||
})
|
||||
g.Expect(err).To(HaveOccurred())
|
||||
g.Expect(err.Error()).To(ContainSubstring("failed to decrypt sops data key from Vault transit backend"))
|
||||
}
|
||||
|
||||
func TestServer_EncryptDecrypt_HCVault_Fallback(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
fallback := NewMockKeyServer()
|
||||
s := NewServer(WithDefaultServer{Server: fallback})
|
||||
|
||||
key := KeyFromMasterKey(hcvault.MasterKeyFromAddress("https://example.com", "engine-path", "key-name"))
|
||||
encReq := &keyservice.EncryptRequest{
|
||||
Key: &key,
|
||||
Plaintext: []byte("some data key"),
|
||||
}
|
||||
_, err := s.Encrypt(context.TODO(), encReq)
|
||||
g.Expect(err).To(HaveOccurred())
|
||||
g.Expect(fallback.encryptReqs).To(HaveLen(1))
|
||||
g.Expect(fallback.encryptReqs).To(ContainElement(encReq))
|
||||
g.Expect(fallback.decryptReqs).To(HaveLen(0))
|
||||
|
||||
fallback = NewMockKeyServer()
|
||||
s = NewServer(WithDefaultServer{Server: fallback})
|
||||
|
||||
decReq := &keyservice.DecryptRequest{
|
||||
Key: &key,
|
||||
Ciphertext: []byte("some ciphertext"),
|
||||
}
|
||||
_, err = s.Decrypt(context.TODO(), decReq)
|
||||
g.Expect(fallback.decryptReqs).To(HaveLen(1))
|
||||
g.Expect(fallback.decryptReqs).To(ContainElement(decReq))
|
||||
g.Expect(fallback.encryptReqs).To(HaveLen(0))
|
||||
}
|
||||
|
||||
func TestServer_EncryptDecrypt_azkv(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
identity, err := azidentity.NewDefaultAzureCredential(nil)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
s := NewServer(WithAzureToken{Token: azkv.NewToken(identity)})
|
||||
|
||||
key := KeyFromMasterKey(azkv.MasterKeyFromURL("", "", ""))
|
||||
_, err = s.Encrypt(context.TODO(), &keyservice.EncryptRequest{
|
||||
Key: &key,
|
||||
})
|
||||
g.Expect(err).To(HaveOccurred())
|
||||
g.Expect(err.Error()).To(ContainSubstring("failed to encrypt sops data key with Azure Key Vault"))
|
||||
|
||||
_, err = s.Decrypt(context.TODO(), &keyservice.DecryptRequest{
|
||||
Key: &key,
|
||||
})
|
||||
g.Expect(err).To(HaveOccurred())
|
||||
g.Expect(err.Error()).To(ContainSubstring("failed to decrypt sops data key with Azure Key Vault"))
|
||||
|
||||
}
|
||||
|
||||
func TestServer_EncryptDecrypt_azkv_Fallback(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
fallback := NewMockKeyServer()
|
||||
s := NewServer(WithDefaultServer{Server: fallback})
|
||||
|
||||
key := KeyFromMasterKey(azkv.MasterKeyFromURL("", "", ""))
|
||||
encReq := &keyservice.EncryptRequest{
|
||||
Key: &key,
|
||||
Plaintext: []byte("some data key"),
|
||||
}
|
||||
_, err := s.Encrypt(context.TODO(), encReq)
|
||||
g.Expect(err).To(HaveOccurred())
|
||||
g.Expect(fallback.encryptReqs).To(HaveLen(1))
|
||||
g.Expect(fallback.encryptReqs).To(ContainElement(encReq))
|
||||
g.Expect(fallback.decryptReqs).To(HaveLen(0))
|
||||
|
||||
fallback = NewMockKeyServer()
|
||||
s = NewServer(WithDefaultServer{Server: fallback})
|
||||
|
||||
decReq := &keyservice.DecryptRequest{
|
||||
Key: &key,
|
||||
Ciphertext: []byte("some ciphertext"),
|
||||
}
|
||||
_, err = s.Decrypt(context.TODO(), decReq)
|
||||
g.Expect(fallback.decryptReqs).To(HaveLen(1))
|
||||
g.Expect(fallback.decryptReqs).To(ContainElement(decReq))
|
||||
g.Expect(fallback.encryptReqs).To(HaveLen(0))
|
||||
}
|
||||
|
||||
func TestServer_EncryptDecrypt_Nil_KeyType(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
s := NewServer(WithDefaultServer{NewMockKeyServer()})
|
||||
|
||||
expectErr := fmt.Errorf("must provide a key")
|
||||
|
||||
_, err := s.Encrypt(context.TODO(), &keyservice.EncryptRequest{Key: &keyservice.Key{KeyType: nil}})
|
||||
g.Expect(err).To(Equal(expectErr))
|
||||
|
||||
_, err = s.Decrypt(context.TODO(), &keyservice.DecryptRequest{Key: &keyservice.Key{KeyType: nil}})
|
||||
g.Expect(err).To(Equal(expectErr))
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
Copyright 2022 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package keyservice
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"go.mozilla.org/sops/v3/keys"
|
||||
"go.mozilla.org/sops/v3/keyservice"
|
||||
|
||||
"github.com/fluxcd/kustomize-controller/internal/sops/age"
|
||||
"github.com/fluxcd/kustomize-controller/internal/sops/azkv"
|
||||
"github.com/fluxcd/kustomize-controller/internal/sops/hcvault"
|
||||
"github.com/fluxcd/kustomize-controller/internal/sops/pgp"
|
||||
)
|
||||
|
||||
// KeyFromMasterKey converts a SOPS internal MasterKey to an RPC Key that can
|
||||
// be serialized with Protocol Buffers.
|
||||
func KeyFromMasterKey(k keys.MasterKey) keyservice.Key {
|
||||
switch mk := k.(type) {
|
||||
case *pgp.MasterKey:
|
||||
return keyservice.Key{
|
||||
KeyType: &keyservice.Key_PgpKey{
|
||||
PgpKey: &keyservice.PgpKey{
|
||||
Fingerprint: mk.Fingerprint,
|
||||
},
|
||||
},
|
||||
}
|
||||
case *hcvault.MasterKey:
|
||||
return keyservice.Key{
|
||||
KeyType: &keyservice.Key_VaultKey{
|
||||
VaultKey: &keyservice.VaultKey{
|
||||
VaultAddress: mk.VaultAddress,
|
||||
EnginePath: mk.EnginePath,
|
||||
KeyName: mk.KeyName,
|
||||
},
|
||||
},
|
||||
}
|
||||
case *azkv.MasterKey:
|
||||
return keyservice.Key{
|
||||
KeyType: &keyservice.Key_AzureKeyvaultKey{
|
||||
AzureKeyvaultKey: &keyservice.AzureKeyVaultKey{
|
||||
VaultUrl: mk.VaultURL,
|
||||
Name: mk.Name,
|
||||
Version: mk.Version,
|
||||
},
|
||||
},
|
||||
}
|
||||
case *age.MasterKey:
|
||||
return keyservice.Key{
|
||||
KeyType: &keyservice.Key_AgeKey{
|
||||
AgeKey: &keyservice.AgeKey{
|
||||
Recipient: mk.Recipient,
|
||||
},
|
||||
},
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("tried to convert unknown MasterKey type %T to keyservice.Key", mk))
|
||||
}
|
||||
}
|
||||
|
||||
type MockKeyServer struct {
|
||||
encryptReqs []*keyservice.EncryptRequest
|
||||
decryptReqs []*keyservice.DecryptRequest
|
||||
}
|
||||
|
||||
func NewMockKeyServer() *MockKeyServer {
|
||||
return &MockKeyServer{
|
||||
encryptReqs: make([]*keyservice.EncryptRequest, 0),
|
||||
decryptReqs: make([]*keyservice.DecryptRequest, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (ks *MockKeyServer) Encrypt(_ context.Context, req *keyservice.EncryptRequest) (*keyservice.EncryptResponse, error) {
|
||||
ks.encryptReqs = append(ks.encryptReqs, req)
|
||||
return nil, fmt.Errorf("not actually implemented")
|
||||
}
|
||||
|
||||
func (ks *MockKeyServer) Decrypt(_ context.Context, req *keyservice.DecryptRequest) (*keyservice.DecryptResponse, error) {
|
||||
ks.decryptReqs = append(ks.decryptReqs, req)
|
||||
return nil, fmt.Errorf("not actually implemented")
|
||||
}
|
||||
Loading…
Reference in New Issue