Skip to content

Add typed keycloak_client_registration_policy_* resources (closes #715)#1580

Open
xrl wants to merge 6 commits into
keycloak:mainfrom
xrl:dcr
Open

Add typed keycloak_client_registration_policy_* resources (closes #715)#1580
xrl wants to merge 6 commits into
keycloak:mainfrom
xrl:dcr

Conversation

@xrl

@xrl xrl commented May 3, 2026

Copy link
Copy Markdown

Summary

Adds one typed Terraform resource per built-in Keycloak Client Registration
Policy provider, replacing the need for users to manage these via a generic
keycloak_generic_component-style escape hatch. Closes #715.

Eight resources, one per built-in policy factory in
org.keycloak.services.clientregistration.policy.impl:

Resource provider_id
keycloak_client_registration_policy_trusted_hosts trusted-hosts
keycloak_client_registration_policy_max_clients max-clients
keycloak_client_registration_policy_allowed_client_scopes allowed-client-templates (legacy provider id; config keys use modern scopes naming)
keycloak_client_registration_policy_allowed_protocol_mappers allowed-protocol-mappers
keycloak_client_registration_policy_web_origins registration-web-origins
keycloak_client_registration_policy_consent_required consent-required
keycloak_client_registration_policy_full_scope_disallowed scope (resource named after the admin UI label)
keycloak_client_registration_policy_client_disabled client-disabled

Design notes

  • Typed resources, no config = map(string) escape hatch. Each resource
    exposes the policy's config keys as typed schema fields:
    trusted_hosts = ["127.0.0.1", "localhost"], not
    config = { "trusted-hosts" = jsonencode(["127.0.0.1", "localhost"]) }.
    Real plan diffs, schema validation at plan time, per-resource docs.
  • Pattern follows keycloak_realm_keystore_* — one typed resource per
    Component subtype, sharing a small CRUD layer over the existing
    components endpoint plumbing in *KeycloakClient.
  • Multivalued-string fields use TypeSet — Keycloak's components API
    does not preserve list order on round-trip, so TypeList would produce
    spurious diffs. Affects trusted_hosts, web_origins,
    allowed_client_scopes, allowed_protocol_mapper_types.
  • One-line addition to keycloak/component.go: add a
    SubType string json:"subType,omitempty"field on the package-privatecomponentstruct. Required because Client Registration Policies use the ComponentsubType field (anonymous/authenticated); existing keystore / LDAP / IdP-mapper components don't, and omitempty` makes
    this a no-op for them.
  • Three config-less policies share a small Configless() factory in
    provider/generic_client_registration_policy_helpers.go. They are
    byte-for-byte identical except for one provider_id constant; ~250 lines
    of mechanical copy-paste avoided.
  • Importer guards against cross-resource imports. The shared
    importer parses {realmId}/{componentId} and asserts the imported
    component's providerId matches the resource type, so importing a
    trusted-hosts component into a max_clients resource fails with a
    clear error instead of silently corrupting state.

Test plan

18 acceptance tests, all passing against Keycloak 26.5.5 (~9s wall):

  • _basic for each of the 8 resources (Create + ImportStateVerify
    round-trip, exercising TypeBool/TypeInt/TypeSet schemas)
  • _update for each of the 8 resources (mutates every mutable field)
  • _importMismatch — verifies the provider_id guard fires with
    a readable error
  • _fullStack — declares all 8 resources in a single config (4
    anonymous + 4 authenticated) to confirm they coexist cleanly
  • subType=authenticated is exercised by all 3 config-less basic
    tests + the fullstack test
KEYCLOAK_CLIENT_ID=terraform \
KEYCLOAK_CLIENT_SECRET=884e0f95-0f42-4a63-9b1f-94274655669e \
KEYCLOAK_CLIENT_TIMEOUT=120 \
KEYCLOAK_REALM=master \
KEYCLOAK_URL=http://localhost:8080 \
TF_ACC=1 CHECKPOINT_DISABLE=1 \
go test -v -timeout 10m -run 'TestAccKeycloakClientRegistrationPolic' \
  github.com/keycloak/terraform-provider-keycloak/provider

Out of scope

  • A generic keycloak_client_registration_policy for custom SPI policies.
    Can be added separately; nothing in this PR forecloses it.
  • A keycloak_generic_component resource.
  • Documentation pages — happy to add them in a follow-up if maintainers
    prefer; let me know the preferred format/location.

xrl added 5 commits May 2, 2026 22:54
Add ClientRegistrationPolicy struct + CRUD methods on *KeycloakClient,
plus a SubType field on the package-private component struct (omitempty,
so it's a no-op for keystore/LDAP/IdP-mapper components that don't use it).

Smoke-tested against Keycloak 26.5: create + read + update + read +
delete on a trusted-hosts policy under master/anonymous round-trips
cleanly, including the SubType field.

This is the shared layer that the typed
keycloak_client_registration_policy_* resources will sit on top of.

Signed-off-by: Xavier Lange <xrlange@gmail.com>
Add one Terraform resource per Keycloak built-in Client Registration Policy
provider, on top of the shared CRUD primitives:

  keycloak_client_registration_policy_trusted_hosts             (provider_id: trusted-hosts)
  keycloak_client_registration_policy_max_clients               (provider_id: max-clients)
  keycloak_client_registration_policy_allowed_client_scopes     (provider_id: allowed-client-templates)
  keycloak_client_registration_policy_allowed_protocol_mappers  (provider_id: allowed-protocol-mappers)
  keycloak_client_registration_policy_web_origins               (provider_id: registration-web-origins)
  keycloak_client_registration_policy_consent_required          (provider_id: consent-required)
  keycloak_client_registration_policy_full_scope_disallowed     (provider_id: scope)
  keycloak_client_registration_policy_client_disabled           (provider_id: client-disabled)

Resources expose typed scalars (TypeBool, TypeInt) and TypeSet for
multivalued-string config fields - Keycloak's components API does not
preserve list order on round-trip, so TypeSet avoids spurious diffs.

The 3 config-less policy types share a Configless() factory in the
helpers file; each becomes a 6-line wrapper.

The shared importer parses {realmId}/{componentId} and asserts the
component's providerId matches the resource type, so importing a
trusted-hosts component into a max_clients resource fails with a clear
error instead of corrupting state.

Acceptance tests cover trusted_hosts (TypeSet round-trip), max_clients
(TypeInt round-trip), and the cross-resource import-mismatch guard.
All pass against Keycloak 26.5.5.

Signed-off-by: Xavier Lange <xrlange@gmail.com>
…urces

Per-resource basic tests round-trip every field through Terraform's
ImportStateVerify, exercising the typed schemas (TypeBool, TypeInt,
TypeSet) against a live Keycloak.

Additions:
  * Shared crpExists / crpDestroy / crpImportId helpers in
    resource_keycloak_client_registration_policy_helpers_test.go remove
    per-resource boilerplate.
  * The 3 config-less resources share a runConfiglessBasic table-driven
    helper that exercises sub_type = "authenticated" along the way.
  * trusted_hosts gets an _update test that mutates every field
    (including the TypeSet content) and verifies the apply succeeds.
  * A fullstack test declares all 8 resources in a single config (4
    anonymous, 4 authenticated) to confirm they coexist cleanly.

11 tests, ~7s end-to-end against Keycloak 26.5.5.

Signed-off-by: Xavier Lange <xrlange@gmail.com>
Per-resource _update tests for every config-bearing resource:
  - trusted_hosts (already had one)
  - max_clients (rename + change int)
  - allowed_client_scopes (rename + flip bool + shrink set)
  - allowed_protocol_mappers (rename + shrink set)
  - web_origins (rename + shrink set)

The 3 config-less resources get rename-only update tests via a shared
runConfiglessUpdate helper.

18 tests, ~9s against Keycloak 26.5.5.

Signed-off-by: Xavier Lange <xrlange@gmail.com>
Signed-off-by: Xavier Lange <xrlange@gmail.com>
The registration-web-origins client registration policy was added in
Keycloak commit be6a3814 (Oct 2025, shipped in 26.5.0). Acceptance tests
on the upstream test matrix's older entries (26.0.x through 26.4.x) fail
with "Invalid provider type or no such provider" when attempting to
create a keycloak_client_registration_policy_web_origins resource.

The simplest fix is to drop the resource from this PR. The remaining 7
resources are present in every Keycloak version in the matrix.

The web_origins resource can be reintroduced in a follow-up once the
project's minimum supported Keycloak version is bumped to 26.5+, or
with a Keycloak-version-aware skip in the test.

Signed-off-by: Xavier Lange <xrlange@gmail.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds first-class Terraform resources for managing Keycloak Client Registration Policy components (typed schema per built-in provider), backed by a small shared CRUD/import layer over the existing /components endpoint plumbing.

Changes:

  • Introduces typed resources for several built-in client registration policy providers (trusted hosts, max clients, allowed scopes, allowed protocol mappers, and three config-less policies).
  • Adds shared schema/import/delete helpers (including an importer guard to prevent cross-resource imports).
  • Extends the Keycloak component model to support subType and adds a Keycloak client API for CRUD on client registration policy components, with accompanying acceptance tests.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
provider/resource_keycloak_client_registration_policy_trusted_hosts.go Implements the trusted-hosts client registration policy resource with typed config fields.
provider/resource_keycloak_client_registration_policy_trusted_hosts_test.go Acceptance tests for trusted-hosts policy (basic/update/import mismatch).
provider/resource_keycloak_client_registration_policy_max_clients.go Implements the max-clients policy resource with typed max_clients field.
provider/resource_keycloak_client_registration_policy_max_clients_test.go Acceptance tests for max-clients policy (basic/update).
provider/resource_keycloak_client_registration_policy_helpers_test.go Shared acceptance-test helpers for existence/destroy/import ID generation.
provider/resource_keycloak_client_registration_policy_full_scope_disallowed.go Adds config-less wrapper resource for providerId scope.
provider/resource_keycloak_client_registration_policy_consent_required.go Adds config-less wrapper resource for providerId consent-required.
provider/resource_keycloak_client_registration_policy_client_disabled.go Adds config-less wrapper resource for providerId client-disabled.
provider/resource_keycloak_client_registration_policy_configless_test.go Shared acceptance tests for the three config-less policy resources.
provider/resource_keycloak_client_registration_policy_allowed_protocol_mappers.go Implements allowed-protocol-mappers policy resource with a typed set field.
provider/resource_keycloak_client_registration_policy_allowed_protocol_mappers_test.go Acceptance tests for allowed-protocol-mappers policy (basic/update).
provider/resource_keycloak_client_registration_policy_allowed_client_scopes.go Implements allowed-client-scopes policy resource (legacy providerId, modern config keys).
provider/resource_keycloak_client_registration_policy_allowed_client_scopes_test.go Acceptance tests for allowed-client-scopes policy (basic/update).
provider/resource_keycloak_client_registration_policies_fullstack_test.go Full-stack acceptance test asserting multiple policy resources can coexist.
provider/provider.go Registers the new policy resources in the provider resource map.
provider/generic_client_registration_policy_helpers.go Shared schema/importer/CRUD helpers for all client registration policy resources.
keycloak/component.go Adds SubType to the internal component model for serialization.
keycloak/client_registration_policy.go Adds Keycloak client model + CRUD methods for client registration policy components.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread provider/provider.go
Comment on lines +49 to +55
"keycloak_client_registration_policy_trusted_hosts": resourceKeycloakClientRegistrationPolicyTrustedHosts(),
"keycloak_client_registration_policy_max_clients": resourceKeycloakClientRegistrationPolicyMaxClients(),
"keycloak_client_registration_policy_allowed_client_scopes": resourceKeycloakClientRegistrationPolicyAllowedClientScopes(),
"keycloak_client_registration_policy_allowed_protocol_mappers": resourceKeycloakClientRegistrationPolicyAllowedProtocolMappers(),
"keycloak_client_registration_policy_consent_required": resourceKeycloakClientRegistrationPolicyConsentRequired(),
"keycloak_client_registration_policy_full_scope_disallowed": resourceKeycloakClientRegistrationPolicyFullScopeDisallowed(),
"keycloak_client_registration_policy_client_disabled": resourceKeycloakClientRegistrationPolicyClientDisabled(),
Comment on lines +11 to +14
// TestAccKeycloakClientRegistrationPolicies_fullStack declares all 7 typed
// policy resources in a single config (4 anonymous, 3 authenticated) and
// verifies that apply + destroy succeed with no resource interfering with
// any other.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Question: How to set the client registration policies at the realm level?

2 participants