Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add keycloak_realm_keystore_custom resource #937

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
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
59 changes: 59 additions & 0 deletions docs/resources/realm_keystore_custom.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
page_title: "keycloak_realm_keystore_custom Resources"
---

# keycloak\_realm\_keystore\_custom Resources

Allows for creating and managing custom Realm keystores within Keycloak.

A realm keystore manages keys that are used by Keycloak to perform cryptographic signatures and encryption.

## Example Usage

```hcl
resource "keycloak_realm" "realm" {
realm = "my-realm"
}
resource "keycloak_realm_keystore_custom" "keystore_custom" {
name = "my-custom-keystore"
realm_id = keycloak_realm.realm.id

enabled = true
active = true
priority = 100

provider_id = "custom-keystore"
provider_type = "org.company.keys.KeyProvider"

extra_config = {
"key1" = "value1"
"key2" = "value2"
}
}
```

## Argument Reference

- `name` - (Required) Display name of provider when linked in admin console.
- `realm_id` - (Required) The realm this keystore exists in.
- `enabled` - (Optional) When `false`, key is not accessible in this realm. Defaults to `true`.
- `active` - (Optional) When `false`, key in not used for signing. Defaults to `true`.
- `priority` - (Optional) Priority for the provider. Defaults to `0`
- `providerId` - (Required) The ID of the keystore provider.
- `providerType` - (Required) The type of the keystore provider.
- `extra_config` - (Optional) A map of key/value pairs to add extra configuration attributes to this keystore.
``` hcl
extra_config = {
"key1" = "value1"
}
```

## Import

Realm keys can be imported using realm name and keystore id, you can find it in web UI.

Example:

```bash
$ terraform import keycloak_realm_keystore_custom.keystore_custom my-realm/618cfba7-49aa-4c09-9a19-2f699b576f0b
```
140 changes: 140 additions & 0 deletions keycloak/realm_keystore_custom.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package keycloak

import (
"context"
"fmt"
"strconv"
)

type RealmKeystoreCustom struct {
Id string
Name string
RealmId string

Active bool
Enabled bool
Priority int

ProviderId string
ProviderType string

ExtraConfig map[string]interface{} `json:"-"`
}

func convertFromRealmKeystoreCustomToComponent(realmKey *RealmKeystoreCustom) *component {
componentConfig := map[string][]string{
"active": {
strconv.FormatBool(realmKey.Active),
},
"enabled": {
strconv.FormatBool(realmKey.Enabled),
},
"priority": {
strconv.Itoa(realmKey.Priority),
},
"providerId": {
realmKey.ProviderId,
},
"providerType": {
realmKey.ProviderType,
},
}

for key, value := range realmKey.ExtraConfig {
if strVal, ok := value.(string); ok {
componentConfig[key] = []string{strVal}
} else if boolVal, ok := value.(bool); ok {
componentConfig[key] = []string{strconv.FormatBool(boolVal)}
} else if intVal, ok := value.(int); ok {
componentConfig[key] = []string{strconv.Itoa(intVal)}
}
}

return &component{
Id: realmKey.Id,
Name: realmKey.Name,
ParentId: realmKey.RealmId,
ProviderId: realmKey.ProviderId,
ProviderType: realmKey.ProviderType,
Config: componentConfig,
}
}

func convertFromComponentToRealmKeystoreCustom(component *component, realmId string) (*RealmKeystoreCustom, error) {
active, err := parseBoolAndTreatEmptyStringAsFalse(component.getConfig("active"))
if err != nil {
return nil, err
}

enabled, err := parseBoolAndTreatEmptyStringAsFalse(component.getConfig("enabled"))
if err != nil {
return nil, err
}

priority := 0 // Default priority
if component.getConfig("priority") != "" {
priority, err = strconv.Atoi(component.getConfig("priority"))
if err != nil {
return nil, err
}
}

providerId := component.ProviderId
providerType := component.ProviderType

realmKey := &RealmKeystoreCustom{
Id: component.Id,
Name: component.Name,
RealmId: realmId,

Active: active,
Enabled: enabled,
Priority: priority,
ProviderId: providerId,
ProviderType: providerType,
ExtraConfig: make(map[string]interface{}),
}

for key, value := range component.Config {
if len(value) > 0 {
switch key {
case "active", "enabled", "priority":
continue
default:
realmKey.ExtraConfig[key] = value[0]
}
}
}

return realmKey, nil
}

func (keycloakClient *KeycloakClient) NewRealmKeystoreCustom(ctx context.Context, realmKey *RealmKeystoreCustom) error {
_, location, err := keycloakClient.post(ctx, fmt.Sprintf("/realms/%s/components", realmKey.RealmId), convertFromRealmKeystoreCustomToComponent(realmKey))
if err != nil {
return err
}

realmKey.Id = getIdFromLocationHeader(location)

return nil
}

func (keycloakClient *KeycloakClient) GetRealmKeystoreCustom(ctx context.Context, realmId, id string) (*RealmKeystoreCustom, error) {
var component *component

err := keycloakClient.get(ctx, fmt.Sprintf("/realms/%s/components/%s", realmId, id), &component, nil)
if err != nil {
return nil, err
}

return convertFromComponentToRealmKeystoreCustom(component, realmId)
}

func (keycloakClient *KeycloakClient) UpdateRealmKeystoreCustom(ctx context.Context, realmKey *RealmKeystoreCustom) error {
return keycloakClient.put(ctx, fmt.Sprintf("/realms/%s/components/%s", realmKey.RealmId, realmKey.Id), convertFromRealmKeystoreCustomToComponent(realmKey))
}

func (keycloakClient *KeycloakClient) DeleteRealmKeystoreCustom(ctx context.Context, realmId, id string) error {
return keycloakClient.delete(ctx, fmt.Sprintf("/realms/%s/components/%s", realmId, id), nil)
}
1 change: 1 addition & 0 deletions provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ func KeycloakProvider(client *keycloak.KeycloakClient) *schema.Provider {
"keycloak_realm_keystore_java_keystore": resourceKeycloakRealmKeystoreJavaKeystore(),
"keycloak_realm_keystore_rsa": resourceKeycloakRealmKeystoreRsa(),
"keycloak_realm_keystore_rsa_generated": resourceKeycloakRealmKeystoreRsaGenerated(),
"keycloak_realm_keystore_custom": resourceKeycloakRealmKeystoreCustom(),
"keycloak_realm_user_profile": resourceKeycloakRealmUserProfile(),
"keycloak_required_action": resourceKeycloakRequiredAction(),
"keycloak_group": resourceKeycloakGroup(),
Expand Down
171 changes: 171 additions & 0 deletions provider/resource_keycloak_realm_keystore_custom.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
package provider

import (
"context"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mrparkers/terraform-provider-keycloak/keycloak"
"reflect"
)

func resourceKeycloakRealmKeystoreCustom() *schema.Resource {
return &schema.Resource{
CreateContext: resourceKeycloakRealmKeystoreCustomCreate,
ReadContext: resourceKeycloakRealmKeystoreCustomRead,
UpdateContext: resourceKeycloakRealmKeystoreCustomUpdate,
DeleteContext: resourceKeycloakRealmKeystoreCustomDelete,
Importer: &schema.ResourceImporter{
StateContext: resourceKeycloakRealmKeystoreGenericImport,
},
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
Description: "Display name of provider when linked in admin console.",
},
"realm_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"active": {
Type: schema.TypeBool,
Optional: true,
Default: true,
Description: "Set if the keys are enabled",
},
"enabled": {
Type: schema.TypeBool,
Optional: true,
Default: true,
Description: "Set if the keys are enabled",
},
"priority": {
Type: schema.TypeInt,
Optional: true,
Default: 0,
Description: "Priority for the provider",
},
"provider_id": {
Type: schema.TypeString,
Required: true,
Description: "The ID of the custom provider",
},
"provider_type": {
Type: schema.TypeString,
Required: true,
Description: "The type of the custom provider",
},
"extra_config": {
Type: schema.TypeMap,
Optional: true,
ValidateDiagFunc: validateExtraConfig(reflect.ValueOf(&keycloak.RealmKeystoreCustom{}).Elem()),
},
},
}
}

func getRealmKeystoreCustomFromData(data *schema.ResourceData) (*keycloak.RealmKeystoreCustom, error) {
keystore := &keycloak.RealmKeystoreCustom{
Id: data.Id(),
Name: data.Get("name").(string),
RealmId: data.Get("realm_id").(string),

Active: data.Get("active").(bool),
Enabled: data.Get("enabled").(bool),
Priority: data.Get("priority").(int),
ProviderId: data.Get("provider_id").(string),
ProviderType: data.Get("provider_type").(string),

ExtraConfig: getExtraConfigFromData(data),
}

return keystore, nil
}

func setRealmKeystoreCustomData(data *schema.ResourceData, realmKey *keycloak.RealmKeystoreCustom) error {
data.SetId(realmKey.Id)

data.Set("name", realmKey.Name)
data.Set("realm_id", realmKey.RealmId)

data.Set("active", realmKey.Active)
data.Set("enabled", realmKey.Enabled)
data.Set("priority", realmKey.Priority)
data.Set("provider_id", realmKey.ProviderId)
data.Set("provider_type", realmKey.ProviderType)

setExtraConfigData(data, realmKey.ExtraConfig)

return nil
}

func resourceKeycloakRealmKeystoreCustomCreate(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
keycloakClient := meta.(*keycloak.KeycloakClient)

realmKey, err := getRealmKeystoreCustomFromData(data)
if err != nil {
return diag.FromErr(err)
}

err = keycloakClient.NewRealmKeystoreCustom(ctx, realmKey)
if err != nil {
return diag.FromErr(err)
}

err = setRealmKeystoreCustomData(data, realmKey)
if err != nil {
return diag.FromErr(err)
}

return resourceKeycloakRealmKeystoreCustomRead(ctx, data, meta)
}

func resourceKeycloakRealmKeystoreCustomRead(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
keycloakClient := meta.(*keycloak.KeycloakClient)

realmId := data.Get("realm_id").(string)
id := data.Id()

realmKey, err := keycloakClient.GetRealmKeystoreCustom(ctx, realmId, id)
if err != nil {
return handleNotFoundError(ctx, err, data)
}

err = setRealmKeystoreCustomData(data, realmKey)
if err != nil {
return diag.FromErr(err)
}

return nil
}

func resourceKeycloakRealmKeystoreCustomUpdate(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
keycloakClient := meta.(*keycloak.KeycloakClient)

realmKey, err := getRealmKeystoreCustomFromData(data)
if err != nil {
return diag.FromErr(err)
}

err = keycloakClient.UpdateRealmKeystoreCustom(ctx, realmKey)
if err != nil {
return diag.FromErr(err)
}

err = setRealmKeystoreCustomData(data, realmKey)
if err != nil {
return diag.FromErr(err)
}

return nil
}

func resourceKeycloakRealmKeystoreCustomDelete(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
keycloakClient := meta.(*keycloak.KeycloakClient)

realmId := data.Get("realm_id").(string)
id := data.Id()

return diag.FromErr(keycloakClient.DeleteRealmKeystoreCustom(ctx, realmId, id))
}
Loading
Loading