Skip to content

Instantly share code, notes, and snippets.

@jefftaylor-okta
Last active August 20, 2024 15:45
Show Gist options
  • Select an option

  • Save jefftaylor-okta/5a90b09a62fddecefc56f6e52ad64094 to your computer and use it in GitHub Desktop.

Select an option

Save jefftaylor-okta/5a90b09a62fddecefc56f6e52ad64094 to your computer and use it in GitHub Desktop.
This Gist is a companion to the Oktane 2023 presentation Managing Multiple Okta Deployments with Terraform.

Oktane '23: Managing Multiple Okta Deployments

This repository is a company to the Oktane 23 Presentation on Managing Multiple Okta Deployments. This repository will cover the code and instructions mentioned in the presentation.

Scene

The Chief Revenue Officer of a fictional company is getting complaints from the Sales Engineers. They are running into Multifactor Authentication challenges logging into their Sales Pipeline App 😉. The CRO is keen to unblock the Sales Engineers and demands the Director of IT create a simpler method for logging in so they can keep productivity up through the end of the quarter. The Director of IT, who reports up to the Chief Information Officer is concerned about lowering the security when accessing confidential financial data.

The challenge

How can the Director of IT build a passwordless and secure method of authentication for the Sales Engineers accessing a protected resource?

Deploying a Passwordless test through Terraform

Below are the steps and instructions on how to address the challenge by utilizing the Terraform, Okta, and the Okta Terraform provider.

Prerequisites

  • Create a custom Single Page App in your org and call it Sales Pipeline. You can use default values for the configuration. If you would like to set up a new app from an existing Okta sample app, you can use this Okta React Sample App to get started.

  • Create a user in your org and configure your email and Okta Verify factors for authentication. Make Sure in the user's profile that the Department attribute is set to Sales Engineering.

Step 1: Setup Okta and Terraform

First is getting setup with Okta and Terraform. Luckily, all the setup is free from both Okta Terraform. Check out the links below to get started.

Step 2: Configure least privilege access for Terraform in Okta

As the Director of IT, it's important to make sure this Terraform module cannot be used outside of the bounds of this exercise. Okta has ways to create least privilege access for its clients and prevent those who have access to the code from requesting unauthorized scopes.

  1. Create an Application in your Okta Org

    Creating an application will generate OAuth credentials. Switch from Client secret to Public key/Private key

  2. Assign the scopes to your Terraform module

    This will create guardrails for the scopes requested by Terraform when running in your environment ensuring least privilege access

    NOTE: The scopes needed for this example are okta.apps.read, okta.groups.manage, okta.groups.read, okta.authenticators.manage, okta.authenticators.read, okta.policies.manage, okta.policies.read, okta.users.manage, okta.users.read

Step 3: Configure your Terraform module to connect to Okta

With a test Okta tenant and Terraform setup and least privileged access defined, we can now configure the Terraform module to include the latest version of the Okta Terraform provider, and set the necessary connection information to the tenant.

  1. Generate a key in the application settings page.

    You can save this key to a file. Better yet, store the private key in your secrets management solution.

    Note: Okta generates a PKCS #8 key that needs to be converted to PKCS #1 with the following command:

    openssl rsa -in pkcs8.pem -out pkcs1.pem -traditional
  2. Add the configuration keys to your Terraform configuration.

    Create a file main.tf and add these keys to the Terraform module config: org_name, base_url, client_id, scopes, and private_key

Terraform Module with configuration

Here is how your Terraform Module should look after adding the configuration code block:

# Generic Terraform Config
terraform {
  required_providers {
    okta = {
      source = "okta/okta"
      version = "~> 4.4.2"
    }
  }
}

# Configure the Okta Provider
provider "okta" {
  org_name  = "{org_prefix}" # example: "dev-123456"
  base_url  = "{org_domain}" # example: "okta.com"
  client_id = "{cient_id}"   # example: "123abc456def"
  scopes = ["okta.apps.read","okta.authenticators.manage","okta.authenticators.read","okta.groups.manage","okta.groups.read","okta.policies.manage","okta.policies.read","okta.users.manage","okta.users.read"]
  private_key = file("pkcs1.pem") # location of your file in Step 3.1
}

Step 4: Add a Group resource to your Terraform module

To get started on this journey, we need to move Sales Engineers into a group that will be used for Passwordless access. We will create a new group and a group rule that will automatically assign new Sales Engineer to the group for passwordless access. With Terraform, new objects that you create are called Resources. We will create a group resource and group rule resource in our Terraform Module.

  1. Create a top-level resource first

    Building what is shown in the Administration Console can take multiple steps. Some objects can be constructed in one resource.

  2. Build the required components and reference the new object properties

    In this example, we are creating a group first, and adding another resource for the group rule.

    Note: It is best to test either in the module or after applying the changes in a development environment.

Terraform code for creating a Group and Group Rule

Here is the code to create the Passwordless Group and Passwordless Group Rule:

# Group Resource
resource "okta_group" "passwordless_group" {
  name = "Passwordless Group"
}

# Create a Group Rule
resource "okta_group_rule" "passwordless_group_rule" {
  name = "Passwordless Group Rule"
  status = "ACTIVE"
  group_assignments = [ okta_group.passwordless_group.id ]
  expression_value = "String.stringContains(user.department,\"Sales Engineering\")"
}

Step 5. Create an Authenticator Enrollment Policy

In moving away from passwords, you are on the way towards phishing resistance. We know all Sales Engineers can authenticate with email, but we want to encourage the use of Okta Verify as well for phishing resistance. We will setup an enrollment policy where email is REQUIRED and Okta Verify and Password are OPTIONAL. This will allow a password to be enrolled for other applications. In general, when creating a new policy object, you will have to create a policy object first and then a rule associated with that policy.

  1. Create the Authenticator Enrollment Policy resource first

    For this example, we want to push enrollment for non password authenticators and to include a phishing resistance factor to increase adoption of more secure authenticators and increasing convenience

  2. Create a Rule for this policy

    This rule can take default properties, but all policies must have rules

    Note: Here is another good place to test the enrollment experience in a development environment.

Terraform code for creating the Authenticator Enrollment Policy

Here is the code to create the Authenticator Policy and Rule:

# Passwordless Enrollment
resource "okta_policy_mfa" "passwordless_enrollment_policy" {
  name = "Passwordless Authenticator Enrollment Policy"
  status = "ACTIVE"
  is_oie = true
  groups_included = [ okta_group.passwordless_group.id ]
  okta_password = {enroll = "OPTIONAL"}
  okta_email = {enroll = "REQUIRED"}
  okta_verify = {enroll = "OPTIONAL"}
  priority = 1
}

# Create the Enrollment Rule
resource "okta_policy_rule_mfa" "passwordless_enrollment_policy_rule" {
  name      = "Passwordless Enrollment Rule"
  policy_id = okta_policy_mfa.passwordless_enrollment_policy.id
  priority  = 1
}

Step 6: Create a Global Session Policy

In some instances, apps may be using the Classic authentication experience. This is where username and password appear on the initial login screen. For passwordless to work, we need to switch the authentication to collect the username first to determine what factors to present to the user. We will create a Global Session Policy and a rule to support this change.

  1. Create the Global Session Policy resource to enable passwordless rules for a group

    We want to syphon off the members of our new group and push them to a new experience that will enable them to login without a password

  2. Create a Rule in Global Session Policy to describe the new experience

    Setting the primary_factor to “PASSWORD_IDP_ANY_FACTOR” will change the login experience to identifier first

Terraform code for creating the Global Session Policy

Here is the code to create the Global Session Policy and Rule:

# Passwordless Global Session Policy
resource "okta_policy_signon" "passwordless_global_session_policy" {
  name = "Passwordless Global Session Policy"
  status = "ACTIVE"
  priority = 1
  groups_included = [ okta_group.passwordless_group.id ]
}

# Passwordless Policy Rule
resource "okta_policy_rule_signon" "passwordless_global_session_policy_rule" {
  name = "Global Session Policy Rule"
  policy_id = okta_policy_signon.passwordless_global_session_policy.id
  priority = 1
  status = "ACTIVE"
  access = "ALLOW"
  primary_factor = "PASSWORD_IDP_ANY_FACTOR"
}

Step 7: Create an Authentication Policy

Finally, we need to set the Level of Assurance for the Sales Pipeline App. To achieve this in Terraform we will be using a Data Source, or a read-only version of an existing object in the org. We will need to find the Application ID and assigned Application Sign on Policy for the Sales Pipeline App. Then, we will create a new rule to change the Level of Assurance for the Passwordless Group.

  1. Find the Application and Application Sign on Policy

    Use the data source to read the IDs of the application and get the associated Application Sign on Policy where we can add the rule

  2. Create a Rule in the Application Sign on Policy to enforce passwordless rules for a group

    Define the criteria for access to the application without a password for the Passwordless Group

Terraform code for creating the Application Sign on Policy

This section of the code makes use of Data Sources. Data Sources are read only objects that are used to pull properties and can be used to create new resources from existing objects. Here is the code to create the Authencation Policy and Rule:

# Get the Application ID
data "okta_app" "sales_pipeline" {
  label = "Sales Pipeline"
}

# Get the Application Sign on Policy
data "okta_app_signon_policy" "okta_sales_pipeline_authn_policy" {
  app_id = data.okta_app.sales_pipeline.id
  depends_on = [
    data.okta_app.sales_pipeline
  ]
}

# Create the Passwordless Rule
resource "okta_app_signon_policy_rule" "passwordless_authn_rule" {
  name = "Passwordless Rule"
  policy_id = data.okta_app_signon_policy.okta_sales_pipeline_authn_policy.id
  access = "ALLOW"
  factor_mode = "1FA"
  groups_included = [ okta_group.passwordless_group.id ]
  priority = 1
}

Wrap up

Congratulations for making it this far. If you want to see this module in action, you can deploy a simple SPA app and connect it to your Okta org. A good example can be found in the okta/samples-js-react repository on Github. Thank you for reading! Here is the full solution that you can copy and run in your local environment.

If you want to reset the changes to your org, simply run terraform destory from the command line.

# Initialize Terraform
terraform {
required_providers {
okta = {
source = "okta/okta"
version = "~> 4.4.2"
}
}
}
# Configure the Okta Provider
provider "okta" {
org_name = "{org_prefix}" # example: "dev-123456"
base_url = "{org_domain}" # example: "okta.com"
client_id = "{cient_id}" # example: "123abc456def"
scopes = ["okta.apps.read","okta.authenticators.manage","okta.authenticators.read","okta.groups.manage","okta.groups.read","okta.policies.manage","okta.policies.read","okta.users.manage","okta.users.read"]
private_key = file("pkcs1.pem") # location of your file in Step 3.1
}
# Create Passwordless Group
resource "okta_group" "passwordless_group" {
name = "Passwordless Group"
}
# Create a Group Rule
resource "okta_group_rule" "passwordless_group_rule" {
name = "Passwordless Group Rule"
status = "ACTIVE"
group_assignments = [ okta_group.passwordless_group.id ]
expression_value = "String.stringContains(user.department,\"Sales Engineering\")"
}
# Create Authenticator Enrollment Policy
# Passwordless Enrollment
resource "okta_policy_mfa" "passwordless_enrollment_policy" {
name = "Passwordless Authenticator Enrollment Policy"
status = "ACTIVE"
is_oie = true
groups_included = [ okta_group.passwordless_group.id ]
okta_password = {enroll = "NOT_ALLOWED"}
okta_email = {enroll = "REQUIRED"}
okta_verify = {enroll = "OPTIONAL"}
priority = 1
}
# Create the Enrollment Rule
resource "okta_policy_rule_mfa" "passwordless_enrollment_policy_rule" {
name = "Passwordless Enrollment Rule"
policy_id = okta_policy_mfa.passwordless_enrollment_policy.id
priority = 1
}
# Create a passwordless GSoP
# Passwordless Global Session Policy
resource "okta_policy_signon" "passwordless_global_session_policy" {
name = "Passwordless Global Session Policy"
status = "ACTIVE"
priority = 1
groups_included = [ okta_group.passwordless_group.id ]
}
# Passwordless Policy Rule
resource "okta_policy_rule_signon" "passwordless_global_session_policy_rule" {
name = "Global Session Policy Rule"
policy_id = okta_policy_signon.passwordless_global_session_policy.id
priority = 1
status = "ACTIVE"
access = "ALLOW"
primary_factor = "PASSWORD_IDP_ANY_FACTOR"
}
# Build the App Sign On Policy
# Get the Application ID
data "okta_app" "sales_pipeline" {
label = "Sales Pipeline"
}
# Get the Application Sign on Policy
data "okta_app_signon_policy" "okta_sales_pipeline_authn_policy" {
app_id = data.okta_app.sales_pipeline.id
depends_on = [
data.okta_app.sales_pipeline
]
}
# Create the Passwordless Rule
resource "okta_app_signon_policy_rule" "passwordless_authn_rule" {
name = "Passwordless Rule"
policy_id = data.okta_app_signon_policy.okta_sales_pipeline_authn_policy.id
access = "ALLOW"
factor_mode = "1FA"
groups_included = [ okta_group.passwordless_group.id ]
priority = 1
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment