Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/install/installer/pkg/config/v1/validation.go
2501 views
1
// Copyright (c) 2021 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package config
6
7
import (
8
"context"
9
"fmt"
10
"regexp"
11
12
"github.com/gitpod-io/gitpod/installer/pkg/cluster"
13
"github.com/gitpod-io/gitpod/installer/pkg/config/v1/experimental"
14
"golang.org/x/crypto/ssh"
15
"sigs.k8s.io/yaml"
16
17
"github.com/go-playground/validator/v10"
18
corev1 "k8s.io/api/core/v1"
19
"k8s.io/client-go/rest"
20
)
21
22
var InstallationKindList = map[InstallationKind]struct{}{
23
InstallationIDE: {},
24
InstallationWebApp: {},
25
InstallationMeta: {},
26
InstallationWorkspace: {},
27
InstallationFull: {},
28
}
29
30
var LogLevelList = map[LogLevel]struct{}{
31
LogLevelTrace: {},
32
LogLevelDebug: {},
33
LogLevelInfo: {},
34
LogLevelWarning: {},
35
LogLevelError: {},
36
LogLevelFatal: {},
37
LogLevelPanic: {},
38
}
39
40
var ObjectRefKindList = map[ObjectRefKind]struct{}{
41
ObjectRefSecret: {},
42
}
43
44
var FSShiftMethodList = map[FSShiftMethod]struct{}{
45
FSShiftShiftFS: {},
46
}
47
48
// LoadValidationFuncs load custom validation functions for this version of the config API
49
func (v version) LoadValidationFuncs(validate *validator.Validate) error {
50
funcs := map[string]validator.Func{
51
"objectref_kind": func(fl validator.FieldLevel) bool {
52
_, ok := ObjectRefKindList[ObjectRefKind(fl.Field().String())]
53
return ok
54
},
55
"fs_shift_method": func(fl validator.FieldLevel) bool {
56
_, ok := FSShiftMethodList[FSShiftMethod(fl.Field().String())]
57
return ok
58
},
59
"installation_kind": func(fl validator.FieldLevel) bool {
60
_, ok := InstallationKindList[InstallationKind(fl.Field().String())]
61
return ok
62
},
63
"log_level": func(fl validator.FieldLevel) bool {
64
_, ok := LogLevelList[LogLevel(fl.Field().String())]
65
return ok
66
},
67
"block_new_users_passlist": func(fl validator.FieldLevel) bool {
68
if !fl.Parent().FieldByName("Enabled").Bool() {
69
// Not enabled - it's valid
70
return true
71
}
72
73
if fl.Field().Len() == 0 {
74
// No exceptions
75
return false
76
}
77
78
// Use same regex as "fqdn"
79
// @link https://github.com/go-playground/validator/blob/c7e0172e0fd176bdc521afb5186818a7db6b77ac/regexes.go#L52
80
fqdnRegexStringRFC1123 := `^([a-zA-Z0-9]{1}[a-zA-Z0-9-]{0,62})(\.[a-zA-Z0-9]{1}[a-zA-Z0-9-]{0,62})*?(\.[a-zA-Z]{1}[a-zA-Z0-9]{0,62})\.?$`
81
fqdnRegexRFC1123 := regexp.MustCompile(fqdnRegexStringRFC1123)
82
83
for i := 0; i < fl.Field().Len(); i++ {
84
val := fl.Field().Index(i).String()
85
86
if val == "" {
87
// Empty value
88
return false
89
}
90
91
// Check that it validates as a fully-qualified domain name
92
valid := fqdnRegexRFC1123.MatchString(val)
93
if !valid {
94
return false
95
}
96
}
97
return true
98
},
99
}
100
101
for k, v := range experimental.ValidationChecks {
102
funcs[k] = v
103
}
104
105
for n, f := range funcs {
106
err := validate.RegisterValidation(n, f)
107
if err != nil {
108
return err
109
}
110
}
111
112
return nil
113
}
114
115
// ClusterValidation introduces configuration specific cluster validation checks
116
func (v version) ClusterValidation(rcfg interface{}) cluster.ValidationChecks {
117
cfg := rcfg.(*Config)
118
119
var res cluster.ValidationChecks
120
res = append(res, cluster.CheckSecret(cfg.Certificate.Name, cluster.CheckSecretRequiredData("tls.crt", "tls.key")))
121
122
res = append(res, cluster.ValidationCheck{
123
Name: "affinity labels",
124
Check: checkAffinityLabels(getAffinityListByKind(cfg.Kind)),
125
Description: "all required affinity node labels " + fmt.Sprint(getAffinityListByKind(cfg.Kind)) + " are present in the cluster",
126
})
127
128
if cfg.ObjectStorage.CloudStorage != nil {
129
secretName := cfg.ObjectStorage.CloudStorage.ServiceAccount.Name
130
res = append(res, cluster.CheckSecret(secretName, cluster.CheckSecretRequiredData("service-account.json")))
131
}
132
133
if cfg.ObjectStorage.S3 != nil {
134
secretName := cfg.ObjectStorage.S3.Credentials.Name
135
res = append(res, cluster.CheckSecret(secretName, cluster.CheckSecretRequiredData("accessKeyId", "secretAccessKey")))
136
}
137
138
if cfg.ContainerRegistry.External != nil {
139
secretName := cfg.ContainerRegistry.External.Certificate.Name
140
res = append(res, cluster.CheckSecret(secretName, cluster.CheckSecretRequiredData(".dockerconfigjson")))
141
142
if cfg.ContainerRegistry.External.Credentials != nil {
143
credSecretName := cfg.ContainerRegistry.External.Credentials.Name
144
res = append(res, cluster.CheckSecret(credSecretName, cluster.CheckSecretRequiredData("credentials")))
145
}
146
}
147
148
if cfg.ContainerRegistry.S3Storage != nil {
149
secretName := cfg.ContainerRegistry.S3Storage.Certificate.Name
150
res = append(res, cluster.CheckSecret(secretName, cluster.CheckSecretRequiredData("s3AccessKey", "s3SecretKey")))
151
}
152
153
if cfg.Database.CloudSQL != nil {
154
secretName := cfg.Database.CloudSQL.ServiceAccount.Name
155
res = append(res, cluster.CheckSecret(secretName, cluster.CheckSecretRequiredData("credentials.json", "encryptionKeys", "password", "username")))
156
}
157
158
if cfg.Database.External != nil {
159
secretName := cfg.Database.External.Certificate.Name
160
res = append(res, cluster.CheckSecret(secretName, cluster.CheckSecretRequiredData("encryptionKeys", "host", "password", "port", "username")))
161
}
162
163
if cfg.Database.SSL != nil && cfg.Database.SSL.CaCert != nil {
164
secretName := cfg.Database.SSL.CaCert.Name
165
res = append(res, cluster.CheckSecret(secretName, cluster.CheckSecretRequiredData("ca.crt")))
166
}
167
168
if len(cfg.AuthProviders) > 0 {
169
for _, provider := range cfg.AuthProviders {
170
secretName := provider.Name
171
secretKey := "provider"
172
res = append(res, cluster.CheckSecret(secretName, cluster.CheckSecretRequiredData(secretKey), cluster.CheckSecretRule(func(s *corev1.Secret) ([]cluster.ValidationError, error) {
173
errors := make([]cluster.ValidationError, 0)
174
providerData := s.Data[secretKey]
175
176
var provider AuthProviderConfigs
177
err := yaml.Unmarshal(providerData, &provider)
178
if err != nil {
179
return nil, err
180
}
181
182
validate := validator.New()
183
err = v.LoadValidationFuncs(validate)
184
if err != nil {
185
return nil, err
186
}
187
188
err = validate.Struct(provider)
189
if err != nil {
190
validationErrors := err.(validator.ValidationErrors)
191
192
if len(validationErrors) > 0 {
193
for _, v := range validationErrors {
194
errors = append(errors, cluster.ValidationError{
195
Message: fmt.Sprintf("Field '%s' failed %s validation", v.Namespace(), v.Tag()),
196
Type: cluster.ValidationStatusError,
197
})
198
}
199
}
200
}
201
202
return errors, nil
203
})))
204
}
205
}
206
207
if cfg.SSHGatewayHostKey != nil {
208
secretName := cfg.SSHGatewayHostKey.Name
209
res = append(res, cluster.CheckSecret(secretName, cluster.CheckSecretRule(func(s *corev1.Secret) ([]cluster.ValidationError, error) {
210
var signers []ssh.Signer
211
errors := make([]cluster.ValidationError, 0)
212
for field, value := range s.Data {
213
hostSigner, err := ssh.ParsePrivateKey(value)
214
if err != nil {
215
errors = append(errors, cluster.ValidationError{
216
Message: fmt.Sprintf("Field '%s' can't parse to host key %v", field, err),
217
Type: cluster.ValidationStatusWarning,
218
})
219
continue
220
}
221
signers = append(signers, hostSigner)
222
}
223
if len(signers) == 0 {
224
errors = append(errors, cluster.ValidationError{
225
Message: fmt.Sprintf("Secret '%s' does not contain a valid host key", secretName),
226
Type: cluster.ValidationStatusError,
227
})
228
}
229
return errors, nil
230
})))
231
}
232
233
res = append(res, experimental.ClusterValidation(cfg.Experimental)...)
234
235
return res
236
}
237
238
// checkAffinityLabels validates that the nodes have all the required affinity labels applied
239
// It assumes all the values are `true`
240
func checkAffinityLabels(targetAffinityList []string) func(context.Context, *rest.Config, string) ([]cluster.ValidationError, error) {
241
return func(ctx context.Context, config *rest.Config, namespace string) ([]cluster.ValidationError, error) {
242
nodes, err := cluster.ListNodesFromContext(ctx, config)
243
if err != nil {
244
return nil, err
245
}
246
247
affinityList := map[string]bool{}
248
for _, affinity := range targetAffinityList {
249
affinityList[affinity] = false
250
}
251
252
var res []cluster.ValidationError
253
for _, node := range nodes {
254
for k, v := range node.GetLabels() {
255
if _, found := affinityList[k]; found {
256
affinityList[k] = v == "true"
257
}
258
}
259
}
260
261
// Check all the values in the map are `true`
262
for k, v := range affinityList {
263
if !v {
264
res = append(res, cluster.ValidationError{
265
Message: "Affinity label not found in cluster: " + k,
266
Type: cluster.ValidationStatusError,
267
})
268
}
269
}
270
return res, nil
271
}
272
}
273
274
func getAffinityListByKind(kind InstallationKind) []string {
275
var affinityList []string
276
switch kind {
277
case InstallationMeta:
278
affinityList = cluster.AffinityListMeta
279
case InstallationWorkspace:
280
affinityList = cluster.AffinityListWorkspace
281
default:
282
affinityList = cluster.AffinityList
283
}
284
return affinityList
285
}
286
287