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

Conda-store Authentication/Authorization #97

Merged
merged 15 commits into from
Aug 4, 2021
Merged

Conda-store Authentication/Authorization #97

merged 15 commits into from
Aug 4, 2021

Conversation

costrouc
Copy link
Member

@costrouc costrouc commented Jul 26, 2021

Closes #62. This is a large PR in the works. As this progresses I'll detail the authorization model that is chosen along with making the authentication hopefully (nearly) as flexible as jupyterhub. Initial target is support for generic oauth providers (but specifically jupyterhub).

@costrouc
Copy link
Member Author

costrouc commented Jul 28, 2021

image

@costrouc
Copy link
Member Author

costrouc commented Jul 28, 2021

I would like to adopt an rbac model for authentication. The idea is that it would be possible to create tokens that represent the following actions for a set number of namespaces.

  • entity :: user, service via token
  • resource :: <namespace>/<name>
  • permissible actions (can be more in the future)
    • environment::create :: entity can create a new environment with namespace/name
    • environment::read :: entity has permissions to read all builds within a given environment
    • environment::update :: entity has permissions to change the default environment build from <build x> -> <build y>
    • environment::delete :: entity has permissions to delete a given environment and all associated builds or a single build within the environment

For now there will be only one role admin, user, service:

  • admin will effectively have on resources */* actions environment::*`
  • user or service will have:
    • actions environment/* on any namespace the user is a member of (username + groups)
    • actions environment::read on any namespace that is in conda_store.read_only_all_namespaces

Via traitlets I'd like to losely enforce this model and make it easy to customize permissions for given users/services.

@costrouc
Copy link
Member Author

costrouc commented Jul 28, 2021

Going to use a jwt to store the authentication credentials for a user/service. Notice how this jwt has no concept of a "user" and this is on purpose only a primary_namespace is used to represent a user.

primary_namespace: default_namespace_to_create_with
permissions:
  - resource: "namespace1/*"
    actions: ["environment::*"]
  ....

The idea here would be that conda-store could be statelesss for authentication and authorization. The jwt would clearly specify the permissions of the given user.

I could imagine things like:

  • quansight-*/* being supported as a resource which would allow powerful namespaceing / hierarchies.

@costrouc
Copy link
Member Author

costrouc commented Jul 28, 2021

Namespaces == domains.

Within a domain an entity has a role:

  • none -> default role for everyone
  • viewer -> environment::read
  • developer -> environment::read + environment::update + environment::create
  • admin -> environment::*

Roles will be pluggable e.g.:

  • mydeleterole -> environment::delete action

Conda-Store should have pluggable roles that allow for custom roles
that map to actions. So if a user has roles admin + user on a
given domain their available actions is the union of admin actions and
user actions.

To support hierarchical naming domains will have the matching
quansight-*.

So in practice the implementation would be for a given entity to
perform action(s) on a domain.

  1. which domain is the entity trying to access?

  2. what are the default roles for all entities on the given resources?
    E.g. this domain has the default viewer role.

  3. what are the roles for a given entity in that given domain?

  4. union of entity role actions + domain default role actions

  5. what are the entities actions a superset of the required actions for the operation

  6. deny/allow the user to perform given action(s)

@costrouc
Copy link
Member Author

An implementation for authorization

# user chris requests
# DELETE https://conda-store.com/api/v1/environment/example-namespace/example-name
request = {
    # <domain/namespace>/<resource group/name>
    'arn': 'example-namespace/example-name',
    'actions': {'environment::delete'},
}

# user chris roles
entity_authorization = {
   'example-*/*': ['viewer'],
   'chris/*': ['admin'],
}

role_mappings = {
    'viewer': {'environment::read'},
    'admin': {'environment::read', 'environment::update', 'environment::create', 'environment::delete'}
}

# user will not have acccess to resource
# arn resolves to example-namespace/example-name.

import re

ARN_ALLOWED_REGEX = re.compile('[A-Za-z\_\-\*]+/[A-Za-z\_\-\*]+')

def compile_arn(arn):
    if not ARN_ALLOWED_REGEX.match(arn):
        raise ValueError(f'invalid arn={arn}')

    regex_arn = '^' + re.sub('\*', '[A-Za-z_\-]*', arn) + '$'
    return re.compile(regex_arn)

print('should match', compile_arn('e-*/e*').match('e-a/e'))
print('should be none', compile_arn('e-*/e*').match('ea/e'))

def get_entity_roles(arn, entity_authorization):
    roles = set()
    for entity_arn, entity_roles in entity_authorization.items():
        if compile_arn(entity_arn).match(arn):
            roles = roles | set(entity_roles)
    return roles

allowed_roles = get_entity_roles(request['arn'], entity_authorization)
print(allowed_roles)

def roles_to_permissions(roles, role_mappings):
    permissions = set()
    for role in roles:
        permissions = permissions | role_mappings[role]
    return permissions

allowed_permissions = roles_to_permissions(allowed_roles, role_mappings)
print(allowed_permissions)

def authorized(allowed_permissions, required_permissions):
    return required_permissions <= allowed_permissions

print('allowed?', authorized(allowed_permissions, request['actions']))
should match <re.Match object; span=(0, 5), match='e-a/e'>
should be none None
{'viewer'}
{'environment::read'}
allowed? False

@costrouc costrouc merged commit 928ba38 into main Aug 4, 2021
@costrouc costrouc deleted the authentication branch August 4, 2021 18:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Conda Store Authentication Mechanism
1 participant