blob: da1dab7458e1c181f67af53a7e7bd9aeb6e8e6ba [file] [log] [blame]
// Copyright 2016 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package delegation
import (
"fmt"
"strings"
"go.chromium.org/luci/auth/identity"
"go.chromium.org/luci/common/data/stringset"
"go.chromium.org/luci/config/validation"
"go.chromium.org/luci/tokenserver/api/admin/v1"
"go.chromium.org/luci/tokenserver/appengine/impl/utils/policy"
)
// validateConfigBundle validates the structure of a config bundle fetched by
// fetchConfigs.
func validateConfigBundle(ctx *validation.Context, bundle policy.ConfigBundle) {
ctx.SetFile(delegationCfg)
cfg, ok := bundle[delegationCfg].(*admin.DelegationPermissions)
if ok {
validateDelegationCfg(ctx, cfg)
} else {
ctx.Errorf("unexpectedly wrong proto type %T", cfg)
}
}
// validateDelegationCfg checks deserialized delegation.cfg.
func validateDelegationCfg(ctx *validation.Context, cfg *admin.DelegationPermissions) {
names := stringset.New(0)
for i, rule := range cfg.Rules {
if rule.Name != "" {
if names.Has(rule.Name) {
ctx.Errorf("two rules with identical name %q", rule.Name)
}
names.Add(rule.Name)
}
validateRule(ctx, fmt.Sprintf("rule #%d: %q", i+1, rule.Name), rule)
}
}
// validateRule checks single DelegationRule proto.
//
// See config.proto, DelegationRule for the description of allowed values.
func validateRule(ctx *validation.Context, title string, r *admin.DelegationRule) {
ctx.Enter(title)
defer ctx.Exit()
if r.Name == "" {
ctx.Errorf(`"name" is required`)
}
v := identitySetValidator{
Field: "requestor",
Context: ctx,
AllowGroups: true,
}
v.validate(r.Requestor)
v = identitySetValidator{
Field: "allowed_to_impersonate",
Context: ctx,
AllowReservedWords: []string{Requestor, Projects}, // '*' is not allowed here though
AllowGroups: true,
}
v.validate(r.AllowedToImpersonate)
v = identitySetValidator{
Field: "allowed_audience",
Context: ctx,
AllowReservedWords: []string{Requestor, "*"},
AllowGroups: true,
}
v.validate(r.AllowedAudience)
v = identitySetValidator{
Field: "target_service",
Context: ctx,
AllowReservedWords: []string{"*"},
AllowIDKinds: []identity.Kind{identity.Service},
}
v.validate(r.TargetService)
switch {
case r.MaxValidityDuration == 0:
ctx.Errorf(`"max_validity_duration" is required`)
case r.MaxValidityDuration < 0:
ctx.Errorf(`"max_validity_duration" must be positive`)
case r.MaxValidityDuration > 24*3600:
ctx.Errorf(`"max_validity_duration" must be smaller than 86401`)
}
}
type identitySetValidator struct {
Field string // name of the field being validated
Context *validation.Context // where to emit errors to
AllowReservedWords []string // to allow "*" and "REQUESTOR"
AllowGroups bool // true to allow "group:" entries
AllowIDKinds []identity.Kind // permitted identity kinds, or nil if all
}
func (v *identitySetValidator) validate(items []string) {
if len(items) == 0 {
v.Context.Errorf("%q is required", v.Field)
return
}
v.Context.Enter("%q", v.Field)
defer v.Context.Exit()
loop:
for _, s := range items {
// A reserved word?
for _, r := range v.AllowReservedWords {
if s == r {
continue loop
}
}
// A group reference?
if strings.HasPrefix(s, "group:") {
if !v.AllowGroups {
v.Context.Errorf("group entries are not allowed - %q", s)
} else {
if s == "group:" {
v.Context.Errorf("bad group entry %q", s)
}
}
continue
}
// An identity then.
id, err := identity.MakeIdentity(s)
if err != nil {
v.Context.Error(err)
continue
}
if v.AllowIDKinds != nil {
allowed := false
for _, k := range v.AllowIDKinds {
if id.Kind() == k {
allowed = true
break
}
}
if !allowed {
v.Context.Errorf("identity of kind %q is not allowed here - %q", id.Kind(), s)
}
}
}
}