817dd286c1
Signed-off-by: Flavian Missi <fmissi@redhat.com>
387 lines
11 KiB
Go
387 lines
11 KiB
Go
// Copyright 2016 Google LLC
|
|
//
|
|
// 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 iam supports the resource-specific operations of Google Cloud
|
|
// IAM (Identity and Access Management) for the Google Cloud Libraries.
|
|
// See https://cloud.google.com/iam for more about IAM.
|
|
//
|
|
// Users of the Google Cloud Libraries will typically not use this package
|
|
// directly. Instead they will begin with some resource that supports IAM, like
|
|
// a pubsub topic, and call its IAM method to get a Handle for that resource.
|
|
package iam
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
pb "cloud.google.com/go/iam/apiv1/iampb"
|
|
gax "github.com/googleapis/gax-go/v2"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/metadata"
|
|
)
|
|
|
|
// client abstracts the IAMPolicy API to allow multiple implementations.
|
|
type client interface {
|
|
Get(ctx context.Context, resource string) (*pb.Policy, error)
|
|
Set(ctx context.Context, resource string, p *pb.Policy) error
|
|
Test(ctx context.Context, resource string, perms []string) ([]string, error)
|
|
GetWithVersion(ctx context.Context, resource string, requestedPolicyVersion int32) (*pb.Policy, error)
|
|
}
|
|
|
|
// grpcClient implements client for the standard gRPC-based IAMPolicy service.
|
|
type grpcClient struct {
|
|
c pb.IAMPolicyClient
|
|
}
|
|
|
|
var withRetry = gax.WithRetry(func() gax.Retryer {
|
|
return gax.OnCodes([]codes.Code{
|
|
codes.DeadlineExceeded,
|
|
codes.Unavailable,
|
|
}, gax.Backoff{
|
|
Initial: 100 * time.Millisecond,
|
|
Max: 60 * time.Second,
|
|
Multiplier: 1.3,
|
|
})
|
|
})
|
|
|
|
func (g *grpcClient) Get(ctx context.Context, resource string) (*pb.Policy, error) {
|
|
return g.GetWithVersion(ctx, resource, 1)
|
|
}
|
|
|
|
func (g *grpcClient) GetWithVersion(ctx context.Context, resource string, requestedPolicyVersion int32) (*pb.Policy, error) {
|
|
var proto *pb.Policy
|
|
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "resource", resource))
|
|
ctx = insertMetadata(ctx, md)
|
|
|
|
err := gax.Invoke(ctx, func(ctx context.Context, _ gax.CallSettings) error {
|
|
var err error
|
|
proto, err = g.c.GetIamPolicy(ctx, &pb.GetIamPolicyRequest{
|
|
Resource: resource,
|
|
Options: &pb.GetPolicyOptions{
|
|
RequestedPolicyVersion: requestedPolicyVersion,
|
|
},
|
|
})
|
|
return err
|
|
}, withRetry)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return proto, nil
|
|
}
|
|
|
|
func (g *grpcClient) Set(ctx context.Context, resource string, p *pb.Policy) error {
|
|
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "resource", resource))
|
|
ctx = insertMetadata(ctx, md)
|
|
|
|
return gax.Invoke(ctx, func(ctx context.Context, _ gax.CallSettings) error {
|
|
_, err := g.c.SetIamPolicy(ctx, &pb.SetIamPolicyRequest{
|
|
Resource: resource,
|
|
Policy: p,
|
|
})
|
|
return err
|
|
}, withRetry)
|
|
}
|
|
|
|
func (g *grpcClient) Test(ctx context.Context, resource string, perms []string) ([]string, error) {
|
|
var res *pb.TestIamPermissionsResponse
|
|
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "resource", resource))
|
|
ctx = insertMetadata(ctx, md)
|
|
|
|
err := gax.Invoke(ctx, func(ctx context.Context, _ gax.CallSettings) error {
|
|
var err error
|
|
res, err = g.c.TestIamPermissions(ctx, &pb.TestIamPermissionsRequest{
|
|
Resource: resource,
|
|
Permissions: perms,
|
|
})
|
|
return err
|
|
}, withRetry)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return res.Permissions, nil
|
|
}
|
|
|
|
// A Handle provides IAM operations for a resource.
|
|
type Handle struct {
|
|
c client
|
|
resource string
|
|
}
|
|
|
|
// A Handle3 provides IAM operations for a resource. It is similar to a Handle, but provides access to newer IAM features (e.g., conditions).
|
|
type Handle3 struct {
|
|
c client
|
|
resource string
|
|
version int32
|
|
}
|
|
|
|
// InternalNewHandle is for use by the Google Cloud Libraries only.
|
|
//
|
|
// InternalNewHandle returns a Handle for resource.
|
|
// The conn parameter refers to a server that must support the IAMPolicy service.
|
|
func InternalNewHandle(conn grpc.ClientConnInterface, resource string) *Handle {
|
|
return InternalNewHandleGRPCClient(pb.NewIAMPolicyClient(conn), resource)
|
|
}
|
|
|
|
// InternalNewHandleGRPCClient is for use by the Google Cloud Libraries only.
|
|
//
|
|
// InternalNewHandleClient returns a Handle for resource using the given
|
|
// grpc service that implements IAM as a mixin
|
|
func InternalNewHandleGRPCClient(c pb.IAMPolicyClient, resource string) *Handle {
|
|
return InternalNewHandleClient(&grpcClient{c: c}, resource)
|
|
}
|
|
|
|
// InternalNewHandleClient is for use by the Google Cloud Libraries only.
|
|
//
|
|
// InternalNewHandleClient returns a Handle for resource using the given
|
|
// client implementation.
|
|
func InternalNewHandleClient(c client, resource string) *Handle {
|
|
return &Handle{
|
|
c: c,
|
|
resource: resource,
|
|
}
|
|
}
|
|
|
|
// V3 returns a Handle3, which is like Handle except it sets
|
|
// requestedPolicyVersion to 3 when retrieving a policy and policy.version to 3
|
|
// when storing a policy.
|
|
func (h *Handle) V3() *Handle3 {
|
|
return &Handle3{
|
|
c: h.c,
|
|
resource: h.resource,
|
|
version: 3,
|
|
}
|
|
}
|
|
|
|
// Policy retrieves the IAM policy for the resource.
|
|
func (h *Handle) Policy(ctx context.Context) (*Policy, error) {
|
|
proto, err := h.c.Get(ctx, h.resource)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &Policy{InternalProto: proto}, nil
|
|
}
|
|
|
|
// SetPolicy replaces the resource's current policy with the supplied Policy.
|
|
//
|
|
// If policy was created from a prior call to Get, then the modification will
|
|
// only succeed if the policy has not changed since the Get.
|
|
func (h *Handle) SetPolicy(ctx context.Context, policy *Policy) error {
|
|
return h.c.Set(ctx, h.resource, policy.InternalProto)
|
|
}
|
|
|
|
// TestPermissions returns the subset of permissions that the caller has on the resource.
|
|
func (h *Handle) TestPermissions(ctx context.Context, permissions []string) ([]string, error) {
|
|
return h.c.Test(ctx, h.resource, permissions)
|
|
}
|
|
|
|
// A RoleName is a name representing a collection of permissions.
|
|
type RoleName string
|
|
|
|
// Common role names.
|
|
const (
|
|
Owner RoleName = "roles/owner"
|
|
Editor RoleName = "roles/editor"
|
|
Viewer RoleName = "roles/viewer"
|
|
)
|
|
|
|
const (
|
|
// AllUsers is a special member that denotes all users, even unauthenticated ones.
|
|
AllUsers = "allUsers"
|
|
|
|
// AllAuthenticatedUsers is a special member that denotes all authenticated users.
|
|
AllAuthenticatedUsers = "allAuthenticatedUsers"
|
|
)
|
|
|
|
// A Policy is a list of Bindings representing roles
|
|
// granted to members.
|
|
//
|
|
// The zero Policy is a valid policy with no bindings.
|
|
type Policy struct {
|
|
// TODO(jba): when type aliases are available, put Policy into an internal package
|
|
// and provide an exported alias here.
|
|
|
|
// This field is exported for use by the Google Cloud Libraries only.
|
|
// It may become unexported in a future release.
|
|
InternalProto *pb.Policy
|
|
}
|
|
|
|
// Members returns the list of members with the supplied role.
|
|
// The return value should not be modified. Use Add and Remove
|
|
// to modify the members of a role.
|
|
func (p *Policy) Members(r RoleName) []string {
|
|
b := p.binding(r)
|
|
if b == nil {
|
|
return nil
|
|
}
|
|
return b.Members
|
|
}
|
|
|
|
// HasRole reports whether member has role r.
|
|
func (p *Policy) HasRole(member string, r RoleName) bool {
|
|
return memberIndex(member, p.binding(r)) >= 0
|
|
}
|
|
|
|
// Add adds member member to role r if it is not already present.
|
|
// A new binding is created if there is no binding for the role.
|
|
func (p *Policy) Add(member string, r RoleName) {
|
|
b := p.binding(r)
|
|
if b == nil {
|
|
if p.InternalProto == nil {
|
|
p.InternalProto = &pb.Policy{}
|
|
}
|
|
p.InternalProto.Bindings = append(p.InternalProto.Bindings, &pb.Binding{
|
|
Role: string(r),
|
|
Members: []string{member},
|
|
})
|
|
return
|
|
}
|
|
if memberIndex(member, b) < 0 {
|
|
b.Members = append(b.Members, member)
|
|
return
|
|
}
|
|
}
|
|
|
|
// Remove removes member from role r if it is present.
|
|
func (p *Policy) Remove(member string, r RoleName) {
|
|
bi := p.bindingIndex(r)
|
|
if bi < 0 {
|
|
return
|
|
}
|
|
bindings := p.InternalProto.Bindings
|
|
b := bindings[bi]
|
|
mi := memberIndex(member, b)
|
|
if mi < 0 {
|
|
return
|
|
}
|
|
// Order doesn't matter for bindings or members, so to remove, move the last item
|
|
// into the removed spot and shrink the slice.
|
|
if len(b.Members) == 1 {
|
|
// Remove binding.
|
|
last := len(bindings) - 1
|
|
bindings[bi] = bindings[last]
|
|
bindings[last] = nil
|
|
p.InternalProto.Bindings = bindings[:last]
|
|
return
|
|
}
|
|
// Remove member.
|
|
// TODO(jba): worry about multiple copies of m?
|
|
last := len(b.Members) - 1
|
|
b.Members[mi] = b.Members[last]
|
|
b.Members[last] = ""
|
|
b.Members = b.Members[:last]
|
|
}
|
|
|
|
// Roles returns the names of all the roles that appear in the Policy.
|
|
func (p *Policy) Roles() []RoleName {
|
|
if p.InternalProto == nil {
|
|
return nil
|
|
}
|
|
var rns []RoleName
|
|
for _, b := range p.InternalProto.Bindings {
|
|
rns = append(rns, RoleName(b.Role))
|
|
}
|
|
return rns
|
|
}
|
|
|
|
// binding returns the Binding for the suppied role, or nil if there isn't one.
|
|
func (p *Policy) binding(r RoleName) *pb.Binding {
|
|
i := p.bindingIndex(r)
|
|
if i < 0 {
|
|
return nil
|
|
}
|
|
return p.InternalProto.Bindings[i]
|
|
}
|
|
|
|
func (p *Policy) bindingIndex(r RoleName) int {
|
|
if p.InternalProto == nil {
|
|
return -1
|
|
}
|
|
for i, b := range p.InternalProto.Bindings {
|
|
if b.Role == string(r) {
|
|
return i
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
// memberIndex returns the index of m in b's Members, or -1 if not found.
|
|
func memberIndex(m string, b *pb.Binding) int {
|
|
if b == nil {
|
|
return -1
|
|
}
|
|
for i, mm := range b.Members {
|
|
if mm == m {
|
|
return i
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
// insertMetadata inserts metadata into the given context
|
|
func insertMetadata(ctx context.Context, mds ...metadata.MD) context.Context {
|
|
out, _ := metadata.FromOutgoingContext(ctx)
|
|
out = out.Copy()
|
|
for _, md := range mds {
|
|
for k, v := range md {
|
|
out[k] = append(out[k], v...)
|
|
}
|
|
}
|
|
return metadata.NewOutgoingContext(ctx, out)
|
|
}
|
|
|
|
// A Policy3 is a list of Bindings representing roles granted to members.
|
|
//
|
|
// The zero Policy3 is a valid policy with no bindings.
|
|
//
|
|
// It is similar to a Policy, except a Policy3 provides direct access to the
|
|
// list of Bindings.
|
|
//
|
|
// The policy version is always set to 3.
|
|
type Policy3 struct {
|
|
etag []byte
|
|
Bindings []*pb.Binding
|
|
}
|
|
|
|
// Policy retrieves the IAM policy for the resource.
|
|
//
|
|
// requestedPolicyVersion is always set to 3.
|
|
func (h *Handle3) Policy(ctx context.Context) (*Policy3, error) {
|
|
proto, err := h.c.GetWithVersion(ctx, h.resource, h.version)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &Policy3{
|
|
Bindings: proto.Bindings,
|
|
etag: proto.Etag,
|
|
}, nil
|
|
}
|
|
|
|
// SetPolicy replaces the resource's current policy with the supplied Policy.
|
|
//
|
|
// If policy was created from a prior call to Get, then the modification will
|
|
// only succeed if the policy has not changed since the Get.
|
|
func (h *Handle3) SetPolicy(ctx context.Context, policy *Policy3) error {
|
|
return h.c.Set(ctx, h.resource, &pb.Policy{
|
|
Bindings: policy.Bindings,
|
|
Etag: policy.etag,
|
|
Version: h.version,
|
|
})
|
|
}
|
|
|
|
// TestPermissions returns the subset of permissions that the caller has on the resource.
|
|
func (h *Handle3) TestPermissions(ctx context.Context, permissions []string) ([]string, error) {
|
|
return h.c.Test(ctx, h.resource, permissions)
|
|
}
|