From ae83c44effc16feeb47e14b9ce62701019b28eb9 Mon Sep 17 00:00:00 2001 From: Christian Groschupp Date: Wed, 2 Sep 2020 18:23:57 +0200 Subject: [PATCH] add cache policy parameter to ldap user federation --- docs/resources/ldap_user_federation.md | 8 +- keycloak/ldap_user_federation.go | 74 ++++++++++++++- .../resource_keycloak_ldap_user_federation.go | 92 ++++++++++++++++++- ...urce_keycloak_ldap_user_federation_test.go | 34 ++++++- 4 files changed, 200 insertions(+), 8 deletions(-) diff --git a/docs/resources/ldap_user_federation.md b/docs/resources/ldap_user_federation.md index c09c40d06..2016e9e6e 100644 --- a/docs/resources/ldap_user_federation.md +++ b/docs/resources/ldap_user_federation.md @@ -80,7 +80,13 @@ resource "keycloak_ldap_user_federation" "ldap_user_federation" { - `batch_size_for_sync` - (Optional) The number of users to sync within a single transaction. Defaults to `1000`. - `full_sync_period` - (Optional) How frequently Keycloak should sync all LDAP users, in seconds. Omit this property to disable periodic full sync. - `changed_sync_period` - (Optional) How frequently Keycloak should sync changed LDAP users, in seconds. Omit this property to disable periodic changed users sync. -- `cache_policy` - (Optional) Can be one of `DEFAULT`, `EVICT_DAILY`, `EVICT_WEEKLY`, `MAX_LIFESPAN`, or `NO_CACHE`. Defaults to `DEFAULT`. +- `cache_policy` - (Optional) **Deprecated** Can be one of `DEFAULT`, `EVICT_DAILY`, `EVICT_WEEKLY`, `MAX_LIFESPAN`, or `NO_CACHE`. Defaults to `DEFAULT`. +- `cache` - (Optional) A block containing the cache settings. + - `policy` - (Optional) Can be one of `DEFAULT`, `EVICT_DAILY`, `EVICT_WEEKLY`, `MAX_LIFESPAN`, or `NO_CACHE`. Defaults to `DEFAULT`. + - `max_lifespan` - (Optional) Max lifespan of cache entry (duration string). + - `eviction_day` - (Optional) Day of the week the entry will become invalid on + - `eviction_hour` - (Optional) Hour of day the entry will become invalid on. + - `eviction_day` - (Optional) Minute of day the entry will become invalid on. - `kerberos` - (Optional) A block containing the kerberos settings. - `kerberos_realm` - (Required) The name of the kerberos realm, e.g. FOO.LOCAL. - `server_principal` - (Required) The kerberos server principal, e.g. 'HTTP/host.foo.com@FOO.LOCAL'. diff --git a/keycloak/ldap_user_federation.go b/keycloak/ldap_user_federation.go index b9d3aafcb..2e78e7696 100644 --- a/keycloak/ldap_user_federation.go +++ b/keycloak/ldap_user_federation.go @@ -46,7 +46,11 @@ type LdapUserFederation struct { FullSyncPeriod int // either a number, in milliseconds, or -1 if full sync is disabled ChangedSyncPeriod int // either a number, in milliseconds, or -1 if changed sync is disabled - CachePolicy string + CachePolicy string + MaxLifespan string // duration string (ex: 1h30m) + EvictionDay *int + EvictionHour *int + EvictionMinute *int } func convertFromLdapUserFederationToComponent(ldap *LdapUserFederation) (*component, error) { @@ -173,6 +177,31 @@ func convertFromLdapUserFederationToComponent(ldap *LdapUserFederation) (*compon componentConfig["readTimeout"] = []string{} // the keycloak API will not unset this unless the config is present with an empty array } + componentConfig["evictionHour"] = []string{} + componentConfig["evictionMinute"] = []string{} + componentConfig["evictionDay"] = []string{} + componentConfig["maxLifespan"] = []string{} + + if ldap.CachePolicy != "" { + if ldap.EvictionHour != nil { + componentConfig["evictionHour"] = []string{strconv.Itoa(*ldap.EvictionHour)} + } + if ldap.EvictionMinute != nil { + componentConfig["evictionMinute"] = []string{strconv.Itoa(*ldap.EvictionMinute)} + } + if ldap.EvictionDay != nil { + componentConfig["evictionDay"] = []string{strconv.Itoa(*ldap.EvictionDay)} + } + + if ldap.MaxLifespan != "" { + maxLifespanMs, err := getMillisecondsFromDurationString(ldap.MaxLifespan) + if err != nil { + return nil, err + } + componentConfig["maxLifespan"] = []string{maxLifespanMs} + } + } + return &component{ Id: ldap.Id, Name: ldap.Name, @@ -324,6 +353,49 @@ func convertFromComponentToLdapUserFederation(component *component) (*LdapUserFe ldap.ReadTimeout = readTimeoutDurationString } + if maxLifespan, ok := component.getConfigOk("maxLifespan"); ok { + maxLifespanString, err := GetDurationStringFromMilliseconds(maxLifespan) + if err != nil { + return nil, err + } + + ldap.MaxLifespan = maxLifespanString + } + + defaultEvictioValue := -1 + + if evictionDay, ok := component.getConfigOk("evictionDay"); ok { + evictionDayInt, err := strconv.Atoi(evictionDay) + if err != nil { + return nil, fmt.Errorf("unable to parse `evictionDay`: %w", err) + } + + ldap.EvictionDay = &evictionDayInt + } else { + ldap.EvictionDay = &defaultEvictioValue + } + + if evictionHour, ok := component.getConfigOk("evictionHour"); ok { + evictionHourInt, err := strconv.Atoi(evictionHour) + if err != nil { + return nil, fmt.Errorf("unable to parse `evictionHour`: %w", err) + } + + ldap.EvictionHour = &evictionHourInt + } else { + ldap.EvictionHour = &defaultEvictioValue + } + if evictionMinute, ok := component.getConfigOk("evictionMinute"); ok { + evictionMinuteInt, err := strconv.Atoi(evictionMinute) + if err != nil { + return nil, fmt.Errorf("unable to parse `evictionMinute`: %w", err) + } + + ldap.EvictionMinute = &evictionMinuteInt + } else { + ldap.EvictionMinute = &defaultEvictioValue + } + return ldap, nil } diff --git a/provider/resource_keycloak_ldap_user_federation.go b/provider/resource_keycloak_ldap_user_federation.go index 2b234872a..8117a84e7 100644 --- a/provider/resource_keycloak_ldap_user_federation.go +++ b/provider/resource_keycloak_ldap_user_federation.go @@ -223,10 +223,56 @@ func resourceKeycloakLdapUserFederation() *schema.Resource { }, "cache_policy": { - Type: schema.TypeString, - Optional: true, - Default: "DEFAULT", - ValidateFunc: validation.StringInSlice(keycloakUserFederationCachePolicies, false), + Type: schema.TypeString, + Optional: true, + Default: "DEFAULT", + Deprecated: "use cache.policy instead", + ConflictsWith: []string{"cache"}, + ValidateFunc: validation.StringInSlice(keycloakUserFederationCachePolicies, false), + }, + "cache": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Description: "Settings regarding cache policy for this realm.", + ConflictsWith: []string{"cache_policy"}, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "policy": { + Type: schema.TypeString, + Optional: true, + Default: "DEFAULT", + ValidateFunc: validation.StringInSlice(keycloakUserFederationCachePolicies, false), + }, + "max_lifespan": { + Type: schema.TypeString, + Optional: true, + DiffSuppressFunc: suppressDurationStringDiff, + Description: "Max lifespan of cache entry (duration string).", + }, + "eviction_day": { + Type: schema.TypeInt, + Optional: true, + Default: "-1", + ValidateFunc: validation.All(validation.IntAtLeast(0), validation.IntAtMost(6)), + Description: "Day of the week the entry will become invalid on.", + }, + "eviction_hour": { + Type: schema.TypeInt, + Optional: true, + Default: "-1", + ValidateFunc: validation.All(validation.IntAtLeast(0), validation.IntAtMost(23)), + Description: "Hour of day the entry will become invalid on.", + }, + "eviction_minute": { + Type: schema.TypeInt, + Optional: true, + Default: "-1", + ValidateFunc: validation.All(validation.IntAtLeast(0), validation.IntAtMost(59)), + Description: "Minute of day the entry will become invalid on.", + }, + }, + }, }, }, } @@ -289,6 +335,22 @@ func getLdapUserFederationFromData(data *schema.ResourceData) *keycloak.LdapUser CachePolicy: data.Get("cache_policy").(string), } + if cache, ok := data.GetOk("cache"); ok { + cache := cache.([]interface{}) + cacheData := cache[0].(map[string]interface{}) + + evictionDay := cacheData["eviction_day"].(int) + evictionHour := cacheData["eviction_hour"].(int) + evictionMinute := cacheData["eviction_minute"].(int) + + ldapUserFederation.MaxLifespan = cacheData["max_lifespan"].(string) + + ldapUserFederation.EvictionDay = &evictionDay + ldapUserFederation.EvictionHour = &evictionHour + ldapUserFederation.EvictionMinute = &evictionMinute + ldapUserFederation.CachePolicy = cacheData["policy"].(string) + } + if kerberos, ok := data.GetOk("kerberos"); ok { ldapUserFederation.AllowKerberosAuthentication = true kerberosSettingsData := kerberos.(*schema.Set).List()[0] @@ -354,6 +416,28 @@ func setLdapUserFederationData(data *schema.ResourceData, ldap *keycloak.LdapUse data.Set("changed_sync_period", ldap.ChangedSyncPeriod) data.Set("cache_policy", ldap.CachePolicy) + + if _, ok := data.GetOk("cache_policy_settings"); ok { + cachePolicySettings := make(map[string]interface{}) + + if ldap.MaxLifespan != "" { + cachePolicySettings["max_lifespan"] = ldap.MaxLifespan + } + + if ldap.EvictionDay != nil { + cachePolicySettings["eviction_day"] = *ldap.EvictionDay + } + if ldap.EvictionHour != nil { + cachePolicySettings["eviction_hour"] = *ldap.EvictionHour + } + if ldap.EvictionMinute != nil { + cachePolicySettings["eviction_minute"] = *ldap.EvictionMinute + } + + cachePolicySettings["policy"] = ldap.CachePolicy + + data.Set("cache_policy_settings", []interface{}{cachePolicySettings}) + } } func resourceKeycloakLdapUserFederationCreate(data *schema.ResourceData, meta interface{}) error { diff --git a/provider/resource_keycloak_ldap_user_federation_test.go b/provider/resource_keycloak_ldap_user_federation_test.go index 08af99364..1dbdd17b1 100644 --- a/provider/resource_keycloak_ldap_user_federation_test.go +++ b/provider/resource_keycloak_ldap_user_federation_test.go @@ -127,6 +127,10 @@ func generateRandomLdapKerberos(enabled bool) *keycloak.LdapUserFederation { connectionTimeout, _ := keycloak.GetDurationStringFromMilliseconds(strconv.Itoa(acctest.RandIntRange(1, 3600) * 1000)) readTimeout, _ := keycloak.GetDurationStringFromMilliseconds(strconv.Itoa(acctest.RandIntRange(1, 3600) * 1000)) + evictionDay := acctest.RandIntRange(0, 6) + evictionHour := acctest.RandIntRange(0, 23) + evictionMinute := acctest.RandIntRange(0, 59) + return &keycloak.LdapUserFederation{ RealmId: acctest.RandString(10), Name: "terraform-" + acctest.RandString(10), @@ -153,6 +157,10 @@ func generateRandomLdapKerberos(enabled bool) *keycloak.LdapUserFederation { AllowKerberosAuthentication: true, KeyTab: acctest.RandString(10), KerberosRealm: acctest.RandString(10), + MaxLifespan: randomStringInSlice([]string{"1h", "2h", "3h"}), + EvictionDay: &evictionDay, + EvictionHour: &evictionHour, + EvictionMinute: &evictionMinute, } } @@ -225,6 +233,10 @@ func TestAccKeycloakLdapUserFederation_basicUpdateAll(t *testing.T) { firstReadTimeout, _ := keycloak.GetDurationStringFromMilliseconds(strconv.Itoa(acctest.RandIntRange(1, 3600) * 1000)) secondReadTimeout, _ := keycloak.GetDurationStringFromMilliseconds(strconv.Itoa(acctest.RandIntRange(1, 3600) * 1000)) + evictionDay := acctest.RandIntRange(0, 6) + evictionHour := acctest.RandIntRange(0, 23) + evictionMinute := acctest.RandIntRange(0, 59) + firstLdap := &keycloak.LdapUserFederation{ RealmId: realmName, Name: "terraform-" + acctest.RandString(10), @@ -251,8 +263,16 @@ func TestAccKeycloakLdapUserFederation_basicUpdateAll(t *testing.T) { AllowKerberosAuthentication: randomBool(), KeyTab: acctest.RandString(10), KerberosRealm: acctest.RandString(10), + MaxLifespan: randomStringInSlice([]string{"1h", "2h", "3h"}), + EvictionDay: &evictionDay, + EvictionHour: &evictionHour, + EvictionMinute: &evictionMinute, } + evictionDay = acctest.RandIntRange(0, 6) + evictionHour = acctest.RandIntRange(0, 23) + evictionMinute = acctest.RandIntRange(0, 59) + secondLdap := &keycloak.LdapUserFederation{ RealmId: realmName, Name: "terraform-" + acctest.RandString(10), @@ -279,6 +299,10 @@ func TestAccKeycloakLdapUserFederation_basicUpdateAll(t *testing.T) { AllowKerberosAuthentication: randomBool(), KeyTab: acctest.RandString(10), KerberosRealm: acctest.RandString(10), + MaxLifespan: randomStringInSlice([]string{"1h", "2h", "3h"}), + EvictionDay: &evictionDay, + EvictionHour: &evictionHour, + EvictionMinute: &evictionMinute, } resource.Test(t, resource.TestCase{ @@ -659,9 +683,15 @@ resource "keycloak_ldap_user_federation" "openldap" { kerberos_realm = "%s" } - cache_policy = "%s" + cache { + policy = "%s" + max_lifespan = "%s" + eviction_day = %d + eviction_hour = %d + eviction_minute = %d + } } - `, ldap.RealmId, ldap.Name, ldap.Enabled, ldap.UsernameLDAPAttribute, ldap.RdnLDAPAttribute, ldap.UuidLDAPAttribute, arrayOfStringsForTerraformResource(ldap.UserObjectClasses), ldap.ConnectionUrl, ldap.UsersDn, ldap.BindDn, ldap.BindCredential, ldap.SearchScope, ldap.ValidatePasswordPolicy, ldap.UseTruststoreSpi, ldap.ConnectionTimeout, ldap.ReadTimeout, ldap.Pagination, ldap.BatchSizeForSync, ldap.FullSyncPeriod, ldap.ChangedSyncPeriod, ldap.ServerPrincipal, ldap.UseKerberosForPasswordAuthentication, ldap.KeyTab, ldap.KerberosRealm, ldap.CachePolicy) + `, ldap.RealmId, ldap.Name, ldap.Enabled, ldap.UsernameLDAPAttribute, ldap.RdnLDAPAttribute, ldap.UuidLDAPAttribute, arrayOfStringsForTerraformResource(ldap.UserObjectClasses), ldap.ConnectionUrl, ldap.UsersDn, ldap.BindDn, ldap.BindCredential, ldap.SearchScope, ldap.ValidatePasswordPolicy, ldap.UseTruststoreSpi, ldap.ConnectionTimeout, ldap.ReadTimeout, ldap.Pagination, ldap.BatchSizeForSync, ldap.FullSyncPeriod, ldap.ChangedSyncPeriod, ldap.ServerPrincipal, ldap.UseKerberosForPasswordAuthentication, ldap.KeyTab, ldap.KerberosRealm, ldap.CachePolicy, ldap.MaxLifespan, *ldap.EvictionDay, *ldap.EvictionHour, *ldap.EvictionMinute) } func testKeycloakLdapUserFederation_basicWithAttrValidation(attr, realm, ldap, val string) string {