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 GetWithVersion(ctx context.Context, resource string, requestedPolicyVersion int32) (*pb.Policy, error) 42} 43 44// grpcClient implements client for the standard gRPC-based IAMPolicy service. 45type grpcClient struct { 46 c pb.IAMPolicyClient 47} 48 49var withRetry = gax.WithRetry(func() gax.Retryer { 50 return gax.OnCodes([]codes.Code{ 51 codes.DeadlineExceeded, 52 codes.Unavailable, 53 }, gax.Backoff{ 54 Initial: 100 * time.Millisecond, 55 Max: 60 * time.Second, 56 Multiplier: 1.3, 57 }) 58}) 59 60func (g *grpcClient) Get(ctx context.Context, resource string) (*pb.Policy, error) { 61 return g.GetWithVersion(ctx, resource, 1) 62} 63 64func (g *grpcClient) GetWithVersion(ctx context.Context, resource string, requestedPolicyVersion int32) (*pb.Policy, error) { 65 var proto *pb.Policy 66 md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "resource", resource)) 67 ctx = insertMetadata(ctx, md) 68 69 err := gax.Invoke(ctx, func(ctx context.Context, _ gax.CallSettings) error { 70 var err error 71 proto, err = g.c.GetIamPolicy(ctx, &pb.GetIamPolicyRequest{ 72 Resource: resource, 73 Options: &pb.GetPolicyOptions{ 74 RequestedPolicyVersion: requestedPolicyVersion, 75 }, 76 }) 77 return err 78 }, withRetry) 79 if err != nil { 80 return nil, err 81 } 82 return proto, nil 83} 84 85func (g *grpcClient) Set(ctx context.Context, resource string, p *pb.Policy) error { 86 md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "resource", resource)) 87 ctx = insertMetadata(ctx, md) 88 89 return gax.Invoke(ctx, func(ctx context.Context, _ gax.CallSettings) error { 90 _, err := g.c.SetIamPolicy(ctx, &pb.SetIamPolicyRequest{ 91 Resource: resource, 92 Policy: p, 93 }) 94 return err 95 }, withRetry) 96} 97 98func (g *grpcClient) Test(ctx context.Context, resource string, perms []string) ([]string, error) { 99 var res *pb.TestIamPermissionsResponse 100 md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "resource", resource)) 101 ctx = insertMetadata(ctx, md) 102 103 err := gax.Invoke(ctx, func(ctx context.Context, _ gax.CallSettings) error { 104 var err error 105 res, err = g.c.TestIamPermissions(ctx, &pb.TestIamPermissionsRequest{ 106 Resource: resource, 107 Permissions: perms, 108 }) 109 return err 110 }, withRetry) 111 if err != nil { 112 return nil, err 113 } 114 return res.Permissions, nil 115} 116 117// A Handle provides IAM operations for a resource. 118type Handle struct { 119 c client 120 resource string 121} 122 123// A Handle3 provides IAM operations for a resource. It is similar to a Handle, but provides access to newer IAM features (e.g., conditions). 124type Handle3 struct { 125 c client 126 resource string 127 version int32 128} 129 130// InternalNewHandle is for use by the Google Cloud Libraries only. 131// 132// InternalNewHandle returns a Handle for resource. 133// The conn parameter refers to a server that must support the IAMPolicy service. 134func InternalNewHandle(conn grpc.ClientConnInterface, resource string) *Handle { 135 return InternalNewHandleGRPCClient(pb.NewIAMPolicyClient(conn), resource) 136} 137 138// InternalNewHandleGRPCClient is for use by the Google Cloud Libraries only. 139// 140// InternalNewHandleClient returns a Handle for resource using the given 141// grpc service that implements IAM as a mixin 142func InternalNewHandleGRPCClient(c pb.IAMPolicyClient, resource string) *Handle { 143 return InternalNewHandleClient(&grpcClient{c: c}, resource) 144} 145 146// InternalNewHandleClient is for use by the Google Cloud Libraries only. 147// 148// InternalNewHandleClient returns a Handle for resource using the given 149// client implementation. 150func InternalNewHandleClient(c client, resource string) *Handle { 151 return &Handle{ 152 c: c, 153 resource: resource, 154 } 155} 156 157// V3 returns a Handle3, which is like Handle except it sets 158// requestedPolicyVersion to 3 when retrieving a policy and policy.version to 3 159// when storing a policy. 160func (h *Handle) V3() *Handle3 { 161 return &Handle3{ 162 c: h.c, 163 resource: h.resource, 164 version: 3, 165 } 166} 167 168// Policy retrieves the IAM policy for the resource. 169func (h *Handle) Policy(ctx context.Context) (*Policy, error) { 170 proto, err := h.c.Get(ctx, h.resource) 171 if err != nil { 172 return nil, err 173 } 174 return &Policy{InternalProto: proto}, nil 175} 176 177// SetPolicy replaces the resource's current policy with the supplied Policy. 178// 179// If policy was created from a prior call to Get, then the modification will 180// only succeed if the policy has not changed since the Get. 181func (h *Handle) SetPolicy(ctx context.Context, policy *Policy) error { 182 return h.c.Set(ctx, h.resource, policy.InternalProto) 183} 184 185// TestPermissions returns the subset of permissions that the caller has on the resource. 186func (h *Handle) TestPermissions(ctx context.Context, permissions []string) ([]string, error) { 187 return h.c.Test(ctx, h.resource, permissions) 188} 189 190// A RoleName is a name representing a collection of permissions. 191type RoleName string 192 193// Common role names. 194const ( 195 Owner RoleName = "roles/owner" 196 Editor RoleName = "roles/editor" 197 Viewer RoleName = "roles/viewer" 198) 199 200const ( 201 // AllUsers is a special member that denotes all users, even unauthenticated ones. 202 AllUsers = "allUsers" 203 204 // AllAuthenticatedUsers is a special member that denotes all authenticated users. 205 AllAuthenticatedUsers = "allAuthenticatedUsers" 206) 207 208// A Policy is a list of Bindings representing roles 209// granted to members. 210// 211// The zero Policy is a valid policy with no bindings. 212type Policy struct { 213 // TODO(jba): when type aliases are available, put Policy into an internal package 214 // and provide an exported alias here. 215 216 // This field is exported for use by the Google Cloud Libraries only. 217 // It may become unexported in a future release. 218 InternalProto *pb.Policy 219} 220 221// Members returns the list of members with the supplied role. 222// The return value should not be modified. Use Add and Remove 223// to modify the members of a role. 224func (p *Policy) Members(r RoleName) []string { 225 b := p.binding(r) 226 if b == nil { 227 return nil 228 } 229 return b.Members 230} 231 232// HasRole reports whether member has role r. 233func (p *Policy) HasRole(member string, r RoleName) bool { 234 return memberIndex(member, p.binding(r)) >= 0 235} 236 237// Add adds member member to role r if it is not already present. 238// A new binding is created if there is no binding for the role. 239func (p *Policy) Add(member string, r RoleName) { 240 b := p.binding(r) 241 if b == nil { 242 if p.InternalProto == nil { 243 p.InternalProto = &pb.Policy{} 244 } 245 p.InternalProto.Bindings = append(p.InternalProto.Bindings, &pb.Binding{ 246 Role: string(r), 247 Members: []string{member}, 248 }) 249 return 250 } 251 if memberIndex(member, b) < 0 { 252 b.Members = append(b.Members, member) 253 return 254 } 255} 256 257// Remove removes member from role r if it is present. 258func (p *Policy) Remove(member string, r RoleName) { 259 bi := p.bindingIndex(r) 260 if bi < 0 { 261 return 262 } 263 bindings := p.InternalProto.Bindings 264 b := bindings[bi] 265 mi := memberIndex(member, b) 266 if mi < 0 { 267 return 268 } 269 // Order doesn't matter for bindings or members, so to remove, move the last item 270 // into the removed spot and shrink the slice. 271 if len(b.Members) == 1 { 272 // Remove binding. 273 last := len(bindings) - 1 274 bindings[bi] = bindings[last] 275 bindings[last] = nil 276 p.InternalProto.Bindings = bindings[:last] 277 return 278 } 279 // Remove member. 280 // TODO(jba): worry about multiple copies of m? 281 last := len(b.Members) - 1 282 b.Members[mi] = b.Members[last] 283 b.Members[last] = "" 284 b.Members = b.Members[:last] 285} 286 287// Roles returns the names of all the roles that appear in the Policy. 288func (p *Policy) Roles() []RoleName { 289 if p.InternalProto == nil { 290 return nil 291 } 292 var rns []RoleName 293 for _, b := range p.InternalProto.Bindings { 294 rns = append(rns, RoleName(b.Role)) 295 } 296 return rns 297} 298 299// binding returns the Binding for the suppied role, or nil if there isn't one. 300func (p *Policy) binding(r RoleName) *pb.Binding { 301 i := p.bindingIndex(r) 302 if i < 0 { 303 return nil 304 } 305 return p.InternalProto.Bindings[i] 306} 307 308func (p *Policy) bindingIndex(r RoleName) int { 309 if p.InternalProto == nil { 310 return -1 311 } 312 for i, b := range p.InternalProto.Bindings { 313 if b.Role == string(r) { 314 return i 315 } 316 } 317 return -1 318} 319 320// memberIndex returns the index of m in b's Members, or -1 if not found. 321func memberIndex(m string, b *pb.Binding) int { 322 if b == nil { 323 return -1 324 } 325 for i, mm := range b.Members { 326 if mm == m { 327 return i 328 } 329 } 330 return -1 331} 332 333// insertMetadata inserts metadata into the given context 334func insertMetadata(ctx context.Context, mds ...metadata.MD) context.Context { 335 out, _ := metadata.FromOutgoingContext(ctx) 336 out = out.Copy() 337 for _, md := range mds { 338 for k, v := range md { 339 out[k] = append(out[k], v...) 340 } 341 } 342 return metadata.NewOutgoingContext(ctx, out) 343} 344 345// A Policy3 is a list of Bindings representing roles granted to members. 346// 347// The zero Policy3 is a valid policy with no bindings. 348// 349// It is similar to a Policy, except a Policy3 provides direct access to the 350// list of Bindings. 351// 352// The policy version is always set to 3. 353type Policy3 struct { 354 etag []byte 355 Bindings []*pb.Binding 356} 357 358// Policy retrieves the IAM policy for the resource. 359// 360// requestedPolicyVersion is always set to 3. 361func (h *Handle3) Policy(ctx context.Context) (*Policy3, error) { 362 proto, err := h.c.GetWithVersion(ctx, h.resource, h.version) 363 if err != nil { 364 return nil, err 365 } 366 return &Policy3{ 367 Bindings: proto.Bindings, 368 etag: proto.Etag, 369 }, nil 370} 371 372// SetPolicy replaces the resource's current policy with the supplied Policy. 373// 374// If policy was created from a prior call to Get, then the modification will 375// only succeed if the policy has not changed since the Get. 376func (h *Handle3) SetPolicy(ctx context.Context, policy *Policy3) error { 377 return h.c.Set(ctx, h.resource, &pb.Policy{ 378 Bindings: policy.Bindings, 379 Etag: policy.etag, 380 Version: h.version, 381 }) 382} 383 384// TestPermissions returns the subset of permissions that the caller has on the resource. 385func (h *Handle3) TestPermissions(ctx context.Context, permissions []string) ([]string, error) { 386 return h.c.Test(ctx, h.resource, permissions) 387} 388