Skip to content

Commit d3a58c7

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

11 files changed

Lines changed: 593 additions & 100 deletions

File tree

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

cmd/main.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,11 @@ func main() {
331331
setupLog.Error(err, "unable to create validation webhook", "webhook", "K0smotronControlPlaneValidator")
332332
os.Exit(1)
333333
}
334+
335+
if err = (&bootstrap.K0sWorkerConfigValidator{}).SetupK0sWorkerConfigWebhookWithManager(mgr); err != nil {
336+
setupLog.Error(err, "unable to create validation webhook", "webhook", "K0sWorkerConfigValidator")
337+
os.Exit(1)
338+
}
334339
}
335340
}
336341

config/clusterapi/bootstrap/kustomization.yaml

Lines changed: 87 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,10 @@ 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
3844

3945
configurations:
4046
#- kustomizeconfig.yaml
@@ -49,100 +55,84 @@ configurations:
4955
## 'CERTMANAGER' needs to be enabled to use ca injection
5056
##- webhookcainjection_patch.yaml
5157
#
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
58+
replacements:
59+
- source: # Add cert-manager annotation to ValidatingWebhookConfiguration, MutatingWebhookConfiguration and CRDs
60+
kind: Certificate
61+
group: cert-manager.io
62+
version: v1
63+
name: serving-cert # this name should match the one in certificate.yaml
64+
fieldPath: .metadata.namespace # namespace of the certificate CR
65+
targets:
66+
- select:
67+
kind: ValidatingWebhookConfiguration
68+
fieldPaths:
69+
- .metadata.annotations.[cert-manager.io/inject-ca-from]
70+
options:
71+
delimiter: '/'
72+
index: 0
73+
create: true
74+
- select:
75+
kind: CustomResourceDefinition
76+
fieldPaths:
77+
- .metadata.annotations.[cert-manager.io/inject-ca-from]
78+
options:
79+
delimiter: '/'
80+
index: 0
81+
create: true
82+
- source:
83+
kind: Certificate
84+
group: cert-manager.io
85+
version: v1
86+
name: serving-cert # this name should match the one in certificate.yaml
87+
fieldPath: .metadata.name
88+
targets:
89+
- select:
90+
kind: ValidatingWebhookConfiguration
91+
fieldPaths:
92+
- .metadata.annotations.[cert-manager.io/inject-ca-from]
93+
options:
94+
delimiter: '/'
95+
index: 1
96+
create: true
97+
- select:
98+
kind: CustomResourceDefinition
99+
fieldPaths:
100+
- .metadata.annotations.[cert-manager.io/inject-ca-from]
101+
options:
102+
delimiter: '/'
103+
index: 1
104+
create: true
105+
- source: # Add cert-manager annotation to the webhook Service
106+
kind: Service
107+
version: v1
108+
name: webhook-service
109+
fieldPath: .metadata.name # namespace of the service
110+
targets:
111+
- select:
112+
kind: Certificate
113+
group: cert-manager.io
114+
version: v1
115+
fieldPaths:
116+
- .spec.dnsNames.0
117+
- .spec.dnsNames.1
118+
options:
119+
delimiter: '.'
120+
index: 0
121+
create: true
122+
- source:
123+
kind: Service
124+
version: v1
125+
name: webhook-service
126+
fieldPath: .metadata.namespace # namespace of the service
127+
targets:
128+
- select:
129+
kind: Certificate
130+
group: cert-manager.io
131+
version: v1
132+
fieldPaths:
133+
- .spec.dnsNames.0
134+
- .spec.dnsNames.1
135+
options:
136+
delimiter: '.'
137+
index: 1
138+
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: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
apiVersion: apps/v1
2+
kind: Deployment
3+
metadata:
4+
name: controller-manager
5+
namespace: k0smotron
6+
spec:
7+
template:
8+
spec:
9+
containers:
10+
- name: manager
11+
ports:
12+
- containerPort: 9443
13+
name: webhook-server
14+
protocol: TCP
15+
volumeMounts:
16+
- mountPath: /tmp/k8s-webhook-server/serving-certs
17+
name: cert
18+
readOnly: true
19+
volumes:
20+
- name: cert
21+
secret:
22+
defaultMode: 420
23+
secretName: k0smotron-webhook-server-cert

0 commit comments

Comments
 (0)