This document describes proposed solution to the problem of unlimited resource access. Limiting access to certain resolvers is important especially in case of accessing authorization details.
API Consumer - Application / Runtime / Integration System / User
Restrict access to application { api { auth(runtimeID) } }
for Runtimes with ID different than passed runtimeID
RuntimeAgent wants to read auth details for application's API.
The RuntimeAgent should be allowed to read field auth
only with his own ID.
In this case we will use new directive limitAccessFor
and limitManagerAccessTo
.
RuntimeAgent wants to read applications but shouldn't have access to their auth details.
We can restrict the access by current hasScopes
directive.
We should be able to restrict access to any resolver that takes API Consumer ID as one of its parameters by adding directive.
We want to cover following cases:
- Runtime musn't read other Runtimes.
- Application musn't read other Applications.
- Integration System musn't read other Integration Systems.
- Runtime musn't read Applications which are not in the same scenario (
applicationsForRuntime
).
When Integration System request specific Application/Runtime, it should be able to read only object managed by itself.
To achieve those restrictions the following solution is proposed:
We can use information which is added in Tenant Mapping Service to JWT PR:
- Object Type, service is able to determine who calls the API, whether it is Application, Runtime or IntegrationSystem
- Object ID of caller
The enriched JWT is sent to the Compass API.
This directive will limit access to resolvers for specific types of API consumers (by ID).
Proposed directive:
enum consumerType {
RUNTIME
APPLICATION
INTEGRATION_SYSTEM
USER
}
directive @limitAccessFor(consumerType: consumerType!, idField: String!) on FIELD_DEFINITION
The proposed directive does following things:
- get object type and ID from JWT token
- check if object is the same type as
consumerType
argument - if yes, then the directive compare the ID field. If the ID mismatches, the following error is returned
Access Denied
. - if no, the request is allowed
Currently we cannot use this directive on query param: issue.
This directive will limit access to application/runtime resolvers managed by integration system
Proposed directive:
enum managedResourceType {
RUNTIME
APPLICATION
}
directive @limitManagerAccessTo(managedResourceType: managedResourceType!, idField: String!) on FIELD_DEFINITION
The proposed directive does following things:
- get object type and ID from JWT token
- check if object is the
integration system
- if yes, then the directive check in database if given application/runtime is managed by integration system which called the query.
If integration system doesn't manage the application/runtime, the following error is returned
Access Denied
Currently we cannot use this directive on query param ( 99designs/gqlgen#760).
Every resource managed by integration system have to be reachable from integration system perspective.
We will introduce new scopes:
- applicationForRuntime:list - for runtimes and admin user
- runtime:list - for admin user
- application:list - for admin user
- application:auth:read - for application
We should change all read scopes on collections to list for consistency.
New graphql schema after implementing and applying new directives:
type Application {
...
auths: [SystemAuth!]! @hasScopes(path:"graphql.field.application.auths")
}
type APIDefinition {
...
auth(runtimeID: ID!): APIRuntimeAuth! @limitAccessFor(consumerType: RUNTIME, idField: "runtimeID") @hasScopes(path:"graphql.query.application.apidefinition.apis")
auths: [APIRuntimeAuth!]! @hasScopes(path:"graphql.field.apidefinition.auths")
}
type Runtime {
...
auths: [SystemAuth!]! @hasScopes(path:"graphql.field.runtime.auths")
}
type IntegrationSystem {
...
auths: [SystemAuth!]! @hasScopes(path:"graphql.field.integrationSystem.auths")
}
type Query {
...
integrationSystem(id: ID!): IntegrationSystem @hasScopes(path: "graphql.query.integrationSystem") @limitAccessFor(consumerType: INTEGRATION_SYSTEM, idField: "id")
runtime(id: ID!): Runtime @hasScopes(path: "graphql.query.runtime") @limitAccessFor(consumerType: RUNTIME, idField: "id")
application(id: ID!): Application @hasScopes(path: "graphql.query.application")
@limitAccessFor(consumerType: APPLICATION, idField: "id")
@limitManagerAccessTo(managedResourceType: APPLICATION, idField: "id")
applicationsForRuntime(runtimeID: ID!, first: Int = 100, after: PageCursor): ApplicationPage!
@hasScopes(path: "graphql.query.applicationsForRuntime")
@limitAccessFor(consumerType: RUNTIME, idField: "runtimeID")
}
Example based on applicationsForRuntime
flow.
We add limitAccessFor
directive to applicationsForRuntime
query:
type Query {
applicationsForRuntime(runtimeID: ID!, first: Int = 100, after: PageCursor): ApplicationPage!
@hasScopes(path: "graphql.query.applicationsForRuntime")
@limitAccessFor(consumerType: RUNTIME, idField: "runtimeID")
}
Execution flow:
- Tenant Mapping Service recognize API consumer, then add
ID
andconsumerType
to JWT token. - Directive
limitAccessFor
is executed- When Runtime Agent with ID
ABCD
executes queryapplicationsForRuntime
with paramruntimeID
equal toDCBA
, the Runtime Agent will get an errorAccess Denied
, because the IDs are different. - When IntegrationSystem with ID
ABCD
executes queryapplicationsForRuntime
with paramruntimeID
equal toDCBA
, The directive doesn't compare anything, because it's only turn on for theRUNTIME
.
- When Runtime Agent with ID
Example based on application(id)
flow.
In this case besides limitAccessFor
directive we are also going to apply limitManagerAccessTo
.
We add limitManagerAccessTo
directive to application
query:
type Query {
application(id: ID!): Application!
@hasScopes(path: "graphql.query.applicationsForRuntime")
@limitAccessFor(consumerType: APPLICATION, idField: "id")
@limitManagerAccessTo(managedResourceType: APPLICATION, idField: "id")
}
Execution flow:
- Integration system asks for application with id
DCBA
- Tenant Mapping Service recognize API consumer, then add
ID
andconsumerType
to JWT token. - Directive
limitAccessFor
allows the request because consumer is not an application. - Directive
limitManagerAccessTo
is executed. The directive checks if given application is managed by integration system, if yes the request is allowed, in other case theAccess Denied
error is returned.