Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(188)

Side by Side Diff: scheduler/appengine/acl/acl.go

Issue 2986033003: [scheduler]: ACLs phase 1 - per Job ACL specification and enforcement. (Closed)
Patch Set: [WIP] ACLs into engine public API. Created 3 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2017 The LUCI Authors. 1 // Copyright 2017 The LUCI Authors.
2 // 2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); 3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with 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 5 // You may obtain a copy of the License at
6 // 6 //
7 // http://www.apache.org/licenses/LICENSE-2.0 7 // http://www.apache.org/licenses/LICENSE-2.0
8 // 8 //
9 // Unless required by applicable law or agreed to in writing, software 9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, 10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and 12 // See the License for the specific language governing permissions and
13 // limitations under the License. 13 // limitations under the License.
14 14
15 package acl 15 package acl
16 16
17 import ( 17 import (
18 » "golang.org/x/net/context" 18 » "context"
19 » "fmt"
20 » "regexp"
21 » "sort"
22 » "strings"
19 23
24 "github.com/luci/luci-go/common/data/stringset"
25 "github.com/luci/luci-go/common/errors"
26 "github.com/luci/luci-go/scheduler/appengine/messages"
20 "github.com/luci/luci-go/server/auth" 27 "github.com/luci/luci-go/server/auth"
28 "github.com/luci/luci-go/server/auth/identity"
21 ) 29 )
22 30
31 // IsJobOwner is deprecated. UIse GrantsByRole.IsOwner instead.
23 func IsJobOwner(c context.Context, projectID, jobName string) bool { 32 func IsJobOwner(c context.Context, projectID, jobName string) bool {
24 // TODO(vadimsh): Do real ACLs. 33 // TODO(vadimsh): Do real ACLs.
25 ok, err := auth.IsMember(c, "administrators") 34 ok, err := auth.IsMember(c, "administrators")
26 if err != nil { 35 if err != nil {
27 panic(err) 36 panic(err)
28 } 37 }
29 return ok 38 return ok
30 } 39 }
40
41 // GrantsByRole can answer questions who can READ and who OWNS the task.
42 type GrantsByRole struct {
43 Owners []string `gae:",noindex"`
44 Readers []string `gae:",noindex"`
45 }
46
47 func (g *GrantsByRole) IsOwner(c context.Context) (bool, error) {
48 return hasGrant(c, g.Owners, groupsAdministrators)
49 }
50
51 func (g *GrantsByRole) IsReader(c context.Context) (bool, error) {
52 if len(g.Readers) == 0 && len(g.Owners) == 0 {
53 // This is here for backwards compatiblity before ACLs were intr oduced.
54 // If Job doesn't specify READERs nor OWNERS explicitely, everyb ody can read.
55 // TODO(tAndrii): remove once every Job/Trigger has ACLs specifi ed.
56 return true, nil
57 }
58 return hasGrant(c, g.Owners, g.Readers, groupsAdministrators)
59 }
60
61 func (g *GrantsByRole) Equal(o *GrantsByRole) bool {
62 eqSlice := func(a, b []string) bool {
63 if len(a) != len(b) {
64 return false
65 }
66 for i := range a {
67 if a[i] != b[i] {
68 return false
69 }
70 }
71 return true
72 }
73 return eqSlice(g.Owners, o.Owners) && eqSlice(g.Readers, o.Readers)
74 }
75
76 // AclSets are parsed and indexed `AclSet` of a project.
77 type AclSets map[string][]*messages.Acl
78
79 // ValidateAclSets validates list of AclSet of a project and returns AclSets.
80 func ValidateAclSets(sets []*messages.AclSet) (AclSets, error) {
81 as := make(AclSets, len(sets))
82 for _, s := range sets {
83 if s.Name == "" {
84 return nil, fmt.Errorf("missing 'name' field'")
85 }
86 if !aclSetNameRe.MatchString(s.Name) {
87 return nil, fmt.Errorf("%q is not valid value for 'name' field", s.Name)
88 }
89 if _, isDup := as[s.Name]; isDup {
90 return nil, fmt.Errorf("aclSet name %q is not unique", s .Name)
91 }
92 if len(s.GetAcls()) == 0 {
93 return nil, fmt.Errorf("aclSet %q has no entries", s.Nam e)
94 }
95 as[s.Name] = s.GetAcls()
96 }
97 return as, nil
98 }
99
100 // ValidateTaskAcls validates task's ACLs and returns TaskAcls.
101 func ValidateTaskAcls(pSets AclSets, tSets []string, tAcls []*messages.Acl) (*Gr antsByRole, error) {
102 grantsLists := make([][]*messages.Acl, 0, 1+len(tSets))
103 if err := validateGrants(tAcls); err != nil {
104 return nil, err
105 }
106 grantsLists = append(grantsLists, tAcls)
107 for _, set := range tSets {
108 grantsList, exists := pSets[set]
109 if !exists {
110 return nil, fmt.Errorf("referencing AclSet '%s' which do esn't exist", set)
111 }
112 grantsLists = append(grantsLists, grantsList)
113 }
114 mg := mergeGrants(grantsLists...)
115 if n := len(mg.Owners) + len(mg.Readers); n > maxGrantsPerJob {
116 return nil, fmt.Errorf("Job or Trigger can have at most %d acls, but %d given", maxGrantsPerJob, n)
117 }
118 return mg, nil
119 }
120
121 ////////////////////////////////////////////////////////////////////////////////
122
123 var (
124 // aclSetNameRe is used to validate AclSet Name field.
125 aclSetNameRe = regexp.MustCompile(`^[0-9A-Za-z_\-\.]{1,100}$`)
126 // maxGrantsPerJob is how many different grants are specified for a job.
127 maxGrantsPerJob = 32
128
129 groupsAdministrators = []string{"group:administrators"}
130 )
131
132 func validateGrants(gs []*messages.Acl) error {
133 for _, g := range gs {
134 switch {
135 case g.GetRole() != messages.Acl_OWNER && g.GetRole() != message s.Acl_READER:
136 return fmt.Errorf("invalid role %q", g.GetRole())
137 case g.GetGrantedTo() == "":
138 return fmt.Errorf("missing granted_to for role %s", g.Ge tRole())
139 case strings.HasPrefix(g.GetGrantedTo(), "group:"):
140 if g.GetGrantedTo()[len("group:"):] == "" {
141 return fmt.Errorf("invalid granted_to %q for rol e %s: needs a group name", g.GetGrantedTo(), g.GetRole())
142 }
143 default:
144 id := g.GetGrantedTo()
145 if !strings.ContainsRune(g.GetGrantedTo(), ':') {
146 id = "user:" + g.GetGrantedTo()
147 }
148 if _, err := identity.MakeIdentity(id); err != nil {
149 return errors.Annotate(err, "invalid granted_to %q for role %s", g.GetGrantedTo(), g.GetRole()).Err()
150 }
151 }
152 }
153 return nil
154 }
155
156 // mergeGrants merges valid grants into GrantsByRole, removing and sorting dupli cates.
157 func mergeGrants(grantsLists ...[]*messages.Acl) *GrantsByRole {
158 all := map[messages.Acl_Role]stringset.Set{
159 messages.Acl_OWNER: stringset.New(maxGrantsPerJob),
160 messages.Acl_READER: stringset.New(maxGrantsPerJob),
161 }
162 for _, grantsList := range grantsLists {
163 for _, g := range grantsList {
164 all[g.GetRole()].Add(g.GetGrantedTo())
165 }
166 }
167 sortedSlice := func(s stringset.Set) []string {
168 r := s.ToSlice()
169 sort.Strings(r)
170 return r
171 }
172 return &GrantsByRole{
173 Owners: sortedSlice(all[messages.Acl_OWNER]),
174 Readers: sortedSlice(all[messages.Acl_READER]),
175 }
176 }
177
178 // hasGrant is current user is covered by any given grants.
179 func hasGrant(c context.Context, grantsList ...[]string) (bool, error) {
180 currentIdentity := auth.CurrentIdentity(c)
181 groups := []string{}
182 for _, grants := range grantsList {
183 for _, grant := range grants {
184 if strings.HasPrefix(grant, "group:") {
185 groups = append(groups, grant[len("group:"):])
186 continue
187 }
188 grantedIdentity := identity.Identity(grant)
189 if !strings.ContainsRune(grant, ':') {
190 // Just email.
191 grantedIdentity = identity.Identity("user:" + gr ant)
192 }
193 if grantedIdentity == currentIdentity {
194 return true, nil
195 }
196 }
197 }
198 return auth.IsMember(c, groups...)
199 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698