mirror of
https://github.com/kubernetes/client-go.git
synced 2026-06-25 11:14:27 +00:00
Compare commits
25 Commits
v0.37.0-al
...
v0.37.0-al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
68d28fb0b4 | ||
|
|
68262a1cf9 | ||
|
|
fc42561c17 | ||
|
|
d04ac3067f | ||
|
|
f04563ced1 | ||
|
|
848305dfba | ||
|
|
c18a3de8a3 | ||
|
|
29ceea8774 | ||
|
|
f226f2f6e6 | ||
|
|
cba7557c26 | ||
|
|
f4285d806d | ||
|
|
c949b958f1 | ||
|
|
1e5bbe8452 | ||
|
|
9f2f0bbab6 | ||
|
|
23b19597fe | ||
|
|
81deda607d | ||
|
|
bb74650ccf | ||
|
|
9fb400dccd | ||
|
|
ebfbc6844a | ||
|
|
d8fe9715b9 | ||
|
|
394ed525a6 | ||
|
|
6a08ddec11 | ||
|
|
c3667cb92d | ||
|
|
b5201bbace | ||
|
|
b48f101b05 |
@@ -36,7 +36,7 @@ type StatefulSetSpecApplyConfiguration struct {
|
||||
// TODO: Consider a rename of this field.
|
||||
Replicas *int32 `json:"replicas,omitempty"`
|
||||
// selector is a label query over pods that should match the replica count.
|
||||
// If empty, defaulted to labels on the pod template.
|
||||
// It must match the pod template's labels.
|
||||
// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors
|
||||
Selector *v1.LabelSelectorApplyConfiguration `json:"selector,omitempty"`
|
||||
// template is the object that describes the pod that will be created if
|
||||
|
||||
@@ -172,6 +172,8 @@ type PodSpecApplyConfiguration struct {
|
||||
EnableServiceLinks *bool `json:"enableServiceLinks,omitempty"`
|
||||
// PreemptionPolicy is the Policy for preempting pods with lower priority.
|
||||
// One of Never, PreemptLowerPriority.
|
||||
// When Priority Admission Controller is enabled, it prevents users from setting
|
||||
// this field. The admission controller populates this field from PriorityClassName.
|
||||
// Defaults to PreemptLowerPriority if unset.
|
||||
PreemptionPolicy *corev1.PreemptionPolicy `json:"preemptionPolicy,omitempty"`
|
||||
// Overhead represents the resource overhead associated with running a pod for a given RuntimeClass.
|
||||
|
||||
@@ -36,6 +36,9 @@ import (
|
||||
// // +patchStrategy=merge
|
||||
// // +listType=map
|
||||
// // +listMapKey=type
|
||||
// // +k8s:alpha(since: "1.37")=+k8s:optional
|
||||
// // +k8s:alpha(since: "1.37")=+k8s:listType=map
|
||||
// // +k8s:alpha(since: "1.37")=+k8s:listMapKey=type
|
||||
// Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"`
|
||||
//
|
||||
// // other fields
|
||||
|
||||
@@ -25,7 +25,15 @@ package v1alpha3
|
||||
type GangSchedulingPolicyApplyConfiguration struct {
|
||||
// MinCount is the minimum number of pods that must be schedulable or scheduled
|
||||
// at the same time for the scheduler to admit the entire group.
|
||||
// It must be a positive integer.
|
||||
// It must be a positive integer. This field is mutable to support workload scaling.
|
||||
//
|
||||
// Note that the scheduler operates on an eventually consistent model. Updates
|
||||
// to minCount may not be immediately reflected in scheduling decisions due to
|
||||
// propagation delays. If minCount is updated while a scheduling cycle is in
|
||||
// progress for that group, the new value may not take effect until the next
|
||||
// cycle. Moreover, minCount is only enforced during scheduling, meaning that
|
||||
// modifications to this field do not affect already-scheduled pods, applying
|
||||
// only to those evaluated in future cycles.
|
||||
MinCount *int32 `json:"minCount,omitempty"`
|
||||
}
|
||||
|
||||
|
||||
@@ -26,13 +26,18 @@ import (
|
||||
// with apply.
|
||||
//
|
||||
// PodGroupSchedulingPolicy defines the scheduling configuration for a PodGroup.
|
||||
// Exactly one policy must be set.
|
||||
// Exactly one policy must be set. The policy is chosen at creation time by setting either the
|
||||
// Basic or Gang field. The PodGroup may not change policy after creation.
|
||||
// Fields within chosen policy may be updated after creation when their individual fields allow it.
|
||||
type PodGroupSchedulingPolicyApplyConfiguration struct {
|
||||
// Basic specifies that the pods in this group should be scheduled using
|
||||
// standard Kubernetes scheduling behavior.
|
||||
// standard Kubernetes scheduling behavior. Setting this field at group creation time
|
||||
// opts this group to basic scheduling; this field cannot be changed afterward.
|
||||
Basic *schedulingv1alpha3.BasicSchedulingPolicy `json:"basic,omitempty"`
|
||||
// Gang specifies that the pods in this group should be scheduled using
|
||||
// all-or-nothing semantics.
|
||||
// all-or-nothing semantics. Setting this field at group creation time
|
||||
// opts this group to gang scheduling; this field cannot be set or unset afterward.
|
||||
// The minCount field within Gang scheduling policy remains mutable after group creation.
|
||||
Gang *GangSchedulingPolicyApplyConfiguration `json:"gang,omitempty"`
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ type PodGroupSpecApplyConfiguration struct {
|
||||
PodGroupTemplateRef *PodGroupTemplateReferenceApplyConfiguration `json:"podGroupTemplateRef,omitempty"`
|
||||
// SchedulingPolicy defines the scheduling policy for this instance of the PodGroup.
|
||||
// Controllers are expected to fill this field by copying it from a PodGroupTemplate.
|
||||
// This field is immutable.
|
||||
SchedulingPolicy *PodGroupSchedulingPolicyApplyConfiguration `json:"schedulingPolicy,omitempty"`
|
||||
// SchedulingConstraints defines optional scheduling constraints (e.g. topology) for this PodGroup.
|
||||
// Controllers are expected to fill this field by copying it from a PodGroupTemplate.
|
||||
@@ -50,8 +49,6 @@ type PodGroupSpecApplyConfiguration struct {
|
||||
// Controllers are expected to fill this field by copying it from a PodGroupTemplate.
|
||||
// One of Single, All. Defaults to Single if unset.
|
||||
// This field is immutable.
|
||||
// This field is available only when the WorkloadAwarePreemption feature gate
|
||||
// is enabled.
|
||||
DisruptionMode *DisruptionModeApplyConfiguration `json:"disruptionMode,omitempty"`
|
||||
// PriorityClassName defines the priority that should be considered when scheduling this pod group.
|
||||
// Controllers are expected to fill this field by copying it from a PodGroupTemplate.
|
||||
@@ -59,8 +56,6 @@ type PodGroupSpecApplyConfiguration struct {
|
||||
// (i.e. if no priority class is specified, admission control can set this to the global default
|
||||
// priority class if it exists. Otherwise, the pod group's priority will be zero).
|
||||
// This field is immutable.
|
||||
// This field is available only when the WorkloadAwarePreemption feature gate
|
||||
// is enabled.
|
||||
PriorityClassName *string `json:"priorityClassName,omitempty"`
|
||||
// Priority is the value of priority of this pod group. Various system components
|
||||
// use this field to find the priority of the pod group. When Priority Admission
|
||||
@@ -68,8 +63,6 @@ type PodGroupSpecApplyConfiguration struct {
|
||||
// controller populates this field from PriorityClassName.
|
||||
// The higher the value, the higher the priority.
|
||||
// This field is immutable.
|
||||
// This field is available only when the WorkloadAwarePreemption feature gate
|
||||
// is enabled.
|
||||
Priority *int32 `json:"priority,omitempty"`
|
||||
}
|
||||
|
||||
|
||||
@@ -30,11 +30,13 @@ type PodGroupStatusApplyConfiguration struct {
|
||||
// Conditions represent the latest observations of the PodGroup's state.
|
||||
//
|
||||
// Known condition types:
|
||||
// - "PodGroupScheduled": Indicates whether the scheduling requirement has been satisfied.
|
||||
// - "PodGroupInitiallyScheduled": Indicates whether the scheduling requirement has been satisfied.
|
||||
// Once this condition transitions to True, it serves as a terminal state and will never revert to False,
|
||||
// even if pods are subsequently evicted and group constraints are no longer met.
|
||||
// - "DisruptionTarget": Indicates whether the PodGroup is about to be terminated
|
||||
// due to disruption such as preemption.
|
||||
//
|
||||
// Known reasons for the PodGroupScheduled condition:
|
||||
// Known reasons for the PodGroupInitiallyScheduled condition:
|
||||
// - "Unschedulable": The PodGroup cannot be scheduled due to resource constraints,
|
||||
// affinity/anti-affinity rules, or insufficient capacity for the gang.
|
||||
// - "SchedulerError": The PodGroup cannot be scheduled due to some internal error
|
||||
|
||||
@@ -30,6 +30,7 @@ type PodGroupTemplateApplyConfiguration struct {
|
||||
SchedulingPolicy *PodGroupSchedulingPolicyApplyConfiguration `json:"schedulingPolicy,omitempty"`
|
||||
// SchedulingConstraints defines optional scheduling constraints (e.g. topology) for this PodGroupTemplate.
|
||||
// This field is only available when the TopologyAwareWorkloadScheduling feature gate is enabled.
|
||||
// This field is immutable.
|
||||
SchedulingConstraints *PodGroupSchedulingConstraintsApplyConfiguration `json:"schedulingConstraints,omitempty"`
|
||||
// ResourceClaims defines which ResourceClaims may be shared among Pods in
|
||||
// the group. Pods consume the devices allocated to a PodGroup's claim by
|
||||
@@ -44,23 +45,16 @@ type PodGroupTemplateApplyConfiguration struct {
|
||||
ResourceClaims []PodGroupResourceClaimApplyConfiguration `json:"resourceClaims,omitempty"`
|
||||
// DisruptionMode defines the mode in which a given PodGroup can be disrupted.
|
||||
// One of Single, All.
|
||||
// This field is available only when the WorkloadAwarePreemption feature gate
|
||||
// is enabled.
|
||||
// This field is immutable.
|
||||
DisruptionMode *DisruptionModeApplyConfiguration `json:"disruptionMode,omitempty"`
|
||||
// PriorityClassName indicates the priority that should be considered when scheduling
|
||||
// a pod group created from this template. If no priority class is specified, admission
|
||||
// control can set this to the global default priority class if it exists. Otherwise,
|
||||
// pod groups created from this template will have the priority set to zero.
|
||||
// This field is available only when the WorkloadAwarePreemption feature gate
|
||||
// is enabled.
|
||||
// a pod group created from this template.
|
||||
// This field is immutable.
|
||||
PriorityClassName *string `json:"priorityClassName,omitempty"`
|
||||
// Priority is the value of priority of pod groups created from this template. Various
|
||||
// system components use this field to find the priority of the pod group. When
|
||||
// Priority Admission Controller is enabled, it prevents users from setting this field.
|
||||
// The admission controller populates this field from PriorityClassName.
|
||||
// system components use this field to find the priority of the pod group.
|
||||
// The higher the value, the higher the priority.
|
||||
// This field is available only when the WorkloadAwarePreemption feature gate
|
||||
// is enabled.
|
||||
// This field is immutable.
|
||||
Priority *int32 `json:"priority,omitempty"`
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,8 @@ type WorkloadSpecApplyConfiguration struct {
|
||||
// This field is immutable.
|
||||
ControllerRef *TypedLocalObjectReferenceApplyConfiguration `json:"controllerRef,omitempty"`
|
||||
// PodGroupTemplates is the list of templates that make up the Workload.
|
||||
// The maximum number of templates is 8. This field is immutable.
|
||||
// The maximum number of templates is 8. Templates cannot be added or removed after the workload is created.
|
||||
// Existing templates may still be updated where their individual fields allow it.
|
||||
PodGroupTemplates []PodGroupTemplateApplyConfiguration `json:"podGroupTemplates,omitempty"`
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ func ServerSupportsVersion(client DiscoveryInterface, requiredGV schema.GroupVer
|
||||
return err
|
||||
}
|
||||
versions := metav1.ExtractGroupVersions(groups)
|
||||
serverVersions := sets.String{}
|
||||
serverVersions := sets.Set[string]{}
|
||||
for _, v := range versions {
|
||||
serverVersions.Insert(v)
|
||||
}
|
||||
|
||||
8
go.mod
8
go.mod
@@ -23,11 +23,11 @@ require (
|
||||
golang.org/x/time v0.15.0
|
||||
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af
|
||||
gopkg.in/evanphx/json-patch.v4 v4.13.0
|
||||
k8s.io/api v0.0.0-20260609035027-e126b8a6418e
|
||||
k8s.io/apimachinery v0.0.0-20260609072944-ff574143e0ad
|
||||
k8s.io/api v0.37.0-alpha.2
|
||||
k8s.io/apimachinery v0.37.0-alpha.2
|
||||
k8s.io/klog/v2 v2.140.0
|
||||
k8s.io/kube-openapi v0.0.0-20260519202549-bbf5c5577288
|
||||
k8s.io/streaming v0.0.0-20260602192112-0a84e25f8e5e
|
||||
k8s.io/kube-openapi v0.0.0-20260618221249-bc653b64f974
|
||||
k8s.io/streaming v0.37.0-alpha.2
|
||||
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730
|
||||
sigs.k8s.io/randfill v1.0.0
|
||||
|
||||
16
go.sum
16
go.sum
@@ -118,16 +118,16 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
k8s.io/api v0.0.0-20260609035027-e126b8a6418e h1:/mUSCvKhPW3DBo2fMLXRRfVg5GeAhwRPZ3jDpssZovE=
|
||||
k8s.io/api v0.0.0-20260609035027-e126b8a6418e/go.mod h1:Bm3GRuK6YwYUVfVny2zq6C5dDgOF+xZ+YWuSEJPNTh4=
|
||||
k8s.io/apimachinery v0.0.0-20260609072944-ff574143e0ad h1:EWDIHVYyygxlfcRf0lZefe+yTkSyEbPtWoTuoRaeEjE=
|
||||
k8s.io/apimachinery v0.0.0-20260609072944-ff574143e0ad/go.mod h1:fimydsj7+VrAUXJvVoRBL9TgvZfVdrtsBIEHiLUQC4I=
|
||||
k8s.io/api v0.37.0-alpha.2 h1:QlFfNpLR8u4REgMKPJW6stZut+Ig694IAk9v5pmL+7c=
|
||||
k8s.io/api v0.37.0-alpha.2/go.mod h1:QvG6GVNM/dqApHJv1BidtiWpviZA3QbztsHgTUmgmbk=
|
||||
k8s.io/apimachinery v0.37.0-alpha.2 h1:nVMCXf4rfXUIuCkpTOnhcDzZ+p5QS98XTGvfHgTW/Bk=
|
||||
k8s.io/apimachinery v0.37.0-alpha.2/go.mod h1:S4uhVCP6dikhTM/sZqN4bWD53ZJZKAKN+kTZ522D/rA=
|
||||
k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
|
||||
k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=
|
||||
k8s.io/kube-openapi v0.0.0-20260519202549-bbf5c5577288 h1:A7Lby6ekC6nv+6oO38huCMFBRP0Os+tIeq1GkwxOQes=
|
||||
k8s.io/kube-openapi v0.0.0-20260519202549-bbf5c5577288/go.mod h1:V/QaCUYDa+0QpcHhVVc5l99Uz56wEMEXBSj9oCDkNDY=
|
||||
k8s.io/streaming v0.0.0-20260602192112-0a84e25f8e5e h1:hk+SAa1pedjgwlCSfpcY0gHgGddPjfy0hX/ZQglt7js=
|
||||
k8s.io/streaming v0.0.0-20260602192112-0a84e25f8e5e/go.mod h1:QN1+yCAfxcSvo0908Z3rFKkA5Xlr3KgUZAGwKuR6qQk=
|
||||
k8s.io/kube-openapi v0.0.0-20260618221249-bc653b64f974 h1:JVogoTvOj6gutlx8bUwGh0e8o8L4X8nDbTLyONmoVvk=
|
||||
k8s.io/kube-openapi v0.0.0-20260618221249-bc653b64f974/go.mod h1:V/QaCUYDa+0QpcHhVVc5l99Uz56wEMEXBSj9oCDkNDY=
|
||||
k8s.io/streaming v0.37.0-alpha.2 h1:j1g81dEBrCTHq+hW5Q9D5/FcJJoGa/MaUmBSMHnE0ZY=
|
||||
k8s.io/streaming v0.37.0-alpha.2/go.mod h1:QN1+yCAfxcSvo0908Z3rFKkA5Xlr3KgUZAGwKuR6qQk=
|
||||
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU=
|
||||
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
|
||||
|
||||
@@ -33,7 +33,7 @@ func TestDaemonSetLister(t *testing.T) {
|
||||
testCases := []struct {
|
||||
inDSs []*extensions.DaemonSet
|
||||
list func() ([]*extensions.DaemonSet, error)
|
||||
outDaemonSetNames sets.String
|
||||
outDaemonSetNames sets.Set[string]
|
||||
expectErr bool
|
||||
}{
|
||||
// Basic listing
|
||||
@@ -44,7 +44,7 @@ func TestDaemonSetLister(t *testing.T) {
|
||||
list: func() ([]*extensions.DaemonSet, error) {
|
||||
return lister.List(labels.Everything())
|
||||
},
|
||||
outDaemonSetNames: sets.NewString("basic"),
|
||||
outDaemonSetNames: sets.New[string]("basic"),
|
||||
},
|
||||
// Listing multiple daemon sets
|
||||
{
|
||||
@@ -56,7 +56,7 @@ func TestDaemonSetLister(t *testing.T) {
|
||||
list: func() ([]*extensions.DaemonSet, error) {
|
||||
return lister.List(labels.Everything())
|
||||
},
|
||||
outDaemonSetNames: sets.NewString("basic", "complex", "complex2"),
|
||||
outDaemonSetNames: sets.New[string]("basic", "complex", "complex2"),
|
||||
},
|
||||
// No pod labels
|
||||
{
|
||||
@@ -74,7 +74,7 @@ func TestDaemonSetLister(t *testing.T) {
|
||||
}
|
||||
return lister.GetPodDaemonSets(pod)
|
||||
},
|
||||
outDaemonSetNames: sets.NewString(),
|
||||
outDaemonSetNames: sets.New[string](),
|
||||
expectErr: true,
|
||||
},
|
||||
// No DS selectors
|
||||
@@ -94,7 +94,7 @@ func TestDaemonSetLister(t *testing.T) {
|
||||
}
|
||||
return lister.GetPodDaemonSets(pod)
|
||||
},
|
||||
outDaemonSetNames: sets.NewString(),
|
||||
outDaemonSetNames: sets.New[string](),
|
||||
expectErr: true,
|
||||
},
|
||||
// Matching labels to selectors and namespace
|
||||
@@ -123,7 +123,7 @@ func TestDaemonSetLister(t *testing.T) {
|
||||
}
|
||||
return lister.GetPodDaemonSets(pod)
|
||||
},
|
||||
outDaemonSetNames: sets.NewString("bar"),
|
||||
outDaemonSetNames: sets.New[string]("bar"),
|
||||
},
|
||||
}
|
||||
for _, c := range testCases {
|
||||
|
||||
15
tools/cache/mutation_detector_test.go
vendored
15
tools/cache/mutation_detector_test.go
vendored
@@ -53,7 +53,10 @@ func TestMutationDetector(t *testing.T) {
|
||||
period: 1 * time.Second,
|
||||
retainDuration: 2 * time.Minute,
|
||||
failureFunc: func(message string) {
|
||||
mutationFound <- true
|
||||
select {
|
||||
case mutationFound <- true:
|
||||
case <-stopCh:
|
||||
}
|
||||
},
|
||||
}
|
||||
informer.cacheMutationDetector = detector
|
||||
@@ -63,8 +66,14 @@ func TestMutationDetector(t *testing.T) {
|
||||
|
||||
wait.PollImmediate(100*time.Millisecond, wait.ForeverTestTimeout, func() (bool, error) {
|
||||
detector.addedObjsLock.Lock()
|
||||
defer detector.addedObjsLock.Unlock()
|
||||
return len(detector.addedObjs) > 0, nil
|
||||
addedLen := len(detector.addedObjs)
|
||||
detector.addedObjsLock.Unlock()
|
||||
|
||||
detector.compareObjectsLock.Lock()
|
||||
cachedLen := len(detector.cachedObjs)
|
||||
detector.compareObjectsLock.Unlock()
|
||||
|
||||
return addedLen+cachedLen > 0, nil
|
||||
})
|
||||
|
||||
detector.compareObjectsLock.Lock()
|
||||
|
||||
44
tools/cache/shared_informer.go
vendored
44
tools/cache/shared_informer.go
vendored
@@ -177,6 +177,15 @@ type SharedInformer interface {
|
||||
// RemoveEventHandler removes a formerly added event handler given by
|
||||
// its registration handle.
|
||||
// This function is guaranteed to be idempotent, and thread-safe.
|
||||
//
|
||||
// Note: RemoveEventHandler is asynchronous. It stops queueing new events
|
||||
// but does not wait for already-queued events to finish executing.
|
||||
// Goroutines processing the remaining events may still be running and
|
||||
// invoking callbacks after this function returns.
|
||||
//
|
||||
// If the caller needs to wait for all handlers to finish executing (for
|
||||
// example, to safely close channels or release resources used by the handler),
|
||||
// they should use [ShutDownEventHandler].
|
||||
RemoveEventHandler(handle ResourceEventHandlerRegistration) error
|
||||
// GetStore returns the informer's local cache as a Store.
|
||||
GetStore() Store
|
||||
@@ -1014,6 +1023,24 @@ func (s *sharedIndexInformer) RemoveEventHandler(handle ResourceEventHandlerRegi
|
||||
return s.processor.removeListener(handle)
|
||||
}
|
||||
|
||||
// ShutDownEventHandler removes the event handler and blocks until it has fully
|
||||
// stopped processing events.
|
||||
//
|
||||
// Like RemoveEventHandler, it is idempotent and thread-safe. However, it MUST NOT
|
||||
// be called from within the event handler's own callbacks, as that will result
|
||||
// in a deadlock.
|
||||
func ShutDownEventHandler(informer SharedInformer, handle ResourceEventHandlerRegistration) error {
|
||||
if err := informer.RemoveEventHandler(handle); err != nil {
|
||||
return err
|
||||
}
|
||||
if s, ok := handle.(interface{ ShutdownChan() <-chan struct{} }); ok {
|
||||
<-s.ShutdownChan()
|
||||
} else {
|
||||
return fmt.Errorf("handle does not support ShutdownChan()")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// sharedProcessor has a collection of processorListener and can
|
||||
// distribute a notification object to its listeners. There are two
|
||||
// kinds of distribute operations. The sync distributions go to a
|
||||
@@ -1086,6 +1113,8 @@ func (p *sharedProcessor) removeListener(handle ResourceEventHandlerRegistration
|
||||
|
||||
if p.listenersStarted {
|
||||
close(listener.addCh)
|
||||
} else {
|
||||
close(listener.runFinished)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -1209,10 +1238,11 @@ func (p *sharedProcessor) resyncCheckPeriodChanged(logger klog.Logger, resyncChe
|
||||
// processorListener also keeps track of the adjusted requested resync
|
||||
// period of the listener.
|
||||
type processorListener struct {
|
||||
logger klog.Logger
|
||||
nextCh chan interface{}
|
||||
addCh chan interface{}
|
||||
done chan struct{}
|
||||
logger klog.Logger
|
||||
nextCh chan interface{}
|
||||
addCh chan interface{}
|
||||
done chan struct{}
|
||||
runFinished chan struct{}
|
||||
|
||||
handler ResourceEventHandler
|
||||
handlerName string
|
||||
@@ -1265,6 +1295,10 @@ func (p *processorListener) HasSyncedChecker() DoneChecker {
|
||||
return p.syncTracker
|
||||
}
|
||||
|
||||
func (p *processorListener) ShutdownChan() <-chan struct{} {
|
||||
return p.runFinished
|
||||
}
|
||||
|
||||
func newProcessListener(logger klog.Logger, handler ResourceEventHandler, requestedResyncPeriod, resyncPeriod time.Duration, now time.Time, bufferSize int, hasSynced DoneChecker) *processorListener {
|
||||
handlerName := nameForHandler(handler)
|
||||
ret := &processorListener{
|
||||
@@ -1272,6 +1306,7 @@ func newProcessListener(logger klog.Logger, handler ResourceEventHandler, reques
|
||||
nextCh: make(chan interface{}),
|
||||
addCh: make(chan interface{}),
|
||||
done: make(chan struct{}),
|
||||
runFinished: make(chan struct{}),
|
||||
upstreamHasSynced: hasSynced,
|
||||
handler: handler,
|
||||
handlerName: handlerName,
|
||||
@@ -1328,6 +1363,7 @@ func (p *processorListener) pop() {
|
||||
}
|
||||
|
||||
func (p *processorListener) run() {
|
||||
defer close(p.runFinished)
|
||||
// this call blocks until the channel is closed. When a panic happens during the notification
|
||||
// we will catch it, **the offending item will be skipped!**, and after a short delay (one second)
|
||||
// the next notification will be attempted. This is usually better than the alternative of never
|
||||
|
||||
218
tools/cache/shared_informer_test.go
vendored
218
tools/cache/shared_informer_test.go
vendored
@@ -1385,3 +1385,221 @@ func numOccurrences(hay, needle string) int {
|
||||
hay = hay[index+len(needle):]
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveEventHandler_AsynchronousPanic(t *testing.T) {
|
||||
source := newFakeControllerSource(t)
|
||||
informer := NewSharedInformer(source, &v1.Pod{}, 0).(*sharedIndexInformer)
|
||||
|
||||
stop := make(chan struct{})
|
||||
var wg wait.Group
|
||||
wg.StartWithChannel(stop, informer.Run)
|
||||
defer func() {
|
||||
close(stop)
|
||||
wg.Wait()
|
||||
}()
|
||||
|
||||
scheduledPods := make(chan *v1.Pod, 1)
|
||||
blockChan := make(chan struct{})
|
||||
handlerStarted := make(chan struct{})
|
||||
handlerFinished := make(chan struct{})
|
||||
var panicVal any
|
||||
var panicked bool
|
||||
|
||||
handler := ResourceEventHandlerFuncs{
|
||||
AddFunc: func(obj any) {
|
||||
close(handlerStarted)
|
||||
<-blockChan
|
||||
defer close(handlerFinished)
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
panicVal = r
|
||||
panicked = true
|
||||
}
|
||||
}()
|
||||
scheduledPods <- obj.(*v1.Pod)
|
||||
},
|
||||
}
|
||||
|
||||
handle, err := informer.AddEventHandler(handler)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to add event handler: %v", err)
|
||||
}
|
||||
|
||||
// Trigger the event
|
||||
source.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod1"}})
|
||||
|
||||
// Wait for handler to start and block
|
||||
<-handlerStarted
|
||||
|
||||
// Now remove the handler. This should return immediately.
|
||||
err = informer.RemoveEventHandler(handle)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to remove event handler: %v", err)
|
||||
}
|
||||
|
||||
// Close the channel that the handler writes to
|
||||
close(scheduledPods)
|
||||
|
||||
// Unblock the handler
|
||||
close(blockChan)
|
||||
|
||||
// Wait for handler to finish
|
||||
<-handlerFinished
|
||||
|
||||
if !panicked {
|
||||
t.Fatalf("Expected handler to panic (send on closed channel), but it did not")
|
||||
}
|
||||
t.Logf("Caught expected panic: %v", panicVal)
|
||||
}
|
||||
|
||||
func TestRemoveEventHandler_SynchronousShutdown(t *testing.T) {
|
||||
source := newFakeControllerSource(t)
|
||||
informer := NewSharedInformer(source, &v1.Pod{}, 0).(*sharedIndexInformer)
|
||||
|
||||
stop := make(chan struct{})
|
||||
var wg wait.Group
|
||||
wg.StartWithChannel(stop, informer.Run)
|
||||
defer func() {
|
||||
close(stop)
|
||||
wg.Wait()
|
||||
}()
|
||||
|
||||
scheduledPods := make(chan *v1.Pod, 1)
|
||||
blockChan := make(chan struct{})
|
||||
handlerStarted := make(chan struct{})
|
||||
handlerFinished := make(chan struct{})
|
||||
var panicVal any
|
||||
var panicked bool
|
||||
|
||||
handler := ResourceEventHandlerFuncs{
|
||||
AddFunc: func(obj any) {
|
||||
close(handlerStarted)
|
||||
<-blockChan
|
||||
defer close(handlerFinished)
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
panicVal = r
|
||||
panicked = true
|
||||
}
|
||||
}()
|
||||
scheduledPods <- obj.(*v1.Pod)
|
||||
},
|
||||
}
|
||||
|
||||
handle, err := informer.AddEventHandler(handler)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to add event handler: %v", err)
|
||||
}
|
||||
|
||||
// Trigger the event
|
||||
source.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod1"}})
|
||||
|
||||
// Wait for handler to start and block
|
||||
<-handlerStarted
|
||||
|
||||
// Unblock the handler so it can run and exit
|
||||
close(blockChan)
|
||||
|
||||
// Now remove the handler and wait for it to fully stop.
|
||||
err = ShutDownEventHandler(informer, handle)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to shutdown event handler: %v", err)
|
||||
}
|
||||
|
||||
// NOW it is safe to close the channel
|
||||
close(scheduledPods)
|
||||
|
||||
// Wait for handler function to finish (should already be done)
|
||||
<-handlerFinished
|
||||
|
||||
if panicked {
|
||||
t.Fatalf("Handler panicked unexpectedly: %v", panicVal)
|
||||
}
|
||||
}
|
||||
|
||||
func TestShutDownEventHandler_Lifecycles(t *testing.T) {
|
||||
t.Run("InformerNeverStarted", func(t *testing.T) {
|
||||
source := newFakeControllerSource(t)
|
||||
informer := NewSharedInformer(source, &v1.Pod{}, 0).(*sharedIndexInformer)
|
||||
|
||||
handler := ResourceEventHandlerFuncs{
|
||||
AddFunc: func(obj any) {},
|
||||
}
|
||||
handle, err := informer.AddEventHandler(handler)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to add event handler: %v", err)
|
||||
}
|
||||
|
||||
// Shutdown should return immediately because the informer was never started.
|
||||
err = ShutDownEventHandler(informer, handle)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to shutdown event handler: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("StartedThenAddedThenShutDown", func(t *testing.T) {
|
||||
source := newFakeControllerSource(t)
|
||||
informer := NewSharedInformer(source, &v1.Pod{}, 0).(*sharedIndexInformer)
|
||||
|
||||
stop := make(chan struct{})
|
||||
var wg wait.Group
|
||||
wg.StartWithChannel(stop, informer.Run)
|
||||
defer func() {
|
||||
close(stop)
|
||||
wg.Wait()
|
||||
}()
|
||||
|
||||
// Wait deterministically for the informer to sync (proving it has started)
|
||||
syncCtx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
if !WaitFor(syncCtx, "informer sync", informer.HasSyncedChecker()) {
|
||||
t.Fatalf("Informer did not sync")
|
||||
}
|
||||
|
||||
handler := ResourceEventHandlerFuncs{
|
||||
AddFunc: func(obj any) {},
|
||||
}
|
||||
handle, err := informer.AddEventHandler(handler)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to add event handler: %v", err)
|
||||
}
|
||||
|
||||
err = ShutDownEventHandler(informer, handle)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to shutdown event handler: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("AddedThenStartedThenShutDown", func(t *testing.T) {
|
||||
source := newFakeControllerSource(t)
|
||||
informer := NewSharedInformer(source, &v1.Pod{}, 0).(*sharedIndexInformer)
|
||||
|
||||
handler := ResourceEventHandlerFuncs{
|
||||
AddFunc: func(obj any) {},
|
||||
}
|
||||
handle, err := informer.AddEventHandler(handler)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to add event handler: %v", err)
|
||||
}
|
||||
|
||||
stop := make(chan struct{})
|
||||
var wg wait.Group
|
||||
wg.StartWithChannel(stop, informer.Run)
|
||||
defer func() {
|
||||
close(stop)
|
||||
wg.Wait()
|
||||
}()
|
||||
|
||||
// Wait deterministically for the informer to sync (proving it has started)
|
||||
syncCtx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
if !WaitFor(syncCtx, "informer sync", informer.HasSyncedChecker()) {
|
||||
t.Fatalf("Informer did not sync")
|
||||
}
|
||||
|
||||
err = ShutDownEventHandler(informer, handle)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to shutdown event handler: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -18,13 +18,13 @@ package certificate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
cryptorand "crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
@@ -202,6 +202,13 @@ type Config struct {
|
||||
// CertifcateRenewFailure will record a metric that keeps track of
|
||||
// certificate renewal failures.
|
||||
CertificateRenewFailure Counter
|
||||
// GenerateKey is an optional function to generate the private key for a new
|
||||
// certificate signing request. If not set, an ECDSA P-256 key is generated.
|
||||
// Currently only *ecdsa.PrivateKey and *rsa.PrivateKey are supported.
|
||||
// The custom key must be strong enough or an error will be returned
|
||||
// when attempting to generate a CSR. For RSA the minimum bits must be 2048,
|
||||
// for ECDSA the minimum curve size must be 256 bits.
|
||||
GenerateKey func() (crypto.Signer, error)
|
||||
// Name is an optional string that will be used when writing log output
|
||||
// via logger.WithName or returning errors from manager methods.
|
||||
//
|
||||
@@ -269,6 +276,7 @@ type manager struct {
|
||||
signerName string
|
||||
requestedCertificateLifetime *time.Duration
|
||||
getUsages func(privateKey interface{}) []certificates.KeyUsage
|
||||
generateKey func() (crypto.Signer, error)
|
||||
forceRotation bool
|
||||
|
||||
certStore Store
|
||||
@@ -322,6 +330,7 @@ func NewManager(config *Config) (Manager, error) {
|
||||
signerName: config.SignerName,
|
||||
requestedCertificateLifetime: config.RequestedCertificateLifetime,
|
||||
getUsages: getUsages,
|
||||
generateKey: config.GenerateKey,
|
||||
certStore: config.CertificateStore,
|
||||
certificateRotation: config.CertificateRotation,
|
||||
certificateRenewFailure: config.CertificateRenewFailure,
|
||||
@@ -762,19 +771,29 @@ func (m *manager) updateServerError(err error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var generateKeyFunc = generateKeyFuncImpl
|
||||
|
||||
func generateKeyFuncImpl() (crypto.Signer, error) {
|
||||
return ecdsa.GenerateKey(elliptic.P256(), cryptorand.Reader)
|
||||
}
|
||||
|
||||
func (m *manager) generateCSR() (template *x509.CertificateRequest, csrPEM []byte, keyPEM []byte, key interface{}, err error) {
|
||||
generateKey := m.generateKey
|
||||
if generateKey == nil {
|
||||
generateKey = generateKeyFunc
|
||||
}
|
||||
// Generate a new private key.
|
||||
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), cryptorand.Reader)
|
||||
privateKey, err := generateKey()
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, fmt.Errorf("unable to generate a new private key: %w", err)
|
||||
}
|
||||
der, err := x509.MarshalECPrivateKey(privateKey)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, fmt.Errorf("unable to marshal the new key to DER: %w", err)
|
||||
if err = validateKeyStrength(privateKey); err != nil {
|
||||
return nil, nil, nil, nil, fmt.Errorf("the new key is insecure: %w", err)
|
||||
}
|
||||
keyPEM, err = keyutil.MarshalPrivateKeyToPEM(privateKey)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, fmt.Errorf("unable to marshal the new key to PEM: %w", err)
|
||||
}
|
||||
|
||||
keyPEM = pem.EncodeToMemory(&pem.Block{Type: keyutil.ECPrivateKeyBlockType, Bytes: der})
|
||||
|
||||
template = m.getTemplate()
|
||||
if template == nil {
|
||||
return nil, nil, nil, nil, errors.New("unable to create a csr, no template available")
|
||||
@@ -807,3 +826,25 @@ func hasKeyUsage(usages []certificates.KeyUsage, usage certificates.KeyUsage) bo
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// validateKeyStrength checks that the key is strong enough to be used for a certificate.
|
||||
// For RSA the minimum bits must be 2048, for ECDSA the minimum curve size must be 256 bits.
|
||||
func validateKeyStrength(key crypto.Signer) error {
|
||||
const (
|
||||
minRSAKeyBits = 2048
|
||||
minECDSAKeyBits = 256
|
||||
)
|
||||
switch k := key.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
if bits := k.N.BitLen(); bits < minRSAKeyBits {
|
||||
return fmt.Errorf("RSA key size %d bits is below the minimum of %d",
|
||||
bits, minRSAKeyBits)
|
||||
}
|
||||
case *ecdsa.PrivateKey:
|
||||
if bits := k.Curve.Params().BitSize; bits < minECDSAKeyBits {
|
||||
return fmt.Errorf("ECDSA key curve %s (%d bits) is below the minimum of %d",
|
||||
k.Curve.Params().Name, bits, minECDSAKeyBits)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package certificate
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
@@ -43,6 +44,7 @@ import (
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
certificatesclient "k8s.io/client-go/kubernetes/typed/certificates/v1beta1"
|
||||
clienttesting "k8s.io/client-go/testing"
|
||||
"k8s.io/client-go/util/keyutil"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/klog/v2/ktesting"
|
||||
netutils "k8s.io/utils/net"
|
||||
@@ -1186,6 +1188,200 @@ func TestContext(t *testing.T) {
|
||||
require.Contains(t, logger.GetSink().(ktesting.Underlier).GetBuffer().String(), "certificate: Rotating certificates", "contextual log output from manager.rotateCerts")
|
||||
}
|
||||
|
||||
// testGenerateECKeyPEM1 is a EC P-256 private key fixture.
|
||||
// This key is for testing purposes only and is not considered secure.
|
||||
const testGenerateECKeyPEM1 = `-----BEGIN EC PRIVATE KEY-----
|
||||
MHcCAQEEIHb7bL0ccx8oLfPycKQT/R2sKrPH8LU5CnDz/65jUjWooAoGCCqGSM49
|
||||
AwEHoUQDQgAExNsMY0QTw83e3eFOZMLqpT6vqEAvpSMo5+9TSU/faJScYeSsHxlK
|
||||
tO96nbPcQbWCMGjhrpBWYZcn07iu125DpA==
|
||||
-----END EC PRIVATE KEY-----
|
||||
`
|
||||
|
||||
// testGenerateECKeyPEM2 is a EC P-256 private key fixture.
|
||||
// This key is for testing purposes only and is not considered secure.
|
||||
const testGenerateECKeyPEM2 = `-----BEGIN EC PRIVATE KEY-----
|
||||
MHcCAQEEIGy34cJkuN1N5chK9kLnf/Y5OT1rulnzyz6qignGpOJvoAoGCCqGSM49
|
||||
AwEHoUQDQgAE5CqbCF3D+r/QNUv8yrr/+kqOMTP6PGUe2G4AFvisUqGx0KoRj5dq
|
||||
F3qmQC6E+3zzI7+uhpZ3Ju/+696ZQ6GrJQ==
|
||||
-----END EC PRIVATE KEY-----
|
||||
`
|
||||
|
||||
// testGenerateRSAKeyPEM1 is a RSA 2048 private key fixture.
|
||||
// This key is for testing purposes only and is not considered secure.
|
||||
const testGenerateRSAKeyPEM1 = `-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDFav2tigP5v/EM
|
||||
x3ktZ+me1/h+L8zfA/djb58M2GiBZMMfyCpHrLxiBrrj5eRH6HUuETHk8cMymmg1
|
||||
HfzJ7P03/1W99vSiZMe1jsScZUeXZMiqJjF2UkiaJ73I/8xyzz+M/+E1UfZctjqm
|
||||
gV7R8ATivtKXhOW0GPgrYShZ+62pAaZfYixVNxs9LhNmv0BGGLtPsJ4bdcc0lD1b
|
||||
e/n0wFhAN59QwOaTUvRnQtYK8hYXSG/v5sFTsMkc1ZXwyqA360UX3lcYogDx510R
|
||||
SraUAu3Z+Lf4xbokczdEewZJ5jtAG85IwiFQLm8+hkWUx5PWYZH4yyTZhKqKebd3
|
||||
CirlHBzBAgMBAAECggEADjbPNQ4u4xiS09y444VhKMgGuESGImM4DgzHYuFiBORV
|
||||
uEAX6zk2Bx4nmVPAGqf+EpG3tJ2Y9FfHEQFWZiOOHS4L5QrsP5UKBrnU0NM/UwM1
|
||||
VNWTJ3XH4cbiv0og/6iJvC6K7zqLhnldwlymOxoQ//0apJYzrm1DJmcZxKt+Vstg
|
||||
gVUV7umoIsfE8YpFH4qECXAuixQ0vWi99BGuyLlnaeYP65QD6sRpkn9TZisMTpZX
|
||||
GTP/PsRdKJ2rohO3/VjsZytBYsr6ibBk+FZ/+EmRYLKF5m+kROefHYyKpUgKDV+v
|
||||
d5UwREsfs4MxU1IXjEROOWsdAHa+98S74+A5PBiiUQKBgQDwUAVtzODkdBb9LGUF
|
||||
428pumAgox16UFG6Qh0LNNmp77VztolXKRUg40EKIm3qD7BmlR2fjI7YMbjjNlFE
|
||||
XfCDWifBU+sOtHyVa+3rrjeBj13noAq3nXKEOagO6FowGgrD0eC9QTur09yG4dun
|
||||
fYKXjvRlTFm1MVBKDy7noJq+6wKBgQDSTiL8q4PD9pheIcz0VmJ8B4W7JCPwNn+r
|
||||
2mKcls23nIbYiweg14ZKEACby6py1L3+h2Trzy7ktnEsjqxRA/UqC7X56obqLJ7c
|
||||
34zOuZXytwsDEzQZgDWBEP3Hd65eCtiSkdOVKsI5wubS9idyCtPZC4LsWdKFc5S4
|
||||
rNwSD6ugAwKBgEyYpPJXgEMxAXbW5KhY0sDRJ/yfITEwUqx0kD9XLB2vSv3D68i9
|
||||
Tn+6D6wER1Z4g7hexR9qtMkSKCU71fFdo+CqJsvHTL/WJXOXADHDyOth4AOJDoFy
|
||||
DOM6YWfHBaAZXN8HkYOhPDzLfZn8eX/MUIiwRxPWny1St42zgzbPCSPbAoGAXrCX
|
||||
yDRhi6ZITHnjklAi371zVSOcmteu/G3D4MV1sqpjfLR8psrjyA0UeRFmmXV4ZlYH
|
||||
9rS+ZHRQ2MMUixXBGUFUmkYioOWeUczF1X5yKWqJJsVKvACiFo7T9S/J7sXrZXML
|
||||
VSp/cQp0a6AxeoOthxhLxqdaxoOX/t6159vuZokCgYEA0VcQlf0nTABk6UaJptJf
|
||||
CUOQPW3bNxHoiv5q0ZbVPIOGIRNlK4CsORdeh+tQiCuWYk1YzWk0zrKlEnPhjsml
|
||||
N9Yi/TjuQRxDA97qeRMq2i35g/UHO2LZVdc96owTojs5Snxop32nbEV9zsrBjybG
|
||||
T4kkiBn7MJZZreDWZUZWpC4=
|
||||
-----END PRIVATE KEY-----
|
||||
`
|
||||
|
||||
// testGenerateRSAKeyPEMInsecure is a RSA 1024 private key fixture.
|
||||
// This key is for testing purposes only and is not considered secure.
|
||||
const testGenerateRSAKeyPEMInsecure = `-----BEGIN PRIVATE KEY-----
|
||||
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALB6f6vwyY0IZWcE
|
||||
Fv8ViUi5kZ64sWUAiWnQhfK0lrAtICJCqy2p95cloaAVNpqvX6uLK3odT0aA7vRZ
|
||||
PXtYhDtpYTSHvvbAXbHUPl9/u3EPSQ1ByOtdAPio8DmMdWD6xRK4JjfHhH6bLsI+
|
||||
VHblKik/+BK+xgVo1VuNoRWC8NYRAgMBAAECgYALCs0mgGFSE2CJ5LP+J7YwcFkD
|
||||
1AVYfyM59VfOPv2vPhGUxzRf/fKtmMePRUiGfvrm2EVDBaa2T/6zmApcZ4ZVeSf8
|
||||
izNQilInGkCyOQhEbIJ+CE9IUXQG9h5qwdsM9Ehb6+ZkdF/JatkzKtceC0PYlSw8
|
||||
ZouKqMKMylvEXHLr6QJBAOSY7MRdsip/lAaNJOk/1JCqblV15DS9WpxzDZ1+JBhp
|
||||
NYrnEy7qe/p1tUKBQMt3nM2Kt1N2HXyPhWVhMvpitkkCQQDFoi2T9T31nVm/4HFV
|
||||
ALK5wt3FU0MLLn+E+km/++SUjVebtjCC5Gxz+ByAsj963jhCdh+2EzgyPkOKGK5a
|
||||
LwGJAkBBjuXgDuroqzvdgR8D0a15a5dG5Q90XJWe5pQSBbn+UjXrxwdGXjL+CkHY
|
||||
d88ISx5qCA05X1dngJWGFJEVI7gZAkAbWHFOA6TrEzaT4g5MYKhaI6hj4T1pkql6
|
||||
UNdbhRL/qv7wQKk9szV+ZlorRH6cFZtbNtT0cHxaF1tpBDk7qT1hAkEAs8WLPccN
|
||||
N2BAYjQjJanWfMAIyHPdwenmKhlvkWFvnwZWQecHXDmfHZlEREqpkd9IxOIGNii7
|
||||
OiCuooK0UDdbPw==
|
||||
-----END PRIVATE KEY-----
|
||||
`
|
||||
|
||||
// testGenerateECKeyPEMInsecure is a EC P-224 private key fixture.
|
||||
// This key is for testing purposes only and is not considered secure.
|
||||
const testGenerateECKeyPEMInsecure = `-----BEGIN PRIVATE KEY-----
|
||||
MHgCAQAwEAYHKoZIzj0CAQYFK4EEACEEYTBfAgEBBByl2nOZXdac4dWx0UvrHn8G
|
||||
P3p+oI+mjH6Znno3oTwDOgAE5/S2xcvEyJWSOjNilulwy9YnPOPubuZ4ztOh95kU
|
||||
eaVq8eqxqzGSxwanMrwbD/1PQ97kNdi53No=
|
||||
-----END PRIVATE KEY-----
|
||||
`
|
||||
|
||||
func TestGenerateKeyConfig(t *testing.T) {
|
||||
parsedECKey1 := parsePEMKey(t, testGenerateECKeyPEM1)
|
||||
parsedECKey2 := parsePEMKey(t, testGenerateECKeyPEM2)
|
||||
parsedRSAKey1 := parsePEMKey(t, testGenerateRSAKeyPEM1)
|
||||
|
||||
fixedErr := errors.New("some error")
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
generateKey func() (crypto.Signer, error)
|
||||
wantKey crypto.Signer
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "nil returns default EC key (key1)",
|
||||
generateKey: nil,
|
||||
wantKey: parsedECKey1,
|
||||
},
|
||||
{
|
||||
name: "returns custom EC key (key2)",
|
||||
generateKey: func() (crypto.Signer, error) { return parsedECKey2, nil },
|
||||
wantKey: parsedECKey2,
|
||||
},
|
||||
{
|
||||
name: "returns custom RSA key",
|
||||
generateKey: func() (crypto.Signer, error) { return parsedRSAKey1, nil },
|
||||
wantKey: parsedRSAKey1,
|
||||
},
|
||||
{
|
||||
name: "simulate parsing error",
|
||||
generateKey: func() (crypto.Signer, error) { return nil, fixedErr },
|
||||
wantErr: fixedErr,
|
||||
},
|
||||
}
|
||||
|
||||
// Setup default overload.
|
||||
orig := generateKeyFunc
|
||||
generateKeyFunc = func() (crypto.Signer, error) { return parsedECKey1, nil }
|
||||
defer func() { generateKeyFunc = orig }()
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
_, ctx := ktesting.NewTestContext(t)
|
||||
m := manager{
|
||||
getTemplate: func() *x509.CertificateRequest { return &x509.CertificateRequest{} },
|
||||
generateKey: tc.generateKey,
|
||||
now: time.Now,
|
||||
ctx: ctx,
|
||||
}
|
||||
_, _, _, key, err := m.generateCSR()
|
||||
if tc.wantErr != nil {
|
||||
if !errors.Is(err, tc.wantErr) {
|
||||
t.Errorf("expected error %v, got %v", tc.wantErr, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if key != tc.wantKey {
|
||||
t.Errorf("expected key: %T, got: %T", tc.wantKey, key)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateKeyStrength(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
key crypto.Signer
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "RSA 1024 is insecure",
|
||||
key: parsePEMKey(t, testGenerateRSAKeyPEMInsecure),
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "ECDSA P-224 is insecure",
|
||||
key: parsePEMKey(t, testGenerateECKeyPEMInsecure),
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "RSA 2048 is secure",
|
||||
key: parsePEMKey(t, testGenerateRSAKeyPEM1),
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "ECDSA P-256 is secure",
|
||||
key: parsePEMKey(t, testGenerateECKeyPEM1),
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := validateKeyStrength(tc.key)
|
||||
if tc.wantErr != (err != nil) {
|
||||
t.Errorf("expected error: %v, got: %v, error: %v",
|
||||
tc.wantErr, err != nil, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func parsePEMKey(t *testing.T, pem string) crypto.Signer {
|
||||
t.Helper()
|
||||
|
||||
k, err := keyutil.ParsePrivateKeyPEM([]byte(pem))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse key fixture: %v", err)
|
||||
}
|
||||
return k.(crypto.Signer)
|
||||
}
|
||||
|
||||
type fakeClientFailureType int
|
||||
|
||||
const (
|
||||
|
||||
Reference in New Issue
Block a user