Skip to content

Commit

Permalink
Honour --scope flag
Browse files Browse the repository at this point in the history
  • Loading branch information
Marko Mikulicic committed Mar 11, 2020
1 parent d533249 commit a98a565
Show file tree
Hide file tree
Showing 11 changed files with 759 additions and 59 deletions.
14 changes: 9 additions & 5 deletions cmd/kubeseal/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ func openCert(certURL string) (io.ReadCloser, error) {
// Seal reads a k8s Secret resource parsed from an input reader by a given codec, encrypts all its secrets
// with a given public key, using the name and namespace found in the input secret, unless explicitly overridden
// by the overrideName and overrideNamespace arguments.
func seal(in io.Reader, out io.Writer, codecs runtimeserializer.CodecFactory, pubKey *rsa.PublicKey, allowEmptyData bool, overrideName, overrideNamespace string) error {
func seal(in io.Reader, out io.Writer, codecs runtimeserializer.CodecFactory, pubKey *rsa.PublicKey, scope ssv1alpha1.SealingScope, allowEmptyData bool, overrideName, overrideNamespace string) error {
secret, err := readSecret(codecs.UniversalDecoder(), in)
if err != nil {
return err
Expand All @@ -248,6 +248,10 @@ func seal(in io.Reader, out io.Writer, codecs runtimeserializer.CodecFactory, pu
secret.Namespace = overrideNamespace
}

if scope != ssv1alpha1.DefaultScope {
secret.Annotations = ssv1alpha1.UpdateScopeAnnotations(secret.Annotations, scope)
}

if ssv1alpha1.SecretScope(secret) != ssv1alpha1.ClusterWideScope && secret.GetNamespace() == "" {
ns, _, err := namespaceFromClientConfig()
if clientcmd.IsEmptyConfig(err) {
Expand Down Expand Up @@ -393,7 +397,7 @@ func decodeSealedSecret(codecs runtimeserializer.CodecFactory, b []byte) (*ssv1a
return &ss, nil
}

func sealMergingInto(in io.Reader, filename string, codecs runtimeserializer.CodecFactory, pubKey *rsa.PublicKey, allowEmptyData bool) error {
func sealMergingInto(in io.Reader, filename string, codecs runtimeserializer.CodecFactory, pubKey *rsa.PublicKey, scope ssv1alpha1.SealingScope, allowEmptyData bool) error {
b, err := ioutil.ReadFile(filename)
if err != nil {
return err
Expand All @@ -405,7 +409,7 @@ func sealMergingInto(in io.Reader, filename string, codecs runtimeserializer.Cod
}

var buf bytes.Buffer
if err := seal(in, &buf, codecs, pubKey, allowEmptyData, orig.Name, orig.Namespace); err != nil {
if err := seal(in, &buf, codecs, pubKey, scope, allowEmptyData, orig.Name, orig.Namespace); err != nil {
return err
}

Expand Down Expand Up @@ -612,7 +616,7 @@ func run(w io.Writer, secretName, controllerNs, controllerName, certURL string,
}

if mergeInto != "" {
return sealMergingInto(os.Stdin, mergeInto, scheme.Codecs, pubKey, allowEmptyData)
return sealMergingInto(os.Stdin, mergeInto, scheme.Codecs, pubKey, sealingScope, allowEmptyData)
}

if unseal {
Expand Down Expand Up @@ -652,7 +656,7 @@ func run(w io.Writer, secretName, controllerNs, controllerName, certURL string,
return encryptSecretItem(w, secretName, ns, data, sealingScope, pubKey)
}

return seal(os.Stdin, os.Stdout, scheme.Codecs, pubKey, allowEmptyData, secretName, "")
return seal(os.Stdin, os.Stdout, scheme.Codecs, pubKey, sealingScope, allowEmptyData, secretName, "")
}

func main() {
Expand Down
54 changes: 51 additions & 3 deletions cmd/kubeseal/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import (

ssv1alpha1 "github.com/bitnami-labs/sealed-secrets/pkg/apis/sealed-secrets/v1alpha1"
"github.com/bitnami-labs/sealed-secrets/pkg/crypto"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
flag "github.com/spf13/pflag"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -152,6 +154,7 @@ func TestSeal(t *testing.T) {

testCases := []struct {
secret v1.Secret
scope ssv1alpha1.SealingScope
want ssv1alpha1.SealedSecret // partial object
}{
{
Expand Down Expand Up @@ -259,6 +262,48 @@ func TestSeal(t *testing.T) {
},
},
},
{
secret: v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "mysecret",
Namespace: "",
},
Data: map[string][]byte{
"foo": []byte("sekret"),
},
},
scope: ssv1alpha1.NamespaceWideScope,
want: ssv1alpha1.SealedSecret{
ObjectMeta: metav1.ObjectMeta{
Name: "mysecret",
Namespace: "default",
Annotations: map[string]string{
ssv1alpha1.SealedSecretNamespaceWideAnnotation: "true",
},
},
},
},
{
secret: v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "mysecret",
Namespace: "",
},
Data: map[string][]byte{
"foo": []byte("sekret"),
},
},
scope: ssv1alpha1.ClusterWideScope,
want: ssv1alpha1.SealedSecret{
ObjectMeta: metav1.ObjectMeta{
Name: "mysecret",
Namespace: "",
Annotations: map[string]string{
ssv1alpha1.SealedSecretClusterWideAnnotation: "true",
},
},
},
},
}

for i, tc := range testCases {
Expand All @@ -276,7 +321,7 @@ func TestSeal(t *testing.T) {
t.Logf("input is: %s", string(inbuf.Bytes()))

outbuf := bytes.Buffer{}
if err := seal(&inbuf, &outbuf, scheme.Codecs, key, false,"", ""); err != nil {
if err := seal(&inbuf, &outbuf, scheme.Codecs, key, tc.scope, false, "", ""); err != nil {
t.Fatalf("seal() returned error: %v", err)
}

Expand All @@ -295,6 +340,9 @@ func TestSeal(t *testing.T) {
if got, want := smeta.GetNamespace(), tc.want.GetNamespace(); got != want {
t.Errorf("got: %q, want: %q", got, want)
}
if got, want := smeta.GetAnnotations(), tc.want.GetAnnotations(); !cmp.Equal(got, want, cmpopts.EquateEmpty()) {
t.Errorf("got: %q, want: %q", got, want)
}

for n := range tc.secret.Data {
if len(result.Spec.EncryptedData[n]) < 100 {
Expand Down Expand Up @@ -369,7 +417,7 @@ func mkTestSecret(t *testing.T, key, value string, opts ...mkTestSecretOpt) []by
func mkTestSealedSecret(t *testing.T, pubKey *rsa.PublicKey, key, value string, opts ...mkTestSecretOpt) []byte {
inbuf := bytes.NewBuffer(mkTestSecret(t, key, value, opts...))
var outbuf bytes.Buffer
if err := seal(inbuf, &outbuf, scheme.Codecs, pubKey, false, "", ""); err != nil {
if err := seal(inbuf, &outbuf, scheme.Codecs, pubKey, ssv1alpha1.DefaultScope, false, "", ""); err != nil {
t.Fatalf("seal() returned error: %v", err)
}

Expand Down Expand Up @@ -509,7 +557,7 @@ func TestMergeInto(t *testing.T) {
f.Close()

buf := bytes.NewBuffer(newSecret)
if err := sealMergingInto(buf, f.Name(), scheme.Codecs, pubKey, false); err != nil {
if err := sealMergingInto(buf, f.Name(), scheme.Codecs, pubKey, ssv1alpha1.DefaultScope, false); err != nil {
t.Fatal(err)
}

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef // indirect
github.com/gomodule/redigo v2.0.0+incompatible // indirect
github.com/google/go-cmp v0.3.0
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf
github.com/googleapis/gnostic v0.0.0-20171211024024-933c109c13ce // indirect
github.com/mattn/go-isatty v0.0.10
Expand Down
65 changes: 33 additions & 32 deletions pkg/apis/sealed-secrets/v1alpha1/sealedsecret_expansion.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,26 +89,20 @@ func EncryptionLabel(namespace, name string, scope SealingScope) []byte {
return []byte(l)
}

func scopeFromLegacy(o metav1.Object) (SealingScope, bool, bool) {
if o.GetAnnotations()[SealedSecretClusterWideAnnotation] == "true" {
return ClusterWideScope, true, false
}
if o.GetAnnotations()[SealedSecretNamespaceWideAnnotation] == "true" {
return NamespaceWideScope, false, true
}
return StrictScope, false, false
}

// Returns labels followed by clusterWide followed by namespaceWide.
func labelFor(o metav1.Object) ([]byte, bool, bool) {
scope, clusterWide, namespaceWide := scopeFromLegacy(o)
return EncryptionLabel(o.GetNamespace(), o.GetName(), scope), clusterWide, namespaceWide
func labelFor(o metav1.Object) []byte {
return EncryptionLabel(o.GetNamespace(), o.GetName(), SecretScope(o))
}

// SecretScope returns the scope of a secret to be sealed, as annotated in its metadata.
func SecretScope(o metav1.Object) SealingScope {
scope, _, _ := scopeFromLegacy(o)
return scope
if o.GetAnnotations()[SealedSecretClusterWideAnnotation] == "true" {
return ClusterWideScope
}
if o.GetAnnotations()[SealedSecretNamespaceWideAnnotation] == "true" {
return NamespaceWideScope
}
return StrictScope
}

// Scope returns the scope of the sealed secret, as annotated in its metadata.
Expand Down Expand Up @@ -138,7 +132,7 @@ func NewSealedSecretV1(codecs runtimeserializer.CodecFactory, pubKey *rsa.Public

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

ciphertext, err := crypto.HybridEncrypt(rand.Reader, pubKey, plaintext, label)
if err != nil {
Expand All @@ -155,13 +149,27 @@ func NewSealedSecretV1(codecs runtimeserializer.CodecFactory, pubKey *rsa.Public
},
}

if clusterWide {
s.Annotations = map[string]string{SealedSecretClusterWideAnnotation: "true"}
s.Annotations = UpdateScopeAnnotations(s.Annotations, SecretScope(secret))

return s, nil
}

// UpdateScopeAnnotations updates the annotation map so that it reflects the desired scope.
// It does so by updating and/or deleting existing annotations.
func UpdateScopeAnnotations(anno map[string]string, scope SealingScope) map[string]string {
if anno == nil {
anno = map[string]string{}
}
if namespaceWide {
s.Annotations = map[string]string{SealedSecretNamespaceWideAnnotation: "true"}
delete(anno, SealedSecretNamespaceWideAnnotation)
delete(anno, SealedSecretClusterWideAnnotation)

if scope == NamespaceWideScope {
anno[SealedSecretNamespaceWideAnnotation] = "true"
}
return s, nil
if scope == ClusterWideScope {
anno[SealedSecretClusterWideAnnotation] = "true"
}
return anno
}

// StripLastAppliedAnnotations strips annotations added by tools such as kubectl and kubecfg
Expand Down Expand Up @@ -216,7 +224,7 @@ func NewSealedSecret(codecs runtimeserializer.CodecFactory, pubKey *rsa.PublicKe

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

for key, value := range secret.Data {
ciphertext, err := crypto.HybridEncrypt(rand.Reader, pubKey, value, label)
Expand All @@ -234,15 +242,8 @@ func NewSealedSecret(codecs runtimeserializer.CodecFactory, pubKey *rsa.PublicKe
s.Spec.EncryptedData[key] = base64.StdEncoding.EncodeToString(ciphertext)
}

if clusterWide {
if s.Annotations == nil {
s.Annotations = map[string]string{}
}
s.Annotations[SealedSecretClusterWideAnnotation] = "true"
}
if namespaceWide {
s.Annotations = map[string]string{SealedSecretNamespaceWideAnnotation: "true"}
}
s.Annotations = UpdateScopeAnnotations(s.Annotations, SecretScope(secret))

return s, nil
}

Expand All @@ -255,7 +256,7 @@ func (s *SealedSecret) Unseal(codecs runtimeserializer.CodecFactory, privKeys ma
// during encryption. This check ensures that we can't be
// tricked into decrypting a sealed secret into an unexpected
// namespace/name.
label, _, _ := labelFor(smeta)
label := labelFor(smeta)

var secret v1.Secret
if len(s.Spec.EncryptedData) > 0 {
Expand Down
23 changes: 4 additions & 19 deletions pkg/apis/sealed-secrets/v1alpha1/sealedsecret_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,7 @@ func TestLabel(t *testing.T) {
Namespace: "myns",
},
}
l, c, _ := labelFor(&s)
if c {
t.Errorf("Unexpected value for cluster wide annotation: %#v", c)
}
l := labelFor(&s)
if string(l) != "myns/myname" {
t.Errorf("Unexpected label: %#v", l)
}
Expand All @@ -105,10 +102,7 @@ func TestClusterWide(t *testing.T) {
},
},
}
l, c, _ := labelFor(&s)
if !c {
t.Errorf("Unexpected value for cluster wide annotation: %#v", c)
}
l := labelFor(&s)
if string(l) != "" {
t.Errorf("Unexpected label: %#v", l)
}
Expand All @@ -124,10 +118,7 @@ func TestNamespaceWide(t *testing.T) {
},
},
}
l, _, n := labelFor(&s)
if !n {
t.Errorf("Unexpected value for namespace wide annotation: %#v", n)
}
l := labelFor(&s)
if string(l) != "myns" {
t.Errorf("Unexpected label: %#v", l)
}
Expand All @@ -144,13 +135,7 @@ func TestClusterAndNamespaceWide(t *testing.T) {
},
},
}
l, c, n := labelFor(&s)
if !c {
t.Errorf("Unexpected value for cluster wide annotation: %#v", c)
}
if n {
t.Errorf("Unexpected value for namespace wide annotation: %#v", n)
}
l := labelFor(&s)
if string(l) != "" {
t.Errorf("Unexpected label: %#v", l)
}
Expand Down
Loading

0 comments on commit a98a565

Please sign in to comment.