SaFi Bank Space : Okta - GCP POC

A proof of concept to integrate Okta with Google Cloud Platform.

Used the following accounts for the POC.

Test accounts:

  • Terraform Cloud

  • GCP

Trial accounts

  • Google Workspace (good for 14 days)

  • Okta (good for 30 days)

Goals:

Okta to Google Platform Platform SSO

This involves some manual configuration as currently can’t find a way how to set required fields in Single sign-on (SSO) with third-party identity providers (IDPs) of Google Workspace using terraform.

Checked hashicorp/googleworkspace but currently doesn’t have anything specific for setting up third-party IDPs.

Creating Okta Pre-configured GCP application

There are only 2 required fields to set in Okta Google Cloud Platform application.

  • https://console.cloud.google.com in the Default Relay State field.

  • And the company domain.

The required fields can be manually set on Okta Admin but here’s how in terraform.

resource "okta_app_saml" "gcp" {
  preconfigured_app   = "cloudconsole"
  label               = "Google Cloud Platform"
  default_relay_state = "https://console.cloud.google.com"
  status              = "ACTIVE"
}

resource "okta_app_saml_app_settings" "gcp" {
  app_id = okta_app_saml.gcp.id
  settings = jsonencode(
    {
      "domain" : "lazyluna.dev"
    }
  )
}

output "okta_app_saml_gcp" {
  value = okta_app_saml.gcp
}

Note:

We should store values of output "okta_app_saml_gcp" e.g. certificate, SSO sign-in URL in Hashicorp vault.

These values will be used in Single sign-on (SSO) with third-party identity providers (IDPs) on Google Workspace.

On Okta Admin after creation.

Setting up Okta as third-party IDP on Google Workspace

On https://admin.google.com/ (workspace admin required), navigate to,

  • Security>Authentication>SSO with third party IdP>Third-party SSO profile for your organization

Set required fields and save:

  • Enable Set up SSO with third-party identity provider

  • Sign-in page URL

  • Sign-out page URL

  • Verification certificate (use REPLACE CERTIFICATE to upload)

  • Enable Use a domain specific issuer

Get values to use from previously created okta_app_saml.gcp resource.

  • Take note that certificate must be in -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- form before uploading.

  • Sign-in page URL == http_post_binding

  • Sign-out page URL == Okta domain

Sign-in via Okta

For initial sign-in, you should see the usual Okta set new password, setup security methods, etc.

Last part is google account verification then finally a successful sign-in to GCP.

Integrate Okta Roles with GCP

This integration POC does not include auto provisioning of users or Okta managing google user lifecycle and groups syncing.

This is more of a “Phase 1“ approach how to link Okta users role/s to available GCP groups using terraform.

Currently we have a couple of users_*.yaml files where we store both users and and roles to be assigned per user. e.g. users_devops.yaml, users_meiro.yaml, users_firebase.yaml, etc.

Ideally there should be a single source of truth for existing users and since Okta is a prerequisite for accessing services, user's details, like email, should just be in users_okta.yaml.

Then we can maybe have groups_gcp.yaml, groups_tyk.yaml, groups_*.yaml where roles specific details for GCP, Tyk and possibly other services we have will be stored/declared.

We can then link service specific roles to users_okta.yaml by a unique group naming convention.

Here’s an example in code to be clear.

In users_okta.yaml, this should be the single source of user details including the group assignment.

users:
  - email: devops@lazyluna.dev
    first_name: DevOps
    last_name: Infrastructure
    groups:
      - DevOps

  - email: developer@lazyluna.dev
    first_name: Developer
    last_name: Programmer
    groups:
      - Developers

  - email: qa@lazyluna.dev
    first_name: QA
    last_name: Testing
    groups:
      - Meiro

Next is in a groups_<service>.yaml e.g. groups_gcp.yaml would only contain GCP specific roles per projects assigned to a GCP group.

groups:
  - name: DevOps
    projects_iam:
      - project: my-home-sandbox
        roles:
          - roles/editor

  - name: Developers
    projects_iam:
      - project: my-home-sandbox
        roles:
          - roles/viewer
          - roles/logging.viewer
          - roles/container.developer

  - name: Meiro
    projects_iam:
      - project: my-home-sandbox
        roles:
          - roles/viewer

Then to link Okta roles to GCP, is basically what Application is assigned to an Okta group and GCP is just a pre-configured application that can be added in Okta.

groups:
  - name: DevOps
    description: "Group for DevOps"
    apps:
      - GCP
      - DevOpsSpecificApp

  - name: Developers
    description: "Group for Developers"
    apps:
      - GCP
      - DevelopersSpecificApp

  - name: Meiro
    description: "Group for Meiro"
    apps:
      - MeiroSpecificApp
      - GCP

Finally in terraform, where the actual “linking” happens.

In a shared variable, we can pre-create the groupings per application and groups.

locals {
  safi_okta_users  = yamldecode(file("${path.module}/../_files/users_okta.yaml"))["users"]
  safi_okta_groups = yamldecode(file("${path.module}/../_files/groups_okta.yaml"))["groups"]

  safi_okta_groups_with_gcp_app = tomap({ for group in local.safi_okta_groups :
    group.name => group if contains(group.apps, "GCP")
  })

  safi_okta_users_assigned_to_group_with_gcp_app = merge([for group in local.safi_okta_groups_with_gcp_app : {
    for user in local.safi_okta_users :
    user.email => user if contains(user.groups, group.name)
  }]...)

  safi_groups_gcp = yamldecode(file("${path.module}/../_files/groups_gcp.yaml"))["groups"]
}

Then similar to how we currently create GCP roles assignment using the grouping per app/service.

resource "google_project_iam_member" "project" {
  for_each = { for item in flatten([for okta_user in local.safi_okta_users_assigned_to_group_with_gcp_app : [
    # Get Okta user assigned roles from GCP groups
    for gcp_group in local.safi_groups_gcp : [
      for gcp_project in gcp_group.projects_iam : [
        for gcp_project_role in gcp_project.roles : {
          member = okta_user.email, project = gcp_project.project, role = gcp_project_role, group = gcp_group.name
        }
      ]
    ]
    if contains(okta_user.groups, gcp_group.name)
  ]]) : format("%s+%s+%s+%s", item.member, item.project, item.role, item.group) => item }

  project = each.value.project
  role    = each.value.role
  member  = format("user:%s", each.value.member)
}

On Okta, after the successful provisioning.

Then on GCP.