Skip to content

Commit 145c392

Browse files
committed
Add validating webhook for k0sworkerconfig
Signed-off-by: apedriza <adripedriza@gmail.com>
1 parent ee79f00 commit 145c392

24 files changed

Lines changed: 842 additions & 102 deletions

api/bootstrap/v1beta1/k0s_template_types.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,12 @@ package v1beta1
1818

1919
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2020

21-
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
22-
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
21+
var (
22+
conflictingFileSourceMsg = "only one of content or contentFrom may be specified for a single file"
23+
conflictingContentFromMsg = "only one of contentFrom.secretKeyRef or contentFrom.configMapKeyRef may be specified for a single file"
24+
pathConflictMsg = "path property must be unique among all files"
25+
noContentMsg = "either content or contentFrom must be specified for a file"
26+
)
2327

2428
func init() {
2529
SchemeBuilder.Register(&K0sWorkerConfigTemplate{}, &K0sWorkerConfigTemplateList{})

api/bootstrap/v1beta1/k0s_types.go

Lines changed: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,13 @@ limitations under the License.
1717
package v1beta1
1818

1919
import (
20+
"path/filepath"
21+
"strings"
22+
2023
"github.com/k0sproject/k0smotron/internal/provisioner"
2124
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2225
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
23-
"path/filepath"
26+
"k8s.io/apimachinery/pkg/util/validation/field"
2427

2528
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
2629
)
@@ -379,3 +382,109 @@ func (k *K0sWorkerConfig) GetJoinTokenPath() string {
379382
}
380383
return filepath.Join(k.Spec.WorkingDir, "k0s.token")
381384
}
385+
386+
// Validate validates the K0sWorkerConfigSpec.
387+
func (cs *K0sWorkerConfigSpec) Validate(pathPrefix *field.Path) field.ErrorList {
388+
var allErrs field.ErrorList
389+
390+
// TODO: validate Ignition
391+
allErrs = append(allErrs, cs.validateVersion(pathPrefix)...)
392+
allErrs = append(allErrs, cs.validateFiles(pathPrefix)...)
393+
394+
return allErrs
395+
}
396+
397+
func (cs *K0sWorkerConfigSpec) validateFiles(pathPrefix *field.Path) field.ErrorList {
398+
var allErrs field.ErrorList
399+
400+
knownPaths := map[string]struct{}{}
401+
402+
for i, file := range cs.Files {
403+
if file.Content != "" && file.ContentFrom != nil {
404+
allErrs = append(
405+
allErrs,
406+
field.Invalid(
407+
pathPrefix.Child("files").Index(i),
408+
file,
409+
conflictingFileSourceMsg,
410+
),
411+
)
412+
}
413+
414+
if file.ContentFrom == nil && file.Content == "" {
415+
allErrs = append(
416+
allErrs,
417+
field.Invalid(
418+
pathPrefix.Child("files").Index(i),
419+
file,
420+
noContentMsg,
421+
),
422+
)
423+
}
424+
425+
if file.ContentFrom != nil {
426+
if file.ContentFrom.SecretRef != nil && file.ContentFrom.ConfigMapRef != nil {
427+
allErrs = append(
428+
allErrs,
429+
field.Invalid(
430+
pathPrefix.Child("files").Index(i).Child("contentFrom"),
431+
file.ContentFrom,
432+
conflictingContentFromMsg,
433+
),
434+
)
435+
}
436+
437+
if file.ContentFrom.SecretRef != nil && file.ContentFrom.SecretRef.Name == "" {
438+
allErrs = append(
439+
allErrs,
440+
field.Required(
441+
pathPrefix.Child("files").Index(i).Child("contentFrom").Child("secretRef").Child("name"),
442+
"name is required",
443+
),
444+
)
445+
}
446+
447+
if file.ContentFrom.ConfigMapRef != nil && file.ContentFrom.ConfigMapRef.Name == "" {
448+
allErrs = append(
449+
allErrs,
450+
field.Required(
451+
pathPrefix.Child("files").Index(i).Child("contentFrom").Child("configMapRef").Child("name"),
452+
"name is required",
453+
),
454+
)
455+
}
456+
}
457+
_, conflict := knownPaths[file.Path]
458+
if conflict {
459+
allErrs = append(
460+
allErrs,
461+
field.Invalid(
462+
pathPrefix.Child("files").Index(i).Child("path"),
463+
file,
464+
pathConflictMsg,
465+
),
466+
)
467+
}
468+
knownPaths[file.Path] = struct{}{}
469+
}
470+
471+
return allErrs
472+
}
473+
474+
func (cs *K0sWorkerConfigSpec) validateVersion(pathPrefix *field.Path) field.ErrorList {
475+
var allErrs field.ErrorList
476+
477+
if strings.Contains(cs.Version, "-k0s.") {
478+
allErrs = append(
479+
allErrs,
480+
field.Invalid(
481+
pathPrefix.Child("version"),
482+
cs.Version,
483+
"k0s specific versions must be specified using the '+k0s' suffix",
484+
),
485+
)
486+
return allErrs
487+
}
488+
489+
return allErrs
490+
}

cmd/main.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,11 @@ func main() {
270270
setupLog.Error(err, "unable to create controller", "controller", "Bootstrap")
271271
os.Exit(1)
272272
}
273+
274+
if err = (&bootstrap.K0sWorkerConfigValidator{}).SetupK0sWorkerConfigWebhookWithManager(mgr); err != nil {
275+
setupLog.Error(err, "unable to create validation webhook", "webhook", "K0sWorkerConfigValidator")
276+
os.Exit(1)
277+
}
273278
}
274279

275280
if isControllerEnabled(controlPlaneController) {

config/clusterapi/bootstrap/kustomization.yaml

Lines changed: 88 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ resources:
1919
- ../../rbac
2020
- ../../manager
2121
- ../k0smotron.io
22+
- ../../certmanager
23+
- ./webhook
2224
- ./bases/bootstrap.cluster.x-k8s.io_k0scontrollerconfigs.yaml
2325
- ./bases/bootstrap.cluster.x-k8s.io_k0sworkerconfigs.yaml
2426
- ./bases/bootstrap.cluster.x-k8s.io_k0sworkerconfigtemplates.yaml
@@ -35,6 +37,11 @@ patches:
3537
# If you want your controller-manager to expose the /metrics
3638
# endpoint w/o any authn/z, please comment the following line.
3739
- path: manager_config_patch.yaml
40+
- path: patches/webhook_in_k0sworkerconfig.yaml
41+
- path: patches/cainjection_in_k0sworkerconfig.yaml
42+
- path: patches/manager_webhook_patch.yaml
43+
- path: patches/webhook_service_patch.yaml
44+
- path: patches/certificate_patch.yaml
3845

3946
configurations:
4047
#- kustomizeconfig.yaml
@@ -49,100 +56,84 @@ configurations:
4956
## 'CERTMANAGER' needs to be enabled to use ca injection
5057
##- webhookcainjection_patch.yaml
5158
#
52-
#replacements:
53-
# - source: # Add cert-manager annotation to ValidatingWebhookConfiguration, MutatingWebhookConfiguration and CRDs
54-
# kind: Certificate
55-
# group: cert-manager.io
56-
# version: v1
57-
# name: serving-cert # this name should match the one in certificate.yaml
58-
# fieldPath: .metadata.namespace # namespace of the certificate CR
59-
# targets:
60-
# - select:
61-
# kind: ValidatingWebhookConfiguration
62-
# fieldPaths:
63-
# - .metadata.annotations.[cert-manager.io/inject-ca-from]
64-
# options:
65-
# delimiter: '/'
66-
# index: 0
67-
# create: true
68-
# - select:
69-
# kind: MutatingWebhookConfiguration
70-
# fieldPaths:
71-
# - .metadata.annotations.[cert-manager.io/inject-ca-from]
72-
# options:
73-
# delimiter: '/'
74-
# index: 0
75-
# create: true
76-
# - select:
77-
# kind: CustomResourceDefinition
78-
# fieldPaths:
79-
# - .metadata.annotations.[cert-manager.io/inject-ca-from]
80-
# options:
81-
# delimiter: '/'
82-
# index: 0
83-
# create: true
84-
# - source:
85-
# kind: Certificate
86-
# group: cert-manager.io
87-
# version: v1
88-
# name: serving-cert # this name should match the one in certificate.yaml
89-
# fieldPath: .metadata.name
90-
# targets:
91-
# - select:
92-
# kind: ValidatingWebhookConfiguration
93-
# fieldPaths:
94-
# - .metadata.annotations.[cert-manager.io/inject-ca-from]
95-
# options:
96-
# delimiter: '/'
97-
# index: 1
98-
# create: true
99-
# - select:
100-
# kind: MutatingWebhookConfiguration
101-
# fieldPaths:
102-
# - .metadata.annotations.[cert-manager.io/inject-ca-from]
103-
# options:
104-
# delimiter: '/'
105-
# index: 1
106-
# create: true
107-
# - select:
108-
# kind: CustomResourceDefinition
109-
# fieldPaths:
110-
# - .metadata.annotations.[cert-manager.io/inject-ca-from]
111-
# options:
112-
# delimiter: '/'
113-
# index: 1
114-
# create: true
115-
# - source: # Add cert-manager annotation to the webhook Service
116-
# kind: Service
117-
# version: v1
118-
# name: webhook-service
119-
# fieldPath: .metadata.name # namespace of the service
120-
# targets:
121-
# - select:
122-
# kind: Certificate
123-
# group: cert-manager.io
124-
# version: v1
125-
# fieldPaths:
126-
# - .spec.dnsNames.0
127-
# - .spec.dnsNames.1
128-
# options:
129-
# delimiter: '.'
130-
# index: 0
131-
# create: true
132-
# - source:
133-
# kind: Service
134-
# version: v1
135-
# name: webhook-service
136-
# fieldPath: .metadata.namespace # namespace of the service
137-
# targets:
138-
# - select:
139-
# kind: Certificate
140-
# group: cert-manager.io
141-
# version: v1
142-
# fieldPaths:
143-
# - .spec.dnsNames.0
144-
# - .spec.dnsNames.1
145-
# options:
146-
# delimiter: '.'
147-
# index: 1
148-
# create: true
59+
replacements:
60+
- source: # Add cert-manager annotation to ValidatingWebhookConfiguration, MutatingWebhookConfiguration and CRDs
61+
kind: Certificate
62+
group: cert-manager.io
63+
version: v1
64+
name: serving-cert # this name should match the one in certificate.yaml
65+
fieldPath: .metadata.namespace # namespace of the certificate CR
66+
targets:
67+
- select:
68+
kind: ValidatingWebhookConfiguration
69+
fieldPaths:
70+
- .metadata.annotations.[cert-manager.io/inject-ca-from]
71+
options:
72+
delimiter: '/'
73+
index: 0
74+
create: true
75+
- select:
76+
kind: CustomResourceDefinition
77+
fieldPaths:
78+
- .metadata.annotations.[cert-manager.io/inject-ca-from]
79+
options:
80+
delimiter: '/'
81+
index: 0
82+
create: true
83+
- source:
84+
kind: Certificate
85+
group: cert-manager.io
86+
version: v1
87+
name: serving-cert # this name should match the one in certificate.yaml
88+
fieldPath: .metadata.name
89+
targets:
90+
- select:
91+
kind: ValidatingWebhookConfiguration
92+
fieldPaths:
93+
- .metadata.annotations.[cert-manager.io/inject-ca-from]
94+
options:
95+
delimiter: '/'
96+
index: 1
97+
create: true
98+
- select:
99+
kind: CustomResourceDefinition
100+
fieldPaths:
101+
- .metadata.annotations.[cert-manager.io/inject-ca-from]
102+
options:
103+
delimiter: '/'
104+
index: 1
105+
create: true
106+
- source: # Add cert-manager annotation to the webhook Service
107+
kind: Service
108+
version: v1
109+
name: webhook-service
110+
fieldPath: .metadata.name # namespace of the service
111+
targets:
112+
- select:
113+
kind: Certificate
114+
group: cert-manager.io
115+
version: v1
116+
fieldPaths:
117+
- .spec.dnsNames.0
118+
- .spec.dnsNames.1
119+
options:
120+
delimiter: '.'
121+
index: 0
122+
create: true
123+
- source:
124+
kind: Service
125+
version: v1
126+
name: webhook-service
127+
fieldPath: .metadata.namespace # namespace of the service
128+
targets:
129+
- select:
130+
kind: Certificate
131+
group: cert-manager.io
132+
version: v1
133+
fieldPaths:
134+
- .spec.dnsNames.0
135+
- .spec.dnsNames.1
136+
options:
137+
delimiter: '.'
138+
index: 1
139+
create: true
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# The following patch adds a directive for certmanager to inject CA into the CRD
2+
apiVersion: apiextensions.k8s.io/v1
3+
kind: CustomResourceDefinition
4+
metadata:
5+
annotations:
6+
cert-manager.io/inject-ca-from: CERTIFICATE_NAMESPACE/CERTIFICATE_NAME
7+
name: k0sworkerconfigs.bootstrap.cluster.x-k8s.io
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
apiVersion: cert-manager.io/v1
2+
kind: Certificate
3+
metadata:
4+
labels:
5+
app.kubernetes.io/name: certificate
6+
app.kubernetes.io/instance: serving-cert
7+
app.kubernetes.io/component: certificate
8+
app.kubernetes.io/created-by: k0smotron
9+
app.kubernetes.io/part-of: k0smotron
10+
app.kubernetes.io/managed-by: kustomize
11+
name: serving-cert # this name should match the one appeared in kustomizeconfig.yaml
12+
namespace: system
13+
spec:
14+
secretName: k0smotron-webhook-server-cert-bootstrap

0 commit comments

Comments
 (0)