iwarapter / terraform-provider-pingfederate

Ping Federate Terraform Provider
https://registry.terraform.io/providers/iwarapter/pingfederate/latest/docs
MIT License
17 stars 7 forks source link

PingFederate dependancies not honoured #141

Open ashhammond opened 2 years ago

ashhammond commented 2 years ago

Community Note

Terraform Version

Terraform v0.14.11

PingFederate

pingidentity/pingfederate:9.3.3-edge

Affected Resource(s)

A PingFederate authentication policy has been created that includes a selector, adapter and sp connection. These resource are created by using a terraform loop for_each, iterating over a list of strings.

If one of the string is removed from the list of strings, triggering a destroy of those resources, terraform tries to destroy the selector before destroying the policy, showing the below error.

Terraform Configuration Files

terraform {
  required_providers {
    pingfederate = {
      source  = "iwarapter/pingfederate"
      version = "~> 0.0.21"
    }
  }
}

provider "pingfederate" {
  username = "Administrator"
  password = "SuperSecure1234"
  base_url = "https://localhost:9999"
  context  = "/pf-admin-api/v1"
}

variable "connections" {

description = "number of connections to deploy"
type = list(string)
default = ["foo1", "foo2"]

}

resource "pingfederate_server_settings" "settings" {
  federation_info {
    base_url        = "https://foo.com"
    saml2_entity_id = "idp:foo"
  }
  roles_and_protocols {
    enable_idp_discovery = true
    idp_role {
      enable                       = true
      enable_outbound_provisioning = false
      enable_saml10                = false
      enable_saml11                = false
      enable_ws_fed                = false
      enable_ws_trust              = false
      saml20_profile {
        enable = true
      }
    }
    oauth_role {
      enable_oauth          = false
      enable_openid_connect = false
    }
    sp_role {
      enable                      = false
      enable_inbound_provisioning = false
      enable_openid_connect       = false
      enable_saml10               = false
      enable_saml11               = false
      enable_ws_fed               = false
      enable_ws_trust             = false
    }
  }
}

resource "pingfederate_authentication_policy_contract" "foo_pol_contract" {
  name = "foo_pol_contract"
  depends_on = [
    pingfederate_server_settings.settings
  ]
}

resource "pingfederate_keypair_signing" "foo_keypair" {
  city                      = "Test"
  common_name               = "Test"
  country                   = "GB"
  key_algorithm             = "RSA"
  key_size                  = 2048
  organization              = "Test"
  organization_unit         = "Test"
  state                     = "Test"
  valid_days                = 365
  subject_alternative_names = ["foo", "bar"]
}

resource "pingfederate_authentication_selector" "foo_selector" {
  for_each = toset(var.connections)
  name     = each.value
  plugin_descriptor_ref {
    id = "com.pingidentity.pf.selectors.connectionset.ConnectionSetAdapterSelector"
  }
  configuration {
    tables {
      name = "Connections"
      rows {
        fields {
          name  = "Connection"
          value = pingfederate_idp_sp_connection.foo_saml[each.value].entity_id
        }
      }
    }
  }
  depends_on = [
    pingfederate_server_settings.settings
  ]
}

resource "pingfederate_password_credential_validator" "foo_adapter" {
  name = "adapterfoo"
  plugin_descriptor_ref {
    id = "org.sourceid.saml20.domain.SimpleUsernamePasswordCredentialValidator"
  }
  configuration {
    tables {
      name = "Users"
      rows {
        fields {
          name  = "Username"
          value = "foo"
        }
        sensitive_fields {
          name  = "Password"
          value = "SuperSecure123ABC"
        }
        sensitive_fields {
          name  = "Confirm Password"
          value = "SuperSecure123ABC"
        }
        fields {

          name  = "Relax Password Requirements"
          value = "false"
        }
      }
    }
  }
  depends_on = [
    pingfederate_server_settings.settings
  ]
}

resource "pingfederate_idp_sp_connection" "foo_saml" {
  for_each     = toset(var.connections)
  name         = each.value
  entity_id    = each.value
  active       = true
  base_url     = "https://anotherfoo.com"
  logging_mode = "STANDARD"
  credentials {
    signing_settings {
      signing_key_pair_ref {
        id = pingfederate_keypair_signing.foo_keypair.id
      }
      include_cert_in_signature    = false
      include_raw_key_in_signature = false
      algorithm                    = "SHA256withRSA"
    }
  }
  sp_browser_sso {
    protocol         = "SAML20"
    enabled_profiles = ["IDP_INITIATED_SSO"]
    sso_service_endpoints {
      binding    = "POST"
      url        = "/AssertionConsumer"
      is_default = true
      index      = 0
    }
    sign_assertions               = true
    sign_response_as_required     = true
    sp_saml_identity_mapping      = "STANDARD"
    require_signed_authn_requests = false
    assertion_lifetime {
      minutes_before = 5
      minutes_after  = 5
    }
    encryption_policy {
      encrypt_assertion             = false
      encrypt_slo_subject_name_id   = false
      slo_subject_name_id_encrypted = false
      encrypted_attributes          = []
    }
    attribute_contract {
      core_attributes {
        name        = "SAML_SUBJECT"
        name_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
      }
    }
    authentication_policy_contract_assertion_mappings {
      attribute_contract_fulfillment {
        key_name = "SAML_SUBJECT"
        source {
          type = "AUTHENTICATION_POLICY_CONTRACT"
        }
        value = "subject"
      }
      authentication_policy_contract_ref {
        id = pingfederate_authentication_policy_contract.foo_pol_contract.id
      }
      restrict_virtual_entity_ids        = false
      restricted_virtual_entity_ids      = []
      abort_sso_transaction_as_fail_safe = false
    }
  }
}

resource "pingfederate_idp_adapter" "foo_form_adapter" {
  name = "fooapter"
  plugin_descriptor_ref {
    id = "com.pingidentity.adapters.htmlform.idp.HtmlFormIdpAuthnAdapter"
  }

  configuration {
    tables {
      name = "Credential Validators"
      rows {
        fields {
          name  = "Password Credential Validator Instance"
          value = pingfederate_password_credential_validator.foo_adapter.id
        }
      }
    }

    fields {
      name  = "Challenge Retries"
      value = "8"
    }
    fields {
      name  = "Session State"
      value = "None"
    }
    fields {
      name  = "Session Timeout"
      value = "60"
    }
    fields {
      name  = "Session Max Timeout"
      value = "480"
    }
    fields {
      name  = "Allow Password Changes"
      value = "false"
    }
    fields {
      name = "Password Management System"
    }
    fields {
      name  = "Enable 'Remember My Username'"
      value = "false"
    }
    fields {
      name  = "Enable 'This is My Device'"
      value = "false"
    }
    fields {
      name  = "Change Password Email Notification"
      value = "false"
    }
    fields {
      name  = "Show Password Expiring Warning"
      value = "false"
    }
    fields {
      name  = "Password Reset Type"
      value = "NONE"
    }
    fields {
      name = "Password Reset Policy Contract"
    }
    fields {
      name  = "Account Unlock"
      value = "false"
    }
    fields {
      name = "Local Identity Profile"
    }
    fields {
      name = "Notification Publisher"
    }
    fields {
      name  = "Enable Username Recovery"
      value = "false"
    }
    fields {
      name  = "Login Template"
      value = "html.form.login.template.html"
    }
    fields {
      name = "Logout Path"
    }
    fields {
      name = "Logout Redirect"
    }
    fields {
      name  = "Logout Template"
      value = "idp.logout.success.page.template.html"
    }
    fields {
      name  = "Change Password Template"
      value = "html.form.change.password.template.html"
    }
    fields {
      name  = "Change Password Message Template"
      value = "html.form.message.template.html"
    }
    fields {
      name  = "Password Management System Message Template"
      value = "html.form.message.template.html"
    }
    fields {
      name  = "Change Password Email Template"
      value = "message-template-end-user-password-change.html"
    }
    fields {
      name  = "Expiring Password Warning Template"
      value = "html.form.password.expiring.notification.template.html"
    }
    fields {
      name  = "Threshold for Expiring Password Warning"
      value = "7"
    }
    fields {
      name  = "Snooze Interval for Expiring Password Warning"
      value = "24"
    }
    fields {
      name  = "Login Challenge Template"
      value = "html.form.login.challenge.template.html"
    }
    fields {
      name  = "'Remember My Username' Lifetime"
      value = "30"
    }
    fields {
      name  = "'This is My Device' Lifetime"
      value = "30"
    }
    fields {
      name  = "Allow Username Edits During Chaining"
      value = "false"
    }
    fields {
      name  = "Track Authentication Time"
      value = "true"
    }
    fields {
      name  = "Post-Password Change Re-Authentication Delay"
      value = "0"
    }
    fields {
      name  = "Password Reset Username Template"
      value = "forgot-password.html"
    }
    fields {
      name  = "Password Reset Code Template"
      value = "forgot-password-resume.html"
    }
    fields {
      name  = "Password Reset Template"
      value = "forgot-password-change.html"
    }
    fields {
      name  = "Password Reset Error Template"
      value = "forgot-password-error.html"
    }
    fields {
      name  = "Password Reset Success Template"
      value = "forgot-password-success.html"
    }
    fields {
      name  = "Account Unlock Template"
      value = "account-unlock.html"
    }
    fields {
      name  = "OTP Length"
      value = "8"
    }
    fields {
      name  = "OTP Time to Live"
      value = "10"
    }
    fields {
      name = "PingID Properties"
    }
    fields {
      name  = "Require Verified Email"
      value = "false"
    }
    fields {
      name  = "Username Recovery Template"
      value = "username.recovery.template.html"
    }
    fields {
      name  = "Username Recovery Info Template"
      value = "username.recovery.info.template.html"
    }
    fields {
      name  = "Username Recovery Email Template"
      value = "message-template-username-recovery.html"
    }
    fields {
      name  = "CAPTCHA for Authentication"
      value = "false"
    }
    fields {
      name  = "CAPTCHA for Password change"
      value = "false"
    }
    fields {
      name  = "CAPTCHA for Password Reset"
      value = "false"
    }
    fields {
      name  = "CAPTCHA for Username recovery"
      value = "false"
    }
  }

  attribute_mapping {
    attribute_contract_fulfillment {
      key_name = "policy.action"
      source {
        type = "ADAPTER"
      }
      value = "policy.action"
    }
    attribute_contract_fulfillment {
      key_name = "username"
      source {
        type = "ADAPTER"
      }
      value = "username"
    }

  }

  attribute_contract {
    core_attributes {
      name      = "policy.action"
      masked    = false
      pseudonym = false
    }
    core_attributes {
      name      = "username"
      masked    = false
      pseudonym = true
    }
  }
  depends_on = [
    pingfederate_server_settings.settings
  ]
}

resource "pingfederate_authentication_policies_settings" "policy_settings" {
  enable_idp_authn_selection = true
  enable_sp_authn_selection  = false

  depends_on = [
    pingfederate_server_settings.settings
  ]
}

resource "pingfederate_authentication_policies" "policies" {
  fail_if_no_selection = false
  dynamic "authn_selection_trees" {
    for_each = toset(var.connections)
    content {
      name    = authn_selection_trees.key
      enabled = true
      root_node {
        action {
          type = "AUTHN_SELECTOR"
          authentication_selector_ref {
            id = pingfederate_authentication_selector.foo_selector[authn_selection_trees.key].id
          }
        }
        children {
          action {
            type    = "CONTINUE"
            context = "No"
          }
        }
        children {
          action {
            type    = "AUTHN_SOURCE"
            context = "Yes"
            authentication_source {
              type = "IDP_ADAPTER"
              source_ref {
                id = pingfederate_idp_adapter.foo_form_adapter.id
              }
            }
          }
          children {
            action {
              type    = "DONE"
              context = "Fail"
            }
          }
          children {
            action {
              type    = "APC_MAPPING"
              context = "Success"
              authentication_policy_contract_ref {
                id = pingfederate_authentication_policy_contract.foo_pol_contract.id
              }
              attribute_mapping {
                attribute_contract_fulfillment {
                  key_name = "subject"
                  source {
                    type = "ADAPTER"
                    id   = pingfederate_idp_adapter.foo_form_adapter.id
                  }
                  value = "username"
                }
              }
            }
          }
        }
      }
    }
  }

}

Debug Output

pingfederate_authentication_selector.foo_selector["foo2"]: Destroying... [id=foo2]

Error: unable to delete AuthenticationSelectors: The Authentication Selector instance with ID 'foo2' is currently in use. The Authentication Selector must be unmapped before it is deleted.

Panic Output

Expected Behavior

Terraform honours the dependancies.

Actual Behavior

Terraform honours the dependancies.

Steps to Reproduce

Using the above replicator.

  1. terraform apply
  2. remove "foo2"
  3. terraformapply`

Important Factoids

References

iwarapter commented 2 years ago

Hi @ashhammond I believe this is probably going to be a bug with terraform itself - i'll raise it with them and see what they say, but ultimately the provider has no input on how the resource dependencies are managed by terraform.