Best practices
The following sections provide a set of best practices to apply when writing Terraform with the PingOne Terraform provider and associated modules.
These guidelines are not intended to educate on the use of Terraform and are not a getting started guide. You can find more information about Terraform in Hashicorp’s Online Documentation. Learn how to get started with the PingOne Terraform provider in the Getting started guide.
Develop in the admin console, promote using Configuration as Code
Ping Identity recommends performing use-case development activities in the PingOne admin console whenever possible. This recommendation is due to the complex nature of Workforce IAM and Customer IAM deployments that includes policy definition, user experience design, and associated testing and validation of designed use cases.
After you’ve developed a configuration in the PingOne admin console, you can extract it as Configuration as Code to be stored in source control (such as a Git code repository) and linked with CI/CD tooling to automate the delivery of use cases into test and production environments.
For professionals experienced in DevOps development, configuration can be created and altered outside of the PingOne admin console, but care must be taken when modifying complex configuration, such PingOne Authorize, PingOne MFA, PingOne Protect, or PingOne SSO sign-on policies.
Example or bootstrapped configuration dependencies
Deploy to "clean" environments, without example or bootstrapped configuration
Example or bootstrapped configuration is deployed automatically by the PingOne service when an environment is created or new services are provisioned to an existing environment. This is the default behavior of the PingOne admin console and the API.
Example or bootstrapped configuration can be a useful starting point when initially creating use cases with the service (in the development phase), but creates conflicts when migrating the configuration through to test and production environments.
The definition of the example or bootstrapped configuration for new environment can also change over time, as new features are released and use case configuration best practices are defined. An environment created today might not be the same as an environment created a year from now.
It’s best practice to create a new environment as a "clean" (without example or bootstrapped configuration) environment for those environments outside of the initial development one. If environments cannot be recreated or are intended to be long-lasting (such as staging or production), it might be enough to remove bootstrapped configuration manually when an environment is created.
Notable examples of demo configuration include:
Platform
-
The default branding theme
-
Optional directory schema attributes (which can be disabled if not used)
-
accountId
-
address
-
email
-
externalId
-
locale
-
mobilePhone
-
name
-
nickname
-
photo
-
preferredLanguage
-
primaryPhone
-
timezone
-
title
-
type
-
-
The default Keys and Certificates
-
The default notification policies
-
The default
Single_Factor
sign-on policy -
The example password policies
-
The
PingOne Application Portal
(which can be disabled if not used)
Define all configuration dependencies in Terraform or elsewhere in the pipeline
Example or bootstrapped configuration is deployed automatically by the PingOne service when an environment is created or new services are provisioned to an existing environment. This is the default behavior of the PingOne admin console and the API.
Example or bootstrapped configuration can be a useful starting point when initially creating use cases with the service (in the development phase), but creates conflicts when migrating the configuration through to test and production environments.
The definition of the example or bootstrapped configuration for new environment can also change over time as new features are released and use case configuration best practices are defined. An environment created today might not be the same as an environment created a year from now.
It’s best practice to explicitly define all configuration dependencies in Terraform (or as a prior step in the CI/CD pipeline) after developing flows for use cases. Most notably, this practice includes defining the policies (for example, sign-on, MFA Device, FIDO2, or Protect policies) that applications will use in HCL, rather than using the example or bootstrapped environment examples.
Not best practice
The following pingone_population
definition doesn’t follow best practice, as it depends on the "Passphrase" password policy that was deployed by default when the environment was created. This definition assumes that this password policy will always exist and have a consistent definition on every environment creation. However, the password policy could change over time, invalidating the configuration.
data "pingone_password_policy" "ootb_passphrase" {
environment_id = pingone_environment.my_environment.id
name = "Passphrase"
}
resource "pingone_population" "my_population" {
environment_id = pingone_environment.my_environment.id
name = "My awesome population"
description = "My new population for awesome people"
password_policy_id = data.pingone_password_policy.ootb_passphrase.id
lifecycle {
# change the `prevent_destroy` parameter value to `true` to prevent this data carrying resource from being destroyed
prevent_destroy = false
}
}
Best practice
The following pingone_population
definition follows best practice, as the password policy that it depends on is explicitly defined using the pingone_password_policy
resource. This explicit definition ensures that environments are built and configured consistently between development, test, and production.
resource "pingone_password_policy" "my_password_policy" {
environment_id = pingone_environment.my_environment.id
name = "My awesome password policy"
excludes_commonly_used_passwords = true
excludes_profile_data = true
not_similar_to_current = true
history = {
count = 6
retention_days = 365
}
# ... other configuration parameters
}
resource "pingone_population" "my_population" {
environment_id = pingone_environment.my_environment.id
name = "My awesome population"
description = "My new population for awesome people"
password_policy_id = pingone_password_policy.my_password_policy.id
lifecycle {
# change the `prevent_destroy` parameter value to `true` to prevent this data carrying resource from being destroyed
prevent_destroy = false
}
}
Protect service configuration and data
The following sections detail best practices to apply to ensure protection of production data beyond what is covered in Secrets management when using the PingOne Terraform provider.
Regularly rotate worker application secrets
In PingOne, administration management functions against the API can be performed by worker applications with admin roles assigned, as described in Configuring roles for a worker application in the PingOne documentation. To use these worker applications, you might need to generate an application secret and use that secret in downstream applications and services. You should rotate these secrets on a regular basis to help mitigate against unauthorized platform changes.
Rotation can be controlled by a secrets engine that can update with the relevant API, as described in the Update Application Secret in the PingOne developer documentation, but can also be rotated through the Terraform process.
For example, the following Terraform code will rotate an application secret for the application "My Awesome App" every 30 days:
resource "pingone_application" "my_application" {
name = "My Awesome App"
enabled = true
oidc_options = {
type = "WORKER"
grant_types = ["CLIENT_CREDENTIALS"]
token_endpoint_auth_method = "CLIENT_SECRET_BASIC"
}
# ... other configuration parameters
}
resource "time_rotating" "application_secret_rotation" {
rotation_days = 30
}
resource "pingone_application_secret" "foo" {
environment_id = pingone_environment.my_environment.id
application_id = pingone_application.my_application.id
regenerate_trigger_values = {
"rotation_rfc3339" : time_rotating.application_secret_rotation.rotation_rfc3339,
}
}
Review use of API force-delete provider overrides
The PingOne Terraform provider has a provider-level parameter named global_options
that allows administrators to override API behaviors for development, test, and demo purposes. You can find details in the registry documentation of this parameter.
There are two parameters that allow force-deletion of configuration, which could result in loss of data if not used correctly.
global_options.environment.production_type_force_delete
WARNING:
The global_options.environment.production_type_force_delete
global option was removed in the PingOne Terraform provider version v1.0. This section applies to prior provider versions (v0.29 or earlier).
Misuse of the parameter could cause unintended data loss, so it must be used with caution.
The purpose of the parameter is to override the API level restriction of not being able to destroy environments of type "PRODUCTION". The default value of this parameter is false
, meaning that environments will not be force-deleted if a pingone_environment
resource that has a type
value of PRODUCTION
has a destroy plan when run in the terraform apply
phase. Use of this parameter is designed to help facilitate development, testing, or demonstration purposes and should be set to false
or left undefined for environments that carry production data.
The implementation of this option is that the environment type will be changed from PRODUCTION
to SANDBOX
before a delete API request is issued. Instead of using this parameter, consider changing the type to SANDBOX
manually before running a plan that destroys an environment.
global_options.population.contains_users_force_delete
Misuse of the parameter could cause unintended data loss, so it must be used with caution. |
The purpose of the parameter is to override the API level restriction of not being able to destroy populations that contain user data. The default value of this parameter is false
, meaning that populations that contain user data will not be force-deleted if a pingone_population
resource has a destroy plan when run in the terraform apply
phase. Use of this parameter is designed to help facilitate development, testing, or demonstration purposes where non-production user data is created and can be safely discarded. The parameter should strictly be set to false
or left undefined for environments that carry production data.
The implementation of this option is that the provider will find and delete all users assigned to the population being destroyed before a delete API request is issued to the population. Instead of using this parameter, consider removing non-production data manually before running a plan that destroys a population.
Protect configuration and data with the lifecycle.prevent_destroy
meta argument
While some resources are safe to remove and replace, there are some resources that, if removed, can result in data loss.
You should use the lifecycle.prevent_destroy
meta argument to protect against accidental destroy plans that could cause data loss. You might also want to use the meta argument to prevent accidental removal of access policies and applications if dependent applications cannot be updated with Terraform in case of replacement.
For example:
resource "pingone_schema_attribute" "my_attribute" {
environment_id = pingone_environment.my_environment.id
name = "myAttribute"
# ... other configuration parameters
lifecycle {
prevent_destroy = true
}
}
The following resources, if destroyed, put data at risk within a PingOne environment:
-
-
If a custom schema attribute is created, a destroy of the schema attribute will erase that attribute’s data for users.
-
-
-
Users must belong to a population. If a population is removed, the users within that population could be at risk. There are platform controls to prevent accidental deletion of a population that contains users.
-
-
-
Users might belong to the environment’s default population. If the environment is removed, the users within that population could be at risk. There are platform API-level controls to prevent accidental deletion of an environment where the environment’s type is set to
PRODUCTION
.SANDBOX
environments do not have such API restrictions.
-
Multi-team development
Use on-demand sandbox environments
PingOne customer tenants have a "tenant-in-tenant" architecture, whereby a PingOne tenant organisation can contain many individual environments. These individual environments can be purposed for development, test, pre-production, and production purposes. These separate environments allow for easy maintenance of multiple development and test instances.
The recommended approach for multi-team development, when using a GitOps CI/CD promotion process, is to spin up on-demand development and test environments, specific to new features or to individual teams, to allow for development and integration testing that doesn’t conflict with other team’s development and test activities. The Terraform provider allows administrators to use CI/CD automation to provision new environments as required and remove them after the project activity no longer needs them.
In a GitOps CI/CD promotion pipeline, configuration can be translated to Terraform Configuration as Code and then merged (with pull requests) with common test environments, where automated tests can be run. This flow allows the activities in the on-demand environments to be merged into a common promotion pipeline to production environments.
User administrator role assignment
Use group role assignments over Terraform-managed user role assignments
PingOne supports assigning administrator roles to groups, such that members of the group get the administrator roles assigned.
Ping Identity recommends that groups with admin role assignments are controlled by the Joiner/Mover/Leaver Identity Governance processes, separate from the Terraform CI/CD process that configures applications, policies, domain verification, and so on. It could be that the groups with their role assignments are initially seeded by Terraform. In this case, it should still be a separate Terraform process from the process that controls platform configuration, and the user group assignments should still happen in the Joiner/Mover/Leaver Identity Governance process.
You can use Terraform to assign administrator roles to individuals directly, but this is not recommended best practice except in development or non-production environments. Ping Identity recommends that role assignment processes in non-production environments align as closely as possible to role assignment processes in production environments.
Use custom roles to follow principles of least privilege
PingOne supports the creation of custom administrator roles, which allow an administrator to define their own admin role based on a collection of permissions that represent a specific purpose.
Ping Identity recommends that customers create custom administrator roles to follow least privilege principles. To mitigate accidental or malicious changes to environments, assign the minimum required permissions for users to be able to perform their roles.
You can use Terraform to manage custom administrator roles and also manage the assignment of custom roles to groups and users.