Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ services:
volumes:
- postgres:/var/lib/postgresql
openldap:
image: bitnami/openldap:2.6
image: bitnamilegacy/openldap:2.6
Copy link
Author

Choose a reason for hiding this comment

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

https://hub.docker.com/u/bitnamilegacy

bitnami is no longer free

environment:
LDAP_PORT_NUMBER: 389
keycloak:
Expand Down
45 changes: 41 additions & 4 deletions docs/resources/openid_client.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,27 @@ resource "keycloak_openid_client" "openid_client" {
}
```

## Example Usage with Existing default Client

```hcl
resource "keycloak_realm" "realm" {
realm = "my-realm"
enabled = true
}
import {
id = "${keycloak_realm.realm.id}/account"
to = keycloak_openid_client.account
}
resource "keycloak_openid_client" "account" {
realm_id = keycloak_realm.realm.id
client_id = "account"
enabled = false # disable account intentionally
lifecycle {
prevent_destroy = true
}
}
```

## Argument Reference

- `realm_id` - (Required) The realm this client is attached to.
Expand Down Expand Up @@ -182,20 +203,36 @@ is set to `true`.
}
```

- `import` - (Optional) When `true`, the client with the specified `client_id` is assumed to already exist, and it will be imported into state instead of being created. This attribute is useful when dealing with clients that Keycloak creates automatically during realm creation, such as `account` and `admin-cli`. Note, that the client will not be removed during destruction if `import` is `true`.

## Attributes Reference

- `service_account_user_id` - (Computed) When service accounts are enabled for this client, this attribute is the unique ID for the Keycloak user that represents this service account.
- `resource_server_id` - (Computed) When authorization is enabled for this client, this attribute is the unique ID for the client (the same value as the `.id` attribute).

## Import

Clients can be imported using the format `{{realm_id}}/{{client_keycloak_id}}`, where `client_keycloak_id` is the unique ID that Keycloak
assigns to the client upon creation. This value can be found in the URI when editing this client in the GUI, and is typically a GUID.
Clients can be imported using the two formats:

1. `{{realm_id}}/{{client_uuid}}`, where `client_uuid` is the UUID that KeyCloak assigns to the client upon creation.
This value can be found in the URL when editing the client in an admin console.
2. `{{realm_id}}/{{client_id}}`, where `client_id` is the human-readable client ID that KeyCloak requires when creating
a client.

Example:

```bash
terraform import keycloak_openid_client.openid_client my-realm/dcbc4c73-e478-4928-ae2e-d5e420223352
terraform import keycloak_openid_client.account my-realm/account
```

Or in HCL:
```hcl
import {
id = "my-realm/dcbc4c73-e478-4928-ae2e-d5e420223352"
to = keycloak_openid_client.openid_client
}
import {
id = "my-realm/account"
to = keycloak_openid_client.account
}
```
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/keycloak/terraform-provider-keycloak
require (
dario.cat/mergo v1.0.2
github.com/golang-jwt/jwt/v5 v5.3.0
github.com/google/uuid v1.6.0
github.com/hashicorp/errwrap v1.1.0
github.com/hashicorp/go-cty v1.5.0
github.com/hashicorp/go-retryablehttp v0.7.8
Expand Down
22 changes: 22 additions & 0 deletions keycloak/openid_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,28 @@ func (keycloakClient *KeycloakClient) NewOpenidClient(ctx context.Context, clien
return nil
}

func (keycloakClient *KeycloakClient) SearchOpenidClientExact(ctx context.Context, realmId string, clientId string) (*OpenidClient, error) {
Copy link
Author

Choose a reason for hiding this comment

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

This is needed to import clients by their respective ClientId

var clients []*OpenidClient

err := keycloakClient.get(ctx, fmt.Sprintf("/realms/%s/clients", realmId), &clients, map[string]string{
"first": "0",
"max": "101",
"clientId": clientId,
"search": "true",
})
if err != nil {
return nil, err
}
for _, client := range clients {
client.RealmId = realmId
if client.ClientId == clientId {
return client, nil
}
}

return nil, fmt.Errorf("openid clientId %s does not exist in realm %s", clientId, realmId)
}

func (keycloakClient *KeycloakClient) GetOpenidClients(ctx context.Context, realmId string, withSecrets bool) ([]*OpenidClient, error) {
var clients []*OpenidClient
var clientSecret OpenidClientSecret
Expand Down
8 changes: 4 additions & 4 deletions provider/data_source_keycloak_openid_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,19 +110,19 @@ func dataSourceKeycloakOpenidClient() *schema.Resource {
},
"client_offline_session_idle_timeout": {
Type: schema.TypeString,
Computed: true,
Optional: true,
Copy link
Author

Choose a reason for hiding this comment

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

this is part of a revert commit I was telling in the description

},
"client_offline_session_max_lifespan": {
Type: schema.TypeString,
Computed: true,
Optional: true,
},
"client_session_idle_timeout": {
Type: schema.TypeString,
Computed: true,
Optional: true,
},
"client_session_max_lifespan": {
Type: schema.TypeString,
Computed: true,
Optional: true,
},
"exclude_session_state_from_auth_response": {
Type: schema.TypeBool,
Expand Down
10 changes: 10 additions & 0 deletions provider/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"testing"
"time"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/meta"
Expand Down Expand Up @@ -41,6 +42,15 @@ func init() {
panic(err)
}
testAccProvider = KeycloakProvider(keycloakClient)

testAccProvider.ResourcesMap["keycloak_openid_client"].DeleteContext = func(ctx context.Context, data *schema.ResourceData, i interface{}) diag.Diagnostics {
Copy link
Author

@mikhail-putilov mikhail-putilov Oct 22, 2025

Choose a reason for hiding this comment

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

I need to make this part more clearer.
Default clients cannot be deleted. Keycloak returns 400. Terraform acceptance test library simply can't handle this situation. One way what I found to overcome this is to replace DeleteContext function in acceptance tests only.

I believe, we must not alter the behavior of standard response of KeyCloak when client is deleted, nor hide that fact that KeyCloak response is 400 in the production code.

For now, I left a comment in git the commit for this.

if data.State().Attributes["client_id"] == "account" {
return nil
} else {
return resourceKeycloakOpenidClientDelete(ctx, data, i)
}
}

testAccProviderFactories = map[string]func() (*schema.Provider, error){
"keycloak": func() (*schema.Provider, error) {
return testAccProvider, nil
Expand Down
Loading