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

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

Issue 2986033003: [scheduler]: ACLs phase 1 - per Job ACL specification and enforcement. (Closed)
Patch Set: Review. 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
« no previous file with comments | « scheduler/api/scheduler/v1/scheduler.pb.go ('k') | scheduler/appengine/acl/acl_test.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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/common/retry/transient"
27 "github.com/luci/luci-go/scheduler/appengine/messages"
20 "github.com/luci/luci-go/server/auth" 28 "github.com/luci/luci-go/server/auth"
29 "github.com/luci/luci-go/server/auth/identity"
21 ) 30 )
22 31
23 func IsJobOwner(c context.Context, projectID, jobName string) bool { 32 // GrantsByRole can answer questions who can READ and who OWNS the task.
24 » // TODO(vadimsh): Do real ACLs. 33 type GrantsByRole struct {
25 » ok, err := auth.IsMember(c, "administrators") 34 » Owners []string `gae:",noindex"`
26 » if err != nil { 35 » Readers []string `gae:",noindex"`
27 » » panic(err) 36 }
37
38 func (g *GrantsByRole) IsOwner(c context.Context) (bool, error) {
39 » return hasGrant(c, g.Owners, groupsAdministrators)
40 }
41
42 func (g *GrantsByRole) IsReader(c context.Context) (bool, error) {
43 » if len(g.Readers) == 0 && len(g.Owners) == 0 {
44 » » // This is here for backwards compatiblity before ACLs were intr oduced.
45 » » // If Job doesn't specify READERs nor OWNERS explicitely, everyb ody can read.
46 » » // TODO(tAndrii): remove once every Job/Trigger has ACLs specifi ed.
47 » » return true, nil
28 } 48 }
29 » return ok 49 » return hasGrant(c, g.Owners, g.Readers, groupsAdministrators)
30 } 50 }
51
52 func (g *GrantsByRole) Equal(o *GrantsByRole) bool {
53 eqSlice := func(a, b []string) bool {
54 if len(a) != len(b) {
55 return false
56 }
57 for i := range a {
58 if a[i] != b[i] {
59 return false
60 }
61 }
62 return true
63 }
64 return eqSlice(g.Owners, o.Owners) && eqSlice(g.Readers, o.Readers)
65 }
66
67 // AclSets are parsed and indexed `AclSet` of a project.
68 type AclSets map[string][]*messages.Acl
69
70 // ValidateAclSets validates list of AclSet of a project and returns AclSets.
71 func ValidateAclSets(sets []*messages.AclSet) (AclSets, error) {
72 as := make(AclSets, len(sets))
73 for _, s := range sets {
74 if s.Name == "" {
75 return nil, fmt.Errorf("missing 'name' field'")
76 }
77 if !aclSetNameRe.MatchString(s.Name) {
78 return nil, fmt.Errorf("%q is not valid value for 'name' field", s.Name)
79 }
80 if _, isDup := as[s.Name]; isDup {
81 return nil, fmt.Errorf("aclSet name %q is not unique", s .Name)
82 }
83 if len(s.GetAcls()) == 0 {
84 return nil, fmt.Errorf("aclSet %q has no entries", s.Nam e)
85 }
86 as[s.Name] = s.GetAcls()
87 }
88 return as, nil
89 }
90
91 // ValidateTaskAcls validates task's ACLs and returns TaskAcls.
92 func ValidateTaskAcls(pSets AclSets, tSets []string, tAcls []*messages.Acl) (*Gr antsByRole, error) {
93 grantsLists := make([][]*messages.Acl, 0, 1+len(tSets))
94 if err := validateGrants(tAcls); err != nil {
95 return nil, err
96 }
97 grantsLists = append(grantsLists, tAcls)
98 for _, set := range tSets {
99 grantsList, exists := pSets[set]
100 if !exists {
101 return nil, fmt.Errorf("referencing AclSet '%s' which do esn't exist", set)
102 }
103 grantsLists = append(grantsLists, grantsList)
104 }
105 mg := mergeGrants(grantsLists...)
106 if n := len(mg.Owners) + len(mg.Readers); n > maxGrantsPerJob {
107 return nil, fmt.Errorf("Job or Trigger can have at most %d acls, but %d given", maxGrantsPerJob, n)
108 }
109 return mg, nil
110 }
111
112 ////////////////////////////////////////////////////////////////////////////////
113
114 var (
115 // aclSetNameRe is used to validate AclSet Name field.
116 aclSetNameRe = regexp.MustCompile(`^[0-9A-Za-z_\-\.]{1,100}$`)
117 // maxGrantsPerJob is how many different grants are specified for a job.
118 maxGrantsPerJob = 32
119
120 groupsAdministrators = []string{"group:administrators"}
121 )
122
123 func validateGrants(gs []*messages.Acl) error {
124 for _, g := range gs {
125 switch {
126 case g.GetRole() != messages.Acl_OWNER && g.GetRole() != message s.Acl_READER:
127 return fmt.Errorf("invalid role %q", g.GetRole())
128 case g.GetGrantedTo() == "":
129 return fmt.Errorf("missing granted_to for role %s", g.Ge tRole())
130 case strings.HasPrefix(g.GetGrantedTo(), "group:"):
131 if g.GetGrantedTo()[len("group:"):] == "" {
132 return fmt.Errorf("invalid granted_to %q for rol e %s: needs a group name", g.GetGrantedTo(), g.GetRole())
133 }
134 default:
135 id := g.GetGrantedTo()
136 if !strings.ContainsRune(g.GetGrantedTo(), ':') {
137 id = "user:" + g.GetGrantedTo()
138 }
139 if _, err := identity.MakeIdentity(id); err != nil {
140 return errors.Annotate(err, "invalid granted_to %q for role %s", g.GetGrantedTo(), g.GetRole()).Err()
141 }
142 }
143 }
144 return nil
145 }
146
147 // mergeGrants merges valid grants into GrantsByRole, removing and sorting dupli cates.
148 func mergeGrants(grantsLists ...[]*messages.Acl) *GrantsByRole {
149 all := map[messages.Acl_Role]stringset.Set{
150 messages.Acl_OWNER: stringset.New(maxGrantsPerJob),
151 messages.Acl_READER: stringset.New(maxGrantsPerJob),
152 }
153 for _, grantsList := range grantsLists {
154 for _, g := range grantsList {
155 all[g.GetRole()].Add(g.GetGrantedTo())
156 }
157 }
158 sortedSlice := func(s stringset.Set) []string {
159 r := s.ToSlice()
160 sort.Strings(r)
161 return r
162 }
163 return &GrantsByRole{
164 Owners: sortedSlice(all[messages.Acl_OWNER]),
165 Readers: sortedSlice(all[messages.Acl_READER]),
166 }
167 }
168
169 // hasGrant is current user is covered by any given grants.
170 func hasGrant(c context.Context, grantsList ...[]string) (bool, error) {
171 currentIdentity := auth.CurrentIdentity(c)
172 groups := []string{}
173 for _, grants := range grantsList {
174 for _, grant := range grants {
175 if strings.HasPrefix(grant, "group:") {
176 groups = append(groups, grant[len("group:"):])
177 continue
178 }
179 grantedIdentity := identity.Identity(grant)
180 if !strings.ContainsRune(grant, ':') {
181 // Just email.
182 grantedIdentity = identity.Identity("user:" + gr ant)
183 }
184 if grantedIdentity == currentIdentity {
185 return true, nil
186 }
187 }
188 }
189 if isMember, err := auth.IsMember(c, groups...); err != nil {
190 return false, transient.Tag.Apply(err)
191 } else {
192 return isMember, nil
193 }
194 }
OLDNEW
« no previous file with comments | « scheduler/api/scheduler/v1/scheduler.pb.go ('k') | scheduler/appengine/acl/acl_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698