Ensure workspaces are created under same root namespace as agent
Security MRs:
- master: TODO
- backports:
- 16.7: TODO
- 16.6: TODO
- 16.5: TODO
Original implementation MR (closed without merge): https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/3754+
Security issue: https://gitlab.com/gitlab-org/security/gitlab/-/issues/1018+
Description
PROBLEM
User should be prevented from creating workspace in one group/namespace hierarchy which is associated with an agent from a different group
STEPS TO REPRODUCE
- Create or find a user (
user
) - Create a root group/namespace (
root_group_1
) - Create a project in
root_group_1
(agent_project
) - Give
user
developer access toagent_project
- Create a cluster agent for that project (
agent
) - Create a different root group/namespace (
root_group_2
) - Create a project in
root_group_2
(workspace_project
) - Give
user
developer access toworkspace_project
- Make a graphql request to:
- create a workspace
- ...with project set to
workspace_project
- ...with agent set to
agent
EXPECTED
- User is prevented from creating workspace with an error related to authorization, which does not expose any sensitive information. For example the error message should be "The resource that you are attempting to access does not exist or you don't have permission to perform this action".
ACTUAL
- User is allowed to create the workspace
BACKGROUND CONTEXT
- An agent determines where a workspace runs, on some kubernetes cluster somewhere, potentially with access to sensitive data volumes that are exposed within that cluster
- A workspace is associated with an agent.
- Our rule is that you should only be able to run a workspace on an agent that is under the same root group/namespace. This implies that they are at least within the same organization.
- The above rule is especially relevant on gitlab.com / SaaS, where multiple organizations run on the same instance, but are segregated by having different root namespaces.
EXAMPLE SCENARIO
So, the bug here is that (only via the GraphQL API, not the UI) you can violate the rule in number 3, and run a workspace on ANY agent.
An example exploit scenario would be:
- A contractor who has accounts for two independent clients, both of whom have accounts and separate root namespaces/groups on gitlab.com/SaaS, e.g.:
client_a
andclient_b
root groups. - Given the adequate roles on both groups, she uses the GraphQL API to create a workspace for a project under under
client_a
group hierarchy, but using an agent in theclient_b
group hierarchy. - This lets the workspace run on the
client_b
agent's kubernetes cluster, use the cluster resources to run the workspaces, and potentially access sensitive data on the cluster depending on the volume shares and permissions set up the cluster.
Acceptance Criteria
When the agent and project for a workspace create mutation are in different namespaces
-
A workspace cannot be created in this scenario
GraphQL Example
Query showing dev projects and agent project and groups:
query projects432188 {
projects(ids: ["gid://gitlab/Project/47", "gid://gitlab/Project/48", "gid://gitlab/Project/49"]) {
nodes {
id
name
clusterAgent(name: "remotedev") {
id
}
group {
id
fullPath
}
}
}
}
Response from groups query:
{
"data": {
"projects": {
"nodes": [
{
"id": "gid://gitlab/Project/49",
"name": "dev-proj-for-ns-2",
"clusterAgent": null,
"group": {
"id": "gid://gitlab/Group/123",
"fullPath": "ns-2-root/ns-2-dev"
}
},
{
"id": "gid://gitlab/Project/48",
"name": "dev-proj-for-ns-1",
"clusterAgent": null,
"group": {
"id": "gid://gitlab/Group/118",
"fullPath": "ns-1-root/ns-1-dev"
}
},
{
"id": "gid://gitlab/Project/47",
"name": "agent-proj-for-ns-1",
"clusterAgent": {
"id": "gid://gitlab/Clusters::Agent/6"
},
"group": {
"id": "gid://gitlab/Group/117",
"fullPath": "ns-1-root/ns-1-agent"
}
}
]
}
}
}
Mutation violating root namespace check:
mutation workspaceCreate432188Fail {
workspaceCreate(
input: {clusterAgentId: "gid://gitlab/Clusters::Agent/6", projectId: "gid://gitlab/Project/49", desiredState: "Running", editor: "webide", maxHoursBeforeTermination: 24, devfileRef: "main", devfilePath: ".devfile.yaml"}
) {
workspace {
id
name
namespace
url
desiredState
actualState
editor
maxHoursBeforeTermination
devfile
deploymentResourceVersion
projectId
}
errors
}
}
Error response:
{
"data": {
"workspaceCreate": null
},
"errors": [
{
"message": "Workspace's project and agent's project must both be under the same common root group/namespace.",
"locations": [
{
"line": 107,
"column": 3
}
],
"path": [
"workspaceCreate"
]
}
]
}
Mutation not violating check:
mutation workspaceCreate432188Succeed {
workspaceCreate(
input: {clusterAgentId: "gid://gitlab/Clusters::Agent/6", projectId: "gid://gitlab/Project/48", desiredState: "Running", editor: "webide", maxHoursBeforeTermination: 24, devfileRef: "main", devfilePath: ".devfile.yaml"}
) {
workspace {
id
name
namespace
url
desiredState
actualState
editor
maxHoursBeforeTermination
devfile
deploymentResourceVersion
projectId
}
errors
}
}
Success Response
{
"data": {
"workspaceCreate": {
"workspace": {
"id": "gid://gitlab/RemoteDevelopment::Workspace/76",
"name": "workspace-6-1-utkkio",
"namespace": "gl-rd-ns-6-1-utkkio",
"url": "https://60001-workspace-6-1-utkkio.workspaces.localdev.me?folder=%2Fprojects%2Fdev-proj-for-ns-1",
"desiredState": "Running",
"actualState": "CreationRequested",
"editor": "webide",
"maxHoursBeforeTermination": 24,
"devfile": "schemaVersion: 2.2.0\ncomponents:\n- name: tooling-container\n attributes:\n gl/inject-editor: true\n container:\n image: quay.io/mloriedo/universal-developer-image:ubi8-dw-demo\n",
"deploymentResourceVersion": null,
"projectId": "gid://gitlab/Project/48"
},
"errors": []
}
}
}