Skip to content

Commit

Permalink
UPSTREAM: <carry>: add crdvalidation for apiserver.spec.tlsSecurityPr…
Browse files Browse the repository at this point in the history
…ofile

Origin-commit: 84ba7fc304870a30df7136da14bccb4d5232f075
  • Loading branch information
stlaz authored and soltysh committed Sep 8, 2021
1 parent aca622b commit c5bf40e
Show file tree
Hide file tree
Showing 3 changed files with 223 additions and 7 deletions.
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ import (
"regexp"
"strings"

configv1 "github.com/openshift/api/config/v1"
configv1client "github.com/openshift/client-go/config/clientset/versioned/typed/config/v1"

"k8s.io/apimachinery/pkg/api/validation"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/kubernetes/openshift-kube-apiserver/admission/customresourcevalidation"

configv1 "github.com/openshift/api/config/v1"
configv1client "github.com/openshift/client-go/config/clientset/versioned/typed/config/v1"
libgocrypto "github.com/openshift/library-go/pkg/crypto"
)

func toAPIServerV1(uncastObj runtime.Object) (*configv1.APIServer, field.ErrorList) {
Expand Down Expand Up @@ -117,14 +118,22 @@ func (apiserverV1) ValidateStatusUpdate(uncastObj runtime.Object, uncastOldObj r
}

func validateAPIServerSpecCreate(spec configv1.APIServerSpec) field.ErrorList {
errs := field.ErrorList{}
specPath := field.NewPath("spec")

errs = append(errs, validateAdditionalCORSAllowedOrigins(specPath.Child("additionalCORSAllowedOrigins"), spec.AdditionalCORSAllowedOrigins)...)
errs = append(errs, validateTLSSecurityProfile(specPath.Child("tlsSecurityProfile"), spec.TLSSecurityProfile)...)

errs := validateAdditionalCORSAllowedOrigins(field.NewPath("spec").Child("additionalCORSAllowedOrigins"), spec.AdditionalCORSAllowedOrigins)
return errs
}

func validateAPIServerSpecUpdate(newSpec, oldSpec configv1.APIServerSpec) field.ErrorList {
errs := field.ErrorList{}
specPath := field.NewPath("spec")

errs = append(errs, validateAdditionalCORSAllowedOrigins(specPath.Child("additionalCORSAllowedOrigins"), newSpec.AdditionalCORSAllowedOrigins)...)
errs = append(errs, validateTLSSecurityProfile(specPath.Child("tlsSecurityProfile"), newSpec.TLSSecurityProfile)...)

errs := validateAdditionalCORSAllowedOrigins(field.NewPath("spec").Child("additionalCORSAllowedOrigins"), newSpec.AdditionalCORSAllowedOrigins)
return errs
}

Expand All @@ -147,3 +156,82 @@ func validateAdditionalCORSAllowedOrigins(fieldPath *field.Path, cors []string)

return errs
}

func validateTLSSecurityProfile(fieldPath *field.Path, profile *configv1.TLSSecurityProfile) field.ErrorList {
errs := field.ErrorList{}

if profile == nil {
return errs
}

errs = append(errs, validateTLSSecurityProfileType(fieldPath, profile)...)

if profile.Type == configv1.TLSProfileCustomType && profile.Custom != nil {
errs = append(errs, validateCipherSuites(fieldPath.Child("custom", "ciphers"), profile.Custom.Ciphers)...)
errs = append(errs, validateMinTLSVersion(fieldPath.Child("custom", "minTLSVersion"), profile.Custom.MinTLSVersion)...)
}

return errs
}

func validateTLSSecurityProfileType(fieldPath *field.Path, profile *configv1.TLSSecurityProfile) field.ErrorList {
const typeProfileMismatchFmt = "type set to %s, but the corresponding field is unset"
typePath := fieldPath.Child("type")

errs := field.ErrorList{}

availableTypes := []string{
string(configv1.TLSProfileOldType),
string(configv1.TLSProfileIntermediateType),
string(configv1.TLSProfileCustomType),
}

switch profile.Type {
case "":
if profile.Old != nil || profile.Intermediate != nil || profile.Modern != nil || profile.Custom != nil {
errs = append(errs, field.Required(typePath, "one of the profiles is set but 'type' field is empty"))
}
case configv1.TLSProfileOldType:
if profile.Old == nil {
errs = append(errs, field.Required(fieldPath.Child("old"), fmt.Sprintf(typeProfileMismatchFmt, profile.Type)))
}
case configv1.TLSProfileIntermediateType:
if profile.Intermediate == nil {
errs = append(errs, field.Required(fieldPath.Child("intermediate"), fmt.Sprintf(typeProfileMismatchFmt, profile.Type)))
}
case configv1.TLSProfileModernType:
errs = append(errs, field.NotSupported(fieldPath.Child("type"), profile.Type, availableTypes))
case configv1.TLSProfileCustomType:
if profile.Custom == nil {
errs = append(errs, field.Required(fieldPath.Child("custom"), fmt.Sprintf(typeProfileMismatchFmt, profile.Type)))
}
default:
errs = append(errs, field.Invalid(typePath, profile.Type, fmt.Sprintf("unknown type, valid values are: %v", availableTypes)))
}

return errs
}

func validateCipherSuites(fieldPath *field.Path, suites []string) field.ErrorList {
errs := field.ErrorList{}

if ianaSuites := libgocrypto.OpenSSLToIANACipherSuites(suites); len(ianaSuites) == 0 {
errs = append(errs, field.Invalid(fieldPath, suites, "no supported cipher suite found"))
}

return errs
}

func validateMinTLSVersion(fieldPath *field.Path, version configv1.TLSProtocolVersion) field.ErrorList {
errs := field.ErrorList{}

if version == configv1.VersionTLS13 {
return append(errs, field.NotSupported(fieldPath, version, []string{string(configv1.VersionTLS10), string(configv1.VersionTLS11), string(configv1.VersionTLS12)}))
}

if _, err := libgocrypto.TLSVersion(string(version)); err != nil {
errs = append(errs, field.Invalid(fieldPath, version, err.Error()))
}

return errs
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@ package apiserver
import (
"testing"

"k8s.io/apimachinery/pkg/util/validation/field"

configv1 "github.com/openshift/api/config/v1"
configclientfake "github.com/openshift/client-go/config/clientset/versioned/fake"
configv1client "github.com/openshift/client-go/config/clientset/versioned/typed/config/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/validation/field"
)

func TestValidateSNINames(t *testing.T) {
Expand Down Expand Up @@ -115,3 +114,132 @@ func TestValidateSNINames(t *testing.T) {

}
}

func Test_validateTLSSecurityProfile(t *testing.T) {
rootFieldPath := field.NewPath("testSpec")

tests := []struct {
name string
profile *configv1.TLSSecurityProfile
want field.ErrorList
}{
{
name: "nil profile",
profile: nil,
want: field.ErrorList{},
},
{
name: "empty profile",
profile: &configv1.TLSSecurityProfile{},
want: field.ErrorList{},
},
{
name: "type does not match set field",
profile: &configv1.TLSSecurityProfile{
Type: configv1.TLSProfileIntermediateType,
Modern: &configv1.ModernTLSProfile{},
},
want: field.ErrorList{
field.Required(rootFieldPath.Child("intermediate"), "type set to Intermediate, but the corresponding field is unset"),
},
},
{
name: "modern type - currently unsupported",
profile: &configv1.TLSSecurityProfile{
Type: configv1.TLSProfileModernType,
Modern: &configv1.ModernTLSProfile{},
},
want: field.ErrorList{
field.NotSupported(rootFieldPath.Child("type"), configv1.TLSProfileModernType,
[]string{
string(configv1.TLSProfileOldType),
string(configv1.TLSProfileIntermediateType),
string(configv1.TLSProfileCustomType),
}),
},
},
{
name: "unknown type",
profile: &configv1.TLSSecurityProfile{
Type: "something",
},
want: field.ErrorList{
field.Invalid(rootFieldPath.Child("type"), "something", "unknown type, valid values are: [Old Intermediate Custom]"),
},
},
{
name: "unknown cipher",
profile: &configv1.TLSSecurityProfile{
Type: "Custom",
Custom: &configv1.CustomTLSProfile{
TLSProfileSpec: configv1.TLSProfileSpec{
Ciphers: []string{
"UNKNOWN_CIPHER",
},
},
},
},
want: field.ErrorList{
field.Invalid(rootFieldPath.Child("custom", "ciphers"), []string{"UNKNOWN_CIPHER"}, "no supported cipher suite found"),
},
},
{
name: "unknown cipher but a normal cipher",
profile: &configv1.TLSSecurityProfile{
Type: "Custom",
Custom: &configv1.CustomTLSProfile{
TLSProfileSpec: configv1.TLSProfileSpec{
Ciphers: []string{
"UNKNOWN_CIPHER", "ECDHE-ECDSA-CHACHA20-POLY1305",
},
},
},
},
want: field.ErrorList{},
},
{
name: "no ciphers in custom profile",
profile: &configv1.TLSSecurityProfile{
Type: "Custom",
Custom: &configv1.CustomTLSProfile{
TLSProfileSpec: configv1.TLSProfileSpec{},
},
},
want: field.ErrorList{
field.Invalid(rootFieldPath.Child("custom", "ciphers"), []string(nil), "no supported cipher suite found"),
},
},
{
name: "min tls 1.3 - currently unsupported",
profile: &configv1.TLSSecurityProfile{
Type: "Custom",
Custom: &configv1.CustomTLSProfile{
TLSProfileSpec: configv1.TLSProfileSpec{
Ciphers: []string{"ECDHE-ECDSA-CHACHA20-POLY1305"},
MinTLSVersion: configv1.VersionTLS13,
},
},
},
want: field.ErrorList{
field.NotSupported(rootFieldPath.Child("custom", "minTLSVersion"), configv1.VersionTLS13, []string{string(configv1.VersionTLS10), string(configv1.VersionTLS11), string(configv1.VersionTLS12)}),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := validateTLSSecurityProfile(rootFieldPath, tt.profile)

if len(tt.want) != len(got) {
t.Errorf("expected %d errors, got %d: %v", len(tt.want), len(got), got)
return
}

for i, err := range got {
if err.Error() != tt.want[i].Error() {
t.Errorf("expected %v, got %v", tt.want, got)
break
}
}
})
}
}

0 comments on commit c5bf40e

Please sign in to comment.