Skip to content

Commit 481eed1

Browse files
committed
Refactor merge logic and improve secret validation
Add helper functions for security item detection (isSecurityItem, isSecurityAnnotation) Extract filter/extract operations into reusable functions for volumes, annotations, and mounts Improve SPP validation to fail-closed (reject on parse errors instead of allowing) Refactor parseValidSppsFromSecret to reuse ptp4lConf parser for consistency Add detailed comments explaining secret hash change detection mechanism Downgrade controller-gen from v0.19.0 to v0.15.0 for compatibility Add kubebuilder marker to skip auto-generation for Ptp4lConf type Regenerate CRDs and RBAC with ptpSecretName field support
1 parent 8b14b1d commit 481eed1

18 files changed

+614
-523
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ ENVTEST ?= $(LOCALBIN)/setup-envtest
155155

156156
## Tool Versions
157157
KUSTOMIZE_VERSION ?= v4.5.7
158-
CONTROLLER_TOOLS_VERSION ?= v0.19.0
158+
CONTROLLER_TOOLS_VERSION ?= v0.15.0
159159

160160
KUSTOMIZE_INSTALL_SCRIPT ?= "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh"
161161
.PHONY: kustomize

api/v1/ptpconfig_webhook.go

Lines changed: 98 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ type ptp4lConf struct {
7171
sections map[string]ptp4lConfSection
7272
}
7373

74+
// +kubebuilder:object:generate=false
7475
// Ptp4lConf is a public wrapper for ptp4lConf
7576
type Ptp4lConf struct {
7677
conf ptp4lConf
@@ -106,7 +107,7 @@ func (output *ptp4lConf) populatePtp4lConf(config *string, ptp4lopts *string) er
106107
currentLine := strings.Split(line, "]")
107108

108109
if len(currentLine) < 2 {
109-
return errors.New("Section missing closing ']'")
110+
return errors.New("section missing closing ']'")
110111
}
111112

112113
currentSection = fmt.Sprintf("%s]", currentLine[0])
@@ -120,7 +121,7 @@ func (output *ptp4lConf) populatePtp4lConf(config *string, ptp4lopts *string) er
120121
output.sections[currentSection] = section
121122
}
122123
} else {
123-
return errors.New("Config option not in section")
124+
return errors.New("config option not in section")
124125
}
125126
}
126127
_, exist := output.sections["[global]"]
@@ -221,45 +222,57 @@ func (r *PtpConfig) validate() error {
221222
}
222223
}
223224
}
225+
226+
// Validate secret-related settings for this profile
227+
if profile.PtpSecretName != nil && *profile.PtpSecretName != "" {
228+
// Check that secret exists
229+
if err := r.validateSecretExistsForProfile(context.Background(), profile); err != nil {
230+
return err
231+
}
232+
233+
// Check for conflicts with other PtpConfigs
234+
if err := r.validateSecretConflictsForProfile(context.Background(), profile); err != nil {
235+
return err
236+
}
237+
}
224238
}
225239
return nil
226240
}
227241

228-
// validateSecretConflicts checks if this PtpConfig's sa_file + secret combination
242+
// validateSecretConflictsForProfile checks if a single profile's sa_file + secret combination
229243
// conflicts with any existing PtpConfigs in the openshift-ptp namespace
230-
func (r *PtpConfig) validateSecretConflicts(ctx context.Context) error {
244+
func (r *PtpConfig) validateSecretConflictsForProfile(ctx context.Context, profile PtpProfile) error {
231245
if webhookClient == nil {
232246
ptpconfiglog.Info("webhook client not initialized, skipping cross-PtpConfig validation")
233247
return nil
234248
}
235249

236-
// Build map of sa_file -> secret for THIS PtpConfig
237-
currentSaFileToSecret := make(map[string]string)
238-
for _, profile := range r.Spec.Profile {
239-
if profile.PtpSecretName == nil || *profile.PtpSecretName == "" {
240-
continue
241-
}
242-
if profile.Ptp4lConf == nil {
243-
continue
244-
}
245-
246-
conf := &ptp4lConf{}
247-
if err := conf.populatePtp4lConf(profile.Ptp4lConf, profile.Ptp4lOpts); err != nil {
248-
continue
249-
}
250+
// Skip if profile doesn't use secrets
251+
if profile.PtpSecretName == nil || *profile.PtpSecretName == "" {
252+
return nil
253+
}
254+
if profile.Ptp4lConf == nil {
255+
return nil
256+
}
250257

251-
if globalSection, exists := conf.sections["[global]"]; exists {
252-
if saFile, exists := globalSection.options["sa_file"]; exists && saFile != "" {
253-
currentSaFileToSecret[saFile] = *profile.PtpSecretName
254-
}
255-
}
258+
// Get sa_file for THIS profile
259+
conf := &ptp4lConf{}
260+
if err := conf.populatePtp4lConf(profile.Ptp4lConf, profile.Ptp4lOpts); err != nil {
261+
return nil // Skip if can't parse
256262
}
257263

258-
// If this PtpConfig doesn't use any secrets, no conflict possible
259-
if len(currentSaFileToSecret) == 0 {
264+
globalSection, exists := conf.sections["[global]"]
265+
if !exists {
260266
return nil
261267
}
262268

269+
saFile, exists := globalSection.options["sa_file"]
270+
if !exists || saFile == "" {
271+
return nil // No sa_file, no conflict possible
272+
}
273+
274+
currentSecret := *profile.PtpSecretName
275+
263276
// List all existing PtpConfigs in openshift-ptp namespace
264277
ptpConfigList := &PtpConfigList{}
265278
if err := webhookClient.List(ctx, ptpConfigList, &client.ListOptions{
@@ -270,35 +283,35 @@ func (r *PtpConfig) validateSecretConflicts(ctx context.Context) error {
270283
return nil
271284
}
272285

273-
// Check each existing PtpConfig
286+
// Check each existing PtpConfig for conflicts
274287
for _, existingConfig := range ptpConfigList.Items {
275288
// Skip checking against ourselves (for updates)
276289
if existingConfig.Name == r.Name && existingConfig.Namespace == r.Namespace {
277290
continue
278291
}
279292

280293
// Check each profile in the existing config
281-
for _, profile := range existingConfig.Spec.Profile {
282-
if profile.PtpSecretName == nil || *profile.PtpSecretName == "" {
294+
for _, existingProfile := range existingConfig.Spec.Profile {
295+
if existingProfile.PtpSecretName == nil || *existingProfile.PtpSecretName == "" {
283296
continue
284297
}
285-
if profile.Ptp4lConf == nil {
298+
if existingProfile.Ptp4lConf == nil {
286299
continue
287300
}
288301

289-
conf := &ptp4lConf{}
290-
if err := conf.populatePtp4lConf(profile.Ptp4lConf, profile.Ptp4lOpts); err != nil {
302+
existingConf := &ptp4lConf{}
303+
if err := existingConf.populatePtp4lConf(existingProfile.Ptp4lConf, existingProfile.Ptp4lOpts); err != nil {
291304
continue
292305
}
293306

294-
if globalSection, exists := conf.sections["[global]"]; exists {
295-
if saFile, exists := globalSection.options["sa_file"]; exists && saFile != "" {
296-
// Check if THIS PtpConfig uses the same sa_file
297-
if currentSecret, found := currentSaFileToSecret[saFile]; found {
298-
// Conflict: same sa_file but different secret
299-
if currentSecret != *profile.PtpSecretName {
300-
return fmt.Errorf("sa_file '%s' conflict: PtpConfig '%s' already uses secret '%s' with this sa_file path, but this PtpConfig tries to use secret '%s'. All PtpConfigs using the same sa_file must reference the same secret",
301-
saFile, existingConfig.Name, *profile.PtpSecretName, currentSecret)
307+
if globalSection, exists := existingConf.sections["[global]"]; exists {
308+
if existingSaFile, exists := globalSection.options["sa_file"]; exists && existingSaFile != "" {
309+
// Check if same sa_file as our profile
310+
if existingSaFile == saFile {
311+
// Same sa_file - check if different secret
312+
if *existingProfile.PtpSecretName != currentSecret {
313+
return fmt.Errorf("sa_file '%s' conflict: PtpConfig '%s' already uses secret '%s' with this sa_file path, but this profile tries to use secret '%s'. All PtpConfigs using the same sa_file must reference the same secret",
314+
saFile, existingConfig.Name, *existingProfile.PtpSecretName, currentSecret)
302315
}
303316
}
304317
}
@@ -309,55 +322,54 @@ func (r *PtpConfig) validateSecretConflicts(ctx context.Context) error {
309322
return nil
310323
}
311324

312-
// validateSecretExists checks that ptpSecretName references an existing secret
313-
func (r *PtpConfig) validateSecretExists(ctx context.Context) error {
325+
// validateSecretExistsForProfile checks that a single profile's ptpSecretName references an existing secret
326+
func (r *PtpConfig) validateSecretExistsForProfile(ctx context.Context, profile PtpProfile) error {
314327
if webhookClient == nil {
315328
ptpconfiglog.Info("webhook client not initialized, skipping secret existence validation")
316329
return nil
317330
}
318331

319-
for _, profile := range r.Spec.Profile {
320-
// Skip if no secret specified
321-
if profile.PtpSecretName == nil || *profile.PtpSecretName == "" {
322-
continue
323-
}
332+
// Skip if no secret specified
333+
if profile.PtpSecretName == nil || *profile.PtpSecretName == "" {
334+
return nil
335+
}
324336

325-
secretName := *profile.PtpSecretName
326-
profileName := "unknown"
327-
if profile.Name != nil {
328-
profileName = *profile.Name
329-
}
337+
secretName := *profile.PtpSecretName
338+
profileName := "unknown"
339+
if profile.Name != nil {
340+
profileName = *profile.Name
341+
}
330342

331-
// Try to get the secret from openshift-ptp namespace
332-
secret := &corev1.Secret{}
333-
err := webhookClient.Get(ctx, types.NamespacedName{
334-
Namespace: "openshift-ptp",
335-
Name: secretName,
336-
}, secret)
337-
338-
if err != nil {
339-
if apierrors.IsNotFound(err) {
340-
return fmt.Errorf("secret '%s' referenced by profile '%s' does not exist in namespace 'openshift-ptp'. Please create the secret before referencing it in PtpConfig",
341-
secretName, profileName)
342-
}
343-
// For other errors (like permission issues), log but don't block
344-
ptpconfiglog.Error(err, "failed to verify secret existence", "secret", secretName, "profile", profileName)
345-
// Fail open - don't block if we can't verify
346-
return nil
343+
// Try to get the secret from openshift-ptp namespace
344+
secret := &corev1.Secret{}
345+
err := webhookClient.Get(ctx, types.NamespacedName{
346+
Namespace: "openshift-ptp",
347+
Name: secretName,
348+
}, secret)
349+
350+
if err != nil {
351+
if apierrors.IsNotFound(err) {
352+
return fmt.Errorf("secret '%s' referenced by profile '%s' does not exist in namespace 'openshift-ptp'. Please create the secret before referencing it in PtpConfig",
353+
secretName, profileName)
347354
}
355+
// For other errors (like permission issues), log but don't block
356+
ptpconfiglog.Error(err, "failed to verify secret existence", "secret", secretName, "profile", profileName)
357+
// Fail open - don't block if we can't verify
358+
return nil
359+
}
348360

349-
ptpconfiglog.Info("validated secret exists", "secret", secretName, "profile", profileName)
361+
ptpconfiglog.Info("validated secret exists", "secret", secretName, "profile", profileName)
350362

351-
// Validate SPP (Security Parameter Profile) if specified
352-
if err := r.validateSppInSecret(profile, secret); err != nil {
353-
return err
354-
}
363+
// Validate SPP (Security Parameter Profile) if specified
364+
if err := r.validateSppInSecret(profile, secret); err != nil {
365+
return err
355366
}
356367

357368
return nil
358369
}
359370

360371
// validateSppInSecret checks that the spp value in ptp4lConf exists in the referenced secret
372+
// Combines parsing and validation in one pass for efficiency
361373
func (r *PtpConfig) validateSppInSecret(profile PtpProfile, secret *corev1.Secret) error {
362374
// Skip if no ptp4lConf
363375
if profile.Ptp4lConf == nil {
@@ -387,36 +399,15 @@ func (r *PtpConfig) validateSppInSecret(profile PtpProfile, secret *corev1.Secre
387399
profileName = *profile.Name
388400
}
389401

390-
// Parse the secret data to find valid SPP values
391-
validSpps, err := parseValidSppsFromSecret(secret)
392-
if err != nil {
393-
ptpconfiglog.Error(err, "failed to parse SPPs from secret", "secret", secret.Name, "profile", profileName)
394-
// Fail open - don't block if we can't parse
395-
return nil
396-
}
397-
398-
// Check if the specified SPP exists in the secret
399-
if !contains(validSpps, sppValue) {
400-
return fmt.Errorf("spp '%s' in profile '%s' is not defined in secret '%s'. Valid SPPs in secret: %v",
401-
sppValue, profileName, secret.Name, validSpps)
402-
}
403-
404-
ptpconfiglog.Info("validated spp exists in secret", "spp", sppValue, "secret", secret.Name, "profile", profileName)
405-
return nil
406-
}
407-
408-
// parseValidSppsFromSecret extracts all valid SPP numbers from a PTP security secret
409-
// It looks for any line starting with "spp <number>" regardless of structure or sections
410-
func parseValidSppsFromSecret(secret *corev1.Secret) ([]string, error) {
411-
var validSpps []string
402+
// Validate SPP exists in secret by iterating through secret content
403+
// Early return on match for efficiency
412404

413405
// Iterate through all keys in the secret
414406
for key, value := range secret.Data {
415-
// Parse the security configuration
416407
content := string(value)
417408
lines := strings.Split(content, "\n")
418409

419-
// Look for any line starting with "spp <number>"
410+
// Look for lines starting with "spp <number>"
420411
for _, line := range lines {
421412
line = strings.TrimSpace(line)
422413

@@ -425,74 +416,47 @@ func parseValidSppsFromSecret(secret *corev1.Secret) ([]string, error) {
425416
continue
426417
}
427418

428-
// Check if line starts with "spp " (case-insensitive)
419+
// Check if line starts with "spp "
429420
if strings.HasPrefix(strings.ToLower(line), "spp ") {
430421
parts := strings.Fields(line)
431422
if len(parts) >= 2 {
432-
sppNumber := parts[1]
433-
// Add if not already in list
434-
if !contains(validSpps, sppNumber) {
435-
validSpps = append(validSpps, sppNumber)
423+
secretSppValue := parts[1]
424+
425+
// Check if this matches the PtpConfig's spp
426+
if secretSppValue == sppValue {
427+
ptpconfiglog.Info("validated spp match", "spp", sppValue, "secret", secret.Name, "profile", profileName, "key", key)
428+
return nil // Early return - validation passed ✅
436429
}
437430
}
438431
}
439432
}
440-
441-
ptpconfiglog.Info("parsed SPPs from secret", "key", key, "spps", validSpps)
442433
}
443434

444-
return validSpps, nil
445-
}
446-
447-
// contains checks if a string slice contains a specific string
448-
func contains(slice []string, item string) bool {
449-
for _, s := range slice {
450-
if s == item {
451-
return true
452-
}
453-
}
454-
return false
435+
return fmt.Errorf("spp '%s' in profile '%s' is not found in secret '%s' ",
436+
sppValue, profileName, secret.Name)
455437
}
456438

457439
var _ webhook.Validator = &PtpConfig{}
458440

459441
// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
460442
func (r *PtpConfig) ValidateCreate() (admission.Warnings, error) {
461443
ptpconfiglog.Info("validate create", "name", r.Name)
444+
// validate() now includes secret validation for each profile
462445
if err := r.validate(); err != nil {
463446
return admission.Warnings{}, err
464447
}
465448

466-
// Check that referenced secrets exist
467-
if err := r.validateSecretExists(context.Background()); err != nil {
468-
return admission.Warnings{}, err
469-
}
470-
471-
// Check for cross-PtpConfig secret conflicts
472-
if err := r.validateSecretConflicts(context.Background()); err != nil {
473-
return admission.Warnings{}, err
474-
}
475-
476449
return admission.Warnings{}, nil
477450
}
478451

479452
// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
480453
func (r *PtpConfig) ValidateUpdate(old runtime.Object) (admission.Warnings, error) {
481454
ptpconfiglog.Info("validate update", "name", r.Name)
455+
// validate() now includes secret validation for each profile
482456
if err := r.validate(); err != nil {
483457
return admission.Warnings{}, err
484458
}
485459

486-
// Check that referenced secrets exist
487-
if err := r.validateSecretExists(context.Background()); err != nil {
488-
return admission.Warnings{}, err
489-
}
490-
491-
// Check for cross-PtpConfig secret conflicts
492-
if err := r.validateSecretConflicts(context.Background()); err != nil {
493-
return admission.Warnings{}, err
494-
}
495-
496460
return admission.Warnings{}, nil
497461
}
498462

0 commit comments

Comments
 (0)