Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Factor common IAM policy bits into 'google.cloud.iam'. #3188

Merged
merged 10 commits into from
Apr 17, 2017
Merged

Factor common IAM policy bits into 'google.cloud.iam'. #3188

merged 10 commits into from
Apr 17, 2017

Conversation

tseaver
Copy link
Contributor

@tseaver tseaver commented Mar 22, 2017

Toward #1679.

Pubsub-specific roles, permissions left behind in google.cloud.pubsub.iam.

google.cloud.pubsub.iam.Policy subclasses the core one, extending it to deal with the pubsub-specific roles.

@tseaver tseaver added api: pubsub Issues related to the Pub/Sub API. auth labels Mar 22, 2017
@tseaver tseaver requested review from daspecster and dhermes March 22, 2017 20:31
@googlebot googlebot added the cla: yes This human has signed the Contributor License Agreement. label Mar 22, 2017
@theacodes
Copy link
Contributor

/cc @elibixby

@@ -0,0 +1,217 @@
# Copyright 2016 Google Inc.

This comment was marked as spam.

@elibixby
Copy link

elibixby commented Mar 22, 2017

I had a previous proposal to do this which got abandoned due to slow review cycle, but it's here: See #2031

The key takeaway: the API representation of bindings is really gross to interact with. Client side it would be much nicer if bindings were a dict with roles as keys, and member sets as values. So instead of code like:

def add_role(policy, my_role, my_member):
  roles = [binding['role'] for binding in policy.bindings]
  if my_role in roles:
     members = policy.bindings[roles.index(my_role)]
     if my_member not in members:
       members.append(my_member)
  else:
     policy.bindings.append({'role': my_role, 'members': [my_member]})
  return policy

We had code like:

def add_role(policy, my_role, my_member):
  return policy.bindings.setdefault(my_role, set()).add(my_member)

"""
return 'allAuthenticatedUsers'

def _bind_custom_role(self, role, members):

This comment was marked as spam.

self.viewers = set()

@staticmethod
def user(email):

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

def __init__(self, etag=None, version=None):
self.etag = etag
self.version = version
self.owners = set()

This comment was marked as spam.

This comment was marked as spam.

@elibixby
Copy link

elibixby commented Mar 22, 2017

Additionally note that Roles are an API resource, and should be represented with an object. What does the resource.queryGrantableRoles method return? Raw JSON?

Both resource.queryGrantableRoles and resource.testIamPermissions are part of the IAM meta API and must be implemented on every resource that supports IAM.

Role resources will be user managed with the addition of custom roles (currently in alpha), and will have a list of iam permissions that they grant, which will be important for user protection of custom methods, and for identifying the necessary roles that a member needs to be granted. (combining resource.testIamPermissions and resource.queryGrantableRoles)

Further explanation of suggestions like these can be found in comment threads in #2031

EDIT: Slight correction: testIamPermissions is indeed part of the meta API, but queryGrantableRoles calls directly to the IAM API, but can be called with ANY one platform resource string, so it might make more sense to have it as a method on a Resource object that implements an IAM mixin anyway.

EDIT2: This can probably be tackled in a followup PR, but should be considered when defining the interface? Do we want to make policy.bindings a map <string: set> or a map <Role: set> I think likely still the former, since we wont be receiving role metadata along with a policy, but it's worth consideration.

@staticmethod
def user(email):
"""Factory method for a user member.
def _bind_custom_role(self, role, members):

This comment was marked as spam.

@tseaver
Copy link
Contributor Author

tseaver commented Mar 23, 2017

@elibixby I think I have addressed your concerns about supporting user-defined roles in b625017.

re: queryGrantableRoles: We can't propely target implementing support for not-yet-released APIs in the public repository: my current writ is to get storage up to the same level of IAM support that pubsub currently has. I will be adding storage-specific implementations of get_iam_policy, set_iam_policy, and test_iam_permissions in a separate PR.

@elibixby
Copy link

elibixby commented Mar 23, 2017

@tseaver queryGrantableRoles isn't unreleased...https://cloud.google.com/iam/reference/rest/v1/roles/queryGrantableRoles

But I agree that this can be tackled in a follow-up PR.

Long term I still believe a common implementatoin for One Platform APIs will save you the most heartache. Storage is an oddball because it is so old (i.e. not a One Platform API) and IAM support was "backported" in a slightly weird way. But going forward all OP APIs will share a common IAM signature, and a Mixin could provide drop in support for that.

policy.viewers |= members
else:
policy._bind_custom_role(role, members)
members = sorted(binding['members'])

This comment was marked as spam.

resource['bindings'] = bindings
if len(self.bindings) > 0:
resource['bindings'] = [
{'role': role, 'members': members}

This comment was marked as spam.

for role in self._OWNER_ROLES:
for member in self.bindings.get(role, ()):
result.add(member)
return result

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

@lukesneeringer
Copy link
Contributor

EDIT @lukesneeringer I think doing @tseaver approach and then moving to my approach immediately before GA is a reasonable approach, thoughts @tseaver?

This is fine with me also.

@lukesneeringer
Copy link
Contributor

@tseaver Checking on the status of this. :-)

tseaver added 6 commits April 5, 2017 17:22
Pubsub-specific roles, permissions left behind in 'google.cloud.pubsub.iam'.

'google.cloud.pubsub.iam.Policy' subclasses the core one, extending it
to deal with the pubsub-specific roles.
- google.cloud.iam.Policy holds a 'bindings' mapping, which doesn't
  enforce using known roles.

- Its 'owners', 'editors', and 'viewers' are now properties which
  indirect over that 'bindings' attribute.  Note that this is a
  breaking change, as users who relied on mutating one of those sets
  (rather than re-assigning it) will need to update.
- Don't pass roles w/ empty members to back-end.
- De-duplicate role members when passing to back-end.
- Re-assign 'policy.viewers'/'policy.editors', rather than mutating them
  in place.
@tseaver
Copy link
Contributor Author

tseaver commented Apr 5, 2017

@lukesneeringer I'm back at this, but can't figure out what the desired resolution would be. Here is a sketch at a way forward:

  • Return frozenset instances from the getters for owners, editors, and viewers (breaking anybody who expects to mutate them in place, rather than re-assigning them).
  • Emit DeprecationWarning from the setters for those properties, pointing people to the desired surface.

Then, the question is what the desired surface would be: I'm really leery of exposing the mutable bindings attribute as a dict: it allows us no way to do any checking at all. We could:

  • Make the Policy class dict-like, with roles as keys and lists of principals as values. The __setitem__ implementation would give us the hook to do any required checking.

  • Make Policy.bindings a dict-like object with a similar __setitem__.

  • Add a Policy.bind(role, *principals) method, and use whatever backing store we like.

Updating them in place never actually worked (they were sets created
on the fly), but at least we give an appropriate error now if the
user tries.
@lukesneeringer
Copy link
Contributor

My personal preference is the first option (making Policy dict-like and defining __setitem__ to include appropriate validation).

@tseaver
Copy link
Contributor Author

tseaver commented Apr 6, 2017

@lukesneeringer That is my preference as well. Update on the way.

@tseaver
Copy link
Contributor Author

tseaver commented Apr 6, 2017

@lukesneeringer OK, I've updated Policy to be dict-like, and added deprecations for assignment to the "named" role attributes.

Copy link
Contributor

@lukesneeringer lukesneeringer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved, but giving @elibixby a chance to weigh in before merge.

return self._bindings[key]

def __setitem__(self, key, value):
self._bindings[key] = value

This comment was marked as spam.

@lukesneeringer
Copy link
Contributor

Merging this when CI goes green.

@lukesneeringer lukesneeringer self-assigned this Apr 12, 2017
@tseaver tseaver merged commit 3b8c9f6 into googleapis:master Apr 17, 2017
@tseaver tseaver deleted the 1679-generalize-pubsub-iam-for-storage branch April 17, 2017 20:13
richkadel pushed a commit to richkadel/google-cloud-python that referenced this pull request May 6, 2017
…b-iam-for-storage

Factor common IAM policy bits into 'google.cloud.iam'.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api: pubsub Issues related to the Pub/Sub API. auth cla: yes This human has signed the Contributor License Agreement.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants