1// Copyright 2016 Google LLC 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 iam supports the resource-specific operations of Google Cloud 16// IAM (Identity and Access Management) for the Google Cloud Libraries. 17// See https://cloud.google.com/iam for more about IAM. 18// 19// Users of the Google Cloud Libraries will typically not use this package 20// directly. Instead they will begin with some resource that supports IAM, like 21// a pubsub topic, and call its IAM method to get a Handle for that resource. 22package iam 23 24import ( 25 "context" 26 "fmt" 27 "time" 28 29 gax "github.com/googleapis/gax-go/v2" 30 pb "google.golang.org/genproto/googleapis/iam/v1" 31 "google.golang.org/grpc" 32 "google.golang.org/grpc/codes" 33 "google.golang.org/grpc/metadata" 34) 35 36// client abstracts the IAMPolicy API to allow multiple implementations. 37type client interface { 38 Get(ctx context.Context, resource string) (*pb.Policy, error) 39 Set(ctx context.Context, resource string, p *pb.Policy) error 40 Test(ctx context.Context, resource string, perms []string) ([]string, error) 41} 42 43// grpcClient implements client for the standard gRPC-based IAMPolicy service. 44type grpcClient struct { 45 c pb.IAMPolicyClient 46} 47 48var withRetry = gax.WithRetry(func() gax.Retryer { 49 return gax.OnCodes([]codes.Code{ 50 codes.DeadlineExceeded, 51 codes.Unavailable, 52 }, gax.Backoff{ 53 Initial: 100 * time.Millisecond, 54 Max: 60 * time.Second, 55 Multiplier: 1.3, 56 }) 57}) 58 59func (g *grpcClient) Get(ctx context.Context, resource string) (*pb.Policy, error) { 60 var proto *pb.Policy 61 md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "resource", resource)) 62 ctx = insertMetadata(ctx, md) 63 64 err := gax.Invoke(ctx, func(ctx context.Context, _ gax.CallSettings) error { 65 var err error 66 proto, err = g.c.GetIamPolicy(ctx, &pb.GetIamPolicyRequest{Resource: resource}) 67 return err 68 }, withRetry) 69 if err != nil { 70 return nil, err 71 } 72 return proto, nil 73} 74 75func (g *grpcClient) Set(ctx context.Context, resource string, p *pb.Policy) error { 76 md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "resource", resource)) 77 ctx = insertMetadata(ctx, md) 78 79 return gax.Invoke(ctx, func(ctx context.Context, _ gax.CallSettings) error { 80 _, err := g.c.SetIamPolicy(ctx, &pb.SetIamPolicyRequest{ 81 Resource: resource, 82 Policy: p, 83 }) 84 return err 85 }, withRetry) 86} 87 88func (g *grpcClient) Test(ctx context.Context, resource string, perms []string) ([]string, error) { 89 var res *pb.TestIamPermissionsResponse 90 md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "resource", resource)) 91 ctx = insertMetadata(ctx, md) 92 93 err := gax.Invoke(ctx, func(ctx context.Context, _ gax.CallSettings) error { 94 var err error 95 res, err = g.c.TestIamPermissions(ctx, &pb.TestIamPermissionsRequest{ 96 Resource: resource, 97 Permissions: perms, 98 }) 99 return err 100 }, withRetry) 101 if err != nil { 102 return nil, err 103 } 104 return res.Permissions, nil 105} 106 107// A Handle provides IAM operations for a resource. 108type Handle struct { 109 c client 110 resource string 111} 112 113// InternalNewHandle is for use by the Google Cloud Libraries only. 114// 115// InternalNewHandle returns a Handle for resource. 116// The conn parameter refers to a server that must support the IAMPolicy service. 117func InternalNewHandle(conn *grpc.ClientConn, resource string) *Handle { 118 return InternalNewHandleGRPCClient(pb.NewIAMPolicyClient(conn), resource) 119} 120 121// InternalNewHandleGRPCClient is for use by the Google Cloud Libraries only. 122// 123// InternalNewHandleClient returns a Handle for resource using the given 124// grpc service that implements IAM as a mixin 125func InternalNewHandleGRPCClient(c pb.IAMPolicyClient, resource string) *Handle { 126 return InternalNewHandleClient(&grpcClient{c: c}, resource) 127} 128 129// InternalNewHandleClient is for use by the Google Cloud Libraries only. 130// 131// InternalNewHandleClient returns a Handle for resource using the given 132// client implementation. 133func InternalNewHandleClient(c client, resource string) *Handle { 134 return &Handle{ 135 c: c, 136 resource: resource, 137 } 138} 139 140// Policy retrieves the IAM policy for the resource. 141func (h *Handle) Policy(ctx context.Context) (*Policy, error) { 142 proto, err := h.c.Get(ctx, h.resource) 143 if err != nil { 144 return nil, err 145 } 146 return &Policy{InternalProto: proto}, nil 147} 148 149// SetPolicy replaces the resource's current policy with the supplied Policy. 150// 151// If policy was created from a prior call to Get, then the modification will 152// only succeed if the policy has not changed since the Get. 153func (h *Handle) SetPolicy(ctx context.Context, policy *Policy) error { 154 return h.c.Set(ctx, h.resource, policy.InternalProto) 155} 156 157// TestPermissions returns the subset of permissions that the caller has on the resource. 158func (h *Handle) TestPermissions(ctx context.Context, permissions []string) ([]string, error) { 159 return h.c.Test(ctx, h.resource, permissions) 160} 161 162// A RoleName is a name representing a collection of permissions. 163type RoleName string 164 165// Common role names. 166const ( 167 Owner RoleName = "roles/owner" 168 Editor RoleName = "roles/editor" 169 Viewer RoleName = "roles/viewer" 170) 171 172const ( 173 // AllUsers is a special member that denotes all users, even unauthenticated ones. 174 AllUsers = "allUsers" 175 176 // AllAuthenticatedUsers is a special member that denotes all authenticated users. 177 AllAuthenticatedUsers = "allAuthenticatedUsers" 178) 179 180// A Policy is a list of Bindings representing roles 181// granted to members. 182// 183// The zero Policy is a valid policy with no bindings. 184type Policy struct { 185 // TODO(jba): when type aliases are available, put Policy into an internal package 186 // and provide an exported alias here. 187 188 // This field is exported for use by the Google Cloud Libraries only. 189 // It may become unexported in a future release. 190 InternalProto *pb.Policy 191} 192 193// Members returns the list of members with the supplied role. 194// The return value should not be modified. Use Add and Remove 195// to modify the members of a role. 196func (p *Policy) Members(r RoleName) []string { 197 b := p.binding(r) 198 if b == nil { 199 return nil 200 } 201 return b.Members 202} 203 204// HasRole reports whether member has role r. 205func (p *Policy) HasRole(member string, r RoleName) bool { 206 return memberIndex(member, p.binding(r)) >= 0 207} 208 209// Add adds member member to role r if it is not already present. 210// A new binding is created if there is no binding for the role. 211func (p *Policy) Add(member string, r RoleName) { 212 b := p.binding(r) 213 if b == nil { 214 if p.InternalProto == nil { 215 p.InternalProto = &pb.Policy{} 216 } 217 p.InternalProto.Bindings = append(p.InternalProto.Bindings, &pb.Binding{ 218 Role: string(r), 219 Members: []string{member}, 220 }) 221 return 222 } 223 if memberIndex(member, b) < 0 { 224 b.Members = append(b.Members, member) 225 return 226 } 227} 228 229// Remove removes member from role r if it is present. 230func (p *Policy) Remove(member string, r RoleName) { 231 bi := p.bindingIndex(r) 232 if bi < 0 { 233 return 234 } 235 bindings := p.InternalProto.Bindings 236 b := bindings[bi] 237 mi := memberIndex(member, b) 238 if mi < 0 { 239 return 240 } 241 // Order doesn't matter for bindings or members, so to remove, move the last item 242 // into the removed spot and shrink the slice. 243 if len(b.Members) == 1 { 244 // Remove binding. 245 last := len(bindings) - 1 246 bindings[bi] = bindings[last] 247 bindings[last] = nil 248 p.InternalProto.Bindings = bindings[:last] 249 return 250 } 251 // Remove member. 252 // TODO(jba): worry about multiple copies of m? 253 last := len(b.Members) - 1 254 b.Members[mi] = b.Members[last] 255 b.Members[last] = "" 256 b.Members = b.Members[:last] 257} 258 259// Roles returns the names of all the roles that appear in the Policy. 260func (p *Policy) Roles() []RoleName { 261 if p.InternalProto == nil { 262 return nil 263 } 264 var rns []RoleName 265 for _, b := range p.InternalProto.Bindings { 266 rns = append(rns, RoleName(b.Role)) 267 } 268 return rns 269} 270 271// binding returns the Binding for the suppied role, or nil if there isn't one. 272func (p *Policy) binding(r RoleName) *pb.Binding { 273 i := p.bindingIndex(r) 274 if i < 0 { 275 return nil 276 } 277 return p.InternalProto.Bindings[i] 278} 279 280func (p *Policy) bindingIndex(r RoleName) int { 281 if p.InternalProto == nil { 282 return -1 283 } 284 for i, b := range p.InternalProto.Bindings { 285 if b.Role == string(r) { 286 return i 287 } 288 } 289 return -1 290} 291 292// memberIndex returns the index of m in b's Members, or -1 if not found. 293func memberIndex(m string, b *pb.Binding) int { 294 if b == nil { 295 return -1 296 } 297 for i, mm := range b.Members { 298 if mm == m { 299 return i 300 } 301 } 302 return -1 303} 304 305// insertMetadata inserts metadata into the given context 306func insertMetadata(ctx context.Context, mds ...metadata.MD) context.Context { 307 out, _ := metadata.FromOutgoingContext(ctx) 308 out = out.Copy() 309 for _, md := range mds { 310 for k, v := range md { 311 out[k] = append(out[k], v...) 312 } 313 } 314 return metadata.NewOutgoingContext(ctx, out) 315} 316