-
Notifications
You must be signed in to change notification settings - Fork 52
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
Conversation
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.
For now there will be only one role
Via traitlets I'd like to losely enforce this model and make it easy to customize permissions for given users/services. |
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: 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:
|
Namespaces == domains. Within a domain an entity has a role:
Roles will be pluggable e.g.:
Conda-Store should have pluggable roles that allow for custom roles To support hierarchical naming domains will have the matching So in practice the implementation would be for a given entity to
|
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']))
|
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).