diff --git a/docs/resources/openid_client.md b/docs/resources/openid_client.md index 57ae9b483..723263f45 100644 --- a/docs/resources/openid_client.md +++ b/docs/resources/openid_client.md @@ -53,6 +53,11 @@ resource "keycloak_openid_client" "openid_client" { URIs for security. This client should be used for applications using the Implicit grant flow. - `BEARER-ONLY` - Used for services that never initiate a login. This client will only allow bearer token requests. - `client_secret` - (Optional) The secret for clients with an `access_type` of `CONFIDENTIAL` or `BEARER-ONLY`. This value is sensitive and should be treated with the same care as a password. If omitted, this will be generated by Keycloak. +- `client_authenticator_type` - (Optional) Defaults to `client-secret` The authenticator type for clients with an `access_type` of `CONFIDENTIAL` or `BEARER-ONLY`. Can be one of the following: + - `client-secret` (Default) Use client id and client secret to authenticate client. + - `client-jwt` Use signed JWT to authenticate client. Set signing algorithm in `extra_config` with `attributes.token.endpoint.auth.signing.alg = ` + - `client-x509` Use x509 certificate to authenticate client. Set Subject DN in `extra_config` with `attributes.x509.subjectdn = ` + - `client-secret-jwt` Use signed JWT with client secret to authenticate client. Set signing algorithm in `extra_config` with `attributes.token.endpoint.auth.signing.alg = ` - `standard_flow_enabled` - (Optional) When `true`, the OAuth2 Authorization Code Grant will be enabled for this client. Defaults to `false`. - `implicit_flow_enabled` - (Optional) When `true`, the OAuth2 Implicit Grant will be enabled for this client. Defaults to `false`. - `direct_access_grants_enabled` - (Optional) When `true`, the OAuth2 Resource Owner Password Grant will be enabled for this client. Defaults to `false`. diff --git a/keycloak/openid_client.go b/keycloak/openid_client.go index 357686f15..87f3fcf2a 100644 --- a/keycloak/openid_client.go +++ b/keycloak/openid_client.go @@ -31,8 +31,8 @@ type OpenidClient struct { ClientId string `json:"clientId"` RealmId string `json:"-"` Name string `json:"name"` - Protocol string `json:"protocol"` // always openid-connect for this resource - ClientAuthenticatorType string `json:"clientAuthenticatorType"` // always client-secret for now, don't have a need for JWT here + Protocol string `json:"protocol"` // always openid-connect for this resource + ClientAuthenticatorType string `json:"clientAuthenticatorType"` ClientSecret string `json:"secret,omitempty"` Enabled bool `json:"enabled"` Description string `json:"description"` @@ -119,7 +119,6 @@ func (keycloakClient *KeycloakClient) ValidateOpenidClient(client *OpenidClient) func (keycloakClient *KeycloakClient) NewOpenidClient(client *OpenidClient) error { client.Protocol = "openid-connect" - client.ClientAuthenticatorType = "client-secret" _, location, err := keycloakClient.post(fmt.Sprintf("/realms/%s/clients", client.RealmId), client) if err != nil { @@ -222,7 +221,6 @@ func (keycloakClient *KeycloakClient) GetOpenidClientByClientId(realmId, clientI func (keycloakClient *KeycloakClient) UpdateOpenidClient(client *OpenidClient) error { client.Protocol = "openid-connect" - client.ClientAuthenticatorType = "client-secret" return keycloakClient.put(fmt.Sprintf("/realms/%s/clients/%s", client.RealmId, client.Id), client) } diff --git a/provider/data_source_keycloak_openid_client.go b/provider/data_source_keycloak_openid_client.go index eff12d94e..dbfd12d0d 100644 --- a/provider/data_source_keycloak_openid_client.go +++ b/provider/data_source_keycloak_openid_client.go @@ -39,6 +39,10 @@ func dataSourceKeycloakOpenidClient() *schema.Resource { Computed: true, Sensitive: true, }, + "client_authenticator_type": { + Type: schema.TypeString, + Computed: true, + }, "standard_flow_enabled": { Type: schema.TypeBool, Computed: true, diff --git a/provider/resource_keycloak_openid_client.go b/provider/resource_keycloak_openid_client.go index 247541584..70b261e0f 100644 --- a/provider/resource_keycloak_openid_client.go +++ b/provider/resource_keycloak_openid_client.go @@ -18,6 +18,7 @@ var ( keycloakOpenidClientAuthorizationPolicyEnforcementMode = []string{"ENFORCING", "PERMISSIVE", "DISABLED"} keycloakOpenidClientResourcePermissionDecisionStrategies = []string{"UNANIMOUS", "AFFIRMATIVE", "CONSENSUS"} keycloakOpenidClientPkceCodeChallengeMethod = []string{"", "plain", "S256"} + keycloakOpenidClientAuthenticatorTypes = []string{"client-secret", "client-jwt", "client-x509", "client-secret-jwt"} ) func resourceKeycloakOpenidClient() *schema.Resource { @@ -64,6 +65,12 @@ func resourceKeycloakOpenidClient() *schema.Resource { Computed: true, Sensitive: true, }, + "client_authenticator_type": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice(keycloakOpenidClientAuthenticatorTypes, false), + Default: "client-secret", + }, "standard_flow_enabled": { Type: schema.TypeBool, Optional: true, @@ -293,6 +300,7 @@ func getOpenidClientFromData(data *schema.ResourceData) (*keycloak.OpenidClient, Enabled: data.Get("enabled").(bool), Description: data.Get("description").(string), ClientSecret: data.Get("client_secret").(string), + ClientAuthenticatorType: data.Get("client_authenticator_type").(string), StandardFlowEnabled: data.Get("standard_flow_enabled").(bool), ImplicitFlowEnabled: data.Get("implicit_flow_enabled").(bool), DirectAccessGrantsEnabled: data.Get("direct_access_grants_enabled").(bool), @@ -388,6 +396,7 @@ func setOpenidClientData(keycloakClient *keycloak.KeycloakClient, data *schema.R data.Set("enabled", client.Enabled) data.Set("description", client.Description) data.Set("client_secret", client.ClientSecret) + data.Set("client_authenticator_type", client.ClientAuthenticatorType) data.Set("standard_flow_enabled", client.StandardFlowEnabled) data.Set("implicit_flow_enabled", client.ImplicitFlowEnabled) data.Set("direct_access_grants_enabled", client.DirectAccessGrantsEnabled) diff --git a/provider/resource_keycloak_openid_client_test.go b/provider/resource_keycloak_openid_client_test.go index d4e582294..72bf273d1 100644 --- a/provider/resource_keycloak_openid_client_test.go +++ b/provider/resource_keycloak_openid_client_test.go @@ -120,6 +120,34 @@ func TestAccKeycloakOpenidClient_accessType(t *testing.T) { }, }) } +func TestAccKeycloakOpenidClient_clientAuthenticatorType(t *testing.T) { + t.Parallel() + clientId := acctest.RandomWithPrefix("tf-acc") + + resource.Test(t, resource.TestCase{ + ProviderFactories: testAccProviderFactories, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckKeycloakOpenidClientDestroy(), + Steps: []resource.TestStep{ + { + Config: testKeycloakOpenidClient_clientAuthenticatorType(clientId, "client-secret"), + Check: testAccCheckKeycloakOpenidClientAuthenticatorType("keycloak_openid_client.client", "client-secret"), + }, + { + Config: testKeycloakOpenidClient_clientAuthenticatorType(clientId, "client-jwt"), + Check: testAccCheckKeycloakOpenidClientAuthenticatorType("keycloak_openid_client.client", "client-jwt"), + }, + { + Config: testKeycloakOpenidClient_clientAuthenticatorType(clientId, "client-secret-jwt"), + Check: testAccCheckKeycloakOpenidClientAuthenticatorType("keycloak_openid_client.client", "client-secret-jwt"), + }, + { + Config: testKeycloakOpenidClient_clientAuthenticatorType(clientId, "client-x509"), + Check: testAccCheckKeycloakOpenidClientAuthenticatorType("keycloak_openid_client.client", "client-x509"), + }, + }, + }) +} func TestAccKeycloakOpenidClient_updateInPlace(t *testing.T) { t.Parallel() @@ -796,6 +824,21 @@ func testAccCheckKeycloakOpenidClientAccessType(resourceName string, public, bea } } +func testAccCheckKeycloakOpenidClientAuthenticatorType(resourceName string, authType string) resource.TestCheckFunc { + return func(s *terraform.State) error { + client, err := getOpenidClientFromState(s, resourceName) + if err != nil { + return err + } + + if client.ClientAuthenticatorType != authType { + return fmt.Errorf("expected openid client to have client_authenticator_type set to %s, but got %s", authType, client.ClientAuthenticatorType) + } + + return nil + } +} + func testAccCheckKeycloakOpenidClientBelongsToRealm(resourceName, realm string) resource.TestCheckFunc { return func(s *terraform.State) error { client, err := getOpenidClientFromState(s, resourceName) @@ -1092,6 +1135,21 @@ resource "keycloak_openid_client" "client" { `, testAccRealm.Realm, clientId, accessType) } +func testKeycloakOpenidClient_clientAuthenticatorType(clientId, authType string) string { + return fmt.Sprintf(` +data "keycloak_realm" "realm" { + realm = "%s" +} + +resource "keycloak_openid_client" "client" { + realm_id = data.keycloak_realm.realm.id + client_id = "%s" + access_type = "CONFIDENTIAL" + client_authenticator_type = "%s" +} + `, testAccRealm.Realm, clientId, authType) +} + func testKeycloakOpenidClient_pkceChallengeMethod(clientId, pkceChallengeMethod string) string { return fmt.Sprintf(`