• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kubevirt / hyperconverged-cluster-operator / 20689747098

04 Jan 2026 07:49AM UTC coverage: 72.256% (-0.2%) from 72.483%
20689747098

push

github

web-flow
[release 1.14] Add ValidatingAdmissionPolicy to validate the HyperConverged namespace (#3940)

* Add the new admission policy controller

The current implementation of preventing the creation of the
HyperConverged CR in non-allowed namespace, is not working in Openshift,
where becasue of a race condition, the webhook's namespace selector is
removed by OLM.

This commit adds a new controller, to create and reconcile a
ValidatingAdmissionPolicy and the related
ValidatingAdmissionPolicyBinding, to perform the same validation.

The reason we're doing it in a new controller, is because we need the
ValidatingAdmissionPolicy to be set, even if the HyperConverged CR is
not deployed, while our main controller only reconciles resources if
the HyperConverged CR is deployed.



* Register the admission policy controller on boot



* Remove the current validation

Remove the existing validation of the HyperConverged CR namespace from
the validation webhook, as it is now done by the policy, created by the
admission policy controller.



* Don't remove the namespace selector from the validation wh

OLM adds a namespace selection on the validation webhook CR, causing the
namespace validation to be not relevant.

The webhook setup logic removes this selector, but actually this is
reconciled by OLM, and eventually, user can still create the
HyperConverged CR in any namespace.

The issue is now handled by a ValidationgAdmissionPolicy, and so we
don't need this logic anymore, and so this commit removes it.



---------

Signed-off-by: Nahshon Unna Tsameret <nahsh.ut@gmail.com>

138 of 215 new or added lines in 3 files covered. (64.19%)

6 existing lines in 2 files now uncovered.

6675 of 9238 relevant lines covered (72.26%)

0.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.86
/pkg/webhooks/validator/validator.go
1
package validator
2

3
import (
4
        "context"
5
        "errors"
6
        "fmt"
7
        "net/http"
8
        "reflect"
9
        "strings"
10
        "time"
11

12
        "github.com/go-logr/logr"
13
        openshiftconfigv1 "github.com/openshift/api/config/v1"
14
        "github.com/samber/lo"
15
        xsync "golang.org/x/sync/errgroup"
16
        admissionv1 "k8s.io/api/admission/v1"
17
        apierrors "k8s.io/apimachinery/pkg/api/errors"
18
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
19
        "k8s.io/utils/strings/slices"
20
        "sigs.k8s.io/controller-runtime/pkg/client"
21
        "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
22

23
        networkaddonsv1 "github.com/kubevirt/cluster-network-addons-operator/pkg/apis/networkaddonsoperator/v1"
24
        kubevirtcorev1 "kubevirt.io/api/core/v1"
25
        cdiv1beta1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1"
26
        sspv1beta2 "kubevirt.io/ssp-operator/api/v1beta2"
27

28
        "github.com/kubevirt/hyperconverged-cluster-operator/api/v1beta1"
29
        "github.com/kubevirt/hyperconverged-cluster-operator/controllers/operands"
30
        hcoutil "github.com/kubevirt/hyperconverged-cluster-operator/pkg/util"
31
)
32

33
const (
34
        updateDryRunTimeOut = time.Second * 3
35
)
36

37
type ValidationWarning struct {
38
        warnings []string
39
}
40

41
func newValidationWarning(warnings []string) *ValidationWarning {
1✔
42
        return &ValidationWarning{
1✔
43
                warnings: warnings,
1✔
44
        }
1✔
45
}
1✔
46

47
func (v *ValidationWarning) Error() string {
×
48
        return ""
×
49
}
×
50

51
func (v *ValidationWarning) Warnings() []string {
×
52
        return v.warnings
×
53
}
×
54

55
type WebhookHandler struct {
56
        logger      logr.Logger
57
        cli         client.Client
58
        namespace   string
59
        isOpenshift bool
60
        decoder     admission.Decoder
61
}
62

63
var hcoTLSConfigCache *openshiftconfigv1.TLSSecurityProfile
64

65
func NewWebhookHandler(logger logr.Logger, cli client.Client, decoder admission.Decoder, namespace string, isOpenshift bool, hcoTLSSecurityProfile *openshiftconfigv1.TLSSecurityProfile) *WebhookHandler {
1✔
66
        hcoTLSConfigCache = hcoTLSSecurityProfile
1✔
67
        return &WebhookHandler{
1✔
68
                logger:      logger,
1✔
69
                cli:         cli,
1✔
70
                namespace:   namespace,
1✔
71
                isOpenshift: isOpenshift,
1✔
72
                decoder:     decoder,
1✔
73
        }
1✔
74
}
1✔
75

76
func (wh *WebhookHandler) Handle(ctx context.Context, req admission.Request) admission.Response {
1✔
77

1✔
78
        ctx = admission.NewContextWithRequest(ctx, req)
1✔
79

1✔
80
        // Get the object in the request
1✔
81
        obj := &v1beta1.HyperConverged{}
1✔
82

1✔
83
        dryRun := req.DryRun != nil && *req.DryRun
1✔
84

1✔
85
        var err error
1✔
86
        switch req.Operation {
1✔
87
        case admissionv1.Create:
1✔
88
                if err := wh.decoder.Decode(req, obj); err != nil {
2✔
89
                        return admission.Errored(http.StatusBadRequest, err)
1✔
90
                }
1✔
91

92
                err = wh.ValidateCreate(ctx, dryRun, obj)
1✔
93
        case admissionv1.Update:
1✔
94
                oldObj := &v1beta1.HyperConverged{}
1✔
95
                if err := wh.decoder.DecodeRaw(req.Object, obj); err != nil {
2✔
96
                        return admission.Errored(http.StatusBadRequest, err)
1✔
97
                }
1✔
98
                if err := wh.decoder.DecodeRaw(req.OldObject, oldObj); err != nil {
2✔
99
                        return admission.Errored(http.StatusBadRequest, err)
1✔
100
                }
1✔
101

102
                err = wh.ValidateUpdate(ctx, dryRun, obj, oldObj)
1✔
103
        case admissionv1.Delete:
1✔
104
                // In reference to PR: https://github.com/kubernetes/kubernetes/pull/76346
1✔
105
                // OldObject contains the object being deleted
1✔
106
                if err := wh.decoder.DecodeRaw(req.OldObject, obj); err != nil {
2✔
107
                        return admission.Errored(http.StatusBadRequest, err)
1✔
108
                }
1✔
109

110
                err = wh.ValidateDelete(ctx, dryRun, obj)
1✔
111
        default:
1✔
112
                return admission.Errored(http.StatusBadRequest, fmt.Errorf("unknown operation request %q", req.Operation))
1✔
113
        }
114

115
        // Check the error message first.
116
        if err != nil {
1✔
UNCOV
117
                var apiStatus apierrors.APIStatus
×
UNCOV
118
                if errors.As(err, &apiStatus) {
×
119
                        return validationResponseFromStatus(false, apiStatus.Status())
×
120
                }
×
121

UNCOV
122
                var vw *ValidationWarning
×
UNCOV
123
                if errors.As(err, &vw) {
×
124
                        return admission.Allowed("").WithWarnings(vw.Warnings()...)
×
125
                }
×
126

UNCOV
127
                return admission.Denied(err.Error())
×
128
        }
129

130
        // Return allowed if everything succeeded.
131
        return admission.Allowed("")
1✔
132
}
133

134
func (wh *WebhookHandler) ValidateCreate(_ context.Context, dryrun bool, hc *v1beta1.HyperConverged) error {
1✔
135
        wh.logger.Info("Validating create", "name", hc.Name, "namespace:", hc.Namespace)
1✔
136

1✔
137
        if err := wh.validateCertConfig(hc); err != nil {
1✔
138
                return err
×
139
        }
×
140

141
        if err := wh.validateDataImportCronTemplates(hc); err != nil {
2✔
142
                return err
1✔
143
        }
1✔
144

145
        if err := wh.validateTLSSecurityProfiles(hc); err != nil {
2✔
146
                return err
1✔
147
        }
1✔
148

149
        if err := wh.validateMediatedDeviceTypes(hc); err != nil {
2✔
150
                return err
1✔
151
        }
1✔
152

153
        if err := wh.validateFeatureGates(hc); err != nil {
2✔
154
                return err
1✔
155
        }
1✔
156

157
        if _, err := operands.NewKubeVirt(hc); err != nil {
2✔
158
                return err
1✔
159
        }
1✔
160

161
        if _, err := operands.NewCDI(hc); err != nil {
2✔
162
                return err
1✔
163
        }
1✔
164

165
        if _, err := operands.NewNetworkAddons(hc); err != nil {
2✔
166
                return err
1✔
167
        }
1✔
168

169
        if _, _, err := operands.NewSSP(hc); err != nil {
2✔
170
                return err
1✔
171
        }
1✔
172

173
        if !dryrun {
2✔
174
                hcoTLSConfigCache = hc.Spec.TLSSecurityProfile
1✔
175
        }
1✔
176

177
        return nil
1✔
178
}
179

180
func (wh *WebhookHandler) getOperands(requested *v1beta1.HyperConverged) (*kubevirtcorev1.KubeVirt, *cdiv1beta1.CDI, *networkaddonsv1.NetworkAddonsConfig, error) {
1✔
181
        if err := wh.validateCertConfig(requested); err != nil {
2✔
182
                return nil, nil, nil, err
1✔
183
        }
1✔
184

185
        kv, err := operands.NewKubeVirt(requested)
1✔
186
        if err != nil {
2✔
187
                return nil, nil, nil, err
1✔
188
        }
1✔
189

190
        cdi, err := operands.NewCDI(requested)
1✔
191
        if err != nil {
2✔
192
                return nil, nil, nil, err
1✔
193
        }
1✔
194

195
        cna, err := operands.NewNetworkAddons(requested)
1✔
196
        if err != nil {
2✔
197
                return nil, nil, nil, err
1✔
198
        }
1✔
199

200
        return kv, cdi, cna, nil
1✔
201
}
202

203
// ValidateUpdate is the ValidateUpdate webhook implementation. It calls all the resources in parallel, to dry-run the
204
// upgrade.
205
func (wh *WebhookHandler) ValidateUpdate(ctx context.Context, dryrun bool, requested *v1beta1.HyperConverged, exists *v1beta1.HyperConverged) error {
1✔
206
        wh.logger.Info("Validating update", "name", requested.Name)
1✔
207

1✔
208
        if err := wh.validateDataImportCronTemplates(requested); err != nil {
1✔
209
                return err
×
210
        }
×
211

212
        if err := wh.validateTLSSecurityProfiles(requested); err != nil {
2✔
213
                return err
1✔
214
        }
1✔
215

216
        if err := wh.validateMediatedDeviceTypes(requested); err != nil {
2✔
217
                return err
1✔
218
        }
1✔
219

220
        if err := wh.validateFeatureGates(requested); err != nil {
2✔
221
                return err
1✔
222
        }
1✔
223

224
        // If no change is detected in the spec nor the annotations - nothing to validate
225
        if reflect.DeepEqual(exists.Spec, requested.Spec) &&
1✔
226
                reflect.DeepEqual(exists.Annotations, requested.Annotations) {
2✔
227
                return nil
1✔
228
        }
1✔
229

230
        kv, cdi, cna, err := wh.getOperands(requested)
1✔
231
        if err != nil {
2✔
232
                return err
1✔
233
        }
1✔
234

235
        toCtx, cancel := context.WithTimeout(ctx, updateDryRunTimeOut)
1✔
236
        defer cancel()
1✔
237

1✔
238
        eg, egCtx := xsync.WithContext(toCtx)
1✔
239
        opts := &client.UpdateOptions{DryRun: []string{metav1.DryRunAll}}
1✔
240

1✔
241
        resources := []client.Object{
1✔
242
                kv,
1✔
243
                cdi,
1✔
244
                cna,
1✔
245
        }
1✔
246

1✔
247
        if wh.isOpenshift {
2✔
248
                ssp, _, err := operands.NewSSP(requested)
1✔
249
                if err != nil {
2✔
250
                        return err
1✔
251
                }
1✔
252
                resources = append(resources, ssp)
1✔
253
        }
254

255
        for _, obj := range resources {
2✔
256
                func(o client.Object) {
2✔
257
                        eg.Go(func() error {
2✔
258
                                return wh.updateOperatorCr(egCtx, requested, o, opts)
1✔
259
                        })
1✔
260
                }(obj)
261
        }
262

263
        err = eg.Wait()
1✔
264
        if err != nil {
2✔
265
                return err
1✔
266
        }
1✔
267

268
        if !dryrun {
2✔
269
                hcoTLSConfigCache = requested.Spec.TLSSecurityProfile
1✔
270
        }
1✔
271

272
        return nil
1✔
273
}
274

275
func (wh *WebhookHandler) updateOperatorCr(ctx context.Context, hc *v1beta1.HyperConverged, exists client.Object, opts *client.UpdateOptions) error {
1✔
276
        err := hcoutil.GetRuntimeObject(ctx, wh.cli, exists)
1✔
277
        if err != nil {
2✔
278
                wh.logger.Error(err, "failed to get object from kubernetes", "kind", exists.GetObjectKind())
1✔
279
                return err
1✔
280
        }
1✔
281

282
        switch existing := exists.(type) {
1✔
283
        case *kubevirtcorev1.KubeVirt:
1✔
284
                required, err := operands.NewKubeVirt(hc)
1✔
285
                if err != nil {
1✔
286
                        return err
×
287
                }
×
288
                required.Spec.DeepCopyInto(&existing.Spec)
1✔
289

290
        case *cdiv1beta1.CDI:
1✔
291
                required, err := operands.NewCDI(hc)
1✔
292
                if err != nil {
1✔
293
                        return err
×
294
                }
×
295
                required.Spec.DeepCopyInto(&existing.Spec)
1✔
296

297
        case *networkaddonsv1.NetworkAddonsConfig:
1✔
298
                required, err := operands.NewNetworkAddons(hc)
1✔
299
                if err != nil {
1✔
300
                        return err
×
301
                }
×
302
                required.Spec.DeepCopyInto(&existing.Spec)
1✔
303

304
        case *sspv1beta2.SSP:
1✔
305
                required, _, err := operands.NewSSP(hc)
1✔
306
                if err != nil {
1✔
307
                        return err
×
308
                }
×
309
                required.Spec.DeepCopyInto(&existing.Spec)
1✔
310
        }
311

312
        if err = wh.cli.Update(ctx, exists, opts); err != nil {
2✔
313
                wh.logger.Error(err, "failed to dry-run update the object", "kind", exists.GetObjectKind())
1✔
314
                return err
1✔
315
        }
1✔
316

317
        wh.logger.Info("dry-run update the object passed", "kind", exists.GetObjectKind())
1✔
318
        return nil
1✔
319
}
320

321
func (wh *WebhookHandler) ValidateDelete(ctx context.Context, dryrun bool, hc *v1beta1.HyperConverged) error {
1✔
322
        wh.logger.Info("Validating delete", "name", hc.Name, "namespace", hc.Namespace)
1✔
323

1✔
324
        kv := operands.NewKubeVirtWithNameOnly(hc)
1✔
325
        cdi := operands.NewCDIWithNameOnly(hc)
1✔
326

1✔
327
        for _, obj := range []client.Object{
1✔
328
                kv,
1✔
329
                cdi,
1✔
330
        } {
2✔
331
                _, err := hcoutil.EnsureDeleted(ctx, wh.cli, obj, hc.Name, wh.logger, true, false, true)
1✔
332
                if err != nil {
2✔
333
                        wh.logger.Error(err, "Delete validation failed", "GVK", obj.GetObjectKind().GroupVersionKind())
1✔
334
                        return err
1✔
335
                }
1✔
336
        }
337
        if !dryrun {
2✔
338
                hcoTLSConfigCache = nil
1✔
339
        }
1✔
340
        return nil
1✔
341
}
342

343
func (wh *WebhookHandler) validateCertConfig(hc *v1beta1.HyperConverged) error {
1✔
344
        minimalDuration := metav1.Duration{Duration: 10 * time.Minute}
1✔
345

1✔
346
        ccValues := make(map[string]time.Duration)
1✔
347
        ccValues["spec.certConfig.ca.duration"] = hc.Spec.CertConfig.CA.Duration.Duration
1✔
348
        ccValues["spec.certConfig.ca.renewBefore"] = hc.Spec.CertConfig.CA.RenewBefore.Duration
1✔
349
        ccValues["spec.certConfig.server.duration"] = hc.Spec.CertConfig.Server.Duration.Duration
1✔
350
        ccValues["spec.certConfig.server.renewBefore"] = hc.Spec.CertConfig.Server.RenewBefore.Duration
1✔
351

1✔
352
        for key, value := range ccValues {
2✔
353
                if value < minimalDuration.Duration {
2✔
354
                        return fmt.Errorf("%v: value is too small", key)
1✔
355
                }
1✔
356
        }
357

358
        if hc.Spec.CertConfig.CA.Duration.Duration < hc.Spec.CertConfig.CA.RenewBefore.Duration {
2✔
359
                return errors.New("spec.certConfig.ca: duration is smaller than renewBefore")
1✔
360
        }
1✔
361

362
        if hc.Spec.CertConfig.Server.Duration.Duration < hc.Spec.CertConfig.Server.RenewBefore.Duration {
2✔
363
                return errors.New("spec.certConfig.server: duration is smaller than renewBefore")
1✔
364
        }
1✔
365

366
        if hc.Spec.CertConfig.CA.Duration.Duration < hc.Spec.CertConfig.Server.Duration.Duration {
2✔
367
                return errors.New("spec.certConfig: ca.duration is smaller than server.duration")
1✔
368
        }
1✔
369

370
        return nil
1✔
371
}
372

373
func (wh *WebhookHandler) validateDataImportCronTemplates(hc *v1beta1.HyperConverged) error {
1✔
374

1✔
375
        for _, dict := range hc.Spec.DataImportCronTemplates {
2✔
376
                val, ok := dict.Annotations[hcoutil.DataImportCronEnabledAnnotation]
1✔
377
                val = strings.ToLower(val)
1✔
378
                if ok && !(val == "false" || val == "true") {
2✔
379
                        return fmt.Errorf(`the %s annotation of a dataImportCronTemplate must be either "true" or "false"`, hcoutil.DataImportCronEnabledAnnotation)
1✔
380
                }
1✔
381

382
                enabled := !ok || val == "true"
1✔
383

1✔
384
                if enabled && dict.Spec == nil {
2✔
385
                        return fmt.Errorf("dataImportCronTemplate spec is empty for an enabled DataImportCronTemplate")
1✔
386
                }
1✔
387
        }
388

389
        return nil
1✔
390
}
391

392
func (wh *WebhookHandler) validateTLSSecurityProfiles(hc *v1beta1.HyperConverged) error {
1✔
393
        tlsSP := hc.Spec.TLSSecurityProfile
1✔
394

1✔
395
        if tlsSP == nil || tlsSP.Custom == nil {
2✔
396
                return nil
1✔
397
        }
1✔
398

399
        if !isValidTLSProtocolVersion(tlsSP.Custom.MinTLSVersion) {
2✔
400
                return fmt.Errorf("invalid value for spec.tlsSecurityProfile.custom.minTLSVersion")
1✔
401
        }
1✔
402

403
        if tlsSP.Custom.MinTLSVersion < openshiftconfigv1.VersionTLS13 && !hasRequiredHTTP2Ciphers(tlsSP.Custom.Ciphers) {
2✔
404
                return fmt.Errorf("http2: TLSConfig.CipherSuites is missing an HTTP/2-required AES_128_GCM_SHA256 cipher (need at least one of ECDHE-RSA-AES128-GCM-SHA256 or ECDHE-ECDSA-AES128-GCM-SHA256)")
1✔
405
        } else if tlsSP.Custom.MinTLSVersion == openshiftconfigv1.VersionTLS13 && len(tlsSP.Custom.Ciphers) > 0 {
3✔
406
                return fmt.Errorf("custom ciphers cannot be selected when minTLSVersion is VersionTLS13")
1✔
407
        }
1✔
408

409
        return nil
1✔
410
}
411

412
func (wh *WebhookHandler) validateMediatedDeviceTypes(hc *v1beta1.HyperConverged) error {
1✔
413
        mdc := hc.Spec.MediatedDevicesConfiguration
1✔
414
        if mdc != nil {
2✔
415
                if len(mdc.MediatedDevicesTypes) > 0 && len(mdc.MediatedDeviceTypes) > 0 && !slices.Equal(mdc.MediatedDevicesTypes, mdc.MediatedDeviceTypes) { //nolint SA1019
2✔
416
                        return fmt.Errorf("mediatedDevicesTypes is deprecated, please use mediatedDeviceTypes instead")
1✔
417
                }
1✔
418
                for _, nmdc := range mdc.NodeMediatedDeviceTypes {
2✔
419
                        if len(nmdc.MediatedDevicesTypes) > 0 && len(nmdc.MediatedDeviceTypes) > 0 && !slices.Equal(nmdc.MediatedDevicesTypes, nmdc.MediatedDeviceTypes) { //nolint SA1019
2✔
420
                                return fmt.Errorf("mediatedDevicesTypes is deprecated, please use mediatedDeviceTypes instead")
1✔
421
                        }
1✔
422
                }
423
        }
424
        return nil
1✔
425
}
426

427
const (
428
        fgMovedWarning       = "spec.featureGates.%[1]s is deprecated and ignored. It will removed in a future version; use spec.%[1]s instead"
429
        fgDeprecationWarning = "spec.featureGates.%s is deprecated and ignored. It will be removed in a future version;"
430
)
431

432
func (wh *WebhookHandler) validateFeatureGates(hc *v1beta1.HyperConverged) error {
1✔
433
        var warnings []string
1✔
434

1✔
435
        //nolint:staticcheck
1✔
436
        if hc.Spec.FeatureGates.WithHostPassthroughCPU != nil {
2✔
437
                warnings = append(warnings, fmt.Sprintf(fgDeprecationWarning, "withHostPassthroughCPU"))
1✔
438
        }
1✔
439

440
        //nolint:staticcheck
441
        if hc.Spec.FeatureGates.DeployTektonTaskResources != nil {
2✔
442
                warnings = append(warnings, fmt.Sprintf(fgDeprecationWarning, "deployTektonTaskResources"))
1✔
443
        }
1✔
444

445
        //nolint:staticcheck
446
        if hc.Spec.FeatureGates.NonRoot != nil {
2✔
447
                warnings = append(warnings, fmt.Sprintf(fgDeprecationWarning, "nonRoot"))
1✔
448
        }
1✔
449

450
        //nolint:staticcheck
451
        if hc.Spec.FeatureGates.EnableManagedTenantQuota != nil {
2✔
452
                warnings = append(warnings, fmt.Sprintf(fgDeprecationWarning, "enableManagedTenantQuota"))
1✔
453
        }
1✔
454

455
        if len(warnings) > 0 {
2✔
456
                return newValidationWarning(warnings)
1✔
457
        }
1✔
458

459
        return nil
1✔
460
}
461

462
func hasRequiredHTTP2Ciphers(ciphers []string) bool {
1✔
463
        var requiredHTTP2Ciphers = []string{
1✔
464
                "ECDHE-RSA-AES128-GCM-SHA256",
1✔
465
                "ECDHE-ECDSA-AES128-GCM-SHA256",
1✔
466
        }
1✔
467

1✔
468
        // lo.Some returns true if at least 1 element of a subset is contained into a collection
1✔
469
        return lo.Some[string](requiredHTTP2Ciphers, ciphers)
1✔
470
}
1✔
471

472
// validationResponseFromStatus returns a response for admitting a request with provided Status object.
473
func validationResponseFromStatus(allowed bool, status metav1.Status) admission.Response {
×
474
        resp := admission.Response{
×
475
                AdmissionResponse: admissionv1.AdmissionResponse{
×
476
                        Allowed: allowed,
×
477
                        Result:  &status,
×
478
                },
×
479
        }
×
480
        return resp
×
481
}
×
482

483
func SelectCipherSuitesAndMinTLSVersion() ([]string, openshiftconfigv1.TLSProtocolVersion) {
1✔
484
        ci := hcoutil.GetClusterInfo()
1✔
485
        profile := ci.GetTLSSecurityProfile(hcoTLSConfigCache)
1✔
486

1✔
487
        if profile.Custom != nil {
1✔
488
                return profile.Custom.TLSProfileSpec.Ciphers, profile.Custom.TLSProfileSpec.MinTLSVersion
×
489
        }
×
490

491
        return openshiftconfigv1.TLSProfiles[profile.Type].Ciphers, openshiftconfigv1.TLSProfiles[profile.Type].MinTLSVersion
1✔
492
}
493

494
func isValidTLSProtocolVersion(pv openshiftconfigv1.TLSProtocolVersion) bool {
1✔
495
        switch pv {
1✔
496
        case
497
                openshiftconfigv1.VersionTLS10,
498
                openshiftconfigv1.VersionTLS11,
499
                openshiftconfigv1.VersionTLS12,
500
                openshiftconfigv1.VersionTLS13:
1✔
501
                return true
1✔
502
        }
503
        return false
1✔
504
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc