Skip to content

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

  1. Create or find a user (user)
  2. Create a root group/namespace (root_group_1)
  3. Create a project in root_group_1 (agent_project)
  4. Give user developer access to agent_project
  5. Create a cluster agent for that project (agent)
  6. Create a different root group/namespace (root_group_2)
  7. Create a project in root_group_2 (workspace_project)
  8. Give user developer access to workspace_project
  9. Make a graphql request to:
    1. create a workspace
    2. ...with project set to workspace_project
    3. ...with agent set to agent

EXPECTED

  1. 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

  1. User is allowed to create the workspace

BACKGROUND CONTEXT

  1. 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
  2. A workspace is associated with an agent.
  3. 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.
  4. 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:

  1. 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 and client_b root groups.
  2. 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 the client_b group hierarchy.
  3. 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": []
    }
  }
}
Edited by Chad Woolley