Skip to content

Commit

Permalink
Omit empty spec.data, and improve integration test coverage
Browse files Browse the repository at this point in the history
Improve integration test coverage to exercise both negative
paths (various types of invalid SealedSecrets), and new per-key
encryption.

Add `omitempty` to spec.data, fixing an issue when Spec.Data was
`nil` (yay for tests).

Also: bump version of kubecfg used to generate manifest YAML.
  • Loading branch information
anguslees committed Mar 21, 2018
1 parent 70d1aae commit bda0af6
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 16 deletions.
10 changes: 6 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ os:

env:
global:
- PATH=$PATH:$GOPATH/bin
- CONTROLLER_IMAGE_NAME=quay.io/bitnami/sealed-secrets-controller
- CONTROLLER_IMAGE=${CONTROLLER_IMAGE_NAME}:${TRAVIS_TAG:-build-$TRAVIS_BUILD_ID}
- MINIKUBE_WANTUPDATENOTIFICATION=false
Expand Down Expand Up @@ -59,10 +60,11 @@ install:
go get github.com/onsi/ginkgo/ginkgo
fi
- >-
wget -O $GOPATH/bin/kubecfg
https://github.com/ksonnet/kubecfg/releases/download/v0.4.0/kubecfg-$(go env GOOS)-$(go env GOARCH)
- chmod +x $GOPATH/bin/kubecfg
- |
if ! which kubecfg; then
wget -O $GOPATH/bin/kubecfg https://github.com/ksonnet/kubecfg/releases/download/v0.7.1/kubecfg-$(go env GOOS)-$(go env GOARCH)
chmod +x $GOPATH/bin/kubecfg
fi
- git clone --depth=1 https://github.com/ksonnet/ksonnet-lib.git
- export KUBECFG_JPATH=$PWD/ksonnet-lib

Expand Down
4 changes: 2 additions & 2 deletions controller.jsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ local clusterRole(name, rules) = {
rules: rules,
};

local role(name, namespace="default", rules) = {
local role(name, namespace, rules) = {
apiVersion: "rbac.authorization.k8s.io/v1beta1",
kind: "Role",
metadata: objectMeta.name(name) + objectMeta.namespace(namespace),
Expand Down Expand Up @@ -46,7 +46,7 @@ local clusterRoleBinding(name, role, subjects) = {
roleRef: crossGroupRef(role),
};

local roleBinding(name, namespace="default", role, subjects) = {
local roleBinding(name, namespace, role, subjects) = {
apiVersion: "rbac.authorization.k8s.io/v1beta1",
kind: "RoleBinding",
metadata: objectMeta.name(name) + objectMeta.namespace(namespace),
Expand Down
177 changes: 168 additions & 9 deletions integration/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,21 @@
package integration

import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"fmt"

"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/scheme"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
certUtil "k8s.io/client-go/util/cert"

ssv1alpha1 "github.com/bitnami-labs/sealed-secrets/pkg/apis/sealed-secrets/v1alpha1"
ssclient "github.com/bitnami-labs/sealed-secrets/pkg/client/clientset/versioned"
"github.com/bitnami-labs/sealed-secrets/pkg/crypto"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
Expand Down Expand Up @@ -54,12 +57,14 @@ var _ = Describe("create", func() {
const secretName = "testsecret"
var ss *ssv1alpha1.SealedSecret
var s *v1.Secret
var pubKey *rsa.PublicKey

BeforeEach(func() {
conf := clusterConfigOrDie()
c = corev1.NewForConfigOrDie(conf)
ssc = ssclient.NewForConfigOrDie(conf)
ns = createNsOrDie(c, "create")

s = &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: ns,
Expand All @@ -69,30 +74,184 @@ var _ = Describe("create", func() {
"foo": []byte("bar"),
},
}

_, certs, err := fetchKeys(c)
Expect(err).NotTo(HaveOccurred())
pubKey = certs[0].PublicKey.(*rsa.PublicKey)

fmt.Fprintf(GinkgoWriter, "Sealing Secret %#v", s)
ss, err = ssv1alpha1.NewSealedSecret(scheme.Codecs, pubKey, s)
Expect(err).NotTo(HaveOccurred())

})
AfterEach(func() {
deleteNsOrDie(c, ns)
})

JustBeforeEach(func() {
var err error
fmt.Fprintf(GinkgoWriter, "Creating SealedSecret: %#v", ss)
ss, err = ssc.BitnamiV1alpha1().SealedSecrets(ss.Namespace).Create(ss)
Expect(err).NotTo(HaveOccurred())
})

Describe("Simple change", func() {
Context("With no existing object (create)", func() {
It("should produce expected Secret", func() {
expected := map[string][]byte{
"foo": []byte("bar"),
}
Eventually(func() (*v1.Secret, error) {
return c.Secrets(ns).Get(secretName, metav1.GetOptions{})
}).Should(WithTransform(getData, Equal(expected)))
})
})

Context("With existing object (update)", func() {
JustBeforeEach(func() {
var err error
resVer := ss.ResourceVersion

// update
s.Data["foo"] = []byte("baz")
ss, err = ssv1alpha1.NewSealedSecret(scheme.Codecs, pubKey, s)
ss.ResourceVersion = resVer

fmt.Fprintf(GinkgoWriter, "Updating to SealedSecret: %#v", ss)
ss, err = ssc.BitnamiV1alpha1().SealedSecrets(ss.Namespace).Update(ss)
Expect(err).NotTo(HaveOccurred())
})

It("should produce updated Secret", func() {
expected := map[string][]byte{
"foo": []byte("baz"),
}
Eventually(func() (*v1.Secret, error) {
return c.Secrets(ns).Get(secretName, metav1.GetOptions{})
}).Should(WithTransform(getData, Equal(expected)))
})
})

Context("With renamed encrypted keys", func() {
BeforeEach(func() {
ss.Spec.EncryptedData = map[string][]byte{
"xyzzy": ss.Spec.EncryptedData["foo"],
}
})
It("should produce expected Secret", func() {
expected := map[string][]byte{
// renamed key
"xyzzy": []byte("bar"),
}
Eventually(func() (*v1.Secret, error) {
return c.Secrets(ns).Get(secretName, metav1.GetOptions{})
}).Should(WithTransform(getData, Equal(expected)))
})
})

Context("With appended encrypted keys", func() {
BeforeEach(func() {
label := fmt.Sprintf("%s/%s", s.Namespace, s.Name)
ciphertext, err := crypto.HybridEncrypt(rand.Reader, pubKey, []byte("new!"), []byte(label))
Expect(err).NotTo(HaveOccurred())

ss.Spec.EncryptedData["foo2"] = ciphertext
})
It("should produce expected Secret", func() {
expected := map[string][]byte{
"foo": []byte("bar"),
"foo2": []byte("new!"),
}
Eventually(func() (*v1.Secret, error) {
return c.Secrets(ns).Get(secretName, metav1.GetOptions{})
}).Should(WithTransform(getData, Equal(expected)))
})
})
})

Describe("Same name, wrong key", func() {
BeforeEach(func() {
_, certs, err := fetchKeys(c)
// NB: weak keysize - this is just a test case
wrongkey, err := rsa.GenerateKey(rand.Reader, 1024)
Expect(err).NotTo(HaveOccurred())

ss, err = ssv1alpha1.NewSealedSecret(scheme.Codecs, certs[0].PublicKey.(*rsa.PublicKey), s)
fmt.Fprintf(GinkgoWriter, "Resealing with wrong key")
ss, err = ssv1alpha1.NewSealedSecret(scheme.Codecs, &wrongkey.PublicKey, s)
Expect(err).NotTo(HaveOccurred())
})
JustBeforeEach(func() {
var err error
ss, err = ssc.BitnamiV1alpha1().SealedSecrets(ns).Create(ss)
Expect(err).NotTo(HaveOccurred())

It("should *not* produce a Secret", func() {
Consistently(func() error {
_, err := c.Secrets(ns).Get(secretName, metav1.GetOptions{})
return err
}).Should(WithTransform(errors.IsNotFound, Equal(true)))
})

Context("With no existing object (create)", func() {
// TODO: Check for a suitable error event on the
// SealedSecret (once implemented)
})

Describe("Different name/namespace", func() {
Context("With wrong name", func() {
const secretName2 = "not-testsecret"
BeforeEach(func() {
ss.Name = secretName2
})
It("should *not* produce a Secret", func() {
Consistently(func() error {
_, err := c.Secrets(ns).Get(secretName2, metav1.GetOptions{})
return err
}).Should(WithTransform(errors.IsNotFound, Equal(true)))
})

// TODO: Check for a suitable error event on
// the SealedSecret (once implemented)
})

Context("With wrong namespace", func() {
var ns2 string
BeforeEach(func() {
ns2 = createNsOrDie(c, "create")
ss.Namespace = ns2
})
AfterEach(func() {
deleteNsOrDie(c, ns2)
})

It("should *not* produce a Secret", func() {
Consistently(func() error {
_, err := c.Secrets(ns2).Get(secretName, metav1.GetOptions{})
return err
}).Should(WithTransform(errors.IsNotFound, Equal(true)))
})

// TODO: Check for a suitable error event on
// the SealedSecret (once implemented)
})

Context("With cluster-wide annotation", func() {
const secretName2 = "not-testsecret"
BeforeEach(func() {
var err error

s.Annotations = map[string]string{
ssv1alpha1.SealedSecretClusterWideAnnotation: "true",
}

fmt.Fprintf(GinkgoWriter, "Re-sealing secret %#v", s)
ss, err = ssv1alpha1.NewSealedSecret(scheme.Codecs, pubKey, s)
Expect(err).NotTo(HaveOccurred())
})
BeforeEach(func() {
ss.Name = secretName2
})
It("should produce expected Secret", func() {
expected := map[string][]byte{
"foo": []byte("bar"),
}
Eventually(func() (*v1.Secret, error) {
return c.Secrets(ns).Get(secretName, metav1.GetOptions{})
}).Should(WithTransform(getData, Equal(s.Data)))
return c.Secrets(ns).Get(secretName2, metav1.GetOptions{})
}).Should(WithTransform(getData, Equal(expected)))
})
})
})
Expand Down
2 changes: 1 addition & 1 deletion pkg/apis/sealed-secrets/v1alpha1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const (
// SealedSecretSpec is the specification of a SealedSecret
type SealedSecretSpec struct {
// Data is deprecated and will be removed eventually. Use per-value EncryptedData instead.
Data []byte `json:"data"`
Data []byte `json:"data,omitempty"`
EncryptedData map[string][]byte `json:"encryptedData"`
}

Expand Down

0 comments on commit bda0af6

Please sign in to comment.