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  }