This week, we are looking at automating Security Policies, like Authenticators, Global Session Policies, Application Authentication Policies, Password Policies & Rules, and more. Let’s check it out below.
Manage Security Policies
This will be less about Automation and more about controlling security policies through Terraform. You could automate this through various situations, events, if statements, etc., but keep it simple (stupid).
Managing Authenticators
Authenticator Configurations
This is probably the simplest thing to do with Terraform and Okta.
We need to import all of our existing authenticators from Okta into Terraform’s State. As we use Kolide, but only have Kolide in a single environment, we need to have special conditions to only import it in the production environment, not the preview environment. Initially, we tried to deploy our imports this way, as I was hoping to make this very easy… but… of course not.
But what do we do with the External IDP? Unfortunately here, we would need to select the workspace, and manually import it at the command line.
1
2
3
4
terraform workspace list
terraform workspace select$yourworkspaceterraform state list | grep -i okta_authenticator # To double check what authenticators are already in the stateterraform import okta_authenticator.$resourcename$resourceidoftenant
Now, we can manage the Authenticators in Terraform.
# OV Push Authenticator Methods cannot be managed via provider at the moment. Additional call to enable Okta Verify Push & FastPass below (workaround)
resource"null_resource" "enable_ov_push" {
depends_on=[okta_authenticator.okta_verify] triggers= {
always_run="${timestamp()}" # Forces this resource to run every time
}
provisioner"local-exec" {
command="curl -X POST -H \"Authorization:SSWS ${var.TFC_OKTA_API_TOKEN}\" https://${var.TFC_OKTA_ORG_NAME}.${var.TFC_OKTA_BASE_URL}/api/v1/authenticators/${okta_authenticator.okta_verify.id}/methods/push/lifecycle/activate" }
}
resource"null_resource" "enable_ov_fastpass" {
depends_on=[okta_authenticator.okta_verify] triggers= {
always_run="${timestamp()}" # Forces this resource to run every time
}
provisioner"local-exec" {
command="curl -X POST -H \"Authorization:SSWS ${var.TFC_OKTA_API_TOKEN}\" https://${var.TFC_OKTA_ORG_NAME}.${var.TFC_OKTA_BASE_URL}/api/v1/authenticators/${okta_authenticator.okta_verify.id}/methods/signed_nonce/lifecycle/activate" }
}# End section for workaround
This would then allow for us to be sure that we are enforcing Okta Verify Push and FastPass on every single run, making sure that if someone has accidentally opted to turn off the configurations, they get re-enabled.
For the External IDP example in the beginning of this section, we need the ability to manage this without it being imported into Okta in the preview environment, the best way to do that is to use the count function:
I can’t post what our actual Authenticator Enrollment Configuration looks like, but, what I can do is provide the code we used to import our existing Okta Authenticator Enrollment configuration via a python script.
$ ./bin/python3 main.py --full-domain $YOURDOMAIN --api-token $YOURAPITOKEN --terraform-fmt --output ./policy-mfa-enroll.tf
Organization pipeline: idx -> is_oie set to True
Terraform configuration written to ./policy-mfa-enroll.tf
policy-mfa-enroll.tf
Formatted ./policy-mfa-enroll.tf successfully.
$ ./bin/python3 main.py --help
usage: main.py [-h][--subdomain SUBDOMAIN][--domain DOMAIN][--full-domain FULL_DOMAIN] --api-token API_TOKEN [--output OUTPUT][--terraform-fmt]Generate Terraform code for Okta MFA policies and rules from the Okta API.
options:
-h, --help show this help message and exit --subdomain SUBDOMAIN
Subdomain for the Okta domain
--domain DOMAIN Domain for the Okta domain
--full-domain FULL_DOMAIN
Full domain for the Okta domain (without protocol) --api-token API_TOKEN
Okta API token (used only for fetching policies and rules) --output OUTPUT Output file name for the Terraform configuration
--terraform-fmt Run 'terraform fmt' on the generated file
Running this, will automatically determine if the tenant is an OIE environment, and then generate an OIE compatible environment or a Classic Engine compatible environment terraform file.
Which will then generate something that looks like this (based on a demo environment):
You can then drop that file into your existing terraform repo.
Managing Password Policies & Rules
Additionally, I am unable to provide the password policy and rules we have set, but we will accomplish the same thing. Let’s import the configuration we have in Okta into a Terraform file automatically. This will query the API for all policies as a PASSWORD,
1
2
3
4
./bin/python3 main.py --full-domain $YOURDOMAIN --api-token $YOURAPITOKEN --terraform-fmt --output ./policy-password.tf
Terraform configuration generated and written to ./policy-password.tf
policy-password.tf
Formatted ./policy-password.tf successfully.
Which will automatically generate a file that looks like this:
And again, as above, using the policy-global-session-policies-generator, this will generate all of the global session configurations you have in Okta in to a Terraform file. Similar to the items above.
Managing Application Authentication Policies
Automatically creating all configurations
We will be using Python to generate terraform files for this. Key things to note here though, are:
This will create multiple terraform files, one for each authentication policy
This will run terraform fmt on all of the files created
I created a policy-auth_signon-dual_env generator, you can find a lot more information here. This will automate the creation of all policies & rules, however unlike the others, this one does not do interpolation - instead it will add a count = var.CONFIG == "prod" ? 1 : 0 (dependent on prod or test which you can change depending on your environment).
This will also add import blocks for policy or rule, so that the entire file will be a drop in replacement.
Creating Custom Configurations - Importing the Okta Default / “Any Two Factors” Policy
Note
There seem to be several bugs with this resource/provider, when it comes to Auth and Sign On policies. Currently, we have run into issues with:
Auth Chain
Default or Okta Created Authentication Policies
You need to do this in steps though, as if you do all of them at the same time, it will cause issues.
First off, we previously had renamed the policies to be something more relevant with what we used the policy for. The default labeled policy must be named Any two factors, and must have a description of Require two factors to access., otherwise Terraform will fail to import the resource. Complaining of one of two things:
403 Forbidden
Cannot modify the priority attribute because it is read-only.
The same things happens in Postman:
But this behavior does not show up in the Admin GUI Console, where you can edit the Name and Description to whatever you would like. It seems like there is a provider bug already filed. You can find a discussion about this on MacAdmins.
But, we can work around that via a hybrid of Importing the resource to Terraform, and then changing the Name using ClickOps with what will eventually be the Terraform managed resource name, by implementing a lifecycle and ignore_change statements. I added the ignore_change statements after the import had succeeded for both the policy and the rule.
So Step 1:
Obtain the Policy IDs, you can easily obtain this by grabbing the URL from a browser, or by using an API Tool (like Postman)
Add the Locals data
PR your feature branch to preview
Run Terraform Plan & Apply for the Preview Environment (based on the setup that I have covered over the past 6 parts)
PR preview to main
Run Terraform Plan & Apply for the Production Environment
# Step 1
# ## TODO: Adjust for Production and run during first run for import
locals {
app_signon_policy_id= {
okta-preview="previewid" # Preview
okta-production="prodid" # Prod
}[terraform.workspace]}
import {
to=okta_app_signon_policy.default_standard id=local.app_signon_policy_id}# App Sign-On Policy
resource"okta_app_signon_policy" "default_standard" {
name="Any two factors" description="Require two factors to access."lifecycle {
create_before_destroy=true ignore_changes=[name,description] }
}
Once all of this is done, you can then go in and rename the Authentication Policy in Okta via ClickOps, and when the bug is fixed, plan to fix this via the Terraform Provider.
Now we move on to Step 2, importing the Catch-All Rule:
Obtain the Rule IDs, you can more easily with an API Tool (like Postman) or by opening the Developer Tools and switching to the Network tab in Chrome or Firefox
Add the Locals data
PR your feature branch to preview
Run Terraform Plan & Apply for the Preview Environment (based on the setup that I have covered over the past 6 parts)
PR preview to main
Run Terraform Plan & Apply for the Production Environment
locals { # Extract all static priorities from rules (only ones explicitly set)
static_priorities= {
forruleinlocal.standard_signon_policy_rules: rule.name=>rule.priority if try(rule.priority, null) !=null } # Sort rules by explicit priority (if exists) or order in the list
sorted_rules=sort([forruleinlocal.standard_signon_policy_rules: {
name=rule.name priority=try(rule.priority,null) }
],"priority") # Assign incremental priorities, ensuring static values stay in place and max priority < 95
dynamic_priorities= {
fori,ruleinlocal.sorted_rules: rule.name=>( try(rule.priority, null) !=null?rule.priority # Use the static priority if it exists
:min( # Ensure we do not exceed 95
max([forpinvalues(local.static_priorities):p]...)+1+i,95)) }
}# Resource Block for Sign-On Policy Rules
resource"okta_app_signon_policy_rule" "standard_signon_rules" {
for_each= { for rule in local.standard_signon_policy_rules : rule.name=>rule }
name="tf-${each.value.name}" policy_id=okta_app_signon_policy.default_standard.id access=each.value.access # Assign dynamically computed priority or use static priority if defined
priority=local.dynamic_priorities[each.value.name] groups_included=try(each.value.groups_included,null) device_is_registered=try(each.value.device_is_registered,null) device_is_managed=try(each.value.device_is_managed,null) factor_mode=try(each.value.factor_mode,null) constraints=[each.value.constraints] depends_on=[okta_app_signon_policy.default_standard,okta_app_signon_policy_rule.default_standard_catch_all_rule]}
What I would recommend:
Is iterating over the rules you have and put them in a list, so that for each item in the list, is automatically recorded and kept up to date, and that moves and shifts for each (hopefully not frequent) update of the list. Additionally, when you need to leave certain options blank or excluded, the API will fail with various errors, so it is better to null the value and not pass it through in the code wherever possible.
Automated Unit Testing of Authentication Policies
Okta has a tool called “Access Testing”, but I have seen little usage of the actual API behind it being used outside of Okta’s Access Testing Tool. What would happen if we were to use this as part of our Terraform Plan / Apply to validate that the Authentication Policies we have in place all pass, based on the requirements we have set out in the policy. This is currently in the works, but I will see if I can share it when I am done. It should be an exciting option when finished. Need to verify that particular VIP, C-Staff, Service Accounts, or Employees can access a resource before a policy change is pushed out? Done; quickly test all of that in your staging environment first and then push to prod after completing the unit test.
A lot will be covered over the next several parts, which sums up how we have terraformed certain pieces of our Okta environment. If you have questions and are looking for a community resource, I would heavily recommend reaching out to #okta-terraform on MacAdmins, as I would say at least 30% (note, I made this statistic up) of the organizations using Terraform hang out in this channel. Otherwise, you can always find an alternative unofficial community for assistance or ideas.