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 }