JIT Groups JIT Groups, or what's next for the JIT Access project

The primary focus of JIT Access has been privileged access management (PAM) – enabling users to request just-in-time privileged access to Google Cloud projects on a temporary basis. When I published the project in 2022, Google Cloud didn’t provide any such capabilities as part of the platform, so the project filled an important gap.

With Privileged Access Manager in preview now, that situation has changed and managing just-in-time privileged access is now a platform feature. This raises the question of what the role of JIT Access will be going forward.

There are a few differences between JIT Access and Privileged Access Manager, but by and large they provide similar capabilities and work in a similar way. Compared to JIT Access, Privileged Access Manager has the clear benefit that it’s a fully-managed service, which is going to make it the preferred choice for many customers.

However, there are other identity-related problems to be solved beyond privileged access management.

Beyond privileged access management

Reducing the amount of privileges a user holds at any particular time, and enforcing certain rules and workflows around how users obtain privileges are concepts that are important beyond the realm of privileged access: Overpermissioning, inconsistency, and permission creep are risks that are constantly present when managing access, whether highly-privileged or not:

  • Overpermissioning: Resource owners might be too generous in “handing out access”.
  • Access sprawl: If access is managed on an ad-hoc basis, users with the same job function might end up being granted different levels of access, making it increasingly hard to reason about who has access to what.
  • Permission creep: Over time, users tend to be granted access to more and more resources, and it becomes increasingly difficult to understand why they have that access, and if it’s needed at all.

There are a few commonly followed patterns for managing access on Google Cloud, and they differ in how well they’re suited to address these challenges:

  1. Manually managing IAM policies: In this pattern, resource owners (that is, project owners or anybody that is allowed to modify IAM policies) manage access to projects and resources by manually adding or removing users from the resource’s IAM policies.

    By using controls such as domain-restricted sharing policy constraints and limited role granting, it’s possible to set up certain limits w.r.t. which roles resource owners can grant, and which principals they can grant them to. Still, the approach typically doesn’t scale well and it’s particularly prone to all three risks above.

  2. Using Google groups: In this pattern, administrators set up groups to represent job functions (for ex, [email protected]), assign the appropriate IAM roles to the group, and then manage access by adding or removing members to these groups. The groups themselves might be managed in Cloud Identity/Workspace or auto-provisioned from an external source such as Okta or Entra ID.

    By bundling access, this approach can be very effective in reducing sprawl and, to some extent, in reducing permission creep.

    However, managing groups, and creating new groups whenever new resources are being deployed, incurs an overhead. This overhead can be particularly acute when groups are sourced from external systems and creating new groups requires coordinating with different teams.

    As a result, the approach can increase overpermissioning by incentivizing users to reuse existing groups. Using Google groups can also introduce new risks if group settings aren’t locked down appropriately (for example, to prevent external members, arbitrary group nesting, or users undermining IAM deny-policies by leaving groups).

  3. Using infrastructure as code (IaC): In this pattern, administrators use Terraform (or similar tools) to manage IAM policies as code, and follow a GitOps workflow to enforce code reviews and track changes.

    This approach can be appealing to technical staff and has the positive side-effect of producing an audit trail. Code reviews can also be effective in reducing overpermissioning.

    However, the approach can be unsuitable for less-technical staff and, more importantly, might not necessarily help in reducing sprawl or permission creep unless there is additional tooling in place (such as static analysis checks) that restrict how access can be granted, and for how long it’s granted.

The three approaches tend to leave a gap, and room for an access management solution that:

  • allows bundling multiple IAM role bindings across multiple projects and resources
  • lets users discover, request, approved, audit, and review access
  • is usable for less-technical users
  • ensures that all access automatically expires unless explicitly renewed

And this is the goal for JIT Groups, which is essentially JIT Access 2.0 – to provide an open-source solution that addresses these requirements and become a complement to Privileged Access Manager.

JIT Groups

The JIT Access successor introduces the notion of a JIT group. A JIT group is a Cloud Identity security group that represents a certain job function (such as Database Administrators for the Foo app) and bundles any number of IAM roles to one or more resources.

Membership of a JIT group is always time-bound, but the time might vary by group. For a JIT group that grants administrative access, membership might be limited to an hour, for other groups, memberships might last weeks or even months. But all access expires eventually unless users take action – which is a critical difference to most other approaches in which all access persists unless users take action.

And it’s key to achieving the goal of zero standing privileges.

For each JIT group, administrators can define:

  • Who is allowed to join the group, and whether they need approval to join
  • Who is allowed to approve
  • An optional set of constraints that users must meet to join the group
  • Input that users must provide when joining a group, or approving a request.

All these settings are defined as a policy, and an example policy might look like this:

- name: "Server-Admins"
  description: "Admin-level access to servers"
  
  # Who can view, join, approve?
  access:
    # Ann can join and doesn't need approval from anyone
    - principal: "user:[email protected]"
      allow: "JOIN, APPROVE_SELF"
      
    # Ben can also join and approve other's requests to join the group
    - principal: "user:[email protected]"
      allow: "JOIN, APPROVE_SELF, APPROVE_OTHERS"
      
    # Everybody in the managers group can approve requests, but can't join themselves
    - principal: "group:[email protected]"
      allow: "APPROVE_OTHERS"
      
  # Which constraints apply when joining this group?
  constraints:
    join:
      # Let users chose an expiry between 1 and 24 hours
      - type: "expiry"
        min: "PT1H"
        max: "PT24H"
        
  # What does the group grant access to?
  privileges:
    iam:
      # Some roles on sample-project-1
      - project: "sample-project-1"
        role: "roles/compute.osAdminLogin"
      - project: "sample-project-1"
        role: "roles/iap.tunnelResourceAccessor"
      - project: "sample-project-1"
        role: "roles/compute.instanceAdmin.v1"
        
      # A few more roles on sample-project-2
      - project: "sample-project-2"
        role: "roles/iap.tunnelResourceAccessor"
      - project: "sample-project-2"
        role: "roles/compute.instanceAdmin.v1"

In addition to a simple expiry constraint, policies can define more complex constraints which are defined as a CEL expression. A (slightly contrived) example might look like this :

- name: "Server-Admins"
  ...
  constraints:
    join:

      # Require users to enter a justification that matches a certain pattern
      - type: "expression"
        name: "justification"
        displayName: "You must provide a justification"
        expression: "input.justification.matches('^SUPPORT-[0-9]+$')"
        variables:
          - type: "string"
            name: "justification"
            displayName: "Buganizer ID"
            min: 1
            max: 100

      # Require users to enter their age, and check that they're over 18
      - type: "expression"
        name: "age"
        displayName: "You must be over 18"
        expression: "input.age >= 18"
        variables:
          - type: "int"
            name: "age"
            displayName: "Age"
            min: 1
            max: 100

      # Require users to accept some terms
      - type: "expression"
        name: "tos"
        displayName: "You must read and accept our data access policy"
        expression: "input.tos_accepted"
        variables:
          - type: "boolean"
            name: "tos_accepted"
            displayName: "I've read and accept our data access policy"
            
      # Require users choose an expiry between 1 and 30 days
      - type: "expiry"
        min: "P1D"
        max: "P30D"

JIT Groups automatically creates and manages the corresponding Cloud Identity group and its role bindings, and locks down group settings according to good best practices. Eligible users can access the JIT Groups web interface to browse, join, request, and approve group memberships – similar to how that works today in JIT Access.

To compare:

  • While JIT Access 1.x lets users activate individual IAM roles for a single project, JIT Groups lets users join groups, which might grant them access to multiple resources at once.
  • While JIT Access 1.x provisions temporary IAM role bindings, JIT Groups provisions temporary group memberships.
  • While JIT Access 1.x doesn’t let you vary settings by role, JIT Groups lets you use different access settings and constraints by group.

These changes make JIT Groups suitable for managing any kind of Google Cloud access, not just privileged access. And because JIT Groups implements a superset of the existing JIT Access features, it’s backwards-compatible.

Managing policies

JIT Access 1.x uses IAM conditions to model “eligible role bindings”. This approach is lightweight and has worked reasonably well, but it’s too constraining to model more complex policies as the ones shown in the examples above. JIT Groups therefore takes a different approach that’s based on policy documents.

Given a Google Cloud organization, you can segment your resource hierarchy into one or more environments. Examples for environments could be “dev” and “prod”, but the segmentation could be much finer-grained than that.

image

For each environment, JIT Groups maintains:

  • A policy document that defines the groups for this environment. The examples above are excerpts from such a policy document.
  • A Secret Manger secret to store the policy document. The secret can live in any project, and must be appropriately secured.
  • A service account to provision IAM bindings for projects that belong to that environment. Because each environment uses a different service account, a policy document can only grant access to resources that the corresponding service account has access to. This helps keep environments isolated from another.

This design lends itself to be incorporated into a GitOps model that looks as follows:

image

  • Policy documents are stored in Git, managed using a GitOps workflow, and then “deployed” to Secret Manager. The GitOps workflow helps ensure that all policy changes are reviewed and versioned.
  • JIT Groups then takes care of provisioning groups, IAM bindings, and letting users manage their group memberships – without having to deal with code, and all subject to the settings and constraints defined by the policies.

For more information about JIT Groups, take a look at the documentation or, better yet, give it a try!

Any opinions expressed on this blog are Johannes' own. Refer to the respective vendor’s product documentation for authoritative information.
« Back to home