go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/swarming/server/cfg/settings.go (about) 1 // Copyright 2023 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 cfg 16 17 import ( 18 "fmt" 19 "net/url" 20 "strings" 21 22 "go.chromium.org/luci/config/validation" 23 24 configpb "go.chromium.org/luci/swarming/proto/config" 25 "go.chromium.org/luci/swarming/server/validate" 26 ) 27 28 // withDefaultSettings fills in defaults in `cfg` and returns it. 29 func withDefaultSettings(cfg *configpb.SettingsCfg) *configpb.SettingsCfg { 30 if cfg.ReusableTaskAgeSecs == 0 { 31 cfg.ReusableTaskAgeSecs = 7 * 24 * 60 * 60 32 } 33 if cfg.BotDeathTimeoutSecs == 0 { 34 cfg.BotDeathTimeoutSecs = 10 * 60 35 } 36 if cfg.Auth == nil { 37 cfg.Auth = &configpb.AuthSettings{} 38 } 39 if cfg.Auth.AdminsGroup == "" { 40 cfg.Auth.AdminsGroup = "administrators" 41 } 42 if cfg.Auth.BotBootstrapGroup == "" { 43 cfg.Auth.BotBootstrapGroup = cfg.Auth.AdminsGroup 44 } 45 if cfg.Auth.PrivilegedUsersGroup == "" { 46 cfg.Auth.PrivilegedUsersGroup = cfg.Auth.AdminsGroup 47 } 48 if cfg.Auth.UsersGroup == "" { 49 cfg.Auth.UsersGroup = cfg.Auth.AdminsGroup 50 } 51 if cfg.Auth.ViewAllBotsGroup == "" { 52 cfg.Auth.ViewAllBotsGroup = cfg.Auth.AdminsGroup 53 } 54 if cfg.Auth.ViewAllTasksGroup == "" { 55 cfg.Auth.ViewAllTasksGroup = cfg.Auth.AdminsGroup 56 } 57 return cfg 58 } 59 60 // validateSettingsCfg validates settings.cfg, writing errors into `ctx`. 61 func validateSettingsCfg(ctx *validation.Context, cfg *configpb.SettingsCfg) { 62 ctx.Enter("bot_death_timeout_secs") 63 if cfg.BotDeathTimeoutSecs < 0 { 64 ctx.Errorf("must be non-negative, got %d", cfg.BotDeathTimeoutSecs) 65 } 66 ctx.Exit() 67 68 ctx.Enter("reusable_task_age_secs") 69 if cfg.ReusableTaskAgeSecs < 0 { 70 ctx.Errorf("must be non-negative, got %d", cfg.ReusableTaskAgeSecs) 71 } 72 ctx.Exit() 73 74 if cfg.DisplayServerUrlTemplate != "" { 75 if strings.Count(cfg.DisplayServerUrlTemplate, "%s") != 1 { 76 ctx.Enter("display_server_url_template") 77 ctx.Errorf("must have exactly one `%%s` term") 78 ctx.Exit() 79 } else { 80 // Need to do the template substitution now because '%s' is not valid 81 // inside as URL. 82 validateHTTPS(ctx, "display_server_url_template", fmt.Sprintf(cfg.DisplayServerUrlTemplate, "...")) 83 } 84 } 85 86 for _, url := range cfg.ExtraChildSrcCspUrl { 87 validateHTTPS(ctx, "extra_child_src_csp_url", url) 88 } 89 90 if cfg.Cipd != nil { 91 ctx.Enter("cipd") 92 validateHTTPS(ctx, "default_server", cfg.Cipd.DefaultServer) 93 ctx.Enter("default_client_package") 94 if cfg.Cipd.DefaultClientPackage == nil { 95 ctx.Errorf("this is a required field") 96 } else { 97 ctx.Enter("package_name") 98 if err := validate.CipdPackageName(cfg.Cipd.DefaultClientPackage.PackageName); err != nil { 99 ctx.Errorf("%s", err) 100 } 101 ctx.Exit() 102 ctx.Enter("version") 103 if err := validate.CipdPackageVersion(cfg.Cipd.DefaultClientPackage.Version); err != nil { 104 ctx.Errorf("%s", err) 105 } 106 ctx.Exit() 107 } 108 ctx.Exit() 109 ctx.Exit() 110 } 111 112 if cfg.Resultdb != nil { 113 ctx.Enter("resultdb") 114 validateHTTPS(ctx, "server", cfg.Resultdb.Server) 115 ctx.Exit() 116 } 117 118 if cfg.Cas != nil { 119 ctx.Enter("cas") 120 validateHTTPS(ctx, "viewer_server", cfg.Cas.ViewerServer) 121 ctx.Exit() 122 } 123 } 124 125 func validateHTTPS(ctx *validation.Context, key, val string) { 126 ctx.Enter(key) 127 defer ctx.Exit() 128 129 if val == "" { 130 ctx.Errorf("this is a required field") 131 return 132 } 133 134 if !strings.HasPrefix(val, "https://") { 135 ctx.Errorf("must be an https:// URL") 136 return 137 } 138 139 switch parsed, err := url.Parse(val); { 140 case err != nil: 141 ctx.Errorf("%s", err) 142 case parsed.Host == "": 143 ctx.Errorf("URL %q doesn't have a host", val) 144 } 145 }