apiserver/pkg/apis/config/validation/validation_test.go

353 lines
9.2 KiB
Go

/*
Copyright 2019 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 validation
import (
"fmt"
"testing"
"time"
"github.com/google/go-cmp/cmp"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apiserver/pkg/apis/config"
)
func TestStructure(t *testing.T) {
firstResourcePath := root.Index(0)
testCases := []struct {
desc string
in *config.EncryptionConfiguration
want field.ErrorList
}{
{
desc: "nil encryption config",
in: nil,
want: field.ErrorList{
field.Required(root, encryptionConfigNilErr),
},
},
{
desc: "empty encryption config",
in: &config.EncryptionConfiguration{},
want: field.ErrorList{
field.Required(root, fmt.Sprintf(atLeastOneRequiredErrFmt, root)),
},
},
{
desc: "no k8s resources",
in: &config.EncryptionConfiguration{
Resources: []config.ResourceConfiguration{
{
Providers: []config.ProviderConfiguration{
{
AESCBC: &config.AESConfiguration{
Keys: []config.Key{
{
Name: "foo",
Secret: "A/j5CnrWGB83ylcPkuUhm/6TSyrQtsNJtDPwPHNOj4Q=",
},
},
},
},
},
},
},
},
want: field.ErrorList{
field.Required(firstResourcePath.Child("resources"), fmt.Sprintf(atLeastOneRequiredErrFmt, root.Index(0).Child("resources"))),
},
},
{
desc: "no providers",
in: &config.EncryptionConfiguration{
Resources: []config.ResourceConfiguration{
{
Resources: []string{"secrets"},
},
},
},
want: field.ErrorList{
field.Required(firstResourcePath.Child("providers"), fmt.Sprintf(atLeastOneRequiredErrFmt, root.Index(0).Child("providers"))),
},
},
{
desc: "multiple providers",
in: &config.EncryptionConfiguration{
Resources: []config.ResourceConfiguration{
{
Resources: []string{"secrets"},
Providers: []config.ProviderConfiguration{
{
AESGCM: &config.AESConfiguration{
Keys: []config.Key{
{
Name: "foo",
Secret: "A/j5CnrWGB83ylcPkuUhm/6TSyrQtsNJtDPwPHNOj4Q=",
},
},
},
AESCBC: &config.AESConfiguration{
Keys: []config.Key{
{
Name: "foo",
Secret: "A/j5CnrWGB83ylcPkuUhm/6TSyrQtsNJtDPwPHNOj4Q=",
},
},
},
},
},
},
},
},
want: field.ErrorList{
field.Invalid(
firstResourcePath.Child("providers").Index(0),
config.ProviderConfiguration{
AESGCM: &config.AESConfiguration{
Keys: []config.Key{
{
Name: "foo",
Secret: "A/j5CnrWGB83ylcPkuUhm/6TSyrQtsNJtDPwPHNOj4Q=",
},
},
},
AESCBC: &config.AESConfiguration{
Keys: []config.Key{
{
Name: "foo",
Secret: "A/j5CnrWGB83ylcPkuUhm/6TSyrQtsNJtDPwPHNOj4Q=",
},
},
},
},
moreThanOneElementErr),
},
},
{
desc: "valid config",
in: &config.EncryptionConfiguration{
Resources: []config.ResourceConfiguration{
{
Resources: []string{"secrets"},
Providers: []config.ProviderConfiguration{
{
AESGCM: &config.AESConfiguration{
Keys: []config.Key{
{
Name: "foo",
Secret: "A/j5CnrWGB83ylcPkuUhm/6TSyrQtsNJtDPwPHNOj4Q=",
},
},
},
},
},
},
},
},
want: field.ErrorList{},
},
}
for _, tt := range testCases {
t.Run(tt.desc, func(t *testing.T) {
got := ValidateEncryptionConfiguration(tt.in)
if d := cmp.Diff(tt.want, got); d != "" {
t.Fatalf("EncryptionConfiguratoin validation results mismatch (-want +got):\n%s", d)
}
})
}
}
func TestKey(t *testing.T) {
path := root.Index(0).Child("provider").Index(0).Child("key").Index(0)
testCases := []struct {
desc string
in config.Key
want field.ErrorList
}{
{
desc: "valid key",
in: config.Key{Name: "foo", Secret: "c2VjcmV0IGlzIHNlY3VyZQ=="},
want: field.ErrorList{},
},
{
desc: "key without name",
in: config.Key{Secret: "c2VjcmV0IGlzIHNlY3VyZQ=="},
want: field.ErrorList{
field.Required(path.Child("name"), fmt.Sprintf(mandatoryFieldErrFmt, "name", "key")),
},
},
{
desc: "key without secret",
in: config.Key{Name: "foo"},
want: field.ErrorList{
field.Required(path.Child("secret"), fmt.Sprintf(mandatoryFieldErrFmt, "secret", "key")),
},
},
{
desc: "key is not base64 encoded",
in: config.Key{Name: "foo", Secret: "P@ssword"},
want: field.ErrorList{
field.Invalid(path.Child("secret"), "REDACTED", base64EncodingErr),
},
},
{
desc: "key is not of expected length",
in: config.Key{Name: "foo", Secret: "cGFzc3dvcmQK"},
want: field.ErrorList{
field.Invalid(path.Child("secret"), "REDACTED", fmt.Sprintf(keyLenErrFmt, 9, aesKeySizes)),
},
},
}
for _, tt := range testCases {
t.Run(tt.desc, func(t *testing.T) {
got := validateKey(tt.in, path, aesKeySizes)
if d := cmp.Diff(tt.want, got); d != "" {
t.Fatalf("Key validation results mismatch (-want +got):\n%s", d)
}
})
}
}
func TestKMSProviderTimeout(t *testing.T) {
timeoutField := field.NewPath("Resource").Index(0).Child("Provider").Index(0).Child("KMS").Child("Timeout")
negativeTimeout := &metav1.Duration{Duration: -1 * time.Minute}
zeroTimeout := &metav1.Duration{Duration: 0 * time.Minute}
testCases := []struct {
desc string
in *config.KMSConfiguration
want field.ErrorList
}{
{
desc: "valid timeout",
in: &config.KMSConfiguration{Timeout: &metav1.Duration{Duration: 1 * time.Minute}},
want: field.ErrorList{},
},
{
desc: "negative timeout",
in: &config.KMSConfiguration{Timeout: negativeTimeout},
want: field.ErrorList{
field.Invalid(timeoutField, negativeTimeout, fmt.Sprintf(zeroOrNegativeErrFmt, "timeout")),
},
},
{
desc: "zero timeout",
in: &config.KMSConfiguration{Timeout: zeroTimeout},
want: field.ErrorList{
field.Invalid(timeoutField, zeroTimeout, fmt.Sprintf(zeroOrNegativeErrFmt, "timeout")),
},
},
}
for _, tt := range testCases {
t.Run(tt.desc, func(t *testing.T) {
got := validateKMSTimeout(tt.in, timeoutField)
if d := cmp.Diff(tt.want, got); d != "" {
t.Fatalf("KMS Provider validation mismatch (-want +got):\n%s", d)
}
})
}
}
func TestKMSEndpoint(t *testing.T) {
endpointField := field.NewPath("Resource").Index(0).Child("Provider").Index(0).Child("kms").Child("endpoint")
testCases := []struct {
desc string
in *config.KMSConfiguration
want field.ErrorList
}{
{
desc: "valid endpoint",
in: &config.KMSConfiguration{Endpoint: "unix:///socket.sock"},
want: field.ErrorList{},
},
{
desc: "empty endpoint",
in: &config.KMSConfiguration{},
want: field.ErrorList{
field.Invalid(endpointField, "", fmt.Sprintf(mandatoryFieldErrFmt, "endpoint", "kms")),
},
},
{
desc: "non unix endpoint",
in: &config.KMSConfiguration{Endpoint: "https://www.foo.com"},
want: field.ErrorList{
field.Invalid(endpointField, "https://www.foo.com", fmt.Sprintf(unsupportedSchemeErrFmt, "https")),
},
},
{
desc: "invalid url",
in: &config.KMSConfiguration{Endpoint: "unix:///foo\n.socket"},
want: field.ErrorList{
field.Invalid(endpointField, "unix:///foo\n.socket", "invalid endpoint for kms provider, error: parse unix:///foo\n.socket: net/url: invalid control character in URL"),
},
},
}
for _, tt := range testCases {
t.Run(tt.desc, func(t *testing.T) {
got := validateKMSEndpoint(tt.in, endpointField)
if d := cmp.Diff(tt.want, got); d != "" {
t.Fatalf("KMS Provider validation mismatch (-want +got):\n%s", d)
}
})
}
}
func TestKMSProviderCacheSize(t *testing.T) {
cacheField := root.Index(0).Child("kms").Child("cachesize")
negativeCacheSize := int32(-1)
positiveCacheSize := int32(10)
zeroCacheSize := int32(0)
testCases := []struct {
desc string
in *config.KMSConfiguration
want field.ErrorList
}{
{
desc: "valid positive cache size",
in: &config.KMSConfiguration{CacheSize: &positiveCacheSize},
want: field.ErrorList{},
},
{
desc: "invalid zero cache size",
in: &config.KMSConfiguration{CacheSize: &zeroCacheSize},
want: field.ErrorList{
field.Invalid(cacheField, int32(0), fmt.Sprintf(nonZeroErrFmt, "cachesize")),
},
},
{
desc: "valid negative caches size",
in: &config.KMSConfiguration{CacheSize: &negativeCacheSize},
want: field.ErrorList{},
},
}
for _, tt := range testCases {
t.Run(tt.desc, func(t *testing.T) {
got := validateKMSCacheSize(tt.in, cacheField)
if d := cmp.Diff(tt.want, got); d != "" {
t.Fatalf("KMS Provider validation mismatch (-want +got):\n%s", d)
}
})
}
}