Merge pull request #113218 from ahmedtd/kep-3257
Add certificates.k8s.io/v1alpha1 ClusterTrustBundle
This commit is contained in:
@@ -24,6 +24,7 @@ import (
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
"k8s.io/kubernetes/pkg/apis/certificates"
|
||||
v1 "k8s.io/kubernetes/pkg/apis/certificates/v1"
|
||||
"k8s.io/kubernetes/pkg/apis/certificates/v1alpha1"
|
||||
"k8s.io/kubernetes/pkg/apis/certificates/v1beta1"
|
||||
)
|
||||
|
||||
@@ -36,5 +37,6 @@ func Install(scheme *runtime.Scheme) {
|
||||
utilruntime.Must(certificates.AddToScheme(scheme))
|
||||
utilruntime.Must(v1.AddToScheme(scheme))
|
||||
utilruntime.Must(v1beta1.AddToScheme(scheme))
|
||||
utilruntime.Must(scheme.SetVersionPriority(v1.SchemeGroupVersion, v1beta1.SchemeGroupVersion))
|
||||
utilruntime.Must(v1alpha1.AddToScheme(scheme))
|
||||
utilruntime.Must(scheme.SetVersionPriority(v1.SchemeGroupVersion, v1beta1.SchemeGroupVersion, v1alpha1.SchemeGroupVersion))
|
||||
}
|
||||
|
@@ -47,6 +47,8 @@ func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&CertificateSigningRequest{},
|
||||
&CertificateSigningRequestList{},
|
||||
&ClusterTrustBundle{},
|
||||
&ClusterTrustBundleList{},
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
@@ -224,3 +224,56 @@ const (
|
||||
UsageMicrosoftSGC KeyUsage = "microsoft sgc"
|
||||
UsageNetscapeSGC KeyUsage = "netscape sgc"
|
||||
)
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// ClusterTrustBundle is a cluster-scoped container for X.509 trust anchors
|
||||
// (root certificates).
|
||||
//
|
||||
// ClusterTrustBundle objects are considered to be readable by any authenticated
|
||||
// user in the cluster.
|
||||
//
|
||||
// It can be optionally associated with a particular assigner, in which case it
|
||||
// contains one valid set of trust anchors for that signer. Signers may have
|
||||
// multiple associated ClusterTrustBundles; each is an independent set of trust
|
||||
// anchors for that signer.
|
||||
type ClusterTrustBundle struct {
|
||||
metav1.TypeMeta
|
||||
// +optional
|
||||
metav1.ObjectMeta
|
||||
|
||||
// Spec contains the signer (if any) and trust anchors.
|
||||
// +optional
|
||||
Spec ClusterTrustBundleSpec
|
||||
}
|
||||
|
||||
// ClusterTrustBundleSpec contains the signer and trust anchors.
|
||||
type ClusterTrustBundleSpec struct {
|
||||
// SignerName indicates the associated signer, if any.
|
||||
SignerName string
|
||||
|
||||
// TrustBundle contains the individual X.509 trust anchors for this
|
||||
// bundle, as PEM bundle of PEM-wrapped, DER-formatted X.509 certificates.
|
||||
//
|
||||
// The data must consist only of PEM certificate blocks that parse as valid
|
||||
// X.509 certificates. Each certificate must include a basic constraints
|
||||
// extension with the CA bit set. The API server will reject objects that
|
||||
// contain duplicate certificates, or that use PEM block headers.
|
||||
//
|
||||
// Users of ClusterTrustBundles, including Kubelet, are free to reorder and
|
||||
// deduplicate certificate blocks in this file according to their own logic,
|
||||
// as well as to drop PEM block headers and inter-block data.
|
||||
TrustBundle string
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// ClusterTrustBundleList is a collection of ClusterTrustBundle objects
|
||||
type ClusterTrustBundleList struct {
|
||||
metav1.TypeMeta
|
||||
// +optional
|
||||
metav1.ListMeta
|
||||
|
||||
// Items is a collection of ClusterTrustBundle objects
|
||||
Items []ClusterTrustBundle
|
||||
}
|
||||
|
37
pkg/apis/certificates/v1alpha1/conversion.go
Normal file
37
pkg/apis/certificates/v1alpha1/conversion.go
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
Copyright 2022 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 v1alpha1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
func addConversionFuncs(scheme *runtime.Scheme) error {
|
||||
return scheme.AddFieldLabelConversionFunc(
|
||||
SchemeGroupVersion.WithKind("ClusterTrustBundle"),
|
||||
func(label, value string) (string, string, error) {
|
||||
switch label {
|
||||
case "metadata.name", "spec.signerName":
|
||||
return label, value, nil
|
||||
default:
|
||||
return "", "", fmt.Errorf("field label not supported: %s", label)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
23
pkg/apis/certificates/v1alpha1/defaults.go
Normal file
23
pkg/apis/certificates/v1alpha1/defaults.go
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
Copyright 2022 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 v1alpha1
|
||||
|
||||
import "k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
func addDefaultingFuncs(scheme *runtime.Scheme) error {
|
||||
return RegisterDefaults(scheme)
|
||||
}
|
24
pkg/apis/certificates/v1alpha1/doc.go
Normal file
24
pkg/apis/certificates/v1alpha1/doc.go
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
Copyright 2022 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.
|
||||
*/
|
||||
|
||||
// +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/certificates
|
||||
// +k8s:conversion-gen-external-types=k8s.io/api/certificates/v1alpha1
|
||||
// +k8s:defaulter-gen=TypeMeta
|
||||
// +k8s:defaulter-gen-input=k8s.io/api/certificates/v1alpha1
|
||||
|
||||
// +groupName=certificates.k8s.io
|
||||
|
||||
package v1alpha1 // import "k8s.io/kubernetes/pkg/apis/certificates/v1alpha1"
|
43
pkg/apis/certificates/v1alpha1/register.go
Normal file
43
pkg/apis/certificates/v1alpha1/register.go
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
Copyright 2022 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 v1alpha1
|
||||
|
||||
import (
|
||||
certificatesv1alpha1 "k8s.io/api/certificates/v1alpha1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// GroupName is the group name used in this package.
|
||||
const GroupName = "certificates.k8s.io"
|
||||
|
||||
// SchemeGroupVersion is the group and version used in this package.
|
||||
var SchemeGroupVersion = schema.GroupVersion{
|
||||
Group: GroupName,
|
||||
Version: "v1alpha1",
|
||||
}
|
||||
|
||||
var (
|
||||
localSchemeBuilder = &certificatesv1alpha1.SchemeBuilder
|
||||
AddToScheme = localSchemeBuilder.AddToScheme
|
||||
)
|
||||
|
||||
func init() {
|
||||
// We only register manually written functions here. The registration of the
|
||||
// generated functions takes place in the generated files. The separation
|
||||
// makes the code compile even when the generated files are missing.
|
||||
localSchemeBuilder.Register(addDefaultingFuncs, addConversionFuncs)
|
||||
}
|
141
pkg/apis/certificates/v1alpha1/zz_generated.conversion.go
generated
Normal file
141
pkg/apis/certificates/v1alpha1/zz_generated.conversion.go
generated
Normal file
@@ -0,0 +1,141 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 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.
|
||||
*/
|
||||
|
||||
// Code generated by conversion-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
unsafe "unsafe"
|
||||
|
||||
v1alpha1 "k8s.io/api/certificates/v1alpha1"
|
||||
conversion "k8s.io/apimachinery/pkg/conversion"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
certificates "k8s.io/kubernetes/pkg/apis/certificates"
|
||||
)
|
||||
|
||||
func init() {
|
||||
localSchemeBuilder.Register(RegisterConversions)
|
||||
}
|
||||
|
||||
// RegisterConversions adds conversion functions to the given scheme.
|
||||
// Public to allow building arbitrary schemes.
|
||||
func RegisterConversions(s *runtime.Scheme) error {
|
||||
if err := s.AddGeneratedConversionFunc((*v1alpha1.ClusterTrustBundle)(nil), (*certificates.ClusterTrustBundle)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1alpha1_ClusterTrustBundle_To_certificates_ClusterTrustBundle(a.(*v1alpha1.ClusterTrustBundle), b.(*certificates.ClusterTrustBundle), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*certificates.ClusterTrustBundle)(nil), (*v1alpha1.ClusterTrustBundle)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_certificates_ClusterTrustBundle_To_v1alpha1_ClusterTrustBundle(a.(*certificates.ClusterTrustBundle), b.(*v1alpha1.ClusterTrustBundle), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*v1alpha1.ClusterTrustBundleList)(nil), (*certificates.ClusterTrustBundleList)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1alpha1_ClusterTrustBundleList_To_certificates_ClusterTrustBundleList(a.(*v1alpha1.ClusterTrustBundleList), b.(*certificates.ClusterTrustBundleList), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*certificates.ClusterTrustBundleList)(nil), (*v1alpha1.ClusterTrustBundleList)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_certificates_ClusterTrustBundleList_To_v1alpha1_ClusterTrustBundleList(a.(*certificates.ClusterTrustBundleList), b.(*v1alpha1.ClusterTrustBundleList), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*v1alpha1.ClusterTrustBundleSpec)(nil), (*certificates.ClusterTrustBundleSpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1alpha1_ClusterTrustBundleSpec_To_certificates_ClusterTrustBundleSpec(a.(*v1alpha1.ClusterTrustBundleSpec), b.(*certificates.ClusterTrustBundleSpec), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*certificates.ClusterTrustBundleSpec)(nil), (*v1alpha1.ClusterTrustBundleSpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_certificates_ClusterTrustBundleSpec_To_v1alpha1_ClusterTrustBundleSpec(a.(*certificates.ClusterTrustBundleSpec), b.(*v1alpha1.ClusterTrustBundleSpec), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func autoConvert_v1alpha1_ClusterTrustBundle_To_certificates_ClusterTrustBundle(in *v1alpha1.ClusterTrustBundle, out *certificates.ClusterTrustBundle, s conversion.Scope) error {
|
||||
out.ObjectMeta = in.ObjectMeta
|
||||
if err := Convert_v1alpha1_ClusterTrustBundleSpec_To_certificates_ClusterTrustBundleSpec(&in.Spec, &out.Spec, s); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1alpha1_ClusterTrustBundle_To_certificates_ClusterTrustBundle is an autogenerated conversion function.
|
||||
func Convert_v1alpha1_ClusterTrustBundle_To_certificates_ClusterTrustBundle(in *v1alpha1.ClusterTrustBundle, out *certificates.ClusterTrustBundle, s conversion.Scope) error {
|
||||
return autoConvert_v1alpha1_ClusterTrustBundle_To_certificates_ClusterTrustBundle(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_certificates_ClusterTrustBundle_To_v1alpha1_ClusterTrustBundle(in *certificates.ClusterTrustBundle, out *v1alpha1.ClusterTrustBundle, s conversion.Scope) error {
|
||||
out.ObjectMeta = in.ObjectMeta
|
||||
if err := Convert_certificates_ClusterTrustBundleSpec_To_v1alpha1_ClusterTrustBundleSpec(&in.Spec, &out.Spec, s); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_certificates_ClusterTrustBundle_To_v1alpha1_ClusterTrustBundle is an autogenerated conversion function.
|
||||
func Convert_certificates_ClusterTrustBundle_To_v1alpha1_ClusterTrustBundle(in *certificates.ClusterTrustBundle, out *v1alpha1.ClusterTrustBundle, s conversion.Scope) error {
|
||||
return autoConvert_certificates_ClusterTrustBundle_To_v1alpha1_ClusterTrustBundle(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1alpha1_ClusterTrustBundleList_To_certificates_ClusterTrustBundleList(in *v1alpha1.ClusterTrustBundleList, out *certificates.ClusterTrustBundleList, s conversion.Scope) error {
|
||||
out.ListMeta = in.ListMeta
|
||||
out.Items = *(*[]certificates.ClusterTrustBundle)(unsafe.Pointer(&in.Items))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1alpha1_ClusterTrustBundleList_To_certificates_ClusterTrustBundleList is an autogenerated conversion function.
|
||||
func Convert_v1alpha1_ClusterTrustBundleList_To_certificates_ClusterTrustBundleList(in *v1alpha1.ClusterTrustBundleList, out *certificates.ClusterTrustBundleList, s conversion.Scope) error {
|
||||
return autoConvert_v1alpha1_ClusterTrustBundleList_To_certificates_ClusterTrustBundleList(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_certificates_ClusterTrustBundleList_To_v1alpha1_ClusterTrustBundleList(in *certificates.ClusterTrustBundleList, out *v1alpha1.ClusterTrustBundleList, s conversion.Scope) error {
|
||||
out.ListMeta = in.ListMeta
|
||||
out.Items = *(*[]v1alpha1.ClusterTrustBundle)(unsafe.Pointer(&in.Items))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_certificates_ClusterTrustBundleList_To_v1alpha1_ClusterTrustBundleList is an autogenerated conversion function.
|
||||
func Convert_certificates_ClusterTrustBundleList_To_v1alpha1_ClusterTrustBundleList(in *certificates.ClusterTrustBundleList, out *v1alpha1.ClusterTrustBundleList, s conversion.Scope) error {
|
||||
return autoConvert_certificates_ClusterTrustBundleList_To_v1alpha1_ClusterTrustBundleList(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1alpha1_ClusterTrustBundleSpec_To_certificates_ClusterTrustBundleSpec(in *v1alpha1.ClusterTrustBundleSpec, out *certificates.ClusterTrustBundleSpec, s conversion.Scope) error {
|
||||
out.SignerName = in.SignerName
|
||||
out.TrustBundle = in.TrustBundle
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1alpha1_ClusterTrustBundleSpec_To_certificates_ClusterTrustBundleSpec is an autogenerated conversion function.
|
||||
func Convert_v1alpha1_ClusterTrustBundleSpec_To_certificates_ClusterTrustBundleSpec(in *v1alpha1.ClusterTrustBundleSpec, out *certificates.ClusterTrustBundleSpec, s conversion.Scope) error {
|
||||
return autoConvert_v1alpha1_ClusterTrustBundleSpec_To_certificates_ClusterTrustBundleSpec(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_certificates_ClusterTrustBundleSpec_To_v1alpha1_ClusterTrustBundleSpec(in *certificates.ClusterTrustBundleSpec, out *v1alpha1.ClusterTrustBundleSpec, s conversion.Scope) error {
|
||||
out.SignerName = in.SignerName
|
||||
out.TrustBundle = in.TrustBundle
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_certificates_ClusterTrustBundleSpec_To_v1alpha1_ClusterTrustBundleSpec is an autogenerated conversion function.
|
||||
func Convert_certificates_ClusterTrustBundleSpec_To_v1alpha1_ClusterTrustBundleSpec(in *certificates.ClusterTrustBundleSpec, out *v1alpha1.ClusterTrustBundleSpec, s conversion.Scope) error {
|
||||
return autoConvert_certificates_ClusterTrustBundleSpec_To_v1alpha1_ClusterTrustBundleSpec(in, out, s)
|
||||
}
|
33
pkg/apis/certificates/v1alpha1/zz_generated.defaults.go
generated
Normal file
33
pkg/apis/certificates/v1alpha1/zz_generated.defaults.go
generated
Normal file
@@ -0,0 +1,33 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 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.
|
||||
*/
|
||||
|
||||
// Code generated by defaulter-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// RegisterDefaults adds defaulters functions to the given scheme.
|
||||
// Public to allow building arbitrary schemes.
|
||||
// All generated defaulters are covering - they call all nested defaulters.
|
||||
func RegisterDefaults(scheme *runtime.Scheme) error {
|
||||
return nil
|
||||
}
|
@@ -25,6 +25,7 @@ import (
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
utilvalidation "k8s.io/apimachinery/pkg/util/validation"
|
||||
@@ -197,7 +198,7 @@ func validateCertificateSigningRequest(csr *certificates.CertificateSigningReque
|
||||
if !opts.allowLegacySignerName && csr.Spec.SignerName == certificates.LegacyUnknownSignerName {
|
||||
allErrs = append(allErrs, field.Invalid(specPath.Child("signerName"), csr.Spec.SignerName, "the legacy signerName is not allowed via this API version"))
|
||||
} else {
|
||||
allErrs = append(allErrs, ValidateCertificateSigningRequestSignerName(specPath.Child("signerName"), csr.Spec.SignerName)...)
|
||||
allErrs = append(allErrs, ValidateSignerName(specPath.Child("signerName"), csr.Spec.SignerName)...)
|
||||
}
|
||||
if csr.Spec.ExpirationSeconds != nil && *csr.Spec.ExpirationSeconds < 600 {
|
||||
allErrs = append(allErrs, field.Invalid(specPath.Child("expirationSeconds"), *csr.Spec.ExpirationSeconds, "may not specify a duration less than 600 seconds (10 minutes)"))
|
||||
@@ -272,7 +273,7 @@ func validateConditions(fldPath *field.Path, csr *certificates.CertificateSignin
|
||||
// The max length of a namespace name is 63 characters (DNS1123Label max length)
|
||||
// The max length of a resource name is 253 characters (DNS1123Subdomain max length)
|
||||
// We then add an additional 2 characters to account for the one '.' and one '/'.
|
||||
func ValidateCertificateSigningRequestSignerName(fldPath *field.Path, signerName string) field.ErrorList {
|
||||
func ValidateSignerName(fldPath *field.Path, signerName string) field.ErrorList {
|
||||
var el field.ErrorList
|
||||
if len(signerName) == 0 {
|
||||
el = append(el, field.Required(fldPath, ""))
|
||||
@@ -537,3 +538,129 @@ func hasDuplicateUsage(usages []certificates.KeyUsage) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// We require your name to be prefixed by .spec.signerName
|
||||
func validateClusterTrustBundleName(signerName string) func(name string, prefix bool) []string {
|
||||
return func(name string, isPrefix bool) []string {
|
||||
if signerName == "" {
|
||||
if strings.Contains(name, ":") {
|
||||
return []string{"ClusterTrustBundle without signer name must not have \":\" in its name"}
|
||||
}
|
||||
return apimachineryvalidation.NameIsDNSSubdomain(name, isPrefix)
|
||||
}
|
||||
|
||||
requiredPrefix := strings.ReplaceAll(signerName, "/", ":") + ":"
|
||||
if !strings.HasPrefix(name, requiredPrefix) {
|
||||
return []string{fmt.Sprintf("ClusterTrustBundle for signerName %s must be named with prefix %s", signerName, requiredPrefix)}
|
||||
}
|
||||
return apimachineryvalidation.NameIsDNSSubdomain(strings.TrimPrefix(name, requiredPrefix), isPrefix)
|
||||
}
|
||||
}
|
||||
|
||||
type ValidateClusterTrustBundleOptions struct {
|
||||
SuppressBundleParsing bool
|
||||
}
|
||||
|
||||
// ValidateClusterTrustBundle runs all validation checks on bundle.
|
||||
func ValidateClusterTrustBundle(bundle *certificates.ClusterTrustBundle, opts ValidateClusterTrustBundleOptions) field.ErrorList {
|
||||
var allErrors field.ErrorList
|
||||
|
||||
metaErrors := apivalidation.ValidateObjectMeta(&bundle.ObjectMeta, false, validateClusterTrustBundleName(bundle.Spec.SignerName), field.NewPath("metadata"))
|
||||
allErrors = append(allErrors, metaErrors...)
|
||||
|
||||
if bundle.Spec.SignerName != "" {
|
||||
signerNameErrors := ValidateSignerName(field.NewPath("spec", "signerName"), bundle.Spec.SignerName)
|
||||
allErrors = append(allErrors, signerNameErrors...)
|
||||
}
|
||||
|
||||
if !opts.SuppressBundleParsing {
|
||||
pemErrors := validateTrustBundle(field.NewPath("spec", "trustBundle"), bundle.Spec.TrustBundle)
|
||||
allErrors = append(allErrors, pemErrors...)
|
||||
}
|
||||
|
||||
return allErrors
|
||||
}
|
||||
|
||||
// ValidateClusterTrustBundleUpdate runs all update validation checks on an
|
||||
// update.
|
||||
func ValidateClusterTrustBundleUpdate(newBundle, oldBundle *certificates.ClusterTrustBundle) field.ErrorList {
|
||||
// If the caller isn't changing the TrustBundle field, don't parse it.
|
||||
// This helps smoothly handle changes in Go's PEM or X.509 parsing
|
||||
// libraries.
|
||||
opts := ValidateClusterTrustBundleOptions{}
|
||||
if newBundle.Spec.TrustBundle == oldBundle.Spec.TrustBundle {
|
||||
opts.SuppressBundleParsing = true
|
||||
}
|
||||
|
||||
var allErrors field.ErrorList
|
||||
allErrors = append(allErrors, ValidateClusterTrustBundle(newBundle, opts)...)
|
||||
allErrors = append(allErrors, apivalidation.ValidateObjectMetaUpdate(&newBundle.ObjectMeta, &oldBundle.ObjectMeta, field.NewPath("metadata"))...)
|
||||
allErrors = append(allErrors, apivalidation.ValidateImmutableField(newBundle.Spec.SignerName, oldBundle.Spec.SignerName, field.NewPath("spec", "signerName"))...)
|
||||
return allErrors
|
||||
}
|
||||
|
||||
// validateTrustBundle rejects intra-block headers, blocks
|
||||
// that don't parse as X.509 CA certificates, and duplicate trust anchors. It
|
||||
// requires that at least one trust anchor is provided.
|
||||
func validateTrustBundle(path *field.Path, in string) field.ErrorList {
|
||||
var allErrors field.ErrorList
|
||||
|
||||
blockDedupe := map[string][]int{}
|
||||
|
||||
rest := []byte(in)
|
||||
var b *pem.Block
|
||||
i := -1
|
||||
for {
|
||||
b, rest = pem.Decode(rest)
|
||||
if b == nil {
|
||||
break
|
||||
}
|
||||
i++
|
||||
|
||||
if b.Type != "CERTIFICATE" {
|
||||
allErrors = append(allErrors, field.Invalid(path, "<value omitted>", fmt.Sprintf("entry %d has bad block type: %v", i, b.Type)))
|
||||
continue
|
||||
}
|
||||
|
||||
if len(b.Headers) != 0 {
|
||||
allErrors = append(allErrors, field.Invalid(path, "<value omitted>", fmt.Sprintf("entry %d has PEM block headers", i)))
|
||||
continue
|
||||
}
|
||||
|
||||
cert, err := x509.ParseCertificate(b.Bytes)
|
||||
if err != nil {
|
||||
allErrors = append(allErrors, field.Invalid(path, "<value omitted>", fmt.Sprintf("entry %d does not parse as X.509", i)))
|
||||
continue
|
||||
}
|
||||
|
||||
if !cert.IsCA {
|
||||
allErrors = append(allErrors, field.Invalid(path, "<value omitted>", fmt.Sprintf("entry %d does not have the CA bit set", i)))
|
||||
continue
|
||||
}
|
||||
|
||||
if !cert.BasicConstraintsValid {
|
||||
allErrors = append(allErrors, field.Invalid(path, "<value omitted>", fmt.Sprintf("entry %d has invalid basic constraints", i)))
|
||||
continue
|
||||
}
|
||||
|
||||
blockDedupe[string(b.Bytes)] = append(blockDedupe[string(b.Bytes)], i)
|
||||
}
|
||||
|
||||
// If we had a malformed block, don't also output potentially-redundant
|
||||
// errors about duplicate or missing trust anchors.
|
||||
if len(allErrors) != 0 {
|
||||
return allErrors
|
||||
}
|
||||
|
||||
if len(blockDedupe) == 0 {
|
||||
allErrors = append(allErrors, field.Invalid(path, "<value omitted>", "at least one trust anchor must be provided"))
|
||||
}
|
||||
|
||||
for _, indices := range blockDedupe {
|
||||
if len(indices) > 1 {
|
||||
allErrors = append(allErrors, field.Invalid(path, "<value omitted>", fmt.Sprintf("duplicate trust anchor (indices %v)", indices)))
|
||||
}
|
||||
}
|
||||
|
||||
return allErrors
|
||||
}
|
||||
|
@@ -23,12 +23,15 @@ import (
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"math/big"
|
||||
mathrand "math/rand"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
@@ -1095,6 +1098,471 @@ func Test_validateCertificateSigningRequestOptions(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func mustMakeCertificate(t *testing.T, template *x509.Certificate) []byte {
|
||||
gen := mathrand.New(mathrand.NewSource(12345))
|
||||
|
||||
pub, priv, err := ed25519.GenerateKey(gen)
|
||||
if err != nil {
|
||||
t.Fatalf("Error while generating key: %v", err)
|
||||
}
|
||||
|
||||
cert, err := x509.CreateCertificate(gen, template, template, pub, priv)
|
||||
if err != nil {
|
||||
t.Fatalf("Error while making certificate: %v", err)
|
||||
}
|
||||
|
||||
return cert
|
||||
}
|
||||
|
||||
func mustMakePEMBlock(blockType string, headers map[string]string, data []byte) string {
|
||||
return string(pem.EncodeToMemory(&pem.Block{
|
||||
Type: blockType,
|
||||
Headers: headers,
|
||||
Bytes: data,
|
||||
}))
|
||||
}
|
||||
|
||||
func TestValidateClusterTrustBundle(t *testing.T) {
|
||||
goodCert1 := mustMakeCertificate(t, &x509.Certificate{
|
||||
SerialNumber: big.NewInt(0),
|
||||
Subject: pkix.Name{
|
||||
CommonName: "root1",
|
||||
},
|
||||
IsCA: true,
|
||||
BasicConstraintsValid: true,
|
||||
})
|
||||
|
||||
goodCert2 := mustMakeCertificate(t, &x509.Certificate{
|
||||
SerialNumber: big.NewInt(0),
|
||||
Subject: pkix.Name{
|
||||
CommonName: "root2",
|
||||
},
|
||||
IsCA: true,
|
||||
BasicConstraintsValid: true,
|
||||
})
|
||||
|
||||
badNotCACert := mustMakeCertificate(t, &x509.Certificate{
|
||||
SerialNumber: big.NewInt(0),
|
||||
Subject: pkix.Name{
|
||||
CommonName: "root3",
|
||||
},
|
||||
})
|
||||
|
||||
goodCert1Block := string(mustMakePEMBlock("CERTIFICATE", nil, goodCert1))
|
||||
goodCert2Block := string(mustMakePEMBlock("CERTIFICATE", nil, goodCert2))
|
||||
|
||||
goodCert1AlternateBlock := strings.ReplaceAll(goodCert1Block, "\n", "\n\t\n")
|
||||
|
||||
badNotCACertBlock := string(mustMakePEMBlock("CERTIFICATE", nil, badNotCACert))
|
||||
|
||||
badBlockHeadersBlock := string(mustMakePEMBlock("CERTIFICATE", map[string]string{"key": "value"}, goodCert1))
|
||||
badBlockTypeBlock := string(mustMakePEMBlock("NOTACERTIFICATE", nil, goodCert1))
|
||||
badNonParseableBlock := string(mustMakePEMBlock("CERTIFICATE", nil, []byte("this is not a certificate")))
|
||||
|
||||
testCases := []struct {
|
||||
description string
|
||||
bundle *capi.ClusterTrustBundle
|
||||
opts ValidateClusterTrustBundleOptions
|
||||
wantErrors field.ErrorList
|
||||
}{
|
||||
{
|
||||
description: "valid, no signer name",
|
||||
bundle: &capi.ClusterTrustBundle{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo",
|
||||
},
|
||||
Spec: capi.ClusterTrustBundleSpec{
|
||||
TrustBundle: goodCert1Block,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "invalid, no signer name, invalid name",
|
||||
bundle: &capi.ClusterTrustBundle{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "k8s.io:bar:foo",
|
||||
},
|
||||
Spec: capi.ClusterTrustBundleSpec{
|
||||
TrustBundle: goodCert1Block,
|
||||
},
|
||||
},
|
||||
wantErrors: field.ErrorList{
|
||||
field.Invalid(field.NewPath("metadata", "name"), "k8s.io:bar:foo", "ClusterTrustBundle without signer name must not have \":\" in its name"),
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "valid, with signer name",
|
||||
bundle: &capi.ClusterTrustBundle{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "k8s.io:foo:bar",
|
||||
},
|
||||
Spec: capi.ClusterTrustBundleSpec{
|
||||
SignerName: "k8s.io/foo",
|
||||
TrustBundle: goodCert1Block,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "invalid, with signer name, missing name prefix",
|
||||
bundle: &capi.ClusterTrustBundle{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "look-ma-no-prefix",
|
||||
},
|
||||
Spec: capi.ClusterTrustBundleSpec{
|
||||
SignerName: "k8s.io/foo",
|
||||
TrustBundle: goodCert1Block,
|
||||
},
|
||||
},
|
||||
wantErrors: field.ErrorList{
|
||||
field.Invalid(field.NewPath("metadata", "name"), "look-ma-no-prefix", "ClusterTrustBundle for signerName k8s.io/foo must be named with prefix k8s.io:foo:"),
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "invalid, with signer name, empty name suffix",
|
||||
bundle: &capi.ClusterTrustBundle{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "k8s.io:foo:",
|
||||
},
|
||||
Spec: capi.ClusterTrustBundleSpec{
|
||||
SignerName: "k8s.io/foo",
|
||||
TrustBundle: goodCert1Block,
|
||||
},
|
||||
},
|
||||
wantErrors: field.ErrorList{
|
||||
field.Invalid(field.NewPath("metadata", "name"), "k8s.io:foo:", `a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')`),
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "invalid, with signer name, bad name suffix",
|
||||
bundle: &capi.ClusterTrustBundle{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "k8s.io:foo:123notvalidDNSSubdomain",
|
||||
},
|
||||
Spec: capi.ClusterTrustBundleSpec{
|
||||
SignerName: "k8s.io/foo",
|
||||
TrustBundle: goodCert1Block,
|
||||
},
|
||||
},
|
||||
wantErrors: field.ErrorList{
|
||||
field.Invalid(field.NewPath("metadata", "name"), "k8s.io:foo:123notvalidDNSSubdomain", `a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')`),
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "valid, with signer name, with inter-block garbage",
|
||||
bundle: &capi.ClusterTrustBundle{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "k8s.io:foo:abc",
|
||||
},
|
||||
Spec: capi.ClusterTrustBundleSpec{
|
||||
SignerName: "k8s.io/foo",
|
||||
TrustBundle: "garbage\n" + goodCert1Block + "\ngarbage\n" + goodCert2Block,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "invalid, no signer name, no trust anchors",
|
||||
bundle: &capi.ClusterTrustBundle{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo",
|
||||
},
|
||||
Spec: capi.ClusterTrustBundleSpec{},
|
||||
},
|
||||
wantErrors: field.ErrorList{
|
||||
field.Invalid(field.NewPath("spec", "trustBundle"), "<value omitted>", "at least one trust anchor must be provided"),
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "invalid, no trust anchors",
|
||||
bundle: &capi.ClusterTrustBundle{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "k8s.io:foo:abc",
|
||||
},
|
||||
Spec: capi.ClusterTrustBundleSpec{
|
||||
SignerName: "k8s.io/foo",
|
||||
},
|
||||
},
|
||||
wantErrors: field.ErrorList{
|
||||
field.Invalid(field.NewPath("spec", "trustBundle"), "<value omitted>", "at least one trust anchor must be provided"),
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "invalid, bad signer name",
|
||||
bundle: &capi.ClusterTrustBundle{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "invalid:foo",
|
||||
},
|
||||
Spec: capi.ClusterTrustBundleSpec{
|
||||
SignerName: "invalid",
|
||||
TrustBundle: goodCert1Block,
|
||||
},
|
||||
},
|
||||
wantErrors: field.ErrorList{
|
||||
field.Invalid(field.NewPath("spec", "signerName"), "invalid", "must be a fully qualified domain and path of the form 'example.com/signer-name'"),
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "invalid, no blocks",
|
||||
bundle: &capi.ClusterTrustBundle{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo",
|
||||
},
|
||||
Spec: capi.ClusterTrustBundleSpec{
|
||||
TrustBundle: "non block garbage",
|
||||
},
|
||||
},
|
||||
wantErrors: field.ErrorList{
|
||||
field.Invalid(field.NewPath("spec", "trustBundle"), "<value omitted>", "at least one trust anchor must be provided"),
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "invalid, bad block type",
|
||||
bundle: &capi.ClusterTrustBundle{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo",
|
||||
},
|
||||
Spec: capi.ClusterTrustBundleSpec{
|
||||
TrustBundle: goodCert1Block + "\n" + badBlockTypeBlock,
|
||||
},
|
||||
},
|
||||
wantErrors: field.ErrorList{
|
||||
field.Invalid(field.NewPath("spec", "trustBundle"), "<value omitted>", "entry 1 has bad block type: NOTACERTIFICATE"),
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "invalid, block with headers",
|
||||
bundle: &capi.ClusterTrustBundle{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo",
|
||||
},
|
||||
Spec: capi.ClusterTrustBundleSpec{
|
||||
TrustBundle: goodCert1Block + "\n" + badBlockHeadersBlock,
|
||||
},
|
||||
},
|
||||
wantErrors: field.ErrorList{
|
||||
field.Invalid(field.NewPath("spec", "trustBundle"), "<value omitted>", "entry 1 has PEM block headers"),
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "invalid, cert is not a CA cert",
|
||||
bundle: &capi.ClusterTrustBundle{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo",
|
||||
},
|
||||
Spec: capi.ClusterTrustBundleSpec{
|
||||
TrustBundle: badNotCACertBlock,
|
||||
},
|
||||
},
|
||||
wantErrors: field.ErrorList{
|
||||
field.Invalid(field.NewPath("spec", "trustBundle"), "<value omitted>", "entry 0 does not have the CA bit set"),
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "invalid, duplicated blocks",
|
||||
bundle: &capi.ClusterTrustBundle{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo",
|
||||
},
|
||||
Spec: capi.ClusterTrustBundleSpec{
|
||||
TrustBundle: goodCert1Block + "\n" + goodCert1AlternateBlock,
|
||||
},
|
||||
},
|
||||
wantErrors: field.ErrorList{
|
||||
field.Invalid(field.NewPath("spec", "trustBundle"), "<value omitted>", "duplicate trust anchor (indices [0 1])"),
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "invalid, non-certificate entry",
|
||||
bundle: &capi.ClusterTrustBundle{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo",
|
||||
},
|
||||
Spec: capi.ClusterTrustBundleSpec{
|
||||
TrustBundle: goodCert1Block + "\n" + badNonParseableBlock,
|
||||
},
|
||||
},
|
||||
wantErrors: field.ErrorList{
|
||||
field.Invalid(field.NewPath("spec", "trustBundle"), "<value omitted>", "entry 1 does not parse as X.509"),
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "allow any old garbage in the PEM field if we suppress parsing",
|
||||
bundle: &capi.ClusterTrustBundle{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo",
|
||||
},
|
||||
Spec: capi.ClusterTrustBundleSpec{
|
||||
TrustBundle: "garbage",
|
||||
},
|
||||
},
|
||||
opts: ValidateClusterTrustBundleOptions{
|
||||
SuppressBundleParsing: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
gotErrors := ValidateClusterTrustBundle(tc.bundle, tc.opts)
|
||||
if diff := cmp.Diff(gotErrors, tc.wantErrors); diff != "" {
|
||||
t.Fatalf("Unexpected error output from Validate; diff (-got +want)\n%s", diff)
|
||||
}
|
||||
|
||||
// When there are no changes to the object,
|
||||
// ValidateClusterTrustBundleUpdate should not report errors about
|
||||
// the TrustBundle field.
|
||||
tc.bundle.ObjectMeta.ResourceVersion = "1"
|
||||
newBundle := tc.bundle.DeepCopy()
|
||||
newBundle.ObjectMeta.ResourceVersion = "2"
|
||||
gotErrors = ValidateClusterTrustBundleUpdate(newBundle, tc.bundle)
|
||||
|
||||
var filteredWantErrors field.ErrorList
|
||||
for _, err := range tc.wantErrors {
|
||||
if err.Field != "spec.trustBundle" {
|
||||
filteredWantErrors = append(filteredWantErrors, err)
|
||||
}
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(gotErrors, filteredWantErrors); diff != "" {
|
||||
t.Fatalf("Unexpected error output from ValidateUpdate; diff (-got +want)\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateClusterTrustBundleUpdate(t *testing.T) {
|
||||
goodCert1 := mustMakeCertificate(t, &x509.Certificate{
|
||||
SerialNumber: big.NewInt(0),
|
||||
Subject: pkix.Name{
|
||||
CommonName: "root1",
|
||||
},
|
||||
IsCA: true,
|
||||
BasicConstraintsValid: true,
|
||||
})
|
||||
|
||||
goodCert2 := mustMakeCertificate(t, &x509.Certificate{
|
||||
SerialNumber: big.NewInt(0),
|
||||
Subject: pkix.Name{
|
||||
CommonName: "root2",
|
||||
},
|
||||
IsCA: true,
|
||||
BasicConstraintsValid: true,
|
||||
})
|
||||
|
||||
goodCert1Block := string(mustMakePEMBlock("CERTIFICATE", nil, goodCert1))
|
||||
goodCert2Block := string(mustMakePEMBlock("CERTIFICATE", nil, goodCert2))
|
||||
|
||||
testCases := []struct {
|
||||
description string
|
||||
oldBundle, newBundle *capi.ClusterTrustBundle
|
||||
wantErrors field.ErrorList
|
||||
}{
|
||||
{
|
||||
description: "changing signer name disallowed",
|
||||
oldBundle: &capi.ClusterTrustBundle{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "k8s.io:foo:bar",
|
||||
},
|
||||
Spec: capi.ClusterTrustBundleSpec{
|
||||
SignerName: "k8s.io/foo",
|
||||
TrustBundle: goodCert1Block,
|
||||
},
|
||||
},
|
||||
newBundle: &capi.ClusterTrustBundle{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "k8s.io:foo:bar",
|
||||
},
|
||||
Spec: capi.ClusterTrustBundleSpec{
|
||||
SignerName: "k8s.io/bar",
|
||||
TrustBundle: goodCert1Block,
|
||||
},
|
||||
},
|
||||
wantErrors: field.ErrorList{
|
||||
field.Invalid(field.NewPath("metadata", "name"), "k8s.io:foo:bar", "ClusterTrustBundle for signerName k8s.io/bar must be named with prefix k8s.io:bar:"),
|
||||
field.Invalid(field.NewPath("spec", "signerName"), "k8s.io/bar", "field is immutable"),
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "adding certificate allowed",
|
||||
oldBundle: &capi.ClusterTrustBundle{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "k8s.io:foo:bar",
|
||||
},
|
||||
Spec: capi.ClusterTrustBundleSpec{
|
||||
SignerName: "k8s.io/foo",
|
||||
TrustBundle: goodCert1Block,
|
||||
},
|
||||
},
|
||||
newBundle: &capi.ClusterTrustBundle{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "k8s.io:foo:bar",
|
||||
},
|
||||
Spec: capi.ClusterTrustBundleSpec{
|
||||
SignerName: "k8s.io/foo",
|
||||
TrustBundle: goodCert1Block + "\n" + goodCert2Block,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "emptying trustBundle disallowed",
|
||||
oldBundle: &capi.ClusterTrustBundle{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "k8s.io:foo:bar",
|
||||
},
|
||||
Spec: capi.ClusterTrustBundleSpec{
|
||||
SignerName: "k8s.io/foo",
|
||||
TrustBundle: goodCert1Block,
|
||||
},
|
||||
},
|
||||
newBundle: &capi.ClusterTrustBundle{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "k8s.io:foo:bar",
|
||||
},
|
||||
Spec: capi.ClusterTrustBundleSpec{
|
||||
SignerName: "k8s.io/foo",
|
||||
TrustBundle: "",
|
||||
},
|
||||
},
|
||||
wantErrors: field.ErrorList{
|
||||
field.Invalid(field.NewPath("spec", "trustBundle"), "<value omitted>", "at least one trust anchor must be provided"),
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "emptying trustBundle (replace with non-block garbage) disallowed",
|
||||
oldBundle: &capi.ClusterTrustBundle{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "k8s.io:foo:bar",
|
||||
},
|
||||
Spec: capi.ClusterTrustBundleSpec{
|
||||
SignerName: "k8s.io/foo",
|
||||
TrustBundle: goodCert1Block,
|
||||
},
|
||||
},
|
||||
newBundle: &capi.ClusterTrustBundle{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "k8s.io:foo:bar",
|
||||
},
|
||||
Spec: capi.ClusterTrustBundleSpec{
|
||||
SignerName: "k8s.io/foo",
|
||||
TrustBundle: "non block garbage",
|
||||
},
|
||||
},
|
||||
wantErrors: field.ErrorList{
|
||||
field.Invalid(field.NewPath("spec", "trustBundle"), "<value omitted>", "at least one trust anchor must be provided"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
tc.oldBundle.ObjectMeta.ResourceVersion = "1"
|
||||
tc.newBundle.ObjectMeta.ResourceVersion = "2"
|
||||
gotErrors := ValidateClusterTrustBundleUpdate(tc.newBundle, tc.oldBundle)
|
||||
if diff := cmp.Diff(gotErrors, tc.wantErrors); diff != "" {
|
||||
t.Errorf("Unexpected error output from ValidateUpdate; diff (-got +want)\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
validCertificate = []byte(`
|
||||
Leading non-PEM content
|
||||
|
76
pkg/apis/certificates/zz_generated.deepcopy.go
generated
76
pkg/apis/certificates/zz_generated.deepcopy.go
generated
@@ -183,6 +183,82 @@ func (in *CertificateSigningRequestStatus) DeepCopy() *CertificateSigningRequest
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ClusterTrustBundle) DeepCopyInto(out *ClusterTrustBundle) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
out.Spec = in.Spec
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterTrustBundle.
|
||||
func (in *ClusterTrustBundle) DeepCopy() *ClusterTrustBundle {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ClusterTrustBundle)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *ClusterTrustBundle) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ClusterTrustBundleList) DeepCopyInto(out *ClusterTrustBundleList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]ClusterTrustBundle, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterTrustBundleList.
|
||||
func (in *ClusterTrustBundleList) DeepCopy() *ClusterTrustBundleList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ClusterTrustBundleList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *ClusterTrustBundleList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ClusterTrustBundleSpec) DeepCopyInto(out *ClusterTrustBundleSpec) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterTrustBundleSpec.
|
||||
func (in *ClusterTrustBundleSpec) DeepCopy() *ClusterTrustBundleSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ClusterTrustBundleSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in ExtraValue) DeepCopyInto(out *ExtraValue) {
|
||||
{
|
||||
|
@@ -40,6 +40,7 @@ import (
|
||||
batchapiv1 "k8s.io/api/batch/v1"
|
||||
batchapiv1beta1 "k8s.io/api/batch/v1beta1"
|
||||
certificatesapiv1 "k8s.io/api/certificates/v1"
|
||||
certificatesv1alpha1 "k8s.io/api/certificates/v1alpha1"
|
||||
coordinationapiv1 "k8s.io/api/coordination/v1"
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
discoveryv1 "k8s.io/api/discovery/v1"
|
||||
@@ -734,6 +735,7 @@ var (
|
||||
apiserverinternalv1alpha1.SchemeGroupVersion,
|
||||
authenticationv1alpha1.SchemeGroupVersion,
|
||||
resourcev1alpha2.SchemeGroupVersion,
|
||||
certificatesv1alpha1.SchemeGroupVersion,
|
||||
networkingapiv1alpha1.SchemeGroupVersion,
|
||||
storageapiv1alpha1.SchemeGroupVersion,
|
||||
flowcontrolv1alpha1.SchemeGroupVersion,
|
||||
|
@@ -67,6 +67,12 @@ const (
|
||||
// Enables dual-stack --node-ip in kubelet with external cloud providers
|
||||
CloudDualStackNodeIPs featuregate.Feature = "CloudDualStackNodeIPs"
|
||||
|
||||
// owner: @ahmedtd
|
||||
// alpha: v1.26
|
||||
//
|
||||
// Enable ClusterTrustBundle objects and Kubelet integration.
|
||||
ClusterTrustBundle featuregate.Feature = "ClusterTrustBundle"
|
||||
|
||||
// owner: @szuecs
|
||||
// alpha: v1.12
|
||||
//
|
||||
@@ -934,6 +940,8 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
|
||||
|
||||
CloudDualStackNodeIPs: {Default: false, PreRelease: featuregate.Alpha},
|
||||
|
||||
ClusterTrustBundle: {Default: false, PreRelease: featuregate.Alpha},
|
||||
|
||||
CPUCFSQuotaPeriod: {Default: false, PreRelease: featuregate.Alpha},
|
||||
|
||||
CPUManager: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // GA in 1.26
|
||||
|
127
pkg/generated/openapi/zz_generated.openapi.go
generated
127
pkg/generated/openapi/zz_generated.openapi.go
generated
@@ -322,6 +322,9 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
|
||||
"k8s.io/api/certificates/v1.CertificateSigningRequestList": schema_k8sio_api_certificates_v1_CertificateSigningRequestList(ref),
|
||||
"k8s.io/api/certificates/v1.CertificateSigningRequestSpec": schema_k8sio_api_certificates_v1_CertificateSigningRequestSpec(ref),
|
||||
"k8s.io/api/certificates/v1.CertificateSigningRequestStatus": schema_k8sio_api_certificates_v1_CertificateSigningRequestStatus(ref),
|
||||
"k8s.io/api/certificates/v1alpha1.ClusterTrustBundle": schema_k8sio_api_certificates_v1alpha1_ClusterTrustBundle(ref),
|
||||
"k8s.io/api/certificates/v1alpha1.ClusterTrustBundleList": schema_k8sio_api_certificates_v1alpha1_ClusterTrustBundleList(ref),
|
||||
"k8s.io/api/certificates/v1alpha1.ClusterTrustBundleSpec": schema_k8sio_api_certificates_v1alpha1_ClusterTrustBundleSpec(ref),
|
||||
"k8s.io/api/certificates/v1beta1.CertificateSigningRequest": schema_k8sio_api_certificates_v1beta1_CertificateSigningRequest(ref),
|
||||
"k8s.io/api/certificates/v1beta1.CertificateSigningRequestCondition": schema_k8sio_api_certificates_v1beta1_CertificateSigningRequestCondition(ref),
|
||||
"k8s.io/api/certificates/v1beta1.CertificateSigningRequestList": schema_k8sio_api_certificates_v1beta1_CertificateSigningRequestList(ref),
|
||||
@@ -15516,6 +15519,130 @@ func schema_k8sio_api_certificates_v1_CertificateSigningRequestStatus(ref common
|
||||
}
|
||||
}
|
||||
|
||||
func schema_k8sio_api_certificates_v1alpha1_ClusterTrustBundle(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "ClusterTrustBundle is a cluster-scoped container for X.509 trust anchors (root certificates).\n\nClusterTrustBundle objects are considered to be readable by any authenticated user in the cluster, because they can be mounted by pods using the `clusterTrustBundle` projection. All service accounts have read access to ClusterTrustBundles by default. Users who only have namespace-level access to a cluster can read ClusterTrustBundles by impersonating a serviceaccount that they have access to.\n\nIt can be optionally associated with a particular assigner, in which case it contains one valid set of trust anchors for that signer. Signers may have multiple associated ClusterTrustBundles; each is an independent set of trust anchors for that signer. Admission control is used to enforce that only users with permissions on the signer can create or modify the corresponding bundle.",
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"kind": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"apiVersion": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"metadata": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "metadata contains the object metadata.",
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"),
|
||||
},
|
||||
},
|
||||
"spec": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "spec contains the signer (if any) and trust anchors.",
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("k8s.io/api/certificates/v1alpha1.ClusterTrustBundleSpec"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"spec"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"k8s.io/api/certificates/v1alpha1.ClusterTrustBundleSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_k8sio_api_certificates_v1alpha1_ClusterTrustBundleList(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "ClusterTrustBundleList is a collection of ClusterTrustBundle objects",
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"kind": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"apiVersion": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"metadata": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "metadata contains the list metadata.",
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"),
|
||||
},
|
||||
},
|
||||
"items": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "items is a collection of ClusterTrustBundle objects",
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("k8s.io/api/certificates/v1alpha1.ClusterTrustBundle"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"items"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"k8s.io/api/certificates/v1alpha1.ClusterTrustBundle", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_k8sio_api_certificates_v1alpha1_ClusterTrustBundleSpec(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "ClusterTrustBundleSpec contains the signer and trust anchors.",
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"signerName": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "signerName indicates the associated signer, if any.\n\nIn order to create or update a ClusterTrustBundle that sets signerName, you must have the following cluster-scoped permission: group=certificates.k8s.io resource=signers resourceName=<the signer name> verb=attest.\n\nIf signerName is not empty, then the ClusterTrustBundle object must be named with the signer name as a prefix (translating slashes to colons). For example, for the signer name `example.com/foo`, valid ClusterTrustBundle object names include `example.com:foo:abc` and `example.com:foo:v1`.\n\nIf signerName is empty, then the ClusterTrustBundle object's name must not have such a prefix.\n\nList/watch requests for ClusterTrustBundles can filter on this field using a `spec.signerName=NAME` field selector.",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"trustBundle": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "trustBundle contains the individual X.509 trust anchors for this bundle, as PEM bundle of PEM-wrapped, DER-formatted X.509 certificates.\n\nThe data must consist only of PEM certificate blocks that parse as valid X.509 certificates. Each certificate must include a basic constraints extension with the CA bit set. The API server will reject objects that contain duplicate certificates, or that use PEM block headers.\n\nUsers of ClusterTrustBundles, including Kubelet, are free to reorder and deduplicate certificate blocks in this file according to their own logic, as well as to drop PEM block headers and inter-block data.",
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"trustBundle"},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_k8sio_api_certificates_v1beta1_CertificateSigningRequest(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
|
@@ -28,6 +28,7 @@ import (
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
"k8s.io/kubernetes/pkg/apis/admissionregistration"
|
||||
"k8s.io/kubernetes/pkg/apis/apps"
|
||||
"k8s.io/kubernetes/pkg/apis/certificates"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/apis/events"
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
@@ -72,6 +73,7 @@ func NewStorageFactoryConfig() *StorageFactoryConfig {
|
||||
admissionregistration.Resource("validatingadmissionpolicybindings").WithVersion("v1alpha1"),
|
||||
networking.Resource("clustercidrs").WithVersion("v1alpha1"),
|
||||
networking.Resource("ipaddresses").WithVersion("v1alpha1"),
|
||||
certificates.Resource("clustertrustbundles").WithVersion("v1alpha1"),
|
||||
}
|
||||
|
||||
return &StorageFactoryConfig{
|
||||
|
@@ -26,6 +26,7 @@ import (
|
||||
"k8s.io/kubernetes/plugin/pkg/admission/alwayspullimages"
|
||||
"k8s.io/kubernetes/plugin/pkg/admission/antiaffinity"
|
||||
certapproval "k8s.io/kubernetes/plugin/pkg/admission/certificates/approval"
|
||||
"k8s.io/kubernetes/plugin/pkg/admission/certificates/ctbattest"
|
||||
certsigning "k8s.io/kubernetes/plugin/pkg/admission/certificates/signing"
|
||||
certsubjectrestriction "k8s.io/kubernetes/plugin/pkg/admission/certificates/subjectrestriction"
|
||||
"k8s.io/kubernetes/plugin/pkg/admission/defaulttolerationseconds"
|
||||
@@ -90,6 +91,7 @@ var AllOrderedPlugins = []string{
|
||||
runtimeclass.PluginName, // RuntimeClass
|
||||
certapproval.PluginName, // CertificateApproval
|
||||
certsigning.PluginName, // CertificateSigning
|
||||
ctbattest.PluginName, // ClusterTrustBundleAttest
|
||||
certsubjectrestriction.PluginName, // CertificateSubjectRestriction
|
||||
defaultingressclass.PluginName, // DefaultIngressClass
|
||||
denyserviceexternalips.PluginName, // DenyServiceExternalIPs
|
||||
@@ -137,6 +139,7 @@ func RegisterAllAdmissionPlugins(plugins *admission.Plugins) {
|
||||
storageobjectinuseprotection.Register(plugins)
|
||||
certapproval.Register(plugins)
|
||||
certsigning.Register(plugins)
|
||||
ctbattest.Register(plugins)
|
||||
certsubjectrestriction.Register(plugins)
|
||||
}
|
||||
|
||||
@@ -158,6 +161,7 @@ func DefaultOffAdmissionPlugins() sets.String {
|
||||
runtimeclass.PluginName, // RuntimeClass
|
||||
certapproval.PluginName, // CertificateApproval
|
||||
certsigning.PluginName, // CertificateSigning
|
||||
ctbattest.PluginName, // ClusterTrustBundleAttest
|
||||
certsubjectrestriction.PluginName, // CertificateSubjectRestriction
|
||||
defaultingressclass.PluginName, // DefaultIngressClass
|
||||
podsecurity.PluginName, // PodSecurity
|
||||
|
@@ -31,6 +31,7 @@ import (
|
||||
autoscalingv2beta1 "k8s.io/api/autoscaling/v2beta1"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
batchv1beta1 "k8s.io/api/batch/v1beta1"
|
||||
certificatesv1alpha1 "k8s.io/api/certificates/v1alpha1"
|
||||
certificatesv1beta1 "k8s.io/api/certificates/v1beta1"
|
||||
coordinationv1 "k8s.io/api/coordination/v1"
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
@@ -407,6 +408,13 @@ func AddHandlers(h printers.PrintHandler) {
|
||||
_ = h.TableHandler(certificateSigningRequestColumnDefinitions, printCertificateSigningRequest)
|
||||
_ = h.TableHandler(certificateSigningRequestColumnDefinitions, printCertificateSigningRequestList)
|
||||
|
||||
clusterTrustBundleColumnDefinitions := []metav1.TableColumnDefinition{
|
||||
{Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
|
||||
{Name: "SignerName", Type: "string", Description: certificatesv1alpha1.ClusterTrustBundleSpec{}.SwaggerDoc()["signerName"]},
|
||||
}
|
||||
h.TableHandler(clusterTrustBundleColumnDefinitions, printClusterTrustBundle)
|
||||
h.TableHandler(clusterTrustBundleColumnDefinitions, printClusterTrustBundleList)
|
||||
|
||||
leaseColumnDefinitions := []metav1.TableColumnDefinition{
|
||||
{Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
|
||||
{Name: "Holder", Type: "string", Description: coordinationv1.LeaseSpec{}.SwaggerDoc()["holderIdentity"]},
|
||||
@@ -2095,6 +2103,30 @@ func printCertificateSigningRequestList(list *certificates.CertificateSigningReq
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
func printClusterTrustBundle(obj *certificates.ClusterTrustBundle, options printers.GenerateOptions) ([]metav1.TableRow, error) {
|
||||
row := metav1.TableRow{
|
||||
Object: runtime.RawExtension{Object: obj},
|
||||
}
|
||||
signerName := "<none>"
|
||||
if obj.Spec.SignerName != "" {
|
||||
signerName = obj.Spec.SignerName
|
||||
}
|
||||
row.Cells = append(row.Cells, obj.Name, signerName)
|
||||
return []metav1.TableRow{row}, nil
|
||||
}
|
||||
|
||||
func printClusterTrustBundleList(list *certificates.ClusterTrustBundleList, options printers.GenerateOptions) ([]metav1.TableRow, error) {
|
||||
rows := make([]metav1.TableRow, 0, len(list.Items))
|
||||
for i := range list.Items {
|
||||
r, err := printClusterTrustBundle(&list.Items[i], options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rows = append(rows, r...)
|
||||
}
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
func printComponentStatus(obj *api.ComponentStatus, options printers.GenerateOptions) ([]metav1.TableRow, error) {
|
||||
row := metav1.TableRow{
|
||||
Object: runtime.RawExtension{Object: obj},
|
||||
|
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
Copyright 2022 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 storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/registry/generic"
|
||||
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
api "k8s.io/kubernetes/pkg/apis/certificates"
|
||||
"k8s.io/kubernetes/pkg/printers"
|
||||
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
|
||||
printerstorage "k8s.io/kubernetes/pkg/printers/storage"
|
||||
"k8s.io/kubernetes/pkg/registry/certificates/clustertrustbundle"
|
||||
)
|
||||
|
||||
// REST is a RESTStorage for ClusterTrustBundle.
|
||||
type REST struct {
|
||||
*genericregistry.Store
|
||||
}
|
||||
|
||||
var _ rest.StandardStorage = &REST{}
|
||||
var _ rest.TableConvertor = &REST{}
|
||||
var _ genericregistry.GenericStore = &REST{}
|
||||
|
||||
// NewREST returns a RESTStorage object for ClusterTrustBundle objects.
|
||||
func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, error) {
|
||||
store := &genericregistry.Store{
|
||||
NewFunc: func() runtime.Object { return &api.ClusterTrustBundle{} },
|
||||
NewListFunc: func() runtime.Object { return &api.ClusterTrustBundleList{} },
|
||||
DefaultQualifiedResource: api.Resource("clustertrustbundles"),
|
||||
SingularQualifiedResource: api.Resource("clustertrustbundle"),
|
||||
|
||||
CreateStrategy: clustertrustbundle.Strategy,
|
||||
UpdateStrategy: clustertrustbundle.Strategy,
|
||||
DeleteStrategy: clustertrustbundle.Strategy,
|
||||
|
||||
TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
|
||||
}
|
||||
options := &generic.StoreOptions{
|
||||
RESTOptions: optsGetter,
|
||||
AttrFunc: getAttrs,
|
||||
}
|
||||
if err := store.CompleteWithOptions(options); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &REST{store}, nil
|
||||
}
|
||||
|
||||
func getAttrs(obj runtime.Object) (labels.Set, fields.Set, error) {
|
||||
bundle, ok := obj.(*api.ClusterTrustBundle)
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("not a clustertrustbundle")
|
||||
}
|
||||
|
||||
selectableFields := generic.MergeFieldsSets(generic.ObjectMetaFieldsSet(&bundle.ObjectMeta, false), fields.Set{
|
||||
"spec.signerName": bundle.Spec.SignerName,
|
||||
})
|
||||
|
||||
return labels.Set(bundle.Labels), selectableFields, nil
|
||||
}
|
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
Copyright 2022 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 storage
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/registry/generic"
|
||||
genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing"
|
||||
etcd3testing "k8s.io/apiserver/pkg/storage/etcd3/testing"
|
||||
"k8s.io/kubernetes/pkg/apis/certificates"
|
||||
"k8s.io/kubernetes/pkg/registry/registrytest"
|
||||
)
|
||||
|
||||
const validCert1 = `
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDmTCCAoGgAwIBAgIUUW9bIIsHU61w3yQR6amBuVvRFvcwDQYJKoZIhvcNAQEL
|
||||
BQAwXDELMAkGA1UEBhMCeHgxCjAIBgNVBAgMAXgxCjAIBgNVBAcMAXgxCjAIBgNV
|
||||
BAoMAXgxCjAIBgNVBAsMAXgxCzAJBgNVBAMMAmNhMRAwDgYJKoZIhvcNAQkBFgF4
|
||||
MB4XDTIyMTAxODIzNTIyNFoXDTIzMTAxODIzNTIyNFowXDELMAkGA1UEBhMCeHgx
|
||||
CjAIBgNVBAgMAXgxCjAIBgNVBAcMAXgxCjAIBgNVBAoMAXgxCjAIBgNVBAsMAXgx
|
||||
CzAJBgNVBAMMAmNhMRAwDgYJKoZIhvcNAQkBFgF4MIIBIjANBgkqhkiG9w0BAQEF
|
||||
AAOCAQ8AMIIBCgKCAQEA4PeK4SmlsNwpw97gTtjODQytUfyqhBIwdENwJUbc019Y
|
||||
m3VTCRLCGXjUa22mV6/j7V+mZw114ePFYTiGAH+2dUzWAZOphvtzE5ttPuv6A6Zx
|
||||
k2J69lNFwJ2fPd7XQIH7pEIXjiEBaszxKZKMsN9+jOGu6iFFAwYLMemFYDbZHuqb
|
||||
OwdQcSEsy5wO2ANzFRuYzGXuNcS8jYLHftE8g2P+L0wXnV9eW6/lM2ZFxS/nzDJz
|
||||
qtzrEvQrBsmskTNC8gCRRZ7askp3CVdPKjC90sxAPwhpi8JjJZxSe1Bn/WRHUz82
|
||||
GFytEIJNx9hJY2GI316zkxgTbsxfRQe4QLJN7sRtpwIDAQABo1MwUTAdBgNVHQ4E
|
||||
FgQU9FGsI8t+cu68fGkhtvO9FtUd174wHwYDVR0jBBgwFoAU9FGsI8t+cu68fGkh
|
||||
tvO9FtUd174wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAqDIp
|
||||
In5h2xZfEZcijT3mjfG8Bo6taxM2biy1M7wEpmDrElmrjMLsflZepcjgkSoVz9hP
|
||||
cSX/k9ls1zy1H799gcjs+afSpIa1N0nUIxAKF1RHsFa+dvXpSA8YdhUnbEcBnqx0
|
||||
vN2nDBFpdCSNf+EXNEj12+9ZJm6TLzx22f9vHyRCg4D36X3Rj1FCBWxhf0mSt3ek
|
||||
5px3H53Xu42MqzZCiJc8/m+IqZHaixZS4bsayssaxif2fNxzAIZhgTygo8P8QGjI
|
||||
rUmstMbg4PPq62x1yLAxEo+8XCg05saWZs384JE+K1SDqxobm51EROWVwi8jUrNC
|
||||
9nojtkQ+jDZD+1Stiw==
|
||||
-----END CERTIFICATE-----
|
||||
`
|
||||
|
||||
const validCert2 = `
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIC/jCCAeagAwIBAgIBADANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwprdWJl
|
||||
cm5ldGVzMB4XDTIyMTAxOTIzMTY0MFoXDTMyMTAxNjIzMTY0MFowFTETMBEGA1UE
|
||||
AxMKa3ViZXJuZXRlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAO+k
|
||||
zbj35jHIjCd5mxP1FHMwMtvLFPeKUjtaLDP9Bs2jZ97Igmr7NTysn9QZkRP68/XX
|
||||
j993Y8tOLg71N4vRggWiYP+T9Xfo0uHZJmzADKx5XkuC4Gqv79dUdb8IKfAbX9HB
|
||||
ffGmWRnZLLTu8Bv/vfyl0CfE64a57DK+CzNJDwdK46CYYUnEH6Wb9finYrMQ+PLG
|
||||
Oi2c0J4KAYc1WTId5npNwouzf/IMD33PvuXfE7r+/pDbP8u/X03e7U0cc9l7KRxr
|
||||
3gpRQemCG74yRuy1dd3lJ1YCD8q96xVVZimGebnJ0IHi+lORRa2ix/o3OzW3FaP+
|
||||
6kzHU6VnBRDr2rAhMh0CAwEAAaNZMFcwDgYDVR0PAQH/BAQDAgKkMA8GA1UdEwEB
|
||||
/wQFMAMBAf8wHQYDVR0OBBYEFGUVOLM74t1TVoZjifsLl3Rwt1A6MBUGA1UdEQQO
|
||||
MAyCCmt1YmVybmV0ZXMwDQYJKoZIhvcNAQELBQADggEBANHnPVDemZqRybYPN1as
|
||||
Ywxi3iT1I3Wma1rZyxTWeIq8Ik0gnyvbtCD1cFB/5QU1xPW09YnmIFM/E73RIeWT
|
||||
RmCNMgOGmegYxBQRe4UvmwWGJzKNA66c0MBmd2LDHrQlrvdewOCR667Sm9krsGt1
|
||||
tS/t6N/uBXeRSkXKEDXa+jOpYrV3Oq3IntG6zUeCrVbrH2Bs9Ma5fU00TwK3ylw5
|
||||
Ww8KzYdQaxxrLaiRRtFcpM9dFH/vwxl1QUa5vjHcmUjxmZunEmXKplATyLT0FXDw
|
||||
JAo8AuwuuwRh2o+o8SxwzzA+/EBrIREgcv5uIkD352QnfGkEvGu6JOPGZVyd/kVg
|
||||
KA0=
|
||||
-----END CERTIFICATE-----
|
||||
`
|
||||
|
||||
func newStorage(t *testing.T) (*REST, *etcd3testing.EtcdTestServer) {
|
||||
etcdStorage, server := registrytest.NewEtcdStorageForResource(t, certificates.SchemeGroupVersion.WithResource("clustertrustbundles").GroupResource())
|
||||
restOptions := generic.RESTOptions{
|
||||
StorageConfig: etcdStorage,
|
||||
Decorator: generic.UndecoratedStorage,
|
||||
DeleteCollectionWorkers: 1,
|
||||
ResourcePrefix: "clustertrustbundles",
|
||||
}
|
||||
storage, err := NewREST(restOptions)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error from REST storage: %v", err)
|
||||
}
|
||||
return storage, server
|
||||
}
|
||||
|
||||
func TestCreate(t *testing.T) {
|
||||
storage, server := newStorage(t)
|
||||
defer server.Terminate(t)
|
||||
defer storage.Store.DestroyFunc()
|
||||
|
||||
validBundle := &certificates.ClusterTrustBundle{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "ctb1",
|
||||
},
|
||||
Spec: certificates.ClusterTrustBundleSpec{
|
||||
TrustBundle: validCert1,
|
||||
},
|
||||
}
|
||||
|
||||
invalidBundle := &certificates.ClusterTrustBundle{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "ctb1",
|
||||
},
|
||||
Spec: certificates.ClusterTrustBundleSpec{
|
||||
// Empty TrustBundle is invalid.
|
||||
},
|
||||
}
|
||||
|
||||
test := genericregistrytest.New(t, storage.Store)
|
||||
test = test.ClusterScope()
|
||||
|
||||
test.TestCreate(validBundle, invalidBundle)
|
||||
}
|
||||
|
||||
func TestUpdate(t *testing.T) {
|
||||
storage, server := newStorage(t)
|
||||
defer server.Terminate(t)
|
||||
defer storage.Store.DestroyFunc()
|
||||
|
||||
test := genericregistrytest.New(t, storage.Store)
|
||||
test = test.ClusterScope()
|
||||
|
||||
test.TestUpdate(
|
||||
&certificates.ClusterTrustBundle{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "ctb1",
|
||||
},
|
||||
Spec: certificates.ClusterTrustBundleSpec{
|
||||
TrustBundle: validCert1,
|
||||
},
|
||||
},
|
||||
// Valid update
|
||||
func(object runtime.Object) runtime.Object {
|
||||
bundle := object.(*certificates.ClusterTrustBundle)
|
||||
bundle.Spec.TrustBundle = strings.Join([]string{validCert1, validCert2}, "\n")
|
||||
return bundle
|
||||
},
|
||||
// Invalid update
|
||||
func(object runtime.Object) runtime.Object {
|
||||
bundle := object.(*certificates.ClusterTrustBundle)
|
||||
bundle.Spec.TrustBundle = ""
|
||||
return bundle
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
storage, server := newStorage(t)
|
||||
defer server.Terminate(t)
|
||||
defer storage.Store.DestroyFunc()
|
||||
|
||||
test := genericregistrytest.New(t, storage.Store)
|
||||
test = test.ClusterScope()
|
||||
|
||||
test.TestDelete(
|
||||
&certificates.ClusterTrustBundle{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "ctb1",
|
||||
},
|
||||
Spec: certificates.ClusterTrustBundleSpec{
|
||||
TrustBundle: validCert1,
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
storage, server := newStorage(t)
|
||||
defer server.Terminate(t)
|
||||
defer storage.Store.DestroyFunc()
|
||||
|
||||
test := genericregistrytest.New(t, storage.Store)
|
||||
test = test.ClusterScope()
|
||||
|
||||
test.TestGet(
|
||||
&certificates.ClusterTrustBundle{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "ctb1",
|
||||
},
|
||||
Spec: certificates.ClusterTrustBundleSpec{
|
||||
TrustBundle: validCert1,
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
storage, server := newStorage(t)
|
||||
defer server.Terminate(t)
|
||||
defer storage.Store.DestroyFunc()
|
||||
|
||||
test := genericregistrytest.New(t, storage.Store)
|
||||
test = test.ClusterScope()
|
||||
|
||||
test.TestList(
|
||||
&certificates.ClusterTrustBundle{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "ctb1",
|
||||
},
|
||||
Spec: certificates.ClusterTrustBundleSpec{
|
||||
TrustBundle: validCert1,
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func TestWatch(t *testing.T) {
|
||||
storage, server := newStorage(t)
|
||||
defer server.Terminate(t)
|
||||
defer storage.Store.DestroyFunc()
|
||||
|
||||
test := genericregistrytest.New(t, storage.Store)
|
||||
test = test.ClusterScope()
|
||||
|
||||
test.TestWatch(
|
||||
&certificates.ClusterTrustBundle{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "ctb1",
|
||||
},
|
||||
Spec: certificates.ClusterTrustBundleSpec{
|
||||
SignerName: "k8s.io/foo",
|
||||
TrustBundle: validCert1,
|
||||
},
|
||||
},
|
||||
// matching labels
|
||||
[]labels.Set{},
|
||||
// not matching labels
|
||||
[]labels.Set{
|
||||
{"foo": "bar"},
|
||||
},
|
||||
// matching fields
|
||||
[]fields.Set{
|
||||
{"metadata.name": "ctb1"},
|
||||
},
|
||||
// not matching fields
|
||||
[]fields.Set{
|
||||
{"metadata.name": "bar"},
|
||||
},
|
||||
)
|
||||
}
|
81
pkg/registry/certificates/clustertrustbundle/strategy.go
Normal file
81
pkg/registry/certificates/clustertrustbundle/strategy.go
Normal file
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
Copyright 2022 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 clustertrustbundle provides Registry interface and its RESTStorage
|
||||
// implementation for storing ClusterTrustBundle objects.
|
||||
package clustertrustbundle // import "k8s.io/kubernetes/pkg/registry/certificates/clustertrustbundle"
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
"k8s.io/apiserver/pkg/storage/names"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
"k8s.io/kubernetes/pkg/apis/certificates"
|
||||
certvalidation "k8s.io/kubernetes/pkg/apis/certificates/validation"
|
||||
)
|
||||
|
||||
// strategy implements behavior for ClusterTrustBundles.
|
||||
type strategy struct {
|
||||
runtime.ObjectTyper
|
||||
names.NameGenerator
|
||||
}
|
||||
|
||||
// Strategy is the create, update, and delete strategy for ClusterTrustBundles.
|
||||
var Strategy = strategy{legacyscheme.Scheme, names.SimpleNameGenerator}
|
||||
|
||||
var _ rest.RESTCreateStrategy = Strategy
|
||||
var _ rest.RESTUpdateStrategy = Strategy
|
||||
var _ rest.RESTDeleteStrategy = Strategy
|
||||
|
||||
func (strategy) NamespaceScoped() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (strategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {}
|
||||
|
||||
func (strategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
||||
bundle := obj.(*certificates.ClusterTrustBundle)
|
||||
return certvalidation.ValidateClusterTrustBundle(bundle, certvalidation.ValidateClusterTrustBundleOptions{})
|
||||
}
|
||||
|
||||
func (strategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (strategy) Canonicalize(obj runtime.Object) {}
|
||||
|
||||
func (strategy) AllowCreateOnUpdate() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (s strategy) PrepareForUpdate(ctx context.Context, new, old runtime.Object) {}
|
||||
|
||||
func (s strategy) ValidateUpdate(ctx context.Context, new, old runtime.Object) field.ErrorList {
|
||||
newBundle := new.(*certificates.ClusterTrustBundle)
|
||||
oldBundle := old.(*certificates.ClusterTrustBundle)
|
||||
return certvalidation.ValidateClusterTrustBundleUpdate(newBundle, oldBundle)
|
||||
}
|
||||
|
||||
func (strategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (strategy) AllowUnconditionalUpdate() bool {
|
||||
return false
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
Copyright 2022 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 clustertrustbundle
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/apis/certificates"
|
||||
)
|
||||
|
||||
func TestWarningsOnCreate(t *testing.T) {
|
||||
if warnings := Strategy.WarningsOnCreate(context.Background(), &certificates.ClusterTrustBundle{}); warnings != nil {
|
||||
t.Errorf("Got %v, want nil", warnings)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllowCreateOnUpdate(t *testing.T) {
|
||||
if Strategy.AllowCreateOnUpdate() != false {
|
||||
t.Errorf("Got true, want false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWarningsOnUpdate(t *testing.T) {
|
||||
if warnings := Strategy.WarningsOnUpdate(context.Background(), &certificates.ClusterTrustBundle{}, &certificates.ClusterTrustBundle{}); warnings != nil {
|
||||
t.Errorf("Got %v, want nil", warnings)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllowUnconditionalUpdate(t *testing.T) {
|
||||
if Strategy.AllowUnconditionalUpdate() != false {
|
||||
t.Errorf("Got true, want false")
|
||||
}
|
||||
}
|
@@ -18,13 +18,18 @@ package rest
|
||||
|
||||
import (
|
||||
certificatesapiv1 "k8s.io/api/certificates/v1"
|
||||
certificatesapiv1alpha1 "k8s.io/api/certificates/v1alpha1"
|
||||
"k8s.io/apiserver/pkg/registry/generic"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||
serverstorage "k8s.io/apiserver/pkg/server/storage"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
"k8s.io/kubernetes/pkg/apis/certificates"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
certificatestore "k8s.io/kubernetes/pkg/registry/certificates/certificates/storage"
|
||||
clustertrustbundlestore "k8s.io/kubernetes/pkg/registry/certificates/clustertrustbundle/storage"
|
||||
)
|
||||
|
||||
type RESTStorageProvider struct{}
|
||||
@@ -40,17 +45,22 @@ func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorag
|
||||
apiGroupInfo.VersionedResourcesStorageMap[certificatesapiv1.SchemeGroupVersion.Version] = storageMap
|
||||
}
|
||||
|
||||
if storageMap, err := p.v1alpha1Storage(apiResourceConfigSource, restOptionsGetter); err != nil {
|
||||
return genericapiserver.APIGroupInfo{}, err
|
||||
} else if len(storageMap) > 0 {
|
||||
apiGroupInfo.VersionedResourcesStorageMap[certificatesapiv1alpha1.SchemeGroupVersion.Version] = storageMap
|
||||
}
|
||||
|
||||
return apiGroupInfo, nil
|
||||
}
|
||||
|
||||
func (p RESTStorageProvider) v1Storage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (map[string]rest.Storage, error) {
|
||||
storage := map[string]rest.Storage{}
|
||||
|
||||
// certificatesigningrequests
|
||||
if resource := "certificatesigningrequests"; apiResourceConfigSource.ResourceEnabled(certificatesapiv1.SchemeGroupVersion.WithResource(resource)) {
|
||||
csrStorage, csrStatusStorage, csrApprovalStorage, err := certificatestore.NewREST(restOptionsGetter)
|
||||
if err != nil {
|
||||
return storage, err
|
||||
return nil, err
|
||||
}
|
||||
storage[resource] = csrStorage
|
||||
storage[resource+"/status"] = csrStatusStorage
|
||||
@@ -60,6 +70,24 @@ func (p RESTStorageProvider) v1Storage(apiResourceConfigSource serverstorage.API
|
||||
return storage, nil
|
||||
}
|
||||
|
||||
func (p RESTStorageProvider) v1alpha1Storage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (map[string]rest.Storage, error) {
|
||||
storage := map[string]rest.Storage{}
|
||||
|
||||
if resource := "clustertrustbundles"; apiResourceConfigSource.ResourceEnabled(certificatesapiv1alpha1.SchemeGroupVersion.WithResource(resource)) {
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.ClusterTrustBundle) {
|
||||
bundleStorage, err := clustertrustbundlestore.NewREST(restOptionsGetter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
storage[resource] = bundleStorage
|
||||
} else {
|
||||
klog.Warning("ClusterTrustBundle storage is disabled because the ClusterTrustBundle feature gate is disabled")
|
||||
}
|
||||
}
|
||||
|
||||
return storage, nil
|
||||
}
|
||||
|
||||
func (p RESTStorageProvider) GroupName() string {
|
||||
return certificates.GroupName
|
||||
}
|
||||
|
@@ -42,11 +42,11 @@ func NewEtcdStorageForResource(t *testing.T, resource schema.GroupResource) (*st
|
||||
completedConfig.APIResourceConfig = serverstorage.NewResourceConfig()
|
||||
factory, err := completedConfig.New()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
t.Fatalf("Error while making storage factory: %v", err)
|
||||
}
|
||||
resourceConfig, err := factory.NewConfig(resource)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
t.Fatalf("Error while finding storage destination: %v", err)
|
||||
}
|
||||
return resourceConfig, server
|
||||
}
|
||||
|
Reference in New Issue
Block a user