Skip to content

Commit

Permalink
Merge pull request #73 from sullerandras/sa/encrypt-secrets-individua…
Browse files Browse the repository at this point in the history
…lly2

Encrypt secrets individually - rebased
  • Loading branch information
anguslees authored Mar 5, 2018
2 parents 48fda98 + 52f32b5 commit 47a5e87
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 34 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ wanting to use `SealedSecret`s with this cluster. The certificate is
printed to the controller log at startup, and available via an HTTP
GET to `/v1/cert.pem` on the controller.

During encryption, the original `Secret` is JSON-encoded and
During encryption, each value in the original `Secret` is
symmetrically encrypted using AES-GCM (AES-256) with a randomly-generated
single-use 32 byte session key. The session key is then asymmetrically
encrypted with the controller's public key using RSA-OAEP (using SHA256), and the
Expand Down
4 changes: 2 additions & 2 deletions cmd/kubeseal/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,8 @@ func TestSeal(t *testing.T) {
if smeta.GetNamespace() != "myns" {
t.Errorf("Unexpected namespace: %v", smeta.GetNamespace())
}
if len(result.Spec.Data) < 100 {
t.Errorf("Encrypted data is implausibly short: %v", result.Spec.Data)
if len(result.Spec.EncryptedData["foo"]) < 100 {
t.Errorf("Encrypted data is implausibly short: %v", result.Spec.EncryptedData)
}
// NB: See sealedsecret_test.go for e2e crypto test
}
71 changes: 60 additions & 11 deletions pkg/apis/sealed-secrets/v1alpha1/sealedsecret_expansion.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@ func labelFor(o metav1.Object) ([]byte, bool) {
return []byte(label), false
}

// NewSealedSecret creates a new SealedSecret object wrapping the
// provided secret.
func NewSealedSecret(codecs runtimeserializer.CodecFactory, pubKey *rsa.PublicKey, secret *v1.Secret) (*SealedSecret, error) {
// NewSealedSecretV1 creates a new SealedSecret object wrapping the
// provided secret. This encrypts all the secrets into a single encrypted
// blob and stores it in the `Data` attribute. Keeping this for backward
// compatibility.
func NewSealedSecretV1(codecs runtimeserializer.CodecFactory, pubKey *rsa.PublicKey, secret *v1.Secret) (*SealedSecret, error) {
info, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), runtime.ContentTypeJSON)
if !ok {
return nil, fmt.Errorf("binary can't serialize JSON")
Expand Down Expand Up @@ -65,6 +67,42 @@ func NewSealedSecret(codecs runtimeserializer.CodecFactory, pubKey *rsa.PublicKe
return s, nil
}

// NewSealedSecret creates a new SealedSecret object wrapping the
// provided secret. This encrypts only the values of each secrets
// individually, so secrets can be updated one by one.
func NewSealedSecret(codecs runtimeserializer.CodecFactory, pubKey *rsa.PublicKey, secret *v1.Secret) (*SealedSecret, error) {
if secret.GetNamespace() == "" {
return nil, fmt.Errorf("Secret must declare a namespace")
}

s := &SealedSecret{
ObjectMeta: metav1.ObjectMeta{
Name: secret.GetName(),
Namespace: secret.GetNamespace(),
},
Spec: SealedSecretSpec{
EncryptedData: map[string][]byte{},
},
}

// RSA-OAEP will fail to decrypt unless the same label is used
// during decryption.
label, clusterWide := labelFor(secret)

for key, value := range secret.Data {
ciphertext, err := crypto.HybridEncrypt(rand.Reader, pubKey, value, label)
if err != nil {
return nil, err
}
s.Spec.EncryptedData[key] = ciphertext
}

if clusterWide {
s.Annotations = map[string]string{SealedSecretClusterWideAnnotation: "true"}
}
return s, nil
}

// Unseal decypts and returns the embedded v1.Secret.
func (s *SealedSecret) Unseal(codecs runtimeserializer.CodecFactory, privKey *rsa.PrivateKey) (*v1.Secret, error) {
boolTrue := true
Expand All @@ -76,15 +114,26 @@ func (s *SealedSecret) Unseal(codecs runtimeserializer.CodecFactory, privKey *rs
// namespace/name.
label, _ := labelFor(smeta)

plaintext, err := crypto.HybridDecrypt(rand.Reader, privKey, s.Spec.Data, label)
if err != nil {
return nil, err
}

var secret v1.Secret
dec := codecs.UniversalDecoder(secret.GroupVersionKind().GroupVersion())
if err = runtime.DecodeInto(dec, plaintext, &secret); err != nil {
return nil, err
if len(s.Spec.EncryptedData) > 0 {
secret.Data = map[string][]byte{}
for key, value := range s.Spec.EncryptedData {
plaintext, err := crypto.HybridDecrypt(rand.Reader, privKey, value, label)
if err != nil {
return nil, err
}
secret.Data[key] = plaintext
}
} else { // Support decrypting old secrets for backward compatibility
plaintext, err := crypto.HybridDecrypt(rand.Reader, privKey, s.Spec.Data, label)
if err != nil {
return nil, err
}

dec := codecs.UniversalDecoder(secret.GroupVersionKind().GroupVersion())
if err = runtime.DecodeInto(dec, plaintext, &secret); err != nil {
return nil, err
}
}

// Ensure these are set to what we expect
Expand Down
46 changes: 45 additions & 1 deletion pkg/apis/sealed-secrets/v1alpha1/sealedsecret_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,10 @@ func TestSerialize(t *testing.T) {
Namespace: "myns",
},
Spec: SealedSecretSpec{
Data: []byte("xxx"),
EncryptedData: map[string][]byte{
"foo": []byte("secret1"),
"bar": []byte("secret2"),
},
},
}

Expand Down Expand Up @@ -237,3 +240,44 @@ func TestSealRoundTripWithMisMatchClusterWide(t *testing.T) {
t.Fatalf("Unseal did not return expected error: %v", err)
}
}

func TestUnsealingV1Format(t *testing.T) {
scheme := runtime.NewScheme()
codecs := serializer.NewCodecFactory(scheme)

SchemeBuilder.AddToScheme(scheme)
v1.SchemeBuilder.AddToScheme(scheme)

rand := testRand()
key, err := rsa.GenerateKey(rand, 2048)
if err != nil {
t.Fatalf("Failed to generate test key: %v", err)
}

secret := v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "myname",
Namespace: "myns",
Annotations: map[string]string{
SealedSecretClusterWideAnnotation: "true",
},
},
Data: map[string][]byte{
"foo": []byte("bar"),
},
}

ssecret, err := NewSealedSecretV1(codecs, &key.PublicKey, &secret)
if err != nil {
t.Fatalf("NewSealedSecret returned error: %v", err)
}

secret2, err := ssecret.Unseal(codecs, key)
if err != nil {
t.Fatalf("Unseal returned error: %v", err)
}

if !reflect.DeepEqual(secret.Data, secret2.Data) {
t.Errorf("Unsealed secret != original secret: %v != %v", secret, secret2)
}
}
4 changes: 3 additions & 1 deletion pkg/apis/sealed-secrets/v1alpha1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ const (

// SealedSecretSpec is the specification of a SealedSecret
type SealedSecretSpec struct {
Data []byte `json:"data"`
// Data is deprecated and will be removed eventually. Use per-value EncryptedData instead.
Data []byte `json:"data"`
EncryptedData map[string][]byte `json:"encryptedData"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
Expand Down
12 changes: 12 additions & 0 deletions pkg/apis/sealed-secrets/v1alpha1/zz_generated.deepcopy.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,18 @@ func (in *SealedSecretSpec) DeepCopyInto(out *SealedSecretSpec) {
*out = make([]byte, len(*in))
copy(*out, *in)
}
if in.EncryptedData != nil {
in, out := &in.EncryptedData, &out.EncryptedData
*out = make(map[string][]byte, len(*in))
for key, val := range *in {
if val == nil {
(*out)[key] = nil
} else {
(*out)[key] = make([]byte, len(val))
copy((*out)[key], val)
}
}
}
return
}

Expand Down
18 changes: 0 additions & 18 deletions sealedsecret-crd.jsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,6 @@ local crd = {
plural: self.singular + "s",
listKind: self.kind + "List",
},
validation: {
openAPIV3Schema: {
"$schema": "http://json-schema.org/draft-04/schema#",
type: "object",
description: "A sealed (encrypted) Secret",
properties: {
spec: {
type: "object",
properties: {
data: {
type: "string",
pattern: "^[A-Za-z0-9+/=]*$", // base64
},
},
},
},
},
},
},
};

Expand Down

0 comments on commit 47a5e87

Please sign in to comment.