Skip to content

Commit ed9a2d0

Browse files
Fix: improve operator robustness, performance and code quality (#39)
* fix: prevent nil pointer dereference and silent error handling in overcommit logic - Return safe no-op values (1.0, 1.0) when errors occur instead of continuing with zero values or crashing on nil pointer dereference - Remove unused getNamespaceYAML function and YAML roundtrip - Properly handle all error paths in getNamespaceOvercommit and checkOvercommitType to avoid mutating pods with incorrect values - Use direct client.Get for namespace instead of YAML marshal/unmarshal * fix: add idempotency guard to prevent double overcommit mutation - Add annotation 'overcommit.inditex.dev/applied' to track if a pod has already been mutated by the webhook - Skip mutation on reinvocation (reinvocationPolicy=IfNeeded) if pod was already processed by the same overcommit class - Store applied CPU/memory ratios in annotations for observability - Resize operations always re-apply since limits may have changed * perf: remove unnecessary 10s periodic requeue from controllers - Remove RequeueAfter=10s from both Overcommit and OvercommitClass reconcilers to reduce unnecessary API server load - Rely on event-driven reconciliation (watches) which is the standard controller-runtime pattern for reacting to resource changes * refactor: propagate context instead of using context.Background/TODO - Accept context parameter in Overcommit and OvercommitOnResize functions, propagated from the webhook admission handler - Accept context in GetDefaultSpec and GetPodServiceAccount instead of creating context.Background()/context.TODO() internally - Enables proper cancellation, timeouts, and distributed tracing through the full call chain * refactor: remove redundant delegate methods from OvercommitClass - Remove ~30 getter/setter methods that simply forwarded to the embedded metav1.ObjectMeta, which already implements the v1.Object interface via Go struct embedding - Remove unused 'k8s.io/apimachinery/pkg/types' import - Reduces ~140 lines of dead code improving maintainability * fix: add ReDoS protection for excludedNamespaces regex validation - Limit regex length to 512 characters to mitigate catastrophic backtracking (ReDoS) attacks via overly complex patterns - Improve error message to include the actual regexp compilation error for better debugging * fix: reduce TLS certificate duration from 10 years to 1 year - Change certificate duration from 87600h (10y) to 8760h (1y) - Shorter-lived certificates reduce the attack surface if a private key is compromised, following security best practices - cert-manager will still auto-renew 30 days before expiry * refactor: pass context to Overcommit function in tests * chore: run go mod tidy * docs: translate Spanish comments to English * fix: correct certificate duration from 10 years to 1 year in generateResources tests
1 parent b7d12ec commit ed9a2d0

15 files changed

Lines changed: 89 additions & 226 deletions

api/v1alphav1/overcommitclass_types.go

Lines changed: 0 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ package v1alphav1
88
import (
99
corev1 "k8s.io/api/core/v1"
1010
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11-
"k8s.io/apimachinery/pkg/types"
1211
)
1312

1413
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
@@ -68,146 +67,6 @@ type OvercommitClass struct {
6867
Status OvercommitClassStatus `json:"status,omitempty"`
6968
}
7069

71-
// GetAnnotations implements v1.Object.
72-
func (in *OvercommitClass) GetAnnotations() map[string]string {
73-
return in.ObjectMeta.Annotations
74-
}
75-
76-
// GetCreationTimestamp implements v1.Object.
77-
func (in *OvercommitClass) GetCreationTimestamp() metav1.Time {
78-
return in.ObjectMeta.CreationTimestamp
79-
}
80-
81-
// GetDeletionGracePeriodSeconds implements v1.Object.
82-
func (in *OvercommitClass) GetDeletionGracePeriodSeconds() *int64 {
83-
return in.ObjectMeta.DeletionGracePeriodSeconds
84-
}
85-
86-
// GetDeletionTimestamp implements v1.Object.
87-
func (in *OvercommitClass) GetDeletionTimestamp() *metav1.Time {
88-
return in.ObjectMeta.DeletionTimestamp
89-
}
90-
91-
// GetFinalizers implements v1.Object.
92-
func (in *OvercommitClass) GetFinalizers() []string {
93-
return in.ObjectMeta.Finalizers
94-
}
95-
96-
// GetGenerateName implements v1.Object.
97-
func (in *OvercommitClass) GetGenerateName() string {
98-
return in.ObjectMeta.GenerateName
99-
}
100-
101-
// GetGeneration implements v1.Object.
102-
func (in *OvercommitClass) GetGeneration() int64 {
103-
return in.ObjectMeta.Generation
104-
}
105-
106-
// GetLabels implements v1.Object.
107-
func (in *OvercommitClass) GetLabels() map[string]string {
108-
return in.ObjectMeta.Labels
109-
}
110-
111-
// GetManagedFields implements v1.Object.
112-
func (in *OvercommitClass) GetManagedFields() []metav1.ManagedFieldsEntry {
113-
return in.ObjectMeta.ManagedFields
114-
}
115-
116-
// GetName implements v1.Object.
117-
func (in *OvercommitClass) GetName() string {
118-
return in.ObjectMeta.Name
119-
}
120-
121-
// GetNamespace implements v1.Object.
122-
func (in *OvercommitClass) GetNamespace() string {
123-
return in.ObjectMeta.Namespace
124-
}
125-
126-
// GetOwnerReferences implements v1.Object.
127-
func (in *OvercommitClass) GetOwnerReferences() []metav1.OwnerReference {
128-
return in.ObjectMeta.OwnerReferences
129-
}
130-
131-
// GetResourceVersion implements v1.Object.
132-
func (in *OvercommitClass) GetResourceVersion() string {
133-
return in.ObjectMeta.ResourceVersion
134-
}
135-
136-
// GetUID implements v1.Object.
137-
func (in *OvercommitClass) GetUID() types.UID {
138-
return in.ObjectMeta.UID
139-
}
140-
141-
// SetAnnotations implements v1.Object.
142-
func (in *OvercommitClass) SetAnnotations(annotations map[string]string) {
143-
in.ObjectMeta.Annotations = annotations
144-
}
145-
146-
// SetCreationTimestamp implements v1.Object.
147-
func (in *OvercommitClass) SetCreationTimestamp(timestamp metav1.Time) {
148-
in.ObjectMeta.CreationTimestamp = timestamp
149-
}
150-
151-
// SetDeletionGracePeriodSeconds implements v1.Object.
152-
func (in *OvercommitClass) SetDeletionGracePeriodSeconds(seconds *int64) {
153-
in.ObjectMeta.DeletionGracePeriodSeconds = seconds
154-
}
155-
156-
// SetDeletionTimestamp implements v1.Object.
157-
func (in *OvercommitClass) SetDeletionTimestamp(timestamp *metav1.Time) {
158-
in.ObjectMeta.DeletionTimestamp = timestamp
159-
}
160-
161-
// SetFinalizers implements v1.Object.
162-
func (in *OvercommitClass) SetFinalizers(finalizers []string) {
163-
in.ObjectMeta.Finalizers = finalizers
164-
}
165-
166-
// SetGenerateName implements v1.Object.
167-
func (in *OvercommitClass) SetGenerateName(name string) {
168-
in.ObjectMeta.GenerateName = name
169-
}
170-
171-
// SetGeneration implements v1.Object.
172-
func (in *OvercommitClass) SetGeneration(generation int64) {
173-
in.ObjectMeta.Generation = generation
174-
}
175-
176-
// SetLabels implements v1.Object.
177-
func (in *OvercommitClass) SetLabels(labels map[string]string) {
178-
in.ObjectMeta.Labels = labels
179-
}
180-
181-
// SetManagedFields implements v1.Object.
182-
func (in *OvercommitClass) SetManagedFields(managedFields []metav1.ManagedFieldsEntry) {
183-
in.ObjectMeta.ManagedFields = managedFields
184-
}
185-
186-
// SetName implements v1.Object.
187-
func (in *OvercommitClass) SetName(name string) {
188-
in.ObjectMeta.Name = name
189-
}
190-
191-
// SetNamespace implements v1.Object.
192-
func (in *OvercommitClass) SetNamespace(namespace string) {
193-
in.ObjectMeta.Namespace = namespace
194-
}
195-
196-
// SetOwnerReferences implements v1.Object.
197-
func (in *OvercommitClass) SetOwnerReferences(references []metav1.OwnerReference) {
198-
in.ObjectMeta.OwnerReferences = references
199-
}
200-
201-
// SetResourceVersion implements v1.Object.
202-
func (in *OvercommitClass) SetResourceVersion(version string) {
203-
in.ObjectMeta.ResourceVersion = version
204-
}
205-
206-
// SetUID implements v1.Object.
207-
func (in *OvercommitClass) SetUID(uid types.UID) {
208-
in.ObjectMeta.UID = uid
209-
}
210-
21170
// +kubebuilder:object:root=true
21271

21372
// OvercommitClassList contains a list of OvercommitClass

api/v1alphav1/webhook_validations.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,14 @@ func isClassDefault(class OvercommitClass, client client.Client) error {
7070
}
7171

7272
func checkIsRegexValid(regex string) error {
73+
// Limit regex length to prevent ReDoS (catastrophic backtracking)
74+
const maxRegexLen = 512
75+
if len(regex) > maxRegexLen {
76+
return fmt.Errorf("regex is too long (%d chars), maximum allowed is %d", len(regex), maxRegexLen)
77+
}
7378
_, err := regexp.Compile(regex)
7479
if err != nil {
75-
return errors.New("Error: the regex is not valid")
80+
return fmt.Errorf("invalid regex for excludedNamespaces: %w", err)
7681
}
7782
return nil
7883
}

cmd/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ func main() {
150150
os.Exit(1)
151151
}
152152

153-
serviceAccountName, err := utils.GetPodServiceAccount(mgr.GetAPIReader())
153+
serviceAccountName, err := utils.GetPodServiceAccount(ctx, mgr.GetAPIReader())
154154
if err != nil {
155155
setupLog.Error(err, "unable to get pod service account")
156156
os.Exit(1)

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ require (
1212
k8s.io/apimachinery v0.32.1
1313
k8s.io/client-go v0.32.1
1414
sigs.k8s.io/controller-runtime v0.19.0
15-
sigs.k8s.io/yaml v1.4.0
1615
)
1716

1817
require (
@@ -101,4 +100,5 @@ require (
101100
sigs.k8s.io/gateway-api v1.1.0 // indirect
102101
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
103102
sigs.k8s.io/structured-merge-diff/v4 v4.5.0 // indirect
103+
sigs.k8s.io/yaml v1.4.0 // indirect
104104
)

internal/controller/overcommit/overcommit_controller.go

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -390,11 +390,8 @@ func (r *OvercommitReconciler) Reconcile(ctx context.Context, req ctrl.Request)
390390
// Don't fail the reconciliation for status update errors
391391
}
392392

393-
// Only requeue periodically for status checks, not immediately
394-
logger.Info("Reconciliation completed successfully", "nextReconcile", "10 seconds", "time", time.Now().Format("15:04:05"))
395-
return ctrl.Result{
396-
RequeueAfter: time.Second * 10,
397-
}, nil
393+
logger.Info("Reconciliation completed successfully", "time", time.Now().Format("15:04:05"))
394+
return ctrl.Result{}, nil
398395
}
399396

400397
// +kubebuilder:rbac:groups=apps, resources=deployments;replicasets,verbs=get;list;watch;create;update;patch;delete

internal/controller/overcommitclass/overcommitclass_controller.go

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -393,9 +393,6 @@ func (r *OvercommitClassReconciler) Reconcile(ctx context.Context, req ctrl.Requ
393393
return ctrl.Result{}, err
394394
}
395395

396-
// Only requeue periodically for status checks, not immediately
397-
logger.Info("Reconciliation completed successfully", "nextReconcile", "10 seconds", "time", time.Now().Format("15:04:05"))
398-
return ctrl.Result{
399-
RequeueAfter: 10 * time.Second,
400-
}, nil
396+
logger.Info("Reconciliation completed successfully", "time", time.Now().Format("15:04:05"))
397+
return ctrl.Result{}, nil
401398
}

internal/resources/generate_resources_pod_mutating_webhooks.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,10 +147,10 @@ func CreateCertificate(name string, svc corev1.Service) *certmanager.Certificate
147147
Spec: certmanager.CertificateSpec{
148148
SecretName: name + "-webhook-secret",
149149
Duration: &metav1.Duration{
150-
Duration: 87600 * time.Hour,
150+
Duration: 8760 * time.Hour, // 1 year
151151
},
152152
RenewBefore: &metav1.Duration{
153-
Duration: 720 * time.Hour,
153+
Duration: 720 * time.Hour, // 30 days
154154
},
155155
DNSNames: []string{
156156
svc.Name + "." + svc.Namespace + ".svc",

internal/resources/generate_resources_pod_mutating_webhooks_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ func TestCreateCertificate(t *testing.T) {
123123
t.Errorf("Expected secret name 'test-class-webhook-secret', got '%s'", certificate.Spec.SecretName)
124124
}
125125

126-
expectedDuration := 87600 * time.Hour
126+
expectedDuration := 8760 * time.Hour
127127
if certificate.Spec.Duration.Duration != expectedDuration {
128128
t.Errorf("Expected duration '%v', got '%v'", expectedDuration, certificate.Spec.Duration.Duration)
129129
}

internal/utils/getOvercommitClass.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,14 @@ func GetOvercommitClassSpec(ctx context.Context, name string, k8sClient client.C
4545
return &overcommitClass.Spec, nil
4646
}
4747

48-
func GetDefaultSpec(k8sClient client.Client) (*overcommit.OvercommitClassSpec, error) {
48+
func GetDefaultSpec(ctx context.Context, k8sClient client.Client) (*overcommit.OvercommitClassSpec, error) {
4949
if k8sClient == nil {
5050
return nil, errors.New("client parameter cannot be nil")
5151
}
5252

5353
// List all OvercommitClass
5454
var overcommitClasses overcommit.OvercommitClassList
55-
if err := k8sClient.List(context.Background(), &overcommitClasses); err != nil {
55+
if err := k8sClient.List(ctx, &overcommitClasses); err != nil {
5656
return nil, fmt.Errorf("error listing OvercommitClass: %w", err)
5757
}
5858

internal/utils/getOvercommitClass_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ var _ = Describe("GetDefaultSpec", func() {
8282

8383
It("should retrieve the default OvercommitClassSpec correctly", func() {
8484
// Try the GetDefaultSpec function
85-
spec, err := GetDefaultSpec(k8sClient)
85+
spec, err := GetDefaultSpec(context.Background(), k8sClient)
8686
Expect(err).NotTo(HaveOccurred(), "Failed to get default OvercommitClassSpec")
8787
Expect(spec).NotTo(BeNil(), "Spec should not be nil")
8888
Expect(spec.IsDefault).To(BeTrue(), "Spec.IsDefault should be true")

0 commit comments

Comments
 (0)