From bab08a79b5593ee4dcb05a54f79fddef9534da32 Mon Sep 17 00:00:00 2001 From: Ruxandra Laceanu Date: Sun, 7 Sep 2025 08:52:34 +0200 Subject: [PATCH] feature: allow configuring kid in rsa keys Signed-off-by: Ruxandra Laceanu --- docs/resources/realm_keystore_rsa.md | 5 ++ keycloak/realm_keystore_rsa.go | 23 ++++++ .../resource_keycloak_realm_keystore_rsa.go | 7 ++ ...source_keycloak_realm_keystore_rsa_test.go | 77 +++++++++++++++++++ 4 files changed, 112 insertions(+) diff --git a/docs/resources/realm_keystore_rsa.md b/docs/resources/realm_keystore_rsa.md index 6ebe229e6..0f9cac128 100644 --- a/docs/resources/realm_keystore_rsa.md +++ b/docs/resources/realm_keystore_rsa.md @@ -29,6 +29,10 @@ resource "keycloak_realm_keystore_rsa" "keystore_rsa" { algorithm = "RS256" keystore_size = 2048 provider_id = "rsa" + + extra_config = { + kid = "my-key-id" + } } ``` @@ -44,6 +48,7 @@ resource "keycloak_realm_keystore_rsa" "keystore_rsa" { - `algorithm` - (Optional) Intended algorithm for the key. Defaults to `RS256`. Use `RSA-OAEP` for encryption keys - `keystore_size` - (Optional) Size for the generated keys. Defaults to `2048`. - `provider_id` - (Optional) Use `rsa` for signing keys, `rsa-enc` for encryption keys +- `extra_config` - (Optional) Map of additional provider configuration options passed through to the Keycloak component config. For RSA keystores this can include keys like `kid`. ## Import diff --git a/keycloak/realm_keystore_rsa.go b/keycloak/realm_keystore_rsa.go index 6ffadeca7..284848c3e 100644 --- a/keycloak/realm_keystore_rsa.go +++ b/keycloak/realm_keystore_rsa.go @@ -19,6 +19,7 @@ type RealmKeystoreRsa struct { PrivateKey string Certificate string ProviderId string + ExtraConfig map[string]interface{} } func convertFromRealmKeystoreRsaToComponent(realmKey *RealmKeystoreRsa) *component { @@ -43,6 +44,13 @@ func convertFromRealmKeystoreRsaToComponent(realmKey *RealmKeystoreRsa) *compone }, } + // merge extra config + if realmKey.ExtraConfig != nil { + for k, v := range realmKey.ExtraConfig { + componentConfig[k] = []string{fmt.Sprint(v)} + } + } + return &component{ Id: realmKey.Id, Name: realmKey.Name, @@ -86,6 +94,21 @@ func convertFromComponentToRealmKeystoreRsa(component *component, realmId string ProviderId: component.ProviderId, } + // capture extra config (anything not in the known keys) + known := map[string]struct{}{ + "active": {}, "enabled": {}, "priority": {}, "algorithm": {}, "privateKey": {}, "certificate": {}, + } + extra := map[string]interface{}{} + for k, vals := range component.Config { + if _, ok := known[k]; ok { + continue + } + if len(vals) > 0 { + extra[k] = vals[0] + } + } + realmKey.ExtraConfig = extra + return realmKey, nil } diff --git a/provider/resource_keycloak_realm_keystore_rsa.go b/provider/resource_keycloak_realm_keystore_rsa.go index 575157278..2ef1dc421 100644 --- a/provider/resource_keycloak_realm_keystore_rsa.go +++ b/provider/resource_keycloak_realm_keystore_rsa.go @@ -76,6 +76,10 @@ func resourceKeycloakRealmKeystoreRsa() *schema.Resource { Description: "RSA key provider id", ForceNew: true, }, + "extra_config": { + Type: schema.TypeMap, + Optional: true, + }, }, } } @@ -95,6 +99,8 @@ func getRealmKeystoreRsaFromData(data *schema.ResourceData) *keycloak.RealmKeyst ProviderId: data.Get("provider_id").(string), } + mapper.ExtraConfig = getExtraConfigFromData(data) + return mapper } @@ -113,6 +119,7 @@ func setRealmKeystoreRsaData(data *schema.ResourceData, realmKey *keycloak.Realm data.Set("private_key", realmKey.PrivateKey) data.Set("certificate", realmKey.Certificate) } + setExtraConfigData(data, realmKey.ExtraConfig) } func resourceKeycloakRealmKeystoreRsaCreate(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { diff --git a/provider/resource_keycloak_realm_keystore_rsa_test.go b/provider/resource_keycloak_realm_keystore_rsa_test.go index 395711716..a5c45cea7 100644 --- a/provider/resource_keycloak_realm_keystore_rsa_test.go +++ b/provider/resource_keycloak_realm_keystore_rsa_test.go @@ -112,6 +112,30 @@ func TestAccKeycloakRealmKeystoreRsa_algorithmValidation(t *testing.T) { }) } +func TestAccKeycloakRealmKeystoreRsa_extraConfigKid(t *testing.T) { + t.Parallel() + + rsaName := acctest.RandomWithPrefix("tf-acc") + kid := acctest.RandomWithPrefix("tf-acc") + privateKey, certificate := generateKeyAndCert(2048) + + resource.Test(t, resource.TestCase{ + ProviderFactories: testAccProviderFactories, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckRealmKeystoreRsaDestroy(), + Steps: []resource.TestStep{ + { + Config: testKeycloakRealmKeystoreRsa_withKidExtraConfig(rsaName, privateKey, certificate, kid), + Check: resource.ComposeTestCheckFunc( + testAccCheckRealmKeystoreRsaExists("keycloak_realm_keystore_rsa.realm_rsa"), + testAccCheckRealmKeystoreRsaKidInRealmKeys("keycloak_realm_keystore_rsa.realm_rsa", kid), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + func testAccCheckRealmKeystoreRsaExists(resourceName string) resource.TestCheckFunc { return func(s *terraform.State) error { _, err := getKeycloakRealmKeystoreRsaFromState(s, resourceName) @@ -123,6 +147,36 @@ func testAccCheckRealmKeystoreRsaExists(resourceName string) resource.TestCheckF } } +func testAccCheckRealmKeystoreRsaKidInRealmKeys(resourceName, expectedKid string) resource.TestCheckFunc { + return func(s *terraform.State) error { + fetchedKeystore, err := getKeycloakRealmKeystoreRsaFromState(s, resourceName) + if err != nil { + return err + } + + keys, err := keycloakClient.GetRealmKeys(testCtx, fetchedKeystore.RealmId) + if err != nil { + return fmt.Errorf("error fetching realm keys: %w", err) + } + + var candidates []keycloak.Key + for _, k := range keys.Keys { + if k.Algorithm != nil && *k.Algorithm == fetchedKeystore.Algorithm && + k.Certificate != nil && *k.Certificate == fetchedKeystore.Certificate { + candidates = append(candidates, k) + } + } + + for _, c := range candidates { + if c.Kid != nil && *c.Kid == expectedKid { + return nil + } + } + + return fmt.Errorf("could not find expected kid in realm keys. expected kid=%s", expectedKid) + } +} + func testAccCheckRealmKeystoreRsaFetch(resourceName string, keystore *keycloak.RealmKeystoreRsa) resource.TestCheckFunc { return func(s *terraform.State) error { fetchedKeystore, err := getKeycloakRealmKeystoreRsaFromState(s, resourceName) @@ -258,3 +312,26 @@ resource "keycloak_realm_keystore_rsa" "realm_rsa" { } `, testAccRealmUserFederation.Realm, rsaName, attr, val, privateKey, certificate, provider) } + +func testKeycloakRealmKeystoreRsa_withKidExtraConfig(rsaName, privateKey, certificate, kid string) string { + return fmt.Sprintf(` +data "keycloak_realm" "realm" { + realm = "%s" +} + +resource "keycloak_realm_keystore_rsa" "realm_rsa" { + name = "%s" + realm_id = data.keycloak_realm.realm.id + + priority = 100 + algorithm = "RS256" + private_key = "%s" + certificate = "%s" + provider_id = "rsa" + + extra_config = { + "kid" = "%s" + } +} + `, testAccRealmUserFederation.Realm, rsaName, privateKey, certificate, kid) +}