mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-04 01:40:07 +00:00
Add a secretbox implementation for encryption
Uses nacl/secretbox
This commit is contained in:
parent
868cdeca8a
commit
23cd6c52ba
@ -371,6 +371,7 @@ staging/src/k8s.io/apiserver/pkg/storage/storagebackend/factory
|
|||||||
staging/src/k8s.io/apiserver/pkg/storage/storagebackend/factory
|
staging/src/k8s.io/apiserver/pkg/storage/storagebackend/factory
|
||||||
staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/aes
|
staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/aes
|
||||||
staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/identity
|
staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/identity
|
||||||
|
staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/secretbox
|
||||||
staging/src/k8s.io/apiserver/pkg/util/flushwriter
|
staging/src/k8s.io/apiserver/pkg/util/flushwriter
|
||||||
staging/src/k8s.io/apiserver/pkg/util/logs
|
staging/src/k8s.io/apiserver/pkg/util/logs
|
||||||
staging/src/k8s.io/apiserver/plugin/pkg/audit/webhook
|
staging/src/k8s.io/apiserver/plugin/pkg/audit/webhook
|
||||||
|
@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes 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 secretbox transforms values for storage at rest using XSalsa20 and Poly1305.
|
||||||
|
package secretbox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/nacl/secretbox"
|
||||||
|
|
||||||
|
"k8s.io/apiserver/pkg/storage/value"
|
||||||
|
)
|
||||||
|
|
||||||
|
// secretbox implements at rest encryption of the provided values given a 32 byte secret key.
|
||||||
|
// Uses a standard 24 byte nonce (placed at the the beginning of the cipher text) generated
|
||||||
|
// from crypto/rand. Does not perform authentication of the data at rest.
|
||||||
|
type secretboxTransformer struct {
|
||||||
|
key [32]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
const nonceSize = 24
|
||||||
|
|
||||||
|
// NewSecretboxTransformer takes the given key and performs encryption and decryption on the given
|
||||||
|
// data.
|
||||||
|
func NewSecretboxTransformer(key [32]byte) value.Transformer {
|
||||||
|
return &secretboxTransformer{key: key}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *secretboxTransformer) TransformFromStorage(data []byte, context value.Context) ([]byte, bool, error) {
|
||||||
|
if len(data) < (secretbox.Overhead + nonceSize) {
|
||||||
|
return nil, false, fmt.Errorf("the stored data was shorter than the required size")
|
||||||
|
}
|
||||||
|
var nonce [nonceSize]byte
|
||||||
|
copy(nonce[:], data[:nonceSize])
|
||||||
|
data = data[nonceSize:]
|
||||||
|
out := make([]byte, 0, len(data)-secretbox.Overhead)
|
||||||
|
result, ok := secretbox.Open(out, data, &nonce, &t.key)
|
||||||
|
if !ok {
|
||||||
|
return nil, false, fmt.Errorf("output array was not large enough for encryption")
|
||||||
|
}
|
||||||
|
return result, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *secretboxTransformer) TransformToStorage(data []byte, context value.Context) ([]byte, error) {
|
||||||
|
var nonce [nonceSize]byte
|
||||||
|
n, err := rand.Read(nonce[:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if n != nonceSize {
|
||||||
|
return nil, fmt.Errorf("unable to read sufficient random bytes")
|
||||||
|
}
|
||||||
|
return secretbox.Seal(nonce[:], data, &nonce, &t.key), nil
|
||||||
|
}
|
@ -0,0 +1,190 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes 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 secretbox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/apiserver/pkg/storage/value"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
key1 = [32]byte{0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}
|
||||||
|
key2 = [32]byte{0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02}
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSecretboxKeyRotation(t *testing.T) {
|
||||||
|
testErr := fmt.Errorf("test error")
|
||||||
|
context := value.DefaultContext([]byte("authenticated_data"))
|
||||||
|
|
||||||
|
p := value.NewPrefixTransformers(testErr,
|
||||||
|
value.PrefixTransformer{Prefix: []byte("first:"), Transformer: NewSecretboxTransformer(key1)},
|
||||||
|
value.PrefixTransformer{Prefix: []byte("second:"), Transformer: NewSecretboxTransformer(key2)},
|
||||||
|
)
|
||||||
|
out, err := p.TransformToStorage([]byte("firstvalue"), context)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !bytes.HasPrefix(out, []byte("first:")) {
|
||||||
|
t.Fatalf("unexpected prefix: %q", out)
|
||||||
|
}
|
||||||
|
from, stale, err := p.TransformFromStorage(out, context)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if stale || !bytes.Equal([]byte("firstvalue"), from) {
|
||||||
|
t.Fatalf("unexpected data: %t %q", stale, from)
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify changing the context does not fails storage
|
||||||
|
// Secretbox is not currently an authenticating store
|
||||||
|
from, stale, err = p.TransformFromStorage(out, value.DefaultContext([]byte("incorrect_context")))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("secretbox is not authenticated")
|
||||||
|
}
|
||||||
|
|
||||||
|
// reverse the order, use the second key
|
||||||
|
p = value.NewPrefixTransformers(testErr,
|
||||||
|
value.PrefixTransformer{Prefix: []byte("second:"), Transformer: NewSecretboxTransformer(key2)},
|
||||||
|
value.PrefixTransformer{Prefix: []byte("first:"), Transformer: NewSecretboxTransformer(key1)},
|
||||||
|
)
|
||||||
|
from, stale, err = p.TransformFromStorage(out, context)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !stale || !bytes.Equal([]byte("firstvalue"), from) {
|
||||||
|
t.Fatalf("unexpected data: %t %q", stale, from)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkSecretboxRead_32_1024(b *testing.B) { benchmarkSecretboxRead(b, 32, 1024, false) }
|
||||||
|
func BenchmarkSecretboxRead_32_16384(b *testing.B) { benchmarkSecretboxRead(b, 32, 16384, false) }
|
||||||
|
func BenchmarkSecretboxRead_32_16384_Stale(b *testing.B) { benchmarkSecretboxRead(b, 32, 16384, true) }
|
||||||
|
|
||||||
|
func BenchmarkSecretboxWrite_32_1024(b *testing.B) { benchmarkSecretboxWrite(b, 32, 1024) }
|
||||||
|
func BenchmarkSecretboxWrite_32_16384(b *testing.B) { benchmarkSecretboxWrite(b, 32, 16384) }
|
||||||
|
|
||||||
|
func benchmarkSecretboxRead(b *testing.B, keyLength int, valueLength int, stale bool) {
|
||||||
|
p := value.NewPrefixTransformers(nil,
|
||||||
|
value.PrefixTransformer{Prefix: []byte("first:"), Transformer: NewSecretboxTransformer(key1)},
|
||||||
|
value.PrefixTransformer{Prefix: []byte("second:"), Transformer: NewSecretboxTransformer(key2)},
|
||||||
|
)
|
||||||
|
|
||||||
|
context := value.DefaultContext([]byte("authenticated_data"))
|
||||||
|
v := bytes.Repeat([]byte("0123456789abcdef"), valueLength/16)
|
||||||
|
|
||||||
|
out, err := p.TransformToStorage(v, context)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
// reverse the key order if stale
|
||||||
|
if stale {
|
||||||
|
p = value.NewPrefixTransformers(nil,
|
||||||
|
value.PrefixTransformer{Prefix: []byte("first:"), Transformer: NewSecretboxTransformer(key1)},
|
||||||
|
value.PrefixTransformer{Prefix: []byte("second:"), Transformer: NewSecretboxTransformer(key2)},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
from, stale, err := p.TransformFromStorage(out, context)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
if stale {
|
||||||
|
b.Fatalf("unexpected data: %t %q", stale, from)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.StopTimer()
|
||||||
|
}
|
||||||
|
|
||||||
|
func benchmarkSecretboxWrite(b *testing.B, keyLength int, valueLength int) {
|
||||||
|
p := value.NewPrefixTransformers(nil,
|
||||||
|
value.PrefixTransformer{Prefix: []byte("first:"), Transformer: NewSecretboxTransformer(key1)},
|
||||||
|
value.PrefixTransformer{Prefix: []byte("second:"), Transformer: NewSecretboxTransformer(key2)},
|
||||||
|
)
|
||||||
|
|
||||||
|
context := value.DefaultContext([]byte("authenticated_data"))
|
||||||
|
v := bytes.Repeat([]byte("0123456789abcdef"), valueLength/16)
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := p.TransformToStorage(v, context)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.StopTimer()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRoundTrip(t *testing.T) {
|
||||||
|
lengths := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 128, 1024}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
context value.Context
|
||||||
|
t value.Transformer
|
||||||
|
}{
|
||||||
|
{name: "GCM 16 byte key", t: NewSecretboxTransformer(key1)},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
context := tt.context
|
||||||
|
if context == nil {
|
||||||
|
context = value.DefaultContext("")
|
||||||
|
}
|
||||||
|
for _, l := range lengths {
|
||||||
|
data := make([]byte, l)
|
||||||
|
if _, err := io.ReadFull(rand.Reader, data); err != nil {
|
||||||
|
t.Fatalf("unable to read sufficient random bytes: %v", err)
|
||||||
|
}
|
||||||
|
original := append([]byte{}, data...)
|
||||||
|
|
||||||
|
ciphertext, err := tt.t.TransformToStorage(data, context)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("TransformToStorage error = %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
result, stale, err := tt.t.TransformFromStorage(ciphertext, context)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("TransformFromStorage error = %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if stale {
|
||||||
|
t.Errorf("unexpected stale output")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case l == 0:
|
||||||
|
if len(result) != 0 {
|
||||||
|
t.Errorf("Round trip failed len=%d\noriginal:\n%s\nresult:\n%s", l, hex.Dump(original), hex.Dump(result))
|
||||||
|
}
|
||||||
|
case !reflect.DeepEqual(original, result):
|
||||||
|
t.Errorf("Round trip failed len=%d\noriginal:\n%s\nresult:\n%s", l, hex.Dump(original), hex.Dump(result))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user