mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-22 18:16:52 +00:00
Merge pull request #82434 from enj/enj/i/enc_overlapping_prefix
Encryption config: correctly handle overlapping providers
This commit is contained in:
commit
03286258d2
@ -605,3 +605,206 @@ resources:
|
|||||||
func serviceComparer(_, _ envelope.Service) bool {
|
func serviceComparer(_, _ envelope.Service) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCBCKeyRotationWithOverlappingProviders(t *testing.T) {
|
||||||
|
testCBCKeyRotationWithProviders(
|
||||||
|
t,
|
||||||
|
`{
|
||||||
|
"kind": "EncryptionConfiguration",
|
||||||
|
"apiVersion": "apiserver.config.k8s.io/v1",
|
||||||
|
"resources": [
|
||||||
|
{
|
||||||
|
"resources": [
|
||||||
|
"ignored"
|
||||||
|
],
|
||||||
|
"providers": [
|
||||||
|
{
|
||||||
|
"aescbc": {
|
||||||
|
"keys": [
|
||||||
|
{
|
||||||
|
"name": "1",
|
||||||
|
"secret": "Owq7A4JrJpSjrvH8kXkvl4JmOLzvZ6j9BcGRkR8OPQ4="
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"aescbc": {
|
||||||
|
"keys": [
|
||||||
|
{
|
||||||
|
"name": "2",
|
||||||
|
"secret": "+qcnfOFX3aRXM9PuY7lQXDWYIQ3GWUdBc3nYBo91SCA="
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`,
|
||||||
|
"k8s:enc:aescbc:v1:1:",
|
||||||
|
`{
|
||||||
|
"kind": "EncryptionConfiguration",
|
||||||
|
"apiVersion": "apiserver.config.k8s.io/v1",
|
||||||
|
"resources": [
|
||||||
|
{
|
||||||
|
"resources": [
|
||||||
|
"ignored"
|
||||||
|
],
|
||||||
|
"providers": [
|
||||||
|
{
|
||||||
|
"aescbc": {
|
||||||
|
"keys": [
|
||||||
|
{
|
||||||
|
"name": "2",
|
||||||
|
"secret": "+qcnfOFX3aRXM9PuY7lQXDWYIQ3GWUdBc3nYBo91SCA="
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"aescbc": {
|
||||||
|
"keys": [
|
||||||
|
{
|
||||||
|
"name": "1",
|
||||||
|
"secret": "Owq7A4JrJpSjrvH8kXkvl4JmOLzvZ6j9BcGRkR8OPQ4="
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`,
|
||||||
|
"k8s:enc:aescbc:v1:2:",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCBCKeyRotationWithoutOverlappingProviders(t *testing.T) {
|
||||||
|
testCBCKeyRotationWithProviders(
|
||||||
|
t,
|
||||||
|
`{
|
||||||
|
"kind": "EncryptionConfiguration",
|
||||||
|
"apiVersion": "apiserver.config.k8s.io/v1",
|
||||||
|
"resources": [
|
||||||
|
{
|
||||||
|
"resources": [
|
||||||
|
"ignored"
|
||||||
|
],
|
||||||
|
"providers": [
|
||||||
|
{
|
||||||
|
"aescbc": {
|
||||||
|
"keys": [
|
||||||
|
{
|
||||||
|
"name": "A",
|
||||||
|
"secret": "Owq7A4JrJpSjrvH8kXkvl4JmOLzvZ6j9BcGRkR8OPQ4="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "B",
|
||||||
|
"secret": "+qcnfOFX3aRXM9PuY7lQXDWYIQ3GWUdBc3nYBo91SCA="
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`,
|
||||||
|
"k8s:enc:aescbc:v1:A:",
|
||||||
|
`{
|
||||||
|
"kind": "EncryptionConfiguration",
|
||||||
|
"apiVersion": "apiserver.config.k8s.io/v1",
|
||||||
|
"resources": [
|
||||||
|
{
|
||||||
|
"resources": [
|
||||||
|
"ignored"
|
||||||
|
],
|
||||||
|
"providers": [
|
||||||
|
{
|
||||||
|
"aescbc": {
|
||||||
|
"keys": [
|
||||||
|
{
|
||||||
|
"name": "B",
|
||||||
|
"secret": "+qcnfOFX3aRXM9PuY7lQXDWYIQ3GWUdBc3nYBo91SCA="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "A",
|
||||||
|
"secret": "Owq7A4JrJpSjrvH8kXkvl4JmOLzvZ6j9BcGRkR8OPQ4="
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`,
|
||||||
|
"k8s:enc:aescbc:v1:B:",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCBCKeyRotationWithProviders(t *testing.T, firstEncryptionConfig, firstPrefix, secondEncryptionConfig, secondPrefix string) {
|
||||||
|
p := getTransformerFromEncryptionConfig(t, firstEncryptionConfig)
|
||||||
|
|
||||||
|
context := value.DefaultContext([]byte("authenticated_data"))
|
||||||
|
|
||||||
|
out, err := p.TransformToStorage([]byte("firstvalue"), context)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !bytes.HasPrefix(out, []byte(firstPrefix)) {
|
||||||
|
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 fails storage
|
||||||
|
_, _, err = p.TransformFromStorage(out, value.DefaultContext([]byte("incorrect_context")))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("CBC mode does not support authentication: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// reverse the order, use the second key
|
||||||
|
p = getTransformerFromEncryptionConfig(t, secondEncryptionConfig)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err = p.TransformToStorage([]byte("firstvalue"), context)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !bytes.HasPrefix(out, []byte(secondPrefix)) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTransformerFromEncryptionConfig(t *testing.T, encryptionConfig string) value.Transformer {
|
||||||
|
t.Helper()
|
||||||
|
transformers, err := ParseEncryptionConfiguration(strings.NewReader(encryptionConfig))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(transformers) != 1 {
|
||||||
|
t.Fatalf("input config does not have exactly one resource: %s", encryptionConfig)
|
||||||
|
}
|
||||||
|
for _, transformer := range transformers {
|
||||||
|
return transformer
|
||||||
|
}
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
@ -30,6 +30,7 @@ go_library(
|
|||||||
importmap = "k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/storage/value",
|
importmap = "k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/storage/value",
|
||||||
importpath = "k8s.io/apiserver/pkg/storage/value",
|
importpath = "k8s.io/apiserver/pkg/storage/value",
|
||||||
deps = [
|
deps = [
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/util/errors:go_default_library",
|
||||||
"//staging/src/k8s.io/component-base/metrics:go_default_library",
|
"//staging/src/k8s.io/component-base/metrics:go_default_library",
|
||||||
"//staging/src/k8s.io/component-base/metrics/legacyregistry:go_default_library",
|
"//staging/src/k8s.io/component-base/metrics/legacyregistry:go_default_library",
|
||||||
"//vendor/github.com/prometheus/client_golang/prometheus:go_default_library",
|
"//vendor/github.com/prometheus/client_golang/prometheus:go_default_library",
|
||||||
|
@ -22,6 +22,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/util/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -129,6 +131,7 @@ func NewPrefixTransformers(err error, transformers ...PrefixTransformer) Transfo
|
|||||||
// the first transformer.
|
// the first transformer.
|
||||||
func (t *prefixTransformers) TransformFromStorage(data []byte, context Context) ([]byte, bool, error) {
|
func (t *prefixTransformers) TransformFromStorage(data []byte, context Context) ([]byte, bool, error) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
var errs []error
|
||||||
for i, transformer := range t.transformers {
|
for i, transformer := range t.transformers {
|
||||||
if bytes.HasPrefix(data, transformer.Prefix) {
|
if bytes.HasPrefix(data, transformer.Prefix) {
|
||||||
result, stale, err := transformer.Transformer.TransformFromStorage(data[len(transformer.Prefix):], context)
|
result, stale, err := transformer.Transformer.TransformFromStorage(data[len(transformer.Prefix):], context)
|
||||||
@ -144,9 +147,48 @@ func (t *prefixTransformers) TransformFromStorage(data []byte, context Context)
|
|||||||
} else {
|
} else {
|
||||||
RecordTransformation("from_storage", string(transformer.Prefix), start, err)
|
RecordTransformation("from_storage", string(transformer.Prefix), start, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// It is valid to have overlapping prefixes when the same encryption provider
|
||||||
|
// is specified multiple times but with different keys (the first provider is
|
||||||
|
// being rotated to and some later provider is being rotated away from).
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// {
|
||||||
|
// "aescbc": {
|
||||||
|
// "keys": [
|
||||||
|
// {
|
||||||
|
// "name": "2",
|
||||||
|
// "secret": "some key 2"
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "aescbc": {
|
||||||
|
// "keys": [
|
||||||
|
// {
|
||||||
|
// "name": "1",
|
||||||
|
// "secret": "some key 1"
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
//
|
||||||
|
// The transformers for both aescbc configs share the prefix k8s:enc:aescbc:v1:
|
||||||
|
// but a failure in the first one should not prevent a later match from being attempted.
|
||||||
|
// Thus we never short-circuit on a prefix match that results in an error.
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
return result, stale || i != 0, err
|
return result, stale || i != 0, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if err := errors.Reduce(errors.NewAggregate(errs)); err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
RecordTransformation("from_storage", "unknown", start, t.err)
|
RecordTransformation("from_storage", "unknown", start, t.err)
|
||||||
return nil, false, t.err
|
return nil, false, t.err
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ func (t *testTransformer) TransformToStorage(to []byte, context Context) (data [
|
|||||||
|
|
||||||
func TestPrefixFrom(t *testing.T) {
|
func TestPrefixFrom(t *testing.T) {
|
||||||
testErr := fmt.Errorf("test error")
|
testErr := fmt.Errorf("test error")
|
||||||
transformErr := fmt.Errorf("test error")
|
transformErr := fmt.Errorf("transform error")
|
||||||
transformers := []PrefixTransformer{
|
transformers := []PrefixTransformer{
|
||||||
{Prefix: []byte("first:"), Transformer: &testTransformer{from: []byte("value1")}},
|
{Prefix: []byte("first:"), Transformer: &testTransformer{from: []byte("value1")}},
|
||||||
{Prefix: []byte("second:"), Transformer: &testTransformer{from: []byte("value2")}},
|
{Prefix: []byte("second:"), Transformer: &testTransformer{from: []byte("value2")}},
|
||||||
@ -64,7 +64,7 @@ func TestPrefixFrom(t *testing.T) {
|
|||||||
{[]byte("first:value"), []byte("value1"), false, nil, 0},
|
{[]byte("first:value"), []byte("value1"), false, nil, 0},
|
||||||
{[]byte("second:value"), []byte("value2"), true, nil, 1},
|
{[]byte("second:value"), []byte("value2"), true, nil, 1},
|
||||||
{[]byte("third:value"), nil, false, testErr, -1},
|
{[]byte("third:value"), nil, false, testErr, -1},
|
||||||
{[]byte("fails:value"), nil, true, transformErr, 2},
|
{[]byte("fails:value"), nil, false, transformErr, 2},
|
||||||
{[]byte("stale:value"), []byte("value3"), true, nil, 3},
|
{[]byte("stale:value"), []byte("value3"), true, nil, 3},
|
||||||
}
|
}
|
||||||
for i, test := range testCases {
|
for i, test := range testCases {
|
||||||
|
Loading…
Reference in New Issue
Block a user