go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/resultdb/internal/config/validate.go (about) 1 // Copyright 2022 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package config 16 17 import ( 18 "fmt" 19 "regexp" 20 "strconv" 21 "strings" 22 23 "go.chromium.org/luci/auth/identity" 24 luciproto "go.chromium.org/luci/common/proto" 25 "go.chromium.org/luci/config/validation" 26 27 configpb "go.chromium.org/luci/resultdb/proto/config" 28 ) 29 30 var GCSBucketRE = regexp.MustCompile(`^[a-z0-9_\.\-]{3,222}$`) 31 32 func validateStringConfig(ctx *validation.Context, name, cfg string, re *regexp.Regexp) { 33 ctx.Enter(name) 34 defer ctx.Exit() 35 if cfg == "" { 36 ctx.Errorf("empty %s is not allowed", name) 37 return 38 } 39 if !re.MatchString(cfg) { 40 ctx.Errorf("invalid %s: %q", name, cfg) 41 } 42 } 43 44 // Validates according to https://cloud.google.com/storage/docs/objects#naming 45 func validateGCSBucketPrefix(ctx *validation.Context, name string, prefix string) { 46 ctx.Enter(name) 47 defer ctx.Exit() 48 49 prefixLen := len(prefix) 50 if prefixLen < 1 || prefixLen > 1024 { 51 ctx.Errorf("prefix: %q should have length between 1 and 1024 bytes", prefix) 52 } 53 if strings.HasPrefix(prefix, ".well-known/acme-challenge/") { 54 ctx.Errorf("prefix: %q is not allowed", prefix) 55 } 56 if prefix == "." || prefix == ".." { 57 ctx.Errorf("prefix: %q is not allowed, use '*' as wildcard to allow full access", prefix) 58 } 59 notAllowedChars, _ := strconv.Unquote(`"\u000a\u000b\u000c\u000d\u0085\u2028\u2029"`) 60 if strings.ContainsAny(prefix, notAllowedChars) { 61 ctx.Errorf("prefix: %q contains carriage return or line feed characters, which is not allowed", prefix) 62 } 63 } 64 65 func validateGCSAllowlist(ctx *validation.Context, name string, allowList *configpb.GcsAllowList) { 66 ctx.Enter(name) 67 defer ctx.Exit() 68 69 if len(allowList.Users) == 0 { 70 ctx.Errorf("users must have at least one user") 71 } 72 for _, user := range allowList.Users { 73 identity, err := identity.MakeIdentity(user) 74 if err != nil { 75 ctx.Errorf(err.Error()) 76 } 77 err = identity.Validate() 78 if err != nil { 79 ctx.Errorf(err.Error()) 80 } 81 } 82 83 if len(allowList.Buckets) == 0 { 84 ctx.Errorf("buckets must have at least one bucket") 85 } 86 for _, bucket := range allowList.Buckets { 87 validateStringConfig(ctx, "bucket", bucket, GCSBucketRE) 88 } 89 } 90 91 // validateProjectConfigRaw deserializes the project-level config message 92 // and passes it through the validator. 93 func validateProjectConfigRaw(ctx *validation.Context, content string) *configpb.ProjectConfig { 94 msg := &configpb.ProjectConfig{} 95 if err := luciproto.UnmarshalTextML(content, msg); err != nil { 96 ctx.Errorf("failed to unmarshal as text proto: %s", err) 97 return nil 98 } 99 validateProjectConfig(ctx, msg) 100 return msg 101 } 102 103 func validateProjectConfig(ctx *validation.Context, cfg *configpb.ProjectConfig) { 104 for i, allowList := range cfg.GcsAllowList { 105 validateGCSAllowlist(ctx, fmt.Sprintf("gcs_allow_list[%d]", i), allowList) 106 } 107 } 108 109 func validateServiceConfig(ctx *validation.Context, cfg *configpb.Config) { 110 if cfg.BqArtifactExportConfig == nil { 111 ctx.Errorf("missing BQ artifact export config") 112 return 113 } 114 validateBQArtifactExportConfig(ctx, cfg.BqArtifactExportConfig) 115 } 116 117 func validateBQArtifactExportConfig(ctx *validation.Context, cfg *configpb.BqArtifactExportConfig) { 118 ctx.Enter("bq_artifact_export_config") 119 defer ctx.Exit() 120 if cfg.ExportPercent < 0 || cfg.ExportPercent > 100 { 121 ctx.Errorf("export percent must be between 0 and 100") 122 } 123 }