go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/tokenserver/appengine/impl/serviceaccounts/config_validation.go (about)

     1  // Copyright 2020 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 serviceaccounts
    16  
    17  import (
    18  	"go.chromium.org/luci/auth/identity"
    19  	"go.chromium.org/luci/common/data/stringset"
    20  	"go.chromium.org/luci/config/validation"
    21  	"go.chromium.org/luci/server/auth/realms"
    22  
    23  	"go.chromium.org/luci/tokenserver/api/admin/v1"
    24  	"go.chromium.org/luci/tokenserver/appengine/impl/utils/policy"
    25  )
    26  
    27  // validateConfigBundle validates the structure of a config bundle fetched by
    28  // fetchConfigs.
    29  func validateConfigBundle(ctx *validation.Context, bundle policy.ConfigBundle) {
    30  	ctx.SetFile(configFileName)
    31  	cfg, ok := bundle[configFileName].(*admin.ServiceAccountsProjectMapping)
    32  	if ok {
    33  		validateMappingCfg(ctx, cfg)
    34  	} else {
    35  		ctx.Errorf("unexpectedly wrong proto type %T", cfg)
    36  	}
    37  }
    38  
    39  // validateMappingCfg checks deserialized project_owned_accounts.cfg.
    40  func validateMappingCfg(ctx *validation.Context, cfg *admin.ServiceAccountsProjectMapping) {
    41  	seenAccounts := stringset.New(0)
    42  	seenProjects := stringset.New(0)
    43  
    44  	for i, m := range cfg.Mapping {
    45  		ctx.Enter("mapping #%d", i+1)
    46  
    47  		// An empty mapping{...} entry makes no sense.
    48  		if len(m.Project) == 0 {
    49  			ctx.Errorf("at least one project must be given")
    50  		}
    51  		if len(m.ServiceAccount) == 0 {
    52  			ctx.Errorf("at least one service account must be given")
    53  		}
    54  
    55  		for _, project := range m.Project {
    56  			if err := realms.ValidateProjectName(project); err != nil {
    57  				ctx.Errorf("bad project %q: %s", project, err)
    58  			}
    59  			seenProjects.Add(project)
    60  		}
    61  
    62  		// We prefer to use service_account as a sort of a "primary key" in
    63  		// the mapping. There should be only one mapping{...} entry per account.
    64  		for _, account := range m.ServiceAccount {
    65  			if _, err := identity.MakeIdentity("user:" + account); err != nil {
    66  				ctx.Errorf("bad service_account %q: %s", account, err)
    67  			} else if !seenAccounts.Add(account) {
    68  				ctx.Errorf("service_account %q appears in more that one mapping", account)
    69  			}
    70  		}
    71  
    72  		ctx.Exit()
    73  	}
    74  
    75  	for i, project := range cfg.UseProjectScopedAccount {
    76  		if err := realms.ValidateProjectName(project); err != nil {
    77  			ctx.Errorf("bad project in use_project_scoped_account #%d %q: %s", i+1, project, err)
    78  		}
    79  		if seenProjects.Has(project) {
    80  			ctx.Errorf("project %q is in use_project_scoped_account list, but also has mapping entries", project)
    81  		}
    82  	}
    83  }