diff --git a/deli/counter/auth/policy.py b/deli/counter/auth/permission.py similarity index 95% rename from deli/counter/auth/policy.py rename to deli/counter/auth/permission.py index 4d5b5ba..9b4fd15 100644 --- a/deli/counter/auth/policy.py +++ b/deli/counter/auth/permission.py @@ -1,4 +1,4 @@ -SYSTEM_POLICIES = [ +SYSTEM_PERMISSIONS = [ # Roles { "name": "roles:system:create", @@ -20,6 +20,15 @@ "name": "roles:system:delete", "description": "Ability to delete a system role" }, + # Policy + { + "name": "policy:system:get", + "description": "Ability to get system policies" + }, + { + "name": "policy:system:set", + "description": "Ability to set system policies" + }, # Flavors { "name": "flavors:create", @@ -105,7 +114,7 @@ }, ] -PROJECT_POLICIES = [ +PROJECT_PERMISSIONS = [ # Project { "name": "projects:get", @@ -165,6 +174,15 @@ 'editor' ] }, + # Policy + { + "name": "policy:project:get", + "description": "Ability to get project policies" + }, + { + "name": "policy:project:set", + "description": "Ability to set project policies" + }, # Volumes { "name": "volumes:create", diff --git a/deli/counter/auth/token.py b/deli/counter/auth/token.py index d7f0711..d3a946d 100644 --- a/deli/counter/auth/token.py +++ b/deli/counter/auth/token.py @@ -10,7 +10,7 @@ from jose import jwt from simple_settings import settings -from deli.counter.auth.policy import SYSTEM_POLICIES +from deli.counter.auth.permission import SYSTEM_PERMISSIONS from deli.kubernetes.resources.project import Project from deli.kubernetes.resources.v1alpha1.iam_group.model import IAMSystemGroup from deli.kubernetes.resources.v1alpha1.iam_policy.model import IAMPolicy @@ -196,14 +196,15 @@ def get_projects(self) -> List[Project]: return projects - def enforce_policy(self, policy, project=None): + def enforce_permission(self, permission, project=None): if len(self.system_roles) > 0: - if policy in [p['name'] for p in SYSTEM_POLICIES]: + if permission in [p['name'] for p in SYSTEM_PERMISSIONS]: for role_name in self.system_roles: role = IAMSystemRole.get(role_name) - if role is not None and policy in role.policies: + if role is not None and permission in role.permissions: return - raise cherrypy.HTTPError(403, "Insufficient permissions (%s) to perform the requested action." % policy) + raise cherrypy.HTTPError(403, + "Insufficient permissions (%s) to perform the requested action." % permission) if project is not None: project_policy = IAMPolicy.get(project.name) @@ -212,10 +213,10 @@ def enforce_policy(self, policy, project=None): project_roles = self.find_roles(project_policy) for role_name in project_roles: role = IAMProjectRole.get(project, role_name) - if role is not None and policy in role.policies: + if role is not None and permission in role.permissions: return raise cherrypy.HTTPError(403, "Insufficient permissions (%s) to perform the " - "requested action in the project %s." % (policy, project.name)) + "requested action in the project %s." % (permission, project.name)) - raise cherrypy.HTTPError(403, "Insufficient permissions (%s) to perform the requested action." % policy) + raise cherrypy.HTTPError(403, "Insufficient permissions (%s) to perform the requested action." % permission) diff --git a/deli/counter/http/mounts/root/mount.py b/deli/counter/http/mounts/root/mount.py index a6c4431..a75a79e 100644 --- a/deli/counter/http/mounts/root/mount.py +++ b/deli/counter/http/mounts/root/mount.py @@ -46,11 +46,11 @@ def validate_token(self): # The email will contain the project cherrypy.request.login = token.email + '/' + token.metadata['instance'] - def enforce_policy(self, policy_name): + def enforce_permission(self, permission_name): project = None if hasattr(cherrypy.request, 'project'): project = cherrypy.request.project - cherrypy.request.token.enforce_policy(policy_name, project=project) + cherrypy.request.token.enforce_permission(permission_name, project=project) def validate_project_scope(self, delete_param=False): if 'project_name' in cherrypy.request.params: @@ -82,7 +82,7 @@ def __setup_tools(self): cherrypy.tools.project_scope = cherrypy.Tool('on_start_resource', self.validate_project_scope, priority=30) cherrypy.tools.resource_object = cherrypy.Tool('before_request_body', self.resource_object, priority=40) - cherrypy.tools.enforce_policy = cherrypy.Tool('before_request_body', self.enforce_policy, priority=50) + cherrypy.tools.enforce_permission = cherrypy.Tool('before_request_body', self.enforce_permission, priority=50) def __setup_kubernetes(self): if settings.KUBE_CONFIG is not None or settings.KUBE_MASTER is not None: diff --git a/deli/counter/http/mounts/root/routes/compute/v1/flavor.py b/deli/counter/http/mounts/root/routes/compute/v1/flavor.py index 25541be..67bf280 100644 --- a/deli/counter/http/mounts/root/routes/compute/v1/flavor.py +++ b/deli/counter/http/mounts/root/routes/compute/v1/flavor.py @@ -17,7 +17,7 @@ def __init__(self): @Route(methods=[RequestMethods.POST]) @cherrypy.tools.model_in(cls=RequestCreateFlavor) @cherrypy.tools.model_out(cls=ResponseFlavor) - @cherrypy.tools.enforce_policy(policy_name="flavors:create") + @cherrypy.tools.enforce_permission(permission_name="flavors:create") def create(self): """Create a flavor --- @@ -85,7 +85,7 @@ def list(self, limit, marker): @Route(route='{flavor_name}', methods=[RequestMethods.DELETE]) @cherrypy.tools.model_params(cls=ParamsFlavor) @cherrypy.tools.resource_object(id_param="flavor_name", cls=Flavor) - @cherrypy.tools.enforce_policy(policy_name="flavors:delete") + @cherrypy.tools.enforce_permission(permission_name="flavors:delete") def delete(self, **_): """Delete a Flavor --- diff --git a/deli/counter/http/mounts/root/routes/compute/v1/images.py b/deli/counter/http/mounts/root/routes/compute/v1/images.py index 770767b..2d4ae10 100644 --- a/deli/counter/http/mounts/root/routes/compute/v1/images.py +++ b/deli/counter/http/mounts/root/routes/compute/v1/images.py @@ -21,7 +21,7 @@ def __init__(self): @Route(methods=[RequestMethods.POST]) @cherrypy.tools.model_in(cls=RequestCreateImage) @cherrypy.tools.model_out(cls=ResponseImage) - @cherrypy.tools.enforce_policy(policy_name="images:create") + @cherrypy.tools.enforce_permission(permission_name="images:create") def create(self): """Create an image --- @@ -66,7 +66,7 @@ def create(self): @cherrypy.tools.model_params(cls=ParamsImage) @cherrypy.tools.model_out(cls=ResponseImage) @cherrypy.tools.resource_object(id_param="image_name", cls=Image) - @cherrypy.tools.enforce_policy(policy_name="images:get") + @cherrypy.tools.enforce_permission(permission_name="images:get") def get(self, **_): """Get an image --- @@ -86,7 +86,7 @@ def get(self, **_): @Route() @cherrypy.tools.model_params(cls=ParamsListImage) @cherrypy.tools.model_out_pagination(cls=ResponseImage) - @cherrypy.tools.enforce_policy(policy_name="images:list") + @cherrypy.tools.enforce_permission(permission_name="images:list") def list(self, region_name, visibility: ImageVisibility, limit: int, marker: uuid.UUID): """List images --- @@ -118,7 +118,7 @@ def list(self, region_name, visibility: ImageVisibility, limit: int, marker: uui @Route(route='{image_name}', methods=[RequestMethods.DELETE]) @cherrypy.tools.model_params(cls=ParamsImage) @cherrypy.tools.resource_object(id_param="image_name", cls=Image) - @cherrypy.tools.enforce_policy(policy_name="images:delete") + @cherrypy.tools.enforce_permission(permission_name="images:delete") def delete(self, **_): """Delete an image --- diff --git a/deli/counter/http/mounts/root/routes/compute/v1/instance.py b/deli/counter/http/mounts/root/routes/compute/v1/instance.py index 16c7381..3080a8c 100644 --- a/deli/counter/http/mounts/root/routes/compute/v1/instance.py +++ b/deli/counter/http/mounts/root/routes/compute/v1/instance.py @@ -31,7 +31,7 @@ def __init__(self): @Route(methods=[RequestMethods.POST]) @cherrypy.tools.model_in(cls=RequestCreateInstance) @cherrypy.tools.model_out(cls=ResponseInstance) - @cherrypy.tools.enforce_policy(policy_name="instances:create") + @cherrypy.tools.enforce_permission(permission_name="instances:create") def create(self): """Create an instance --- @@ -180,7 +180,7 @@ def create(self): @cherrypy.tools.model_params(cls=ParamsInstance) @cherrypy.tools.model_out(cls=ResponseInstance) @cherrypy.tools.resource_object(id_param="instance_name", cls=Instance) - @cherrypy.tools.enforce_policy(policy_name="instances:get") + @cherrypy.tools.enforce_permission(permission_name="instances:get") def get(self, **_): """Get an instance --- @@ -198,7 +198,7 @@ def get(self, **_): @Route() @cherrypy.tools.model_params(cls=ParamsListInstance) @cherrypy.tools.model_out_pagination(cls=ResponseInstance) - @cherrypy.tools.enforce_policy(policy_name="instances:list") + @cherrypy.tools.enforce_permission(permission_name="instances:list") def list(self, image_name, region_name, zone_name, limit: int, marker: uuid.UUID): """List instances --- @@ -244,7 +244,7 @@ def list(self, image_name, region_name, zone_name, limit: int, marker: uuid.UUID @Route(route='{instance_name}', methods=[RequestMethods.DELETE]) @cherrypy.tools.model_params(cls=ParamsInstance) @cherrypy.tools.resource_object(id_param="instance_name", cls=Instance) - @cherrypy.tools.enforce_policy(policy_name="instances:delete") + @cherrypy.tools.enforce_permission(permission_name="instances:delete") def delete(self, **_): """Delete an instance --- @@ -272,7 +272,7 @@ def delete(self, **_): @Route(route='{instance_name}/action/start', methods=[RequestMethods.PUT]) @cherrypy.tools.model_params(cls=ParamsInstance) @cherrypy.tools.resource_object(id_param="instance_name", cls=Instance) - @cherrypy.tools.enforce_policy(policy_name="instances:action:stop") + @cherrypy.tools.enforce_permission(permission_name="instances:action:stop") def action_start(self, **_): """Start an instance --- @@ -302,7 +302,7 @@ def action_start(self, **_): @cherrypy.tools.model_params(cls=ParamsInstance) @cherrypy.tools.model_in(cls=RequestInstancePowerOffRestart) @cherrypy.tools.resource_object(id_param="instance_name", cls=Instance) - @cherrypy.tools.enforce_policy(policy_name="instances:action:start") + @cherrypy.tools.enforce_permission(permission_name="instances:action:start") def action_stop(self, **_): """Stop an instance --- @@ -335,7 +335,7 @@ def action_stop(self, **_): @cherrypy.tools.model_params(cls=ParamsInstance) @cherrypy.tools.model_in(cls=RequestInstancePowerOffRestart) @cherrypy.tools.resource_object(id_param="instance_name", cls=Instance) - @cherrypy.tools.enforce_policy(policy_name="instances:action:restart") + @cherrypy.tools.enforce_permission(permission_name="instances:action:restart") def action_restart(self, **_): """Restart an instance --- @@ -369,7 +369,7 @@ def action_restart(self, **_): @cherrypy.tools.model_in(cls=RequestInstanceImage) @cherrypy.tools.model_out(cls=ResponseImage) @cherrypy.tools.resource_object(id_param="instance_name", cls=Instance) - @cherrypy.tools.enforce_policy(policy_name="instances:action:image") + @cherrypy.tools.enforce_permission(permission_name="instances:action:image") def action_image(self, **_): """Image an instance --- diff --git a/deli/counter/http/mounts/root/routes/compute/v1/keypairs.py b/deli/counter/http/mounts/root/routes/compute/v1/keypairs.py index e5c9643..10ef129 100644 --- a/deli/counter/http/mounts/root/routes/compute/v1/keypairs.py +++ b/deli/counter/http/mounts/root/routes/compute/v1/keypairs.py @@ -19,7 +19,7 @@ def __init__(self): @Route(methods=[RequestMethods.POST]) @cherrypy.tools.model_in(cls=RequestCreateKeypair) @cherrypy.tools.model_out(cls=ResponseKeypair) - @cherrypy.tools.enforce_policy(policy_name="keypairs:create") + @cherrypy.tools.enforce_permission(permission_name="keypairs:create") def create(self): """Create a keypair --- @@ -52,7 +52,7 @@ def create(self): @cherrypy.tools.model_params(cls=ParamsKeypair) @cherrypy.tools.model_out(cls=ResponseKeypair) @cherrypy.tools.resource_object(id_param="keypair_name", cls=Keypair) - @cherrypy.tools.enforce_policy(policy_name="keypairs:get") + @cherrypy.tools.enforce_permission(permission_name="keypairs:get") def get(self, **_): """Get a keypair --- @@ -70,7 +70,7 @@ def get(self, **_): @Route() @cherrypy.tools.model_params(cls=ParamsListKeypair) @cherrypy.tools.model_out_pagination(cls=ResponseKeypair) - @cherrypy.tools.enforce_policy(policy_name="keypairs:list") + @cherrypy.tools.enforce_permission(permission_name="keypairs:list") def list(self, limit: int, marker: uuid.UUID): """List keypairs --- @@ -91,7 +91,7 @@ def list(self, limit: int, marker: uuid.UUID): @Route(route='{keypair_name}', methods=[RequestMethods.DELETE]) @cherrypy.tools.model_params(cls=ParamsKeypair) @cherrypy.tools.resource_object(id_param="keypair_name", cls=Keypair) - @cherrypy.tools.enforce_policy(policy_name="keypairs:delete") + @cherrypy.tools.enforce_permission(permission_name="keypairs:delete") def delete(self, **_): """Delete a keypair --- diff --git a/deli/counter/http/mounts/root/routes/compute/v1/network_ports.py b/deli/counter/http/mounts/root/routes/compute/v1/network_ports.py index 4126bfe..c56db3d 100644 --- a/deli/counter/http/mounts/root/routes/compute/v1/network_ports.py +++ b/deli/counter/http/mounts/root/routes/compute/v1/network_ports.py @@ -19,7 +19,7 @@ def __init__(self): @cherrypy.tools.model_params(cls=ParamsNetworkPort) @cherrypy.tools.model_out(cls=ResponseNetworkPort) @cherrypy.tools.resource_object(id_param="network_port_id", cls=NetworkPort) - @cherrypy.tools.enforce_policy(policy_name="network_ports:get") + @cherrypy.tools.enforce_permission(permission_name="network_ports:get") def get(self, **_): """Get a network port --- @@ -37,7 +37,7 @@ def get(self, **_): @Route() @cherrypy.tools.model_params(cls=ParamsListNetworkPort) @cherrypy.tools.model_out_pagination(cls=ResponseNetworkPort) - @cherrypy.tools.enforce_policy(policy_name="network_ports:list") + @cherrypy.tools.enforce_permission(permission_name="network_ports:list") def list(self, limit, marker): """List network ports --- @@ -58,7 +58,7 @@ def list(self, limit, marker): @Route(route='{network_port_id}', methods=[RequestMethods.DELETE]) @cherrypy.tools.model_params(cls=ParamsNetworkPort) @cherrypy.tools.resource_object(id_param="network_port_id", cls=NetworkPort) - @cherrypy.tools.enforce_policy(policy_name="network_ports:delete") + @cherrypy.tools.enforce_permission(permission_name="network_ports:delete") def delete(self, **_): """Delete a network port --- diff --git a/deli/counter/http/mounts/root/routes/compute/v1/networks.py b/deli/counter/http/mounts/root/routes/compute/v1/networks.py index af12faa..d617829 100644 --- a/deli/counter/http/mounts/root/routes/compute/v1/networks.py +++ b/deli/counter/http/mounts/root/routes/compute/v1/networks.py @@ -21,7 +21,7 @@ def __init__(self): @Route(methods=[RequestMethods.POST]) @cherrypy.tools.model_in(cls=RequestCreateNetwork) @cherrypy.tools.model_out(cls=ResponseNetwork) - @cherrypy.tools.enforce_policy(policy_name="networks:create") + @cherrypy.tools.enforce_permission(permission_name="networks:create") def create(self): """Create a network --- @@ -50,8 +50,6 @@ def create(self): raise cherrypy.HTTPError(409, 'Can only create a network with a region in the following state: %s'.format( ResourceState.Created)) - # TODO: check duplicate (or overlapping) cidr - network = Network() network.name = request.name network.port_group = request.port_group @@ -118,7 +116,7 @@ def list(self, region, limit: int, marker: uuid.UUID): @Route(route='{network_name}', methods=[RequestMethods.DELETE]) @cherrypy.tools.model_params(cls=ParamsNetwork) @cherrypy.tools.resource_object(id_param="network_name", cls=Network) - @cherrypy.tools.enforce_policy(policy_name="networks:delete") + @cherrypy.tools.enforce_permission(permission_name="networks:delete") def delete(self, **_): """Delete a network --- diff --git a/deli/counter/http/mounts/root/routes/compute/v1/volume.py b/deli/counter/http/mounts/root/routes/compute/v1/volume.py index 797027f..75d363b 100644 --- a/deli/counter/http/mounts/root/routes/compute/v1/volume.py +++ b/deli/counter/http/mounts/root/routes/compute/v1/volume.py @@ -23,7 +23,7 @@ def __init__(self): @Route(methods=[RequestMethods.POST]) @cherrypy.tools.model_in(cls=RequestCreateVolume) @cherrypy.tools.model_out(cls=ResponseVolume) - @cherrypy.tools.enforce_policy(policy_name="volumes:create") + @cherrypy.tools.enforce_permission(permission_name="volumes:create") def create(self): """Create a volume --- @@ -75,7 +75,7 @@ def create(self): @cherrypy.tools.model_params(cls=ParamsVolume) @cherrypy.tools.model_out(cls=ResponseVolume) @cherrypy.tools.resource_object(id_param="volume_name", cls=Volume) - @cherrypy.tools.enforce_policy(policy_name="volumes:get") + @cherrypy.tools.enforce_permission(permission_name="volumes:get") def get(self, **_): """Get a volume --- @@ -93,7 +93,7 @@ def get(self, **_): @Route() @cherrypy.tools.model_params(cls=ParamsListVolume) @cherrypy.tools.model_out_pagination(cls=ResponseVolume) - @cherrypy.tools.enforce_policy(policy_name="volumes:list") + @cherrypy.tools.enforce_permission(permission_name="volumes:list") def list(self, limit: int, marker: uuid.UUID): """List volumes --- @@ -119,7 +119,7 @@ def list(self, limit: int, marker: uuid.UUID): @Route(route='{volume_name}', methods=[RequestMethods.DELETE]) @cherrypy.tools.model_params(cls=ParamsVolume) @cherrypy.tools.resource_object(id_param="volume_name", cls=Volume) - @cherrypy.tools.enforce_policy(policy_name="volumes:delete") + @cherrypy.tools.enforce_permission(permission_name="volumes:delete") def delete(self, **_): """Delete a volume --- @@ -151,7 +151,7 @@ def delete(self, **_): @cherrypy.tools.model_params(cls=ParamsVolume) @cherrypy.tools.model_in(cls=RequestAttachVolume) @cherrypy.tools.resource_object(id_param="volume_name", cls=Volume) - @cherrypy.tools.enforce_policy(policy_name="volumes:action:attach") + @cherrypy.tools.enforce_permission(permission_name="volumes:action:attach") def action_attach(self, **_): """Attach a volume --- @@ -198,7 +198,7 @@ def action_attach(self, **_): @Route(route='{volume_name}/action/detach', methods=[RequestMethods.PUT]) @cherrypy.tools.model_params(cls=ParamsVolume) @cherrypy.tools.resource_object(id_param="volume_name", cls=Volume) - @cherrypy.tools.enforce_policy(policy_name="volumes:action:detach") + @cherrypy.tools.enforce_permission(permission_name="volumes:action:detach") def action_detach(self, **_): """Detach a volume --- @@ -229,7 +229,7 @@ def action_detach(self, **_): @cherrypy.tools.model_params(cls=ParamsVolume) @cherrypy.tools.model_in(cls=RequestGrowVolume) @cherrypy.tools.resource_object(id_param="volume_name", cls=Volume) - @cherrypy.tools.enforce_policy(policy_name="volumes:action:grow") + @cherrypy.tools.enforce_permission(permission_name="volumes:action:grow") def action_grow(self, **_): """Grow a volume --- @@ -278,7 +278,7 @@ def action_grow(self, **_): @cherrypy.tools.model_in(cls=RequestCloneVolume) @cherrypy.tools.model_out(cls=ResponseVolume) @cherrypy.tools.resource_object(id_param="volume_name", cls=Volume) - @cherrypy.tools.enforce_policy(policy_name="volumes:action:grow") + @cherrypy.tools.enforce_permission(permission_name="volumes:action:grow") def action_clone(self, **_): """Clone a volume --- diff --git a/deli/counter/http/mounts/root/routes/iam/v1/permission.py b/deli/counter/http/mounts/root/routes/iam/v1/permission.py new file mode 100644 index 0000000..c681db7 --- /dev/null +++ b/deli/counter/http/mounts/root/routes/iam/v1/permission.py @@ -0,0 +1,119 @@ +import uuid + +import cherrypy +from ingredients_http.route import Route + +from deli.counter.auth.permission import SYSTEM_PERMISSIONS, PROJECT_PERMISSIONS +from deli.counter.http.mounts.root.routes.iam.v1.validation_models.permission import ParamsPermission, \ + ResponsePermission, \ + ParamsListPermission +from deli.counter.http.router import SandwichProjectRouter, SandwichSystemRouter + + +class IAMSystemPermissionRouter(SandwichSystemRouter): + def __init__(self): + super().__init__('permissions') + + @Route(route='{permission_name}') + @cherrypy.tools.model_params(cls=ParamsPermission) + @cherrypy.tools.model_out(cls=ResponsePermission) + def get(self, permission_name): + """Get a system permission + --- + get: + description: Get a system permission + tags: + - iam + - permission + responses: + 200: + description: The permission + """ + permission = None + + for p in SYSTEM_PERMISSIONS: + if p['name'] == permission_name: + permission = p + break + + if permission is None: + raise cherrypy.HTTPError(404, "The resource could not be found.") + + return ResponsePermission(permission) + + @Route() + @cherrypy.tools.model_params(cls=ParamsListPermission) + @cherrypy.tools.model_out_pagination(cls=ResponsePermission) + def list(self, limit: int, marker: uuid.UUID): + """List system permissions + --- + get: + description: List system permissions + tags: + - iam + - permission + responses: + 200: + description: List of system permissions + """ + permissions = [] + + for p in SYSTEM_PERMISSIONS: + permissions.append(ResponsePermission(p)) + + return permissions, False + + +class IAMProjectPermissionRouter(SandwichProjectRouter): + def __init__(self): + super().__init__('permissions') + + @Route(route='{permission_name}') + @cherrypy.tools.model_params(cls=ParamsPermission) + @cherrypy.tools.model_out(cls=ResponsePermission) + def get(self, permission_name): + """Get a project permission + --- + get: + description: Get a project permission + tags: + - iam + - permission + responses: + 200: + description: The permission + """ + + permission = None + + for p in PROJECT_PERMISSIONS: + if p['name'] == permission_name: + permission = p + break + + if permission is None: + raise cherrypy.HTTPError(404, "The resource could not be found.") + + return ResponsePermission(permission) + + @Route() + @cherrypy.tools.model_params(cls=ParamsListPermission) + @cherrypy.tools.model_out_pagination(cls=ResponsePermission) + def list(self, limit: int, marker: uuid.UUID): + """List project permissions + --- + get: + description: List project permissions + tags: + - iam + - permission + responses: + 200: + description: List of project permissions + """ + permissions = [] + + for p in PROJECT_PERMISSIONS: + permissions.append(ResponsePermission(p)) + + return permissions, False diff --git a/deli/counter/http/mounts/root/routes/iam/v1/policy.py b/deli/counter/http/mounts/root/routes/iam/v1/policy.py index 96d298b..bb4abb3 100644 --- a/deli/counter/http/mounts/root/routes/iam/v1/policy.py +++ b/deli/counter/http/mounts/root/routes/iam/v1/policy.py @@ -1,22 +1,24 @@ -import uuid - import cherrypy +from ingredients_http.request_methods import RequestMethods from ingredients_http.route import Route -from deli.counter.auth.policy import SYSTEM_POLICIES, PROJECT_POLICIES -from deli.counter.http.mounts.root.routes.iam.v1.validation_models.policy import ParamsPolicy, ResponsePolicy, \ - ParamsListPolicy -from deli.counter.http.router import SandwichProjectRouter, SandwichSystemRouter +from deli.counter.http.mounts.root.routes.iam.v1.validation_models.policy import ResponsePolicy, RequestSetPolicy +from deli.counter.http.router import SandwichSystemRouter, SandwichProjectRouter +from deli.kubernetes.resources.project import Project +from deli.kubernetes.resources.v1alpha1.iam_group.model import IAMSystemGroup +from deli.kubernetes.resources.v1alpha1.iam_policy.model import IAMPolicy +from deli.kubernetes.resources.v1alpha1.iam_role.model import IAMSystemRole, IAMProjectRole +from deli.kubernetes.resources.v1alpha1.iam_service_account.model import SystemServiceAccount, ProjectServiceAccount class IAMSystemPolicyRouter(SandwichSystemRouter): def __init__(self): - super().__init__('policies') + super().__init__('policy') - @Route(route='{policy_name}') - @cherrypy.tools.model_params(cls=ParamsPolicy) + @Route() @cherrypy.tools.model_out(cls=ResponsePolicy) - def get(self, policy_name): + @cherrypy.tools.enforce_permission(permission_name="policy:system:get") + def get(self): """Get a system policy --- get: @@ -28,49 +30,88 @@ def get(self, policy_name): 200: description: The policy """ - policy = None - - for p in SYSTEM_POLICIES: - if p['name'] == policy_name: - policy = p - break - - if policy is None: - raise cherrypy.HTTPError(404, "The resource could not be found.") + return ResponsePolicy.from_database(IAMPolicy.get("system")) - return ResponsePolicy(policy) - - @Route() - @cherrypy.tools.model_params(cls=ParamsListPolicy) - @cherrypy.tools.model_out_pagination(cls=ResponsePolicy) - def list(self, limit: int, marker: uuid.UUID): - """List system policies + @Route(methods=[RequestMethods.POST]) + @cherrypy.tools.model_in(cls=RequestSetPolicy) + @cherrypy.tools.model_out(cls=ResponsePolicy) + @cherrypy.tools.enforce_permission(permission_name="policy:system:set") + def set(self): + """Set a system policy --- - get: - description: List system policies + post: + description: Set a system policy tags: - iam - policy + requestBody: + description: The policy responses: 200: - description: List of system policies + description: The policy """ - policies = [] + policy = IAMPolicy.get("system") + bindings = [] + request: RequestSetPolicy = cherrypy.request.model + + if request.resource_version != policy.resource_version: + raise cherrypy.HTTPError(400, 'The policy has a newer reosurce version than requested') + + for binding in request.bindings: + role = IAMSystemRole.get(binding.role) + if role is None: + raise cherrypy.HTTPError(404, 'Unknown system role ' + binding.role) + for member in binding.members: + if member.contains(':') is False: + raise cherrypy.HTTPError(400, 'Member must be in the following format: ' + '{user/serviceAccount/group}:{email}') + + kind, email, *junk = member.split(":") + + if len(junk) > 0: + raise cherrypy.HTTPError(400, 'Member must be in the following format: ' + '{user/serviceAccount/group}:{email}') + + username, domain, *_ = email.split("@") + + if kind not in ['user', 'serviceAccount', 'group']: + raise cherrypy.HTTPError(400, 'Member must be in the following format: ' + '{user/serviceAccount/group}:{email}') - for p in SYSTEM_POLICIES: - policies.append(ResponsePolicy(p)) + if kind == 'serviceAccount': + if domain != 'service-account.system.sandwich.local': + raise cherrypy.HTTPError(400, 'Can only add system service accounts to a system policy') - return policies, False + sa = SystemServiceAccount.get(username) + if sa is None: + raise cherrypy.HTTPError(404, 'Unknown System Service Account ' + email) + + if kind == 'group': + if domain != 'group.system.sandwich.local': + raise cherrypy.HTTPError(400, 'Invalid email for group ' + email) + group = IAMSystemGroup.get(username) + if group is None: + raise cherrypy.HTTPError(404, 'Unknown Group ' + username) + + if kind == 'user': + if domain.endswith('sandwich.local'): + raise cherrypy.HTTPError(400, 'Cannot add ' + email + " has a user") + + bindings.append(binding.to_native()) + + policy.bindings = bindings + policy.save() + return ResponsePolicy.from_database(policy) class IAMProjectPolicyRouter(SandwichProjectRouter): def __init__(self): - super().__init__('policies') + super().__init__('policy') - @Route(route='{policy_name}') - @cherrypy.tools.model_params(cls=ParamsPolicy) + @Route() @cherrypy.tools.model_out(cls=ResponsePolicy) - def get(self, policy_name): + @cherrypy.tools.enforce_permission(permission_name="policy:project:get") + def get(self): """Get a project policy --- get: @@ -82,37 +123,89 @@ def get(self, policy_name): 200: description: The policy """ + return ResponsePolicy.from_database(IAMPolicy.get(cherrypy.request.project.name)) - policy = None - - for p in PROJECT_POLICIES: - if p['name'] == policy_name: - policy = p - break - - if policy is None: - raise cherrypy.HTTPError(404, "The resource could not be found.") - - return ResponsePolicy(policy) - - @Route() - @cherrypy.tools.model_params(cls=ParamsListPolicy) - @cherrypy.tools.model_out_pagination(cls=ResponsePolicy) - def list(self, limit: int, marker: uuid.UUID): - """List projectpolicies + @Route(methods=[RequestMethods.POST]) + @cherrypy.tools.model_in(cls=RequestSetPolicy) + @cherrypy.tools.model_out(cls=ResponsePolicy) + @cherrypy.tools.enforce_permission(permission_name="policy:project:set") + def set(self): + """Set a project policy --- - get: - description: List project policies + post: + description: Set a project policy tags: - iam - policy + requestBody: + description: The policy responses: 200: - description: List of project policies + description: The policy """ - policies = [] - - for p in PROJECT_POLICIES: - policies.append(ResponsePolicy(p)) - - return policies, False + project: Project = cherrypy.request.project + policy = IAMPolicy.get(project) + bindings = [] + request: RequestSetPolicy = cherrypy.request.model + + if request.resource_version != policy.resource_version: + raise cherrypy.HTTPError(400, 'The policy has a newer reosurce version than requested') + + for binding in request.bindings: + role = IAMProjectRole.get(project, binding.role) + if role is None: + raise cherrypy.HTTPError(404, 'Unknown project role ' + binding.role) + for member in binding.members: + if member.contains(':') is False: + raise cherrypy.HTTPError(400, 'Member must be in the following format: ' + '{user/serviceAccount/group}:{email}') + + kind, email, *junk = member.split(":") + + if len(junk) > 0: + raise cherrypy.HTTPError(400, 'Member must be in the following format: ' + '{user/serviceAccount/group}:{email}') + + username, domain, *_ = email.split("@") + + if kind not in ['user', 'serviceAccount', 'group']: + raise cherrypy.HTTPError(400, 'Member must be in the following format: ' + '{user/serviceAccount/group}:{email}') + + if kind == 'serviceAccount': + saKind, project_name, *suffix = domain.split(".") + suffix = ".".join(suffix) + + if saKind != 'service-account' or suffix != 'sandwich.local': + raise cherrypy.HTTPError(400, 'Service account emails must be in the following format: ' + 'service-account.{project_name/system}.sandwich.local') + + if project_name == 'system': + sa = SystemServiceAccount.get(username) + if sa is None: + raise cherrypy.HTTPError(404, 'Unknown System Service Account ' + email) + else: + sa_project = Project.get(project_name) + if sa_project is None: + raise cherrypy.HTTPError(404, 'Unknown System Project Account ' + email) + + sa = ProjectServiceAccount.get(sa_project, username) + if sa is None: + raise cherrypy.HTTPError(404, 'Unknown System Project Account ' + email) + + if kind == 'group': + if domain != 'group.system.sandwich.local': + raise cherrypy.HTTPError(400, 'Invalid email for group ' + email) + group = IAMSystemGroup.get(username) + if group is None: + raise cherrypy.HTTPError(404, 'Unknown Group ' + username) + + if kind == 'user': + if domain.endswith('sandwich.local'): + raise cherrypy.HTTPError(400, 'Cannot add ' + email + " has a user") + + bindings.append(binding.to_native()) + + policy.bindings = bindings + policy.save() + return ResponsePolicy.from_database(policy) diff --git a/deli/counter/http/mounts/root/routes/iam/v1/project_quota.py b/deli/counter/http/mounts/root/routes/iam/v1/project_quota.py index f06d94e..6015811 100644 --- a/deli/counter/http/mounts/root/routes/iam/v1/project_quota.py +++ b/deli/counter/http/mounts/root/routes/iam/v1/project_quota.py @@ -15,7 +15,7 @@ def __init__(self): @Route(methods=[RequestMethods.GET]) @cherrypy.tools.model_out(cls=ResponseProjectQuota) - @cherrypy.tools.enforce_policy(policy_name="projects:quota:get") + @cherrypy.tools.enforce_permission(permission_name="projects:quota:get") def get(self): """Get a project's quota --- @@ -34,7 +34,7 @@ def get(self): @Route(methods=[RequestMethods.POST]) @cherrypy.tools.model_in(cls=RequestProjectModifyQuota) - @cherrypy.tools.enforce_policy(policy_name="projects:quota:modify") + @cherrypy.tools.enforce_permission(permission_name="projects:quota:modify") def modify(self): """Modify a project's quota --- diff --git a/deli/counter/http/mounts/root/routes/iam/v1/projects.py b/deli/counter/http/mounts/root/routes/iam/v1/projects.py index b7b9d49..5ca25e9 100644 --- a/deli/counter/http/mounts/root/routes/iam/v1/projects.py +++ b/deli/counter/http/mounts/root/routes/iam/v1/projects.py @@ -23,7 +23,7 @@ def __init__(self): @Route(methods=[RequestMethods.POST]) @cherrypy.tools.model_in(cls=RequestCreateProject) @cherrypy.tools.model_out(cls=ResponseProject) - @cherrypy.tools.enforce_policy(policy_name="projects:create") + @cherrypy.tools.enforce_permission(permission_name="projects:create") def create(self): """Create a project --- @@ -67,7 +67,7 @@ def create(self): @cherrypy.tools.model_out(cls=ResponseProject) @cherrypy.tools.project_scope() @cherrypy.tools.resource_object(id_param="project_name", cls=Project) - @cherrypy.tools.enforce_policy(policy_name="projects:get") + @cherrypy.tools.enforce_permission(permission_name="projects:get") def get(self, **_): """Get a project --- @@ -109,7 +109,7 @@ def list(self, limit: int, marker: uuid.UUID): @cherrypy.tools.model_params(cls=ParamsProject) @cherrypy.tools.project_scope() @cherrypy.tools.resource_object(id_param="project_name", cls=Project) - @cherrypy.tools.enforce_policy(policy_name="projects:delete") + @cherrypy.tools.enforce_permission(permission_name="projects:delete") def delete(self, **_): """Delete a project --- diff --git a/deli/counter/http/mounts/root/routes/iam/v1/role.py b/deli/counter/http/mounts/root/routes/iam/v1/role.py index 9acfd37..7881f45 100644 --- a/deli/counter/http/mounts/root/routes/iam/v1/role.py +++ b/deli/counter/http/mounts/root/routes/iam/v1/role.py @@ -4,7 +4,7 @@ from ingredients_http.request_methods import RequestMethods from ingredients_http.route import Route -from deli.counter.auth.policy import PROJECT_POLICIES, SYSTEM_POLICIES +from deli.counter.auth.permission import PROJECT_PERMISSIONS, SYSTEM_PERMISSIONS from deli.counter.http.mounts.root.routes.iam.v1.validation_models.role import RequestCreateRole, ResponseRole, \ RequestRoleUpdate, ParamsRole, ParamsListRoles from deli.counter.http.router import SandwichProjectRouter, SandwichSystemRouter @@ -29,16 +29,16 @@ def helper_create(self, project: Optional[Project]): role.project = project if project is not None: - policy_names = [p['name'] for p in PROJECT_POLICIES] + permission_names = [p['name'] for p in PROJECT_PERMISSIONS] else: - policy_names = [p['name'] for p in SYSTEM_POLICIES] + permission_names = [p['name'] for p in SYSTEM_PERMISSIONS] - for policy in request.policies: - if policy not in policy_names: - raise cherrypy.HTTPError(404, 'Unknown policy %s' % policy) + for permission in request.permissions: + if permission not in permission_names: + raise cherrypy.HTTPError(404, 'Unknown permission %s' % permission) role.name = request.name - role.policies = request.policies + role.permissions = request.permissions role.create() return ResponseRole.from_database(role) @@ -64,15 +64,15 @@ def helper_update(self, project: Optional[Project]): raise cherrypy.HTTPError(400, 'Role is not in the following state: ' + ResourceState.Created.value) if project is not None: - policy_names = [p['name'] for p in PROJECT_POLICIES] + permission_names = [p['name'] for p in PROJECT_PERMISSIONS] else: - policy_names = [p['name'] for p in SYSTEM_POLICIES] + permission_names = [p['name'] for p in SYSTEM_PERMISSIONS] - for policy in request.policies: - if policy not in policy_names: - raise cherrypy.HTTPError(404, 'Unknown policy %s' % policy) + for permission in request.permissions: + if permission not in permission_names: + raise cherrypy.HTTPError(404, 'Unknown permission %s' % permission) - role.policies = request.policies + role.permissions = request.permissions role.save() def helper_delete(self): @@ -95,7 +95,7 @@ def __init__(self): @Route(methods=[RequestMethods.POST]) @cherrypy.tools.model_in(cls=RequestCreateRole) @cherrypy.tools.model_out(cls=ResponseRole) - @cherrypy.tools.enforce_policy(policy_name="roles:system:create") + @cherrypy.tools.enforce_permission(permission_name="roles:system:create") def create(self): """Create a system role --- @@ -116,7 +116,7 @@ def create(self): @cherrypy.tools.model_params(cls=ParamsRole) @cherrypy.tools.model_out(cls=ResponseRole) @cherrypy.tools.resource_object(id_param="role_name", cls=IAMSystemRole) - @cherrypy.tools.enforce_policy(policy_name="roles:system:get") + @cherrypy.tools.enforce_permission(permission_name="roles:system:get") def get(self, **_): """Get a system role --- @@ -134,7 +134,7 @@ def get(self, **_): @Route() @cherrypy.tools.model_params(cls=ParamsListRoles) @cherrypy.tools.model_out_pagination(cls=ResponseRole) - @cherrypy.tools.enforce_policy(policy_name="roles:system:list") + @cherrypy.tools.enforce_permission(permission_name="roles:system:list") def list(self, limit, marker): """List system roles --- @@ -153,7 +153,7 @@ def list(self, limit, marker): @cherrypy.tools.model_params(cls=ParamsRole) @cherrypy.tools.model_in(cls=RequestRoleUpdate) @cherrypy.tools.resource_object(id_param="role_name", cls=IAMSystemRole) - @cherrypy.tools.enforce_policy(policy_name="roles:system:update") + @cherrypy.tools.enforce_permission(permission_name="roles:system:update") def update(self, **_): """Update a system role --- @@ -173,7 +173,7 @@ def update(self, **_): @Route(route='{role_name}', methods=[RequestMethods.DELETE]) @cherrypy.tools.model_params(cls=ParamsRole) @cherrypy.tools.resource_object(id_param="role_name", cls=IAMSystemRole) - @cherrypy.tools.enforce_policy(policy_name="roles:system:delete") + @cherrypy.tools.enforce_permission(permission_name="roles:system:delete") def delete(self, **_): """Delete a system role --- @@ -196,7 +196,7 @@ def __init__(self): @Route(methods=[RequestMethods.POST]) @cherrypy.tools.model_in(cls=RequestCreateRole) @cherrypy.tools.model_out(cls=ResponseRole) - @cherrypy.tools.enforce_policy(policy_name="roles:project:create") + @cherrypy.tools.enforce_permission(permission_name="roles:project:create") def create(self): """Create a project role --- @@ -217,7 +217,7 @@ def create(self): @cherrypy.tools.model_params(cls=ParamsRole) @cherrypy.tools.model_out(cls=ResponseRole) @cherrypy.tools.resource_object(id_param="role_name", cls=IAMProjectRole) - @cherrypy.tools.enforce_policy(policy_name="roles:project:get") + @cherrypy.tools.enforce_permission(permission_name="roles:project:get") def get(self, **_): """Get a project role --- @@ -235,7 +235,7 @@ def get(self, **_): @Route() @cherrypy.tools.model_params(cls=ParamsListRoles) @cherrypy.tools.model_out_pagination(cls=ResponseRole) - @cherrypy.tools.enforce_policy(policy_name="roles:project:list") + @cherrypy.tools.enforce_permission(permission_name="roles:project:list") def list(self, limit, marker): """List project roles --- @@ -254,7 +254,7 @@ def list(self, limit, marker): @cherrypy.tools.model_params(cls=ParamsRole) @cherrypy.tools.model_in(cls=RequestRoleUpdate) @cherrypy.tools.resource_object(id_param="role_name", cls=IAMProjectRole) - @cherrypy.tools.enforce_policy(policy_name="roles:project:update") + @cherrypy.tools.enforce_permission(permission_name="roles:project:update") def update(self, **_): """Update a project role --- @@ -274,7 +274,7 @@ def update(self, **_): @Route(route='{role_name}', methods=[RequestMethods.DELETE]) @cherrypy.tools.model_params(cls=ParamsRole) @cherrypy.tools.resource_object(id_param="role_name", cls=IAMProjectRole) - @cherrypy.tools.enforce_policy(policy_name="roles:project:delete") + @cherrypy.tools.enforce_permission(permission_name="roles:project:delete") def delete(self, **_): """Delete a project role --- diff --git a/deli/counter/http/mounts/root/routes/iam/v1/service_accounts.py b/deli/counter/http/mounts/root/routes/iam/v1/service_accounts.py index d0534e5..7565d79 100644 --- a/deli/counter/http/mounts/root/routes/iam/v1/service_accounts.py +++ b/deli/counter/http/mounts/root/routes/iam/v1/service_accounts.py @@ -127,7 +127,7 @@ def __init__(self): @Route(methods=[RequestMethods.POST]) @cherrypy.tools.model_in(cls=RequestCreateServiceAccount) @cherrypy.tools.model_out(cls=ResponseServiceAccount) - @cherrypy.tools.enforce_policy(policy_name="service_accounts:system:create") + @cherrypy.tools.enforce_permission(permission_name="service_accounts:system:create") def create(self): """Create a system service account --- @@ -148,7 +148,7 @@ def create(self): @cherrypy.tools.model_params(cls=ParamsServiceAccount) @cherrypy.tools.model_out(cls=ResponseServiceAccount) @cherrypy.tools.resource_object(id_param="service_account_name", cls=SystemServiceAccount) - @cherrypy.tools.enforce_policy(policy_name="service_accounts:system:get") + @cherrypy.tools.enforce_permission(permission_name="service_accounts:system:get") def get(self, **_): """Get a system service account --- @@ -166,7 +166,7 @@ def get(self, **_): @Route() @cherrypy.tools.model_params(cls=ParamsListServiceAccount) @cherrypy.tools.model_out_pagination(cls=ResponseServiceAccount) - @cherrypy.tools.enforce_policy(policy_name="service_accounts:system:list") + @cherrypy.tools.enforce_permission(permission_name="service_accounts:system:list") def list(self, limit, marker): """List system service accounts --- @@ -184,7 +184,7 @@ def list(self, limit, marker): @Route(route='{service_account_name}', methods=[RequestMethods.DELETE]) @cherrypy.tools.model_params(cls=ParamsServiceAccount) @cherrypy.tools.resource_object(id_param="service_account_name", cls=SystemServiceAccount) - @cherrypy.tools.enforce_policy(policy_name="service_accounts:system:delete") + @cherrypy.tools.enforce_permission(permission_name="service_accounts:system:delete") def delete(self, **_): """Delete a system service account --- @@ -204,7 +204,7 @@ def delete(self, **_): @cherrypy.tools.model_in(cls=RequestCreateServiceAccountKey) @cherrypy.tools.model_out(cls=ResponseOAuthToken) @cherrypy.tools.resource_object(id_param="service_account_name", cls=SystemServiceAccount) - @cherrypy.tools.enforce_policy(policy_name="service_accounts:system:key:create") + @cherrypy.tools.enforce_permission(permission_name="service_accounts:system:key:create") def create_key(self, **_): """Create a system service account key --- @@ -224,7 +224,7 @@ def create_key(self, **_): @Route(route='{service_account_name}/keys/{name}', methods=[RequestMethods.DELETE]) @cherrypy.tools.model_params(cls=ParamsServiceAccountKey) @cherrypy.tools.resource_object(id_param="service_account_name", cls=SystemServiceAccount) - @cherrypy.tools.enforce_policy(policy_name="service_accounts:system:key:delete") + @cherrypy.tools.enforce_permission(permission_name="service_accounts:system:key:delete") def delete_key(self, **kwargs): """Delete a system service account key --- @@ -247,7 +247,7 @@ def __init__(self): @Route(methods=[RequestMethods.POST]) @cherrypy.tools.model_in(cls=RequestCreateServiceAccount) @cherrypy.tools.model_out(cls=ResponseServiceAccount) - @cherrypy.tools.enforce_policy(policy_name="service_accounts:project:create") + @cherrypy.tools.enforce_permission(permission_name="service_accounts:project:create") def create(self): """Create a project service account --- @@ -268,7 +268,7 @@ def create(self): @cherrypy.tools.model_params(cls=ParamsServiceAccount) @cherrypy.tools.model_out(cls=ResponseServiceAccount) @cherrypy.tools.resource_object(id_param="service_account_name", cls=ProjectServiceAccount) - @cherrypy.tools.enforce_policy(policy_name="service_accounts:project:get") + @cherrypy.tools.enforce_permission(permission_name="service_accounts:project:get") def get(self, **_): """Get a project service account --- @@ -286,7 +286,7 @@ def get(self, **_): @Route() @cherrypy.tools.model_params(cls=ParamsListServiceAccount) @cherrypy.tools.model_out_pagination(cls=ResponseServiceAccount) - @cherrypy.tools.enforce_policy(policy_name="service_accounts:project:list") + @cherrypy.tools.enforce_permission(permission_name="service_accounts:project:list") def list(self, limit, marker): """List project service accounts --- @@ -304,7 +304,7 @@ def list(self, limit, marker): @Route(route='{service_account_name}', methods=[RequestMethods.DELETE]) @cherrypy.tools.model_params(cls=ParamsServiceAccount) @cherrypy.tools.resource_object(id_param="service_account_name", cls=ProjectServiceAccount) - @cherrypy.tools.enforce_policy(policy_name="service_accounts:project:delete") + @cherrypy.tools.enforce_permission(permission_name="service_accounts:project:delete") def delete(self, **_): """Delete a project service account --- @@ -324,7 +324,7 @@ def delete(self, **_): @cherrypy.tools.model_in(cls=RequestCreateServiceAccountKey) @cherrypy.tools.model_out(cls=ResponseOAuthToken) @cherrypy.tools.resource_object(id_param="service_account_name", cls=ProjectServiceAccount) - @cherrypy.tools.enforce_policy(policy_name="service_accounts:project:key:create") + @cherrypy.tools.enforce_permission(permission_name="service_accounts:project:key:create") def create_key(self, **_): """Create a project service account key --- @@ -344,7 +344,7 @@ def create_key(self, **_): @Route(route='{service_account_name}/keys/{name}', methods=[RequestMethods.DELETE]) @cherrypy.tools.model_params(cls=ParamsServiceAccountKey) @cherrypy.tools.resource_object(id_param="service_account_name", cls=ProjectServiceAccount) - @cherrypy.tools.enforce_policy(policy_name="service_accounts:project:key:delete") + @cherrypy.tools.enforce_permission(permission_name="service_accounts:project:key:delete") def delete_key(self, **kwargs): """Delete a project service account key --- diff --git a/deli/counter/http/mounts/root/routes/iam/v1/validation_models/permission.py b/deli/counter/http/mounts/root/routes/iam/v1/validation_models/permission.py new file mode 100644 index 0000000..d6fe4b6 --- /dev/null +++ b/deli/counter/http/mounts/root/routes/iam/v1/validation_models/permission.py @@ -0,0 +1,17 @@ +from schematics import Model +from schematics.types import StringType, ListType, UUIDType, IntType + + +class ParamsPermission(Model): + permission_name = StringType(required=True) + + +class ParamsListPermission(Model): + limit = IntType(default=100, max_value=100, min_value=1) + marker = UUIDType() + + +class ResponsePermission(Model): + name = StringType(required=True) + description = StringType(required=True) + tags = ListType(StringType) diff --git a/deli/counter/http/mounts/root/routes/iam/v1/validation_models/policy.py b/deli/counter/http/mounts/root/routes/iam/v1/validation_models/policy.py index 7d5b60c..9db26fd 100644 --- a/deli/counter/http/mounts/root/routes/iam/v1/validation_models/policy.py +++ b/deli/counter/http/mounts/root/routes/iam/v1/validation_models/policy.py @@ -1,17 +1,35 @@ +from ingredients_http.schematics.types import KubeName from schematics import Model -from schematics.types import StringType, ListType, UUIDType, IntType +from schematics.types import ListType, ModelType, StringType +from deli.kubernetes.resources.v1alpha1.iam_policy.model import IAMPolicy -class ParamsPolicy(Model): - policy_name = StringType(required=True) - -class ParamsListPolicy(Model): - limit = IntType(default=100, max_value=100, min_value=1) - marker = UUIDType() +class PolicyBinding(Model): + role = KubeName(required=True) + members = ListType(StringType) class ResponsePolicy(Model): - name = StringType(required=True) - description = StringType(required=True) - tags = ListType(StringType) + resource_version = StringType(required=True) + bindings = ListType(ModelType(PolicyBinding), required=True) + + @classmethod + def from_database(cls, policy: IAMPolicy): + model = cls() + model.resource_version = policy.resource_version + + model.bindings = [] + + for binding in policy.bindings: + binding_model = PolicyBinding() + binding_model.role = binding['role'] + binding_model.members = binding_model['members'] + model.bindings.append(binding_model) + + return model + + +class RequestSetPolicy(Model): + resource_version = StringType(required=True) + bindings = ListType(ModelType(PolicyBinding), required=True) diff --git a/deli/counter/http/mounts/root/routes/iam/v1/validation_models/role.py b/deli/counter/http/mounts/root/routes/iam/v1/validation_models/role.py index acda168..6ad56ea 100644 --- a/deli/counter/http/mounts/root/routes/iam/v1/validation_models/role.py +++ b/deli/counter/http/mounts/root/routes/iam/v1/validation_models/role.py @@ -7,7 +7,7 @@ class RequestCreateRole(Model): name = KubeName(required=True, min_length=3) - policies = ListType(StringType, default=list) + permissions = ListType(StringType, default=list) class ParamsRole(Model): @@ -20,12 +20,12 @@ class ParamsListRoles(Model): class RequestRoleUpdate(Model): - policies = ListType(StringType, min_size=1) + permissions = ListType(StringType, min_size=1) class ResponseRole(Model): name = KubeName(required=True, min_length=3) - policies = ListType(StringType, default=list) + permissions = ListType(StringType, default=list) state = EnumType(ResourceState, required=True) created_at = ArrowType(required=True) @@ -33,7 +33,7 @@ class ResponseRole(Model): def from_database(cls, role): model = cls() model.name = role.name - model.policies = role.policies + model.permissions = role.permissions model.state = role.state model.created_at = role.created_at diff --git a/deli/counter/http/mounts/root/routes/location/v1/regions.py b/deli/counter/http/mounts/root/routes/location/v1/regions.py index 541ae6e..2a58d54 100644 --- a/deli/counter/http/mounts/root/routes/location/v1/regions.py +++ b/deli/counter/http/mounts/root/routes/location/v1/regions.py @@ -17,7 +17,7 @@ def __init__(self): @Route(methods=[RequestMethods.POST]) @cherrypy.tools.model_in(cls=RequestCreateRegion) @cherrypy.tools.model_out(cls=ResponseRegion) - @cherrypy.tools.enforce_policy(policy_name="regions:create") + @cherrypy.tools.enforce_permission(permission_name="regions:create") def create(self): """Create a region --- @@ -97,7 +97,7 @@ def list(self, limit, marker): @Route(route='{region_name}', methods=[RequestMethods.DELETE]) @cherrypy.tools.model_params(cls=ParamsRegion) @cherrypy.tools.resource_object(id_param="region_name", cls=Region) - @cherrypy.tools.enforce_policy(policy_name="regions:delete") + @cherrypy.tools.enforce_permission(permission_name="regions:delete") def delete(self, **_): """Delete a region --- @@ -129,7 +129,7 @@ def delete(self, **_): @cherrypy.tools.model_params(cls=ParamsRegion) @cherrypy.tools.model_in(cls=RequestRegionSchedule) @cherrypy.tools.resource_object(id_param="region_name", cls=Region) - @cherrypy.tools.enforce_policy(policy_name="regions:action:schedule") + @cherrypy.tools.enforce_permission(permission_name="regions:action:schedule") def action_schedule(self, **_): """Allow or disallow a region to be scheduled --- diff --git a/deli/counter/http/mounts/root/routes/location/v1/zones.py b/deli/counter/http/mounts/root/routes/location/v1/zones.py index cadb11d..be35ac3 100644 --- a/deli/counter/http/mounts/root/routes/location/v1/zones.py +++ b/deli/counter/http/mounts/root/routes/location/v1/zones.py @@ -17,7 +17,7 @@ def __init__(self): @Route(methods=[RequestMethods.POST]) @cherrypy.tools.model_in(cls=RequestCreateZone) @cherrypy.tools.model_out(cls=ResponseZone) - @cherrypy.tools.enforce_policy(policy_name="zones:create") + @cherrypy.tools.enforce_permission(permission_name="zones:create") def create(self): """Create a zone --- @@ -108,7 +108,7 @@ def list(self, region_name, limit, marker): @Route(route='{zone_name}', methods=[RequestMethods.DELETE]) @cherrypy.tools.model_params(cls=ParamsZone) @cherrypy.tools.resource_object(id_param="zone_name", cls=Zone) - @cherrypy.tools.enforce_policy(policy_name="zones:delete") + @cherrypy.tools.enforce_permission(permission_name="zones:delete") def delete(self, **_): """Delete a zone --- @@ -139,7 +139,7 @@ def delete(self, **_): @cherrypy.tools.model_params(cls=ParamsZone) @cherrypy.tools.model_in(cls=RequestZoneSchedule) @cherrypy.tools.resource_object(id_param="zone_name", cls=Zone) - @cherrypy.tools.enforce_policy(policy_name="zones:action:schedule") + @cherrypy.tools.enforce_permission(permission_name="zones:action:schedule") def action_schedule(self, **_): """Allow or disallow a zone to be scheduled --- diff --git a/deli/counter/http/mounts/root/routes/swagger.py b/deli/counter/http/mounts/root/routes/swagger.py index c52c270..a284989 100644 --- a/deli/counter/http/mounts/root/routes/swagger.py +++ b/deli/counter/http/mounts/root/routes/swagger.py @@ -1,3 +1,4 @@ +# flake8: noqa import cherrypy from ingredients_http.route import Route diff --git a/deli/counter/http/spec/plugins/docstring.py b/deli/counter/http/spec/plugins/docstring.py index f92f797..6d3edb6 100644 --- a/deli/counter/http/spec/plugins/docstring.py +++ b/deli/counter/http/spec/plugins/docstring.py @@ -79,9 +79,9 @@ def docstring_path_helper(spec, path, router, func, **kwargs): 'type': 'string' } }) - - if 'tools.enforce_policy.policy_name' in cp_config: - data['x-required-policy'] = cp_config['tools.enforce_policy.policy_name'] + + if 'tools.enforce_permission.permission_name' in cp_config: + data['x-required-permission'] = cp_config['tools.enforce_permission.permission_name'] return Path(path=path.path, operations=operations) diff --git a/deli/kubernetes/resources/v1alpha1/iam_policy/controller.py b/deli/kubernetes/resources/v1alpha1/iam_policy/controller.py index 49b5b99..dcd6d34 100644 --- a/deli/kubernetes/resources/v1alpha1/iam_policy/controller.py +++ b/deli/kubernetes/resources/v1alpha1/iam_policy/controller.py @@ -52,6 +52,9 @@ def created(self, model: IAMPolicy): needs_save = True model.bindings.remove(binding) + # TODO: do we remove service accounts that no longer exist? + # TODO: do we remove groups that no longer exist? + if needs_save: model.save() diff --git a/deli/kubernetes/resources/v1alpha1/iam_role/controller.py b/deli/kubernetes/resources/v1alpha1/iam_role/controller.py index c490623..899db00 100644 --- a/deli/kubernetes/resources/v1alpha1/iam_role/controller.py +++ b/deli/kubernetes/resources/v1alpha1/iam_role/controller.py @@ -1,4 +1,4 @@ -from deli.counter.auth.policy import SYSTEM_POLICIES, PROJECT_POLICIES +from deli.counter.auth.permission import SYSTEM_PERMISSIONS, PROJECT_PERMISSIONS from deli.kubernetes.controller import ModelController from deli.kubernetes.resources.model import ResourceState from deli.kubernetes.resources.v1alpha1.iam_role.model import IAMProjectRole, IAMSystemRole @@ -35,12 +35,12 @@ def created(self, model: IAMSystemRole): if model.name != 'admin': return - policy_names = [p['name'] for p in SYSTEM_POLICIES] + permission_names = [p['name'] for p in SYSTEM_PERMISSIONS] - if model.policies == policy_names: + if model.permissions == permission_names: return - model.policies = policy_names + model.permissions = permission_names model.save(ignore=True) def to_delete(self, model): @@ -83,20 +83,22 @@ def creating(self, model): model.save() def created(self, model: IAMProjectRole): - policies = { - 'viewer': [policy['name'] for policy in PROJECT_POLICIES if 'viewer' in policy.get('tag', [])], - 'editor': [policy['name'] for policy in PROJECT_POLICIES if 'editor' in policy.get('tag', [])], - 'owner': [policy['name'] for policy in PROJECT_POLICIES] + permissions = { + 'viewer': [permission['name'] for permission in PROJECT_PERMISSIONS if + 'viewer' in permission.get('tag', [])], + 'editor': [permission['name'] for permission in PROJECT_PERMISSIONS if + 'editor' in permission.get('tag', [])], + 'owner': [permission['name'] for permission in PROJECT_PERMISSIONS] } - if model.name not in policies.keys(): + if model.name not in permissions.keys(): return - policies = policies[model.name] - if model.policies == policies: + permissions = permissions[model.name] + if model.permissions == permissions: return - model.policies = policies + model.permissions = permissions model.save() def to_delete(self, model): diff --git a/deli/kubernetes/resources/v1alpha1/iam_role/model.py b/deli/kubernetes/resources/v1alpha1/iam_role/model.py index e87453e..2cfec4a 100644 --- a/deli/kubernetes/resources/v1alpha1/iam_role/model.py +++ b/deli/kubernetes/resources/v1alpha1/iam_role/model.py @@ -1,4 +1,4 @@ -from deli.counter.auth.policy import SYSTEM_POLICIES, PROJECT_POLICIES +from deli.counter.auth.permission import SYSTEM_PERMISSIONS, PROJECT_PERMISSIONS from deli.kubernetes.resources.model import SystemResourceModel, ProjectResourceModel from deli.kubernetes.resources.project import Project @@ -9,22 +9,22 @@ def __init__(self, raw=None): super().__init__(raw) if raw is None: self._raw['spec'] = { - 'policies': [] + 'permissions': [] } @property - def policies(self): - return self._raw['spec']['policies'] + def permissions(self): + return self._raw['spec']['permissions'] - @policies.setter - def policies(self, value): - self._raw['spec']['policies'] = value + @permissions.setter + def permissions(self, value): + self._raw['spec']['permissions'] = value @classmethod def create_default_roles(cls): admin_role = cls() admin_role.name = "admin" - admin_role.policies = [policy['name'] for policy in SYSTEM_POLICIES] + admin_role.permissions = [permission['name'] for permission in SYSTEM_PERMISSIONS] if cls.get(admin_role.name) is None: admin_role.create() @@ -35,33 +35,35 @@ def __init__(self, raw=None): super().__init__(raw) if raw is None: self._raw['spec'] = { - 'policies': [] + 'permissions': [] } @property - def policies(self): - return self._raw['spec']['policies'] + def permissions(self): + return self._raw['spec']['permissions'] - @policies.setter - def policies(self, value): - self._raw['spec']['policies'] = value + @permissions.setter + def permissions(self, value): + self._raw['spec']['permissions'] = value @classmethod def create_default_roles(cls, project: Project): viewer_role = cls() viewer_role.name = "viewer" viewer_role.project = project - viewer_role.policies = [policy['name'] for policy in PROJECT_POLICIES if 'viewer' in policy.get('tag', [])] + viewer_role.permissions = [permission['name'] for permission in PROJECT_PERMISSIONS if + 'viewer' in permission.get('tag', [])] viewer_role.create() editor_role = cls() editor_role.name = "editor" editor_role.project = project - editor_role.policies = [policy['name'] for policy in PROJECT_POLICIES if 'editor' in policy.get('tag', [])] + editor_role.permissions = [permission['name'] for permission in PROJECT_PERMISSIONS if + 'editor' in permission.get('tag', [])] editor_role.create() owner_role = cls() owner_role.name = "owner" owner_role.project = project - owner_role.policies = [policy['name'] for policy in PROJECT_POLICIES] + owner_role.permissions = [permission['name'] for permission in PROJECT_PERMISSIONS] owner_role.create()