Skip to content

Commit

Permalink
adds password policy support for keycloak_realm (#139)
Browse files Browse the repository at this point in the history
* added support for Realm passwordPolicy

* added support for Realm passwordPolicy
  • Loading branch information
tomrutsaert authored and mrparkers committed Jul 31, 2019
1 parent d2cd2d6 commit 669ce05
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 5 deletions.
12 changes: 7 additions & 5 deletions example/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ resource "keycloak_realm" "test" {
enabled = true
display_name = "foo"

smtp_server {
smtp_server = {
host = "mysmtphost.com"
port = 25
from_display_name = "Tom"
Expand All @@ -20,7 +20,7 @@ resource "keycloak_realm" "test" {
starttls = true
envelope_from = "nottom@myhost.com"

auth {
auth = {
username = "tom"
password = "tom"
}
Expand All @@ -30,7 +30,7 @@ resource "keycloak_realm" "test" {

access_code_lifespan = "30m"

internationalization {
internationalization = {
supported_locales = [
"en",
"de",
Expand All @@ -39,8 +39,8 @@ resource "keycloak_realm" "test" {
default_locale = "en"
}

security_defenses {
headers {
security_defenses = {
headers = {
x_frame_options = "DENY"
content_security_policy = "frame-src 'self'; frame-ancestors 'self'; object-src 'none';"
content_security_policy_report_only = ""
Expand All @@ -50,6 +50,8 @@ resource "keycloak_realm" "test" {
strict_transport_security = "max-age=31536000; includeSubDomains"
}
}

password_policy = "upperCase(1) and length(8) and forceExpiredPasswordChange(365) and notUsername"
}

resource "keycloak_required_action" "custom-terms-and-conditions" {
Expand Down
13 changes: 13 additions & 0 deletions keycloak/realm.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package keycloak

import (
"fmt"
"strings"
)

type Key struct {
Expand Down Expand Up @@ -66,6 +67,8 @@ type Realm struct {

//extra attributes of a realm, contains security defenses browser headers and brute force detection parameters(those still nee to be added)
Attributes Attributes `json:"attributes,omitempty"`

PasswordPolicy string `json:"passwordPolicy"`
}

type Attributes struct {
Expand Down Expand Up @@ -174,6 +177,16 @@ func (keycloakClient *KeycloakClient) ValidateRealm(realm *Realm) error {
return fmt.Errorf("validation error: DefaultLocale should be in the SupportLocales")
}

if realm.PasswordPolicy != "" {
policies := strings.Split(realm.PasswordPolicy, " and ")
for _, policyTypeRepresentation := range policies {
policy := strings.Split(policyTypeRepresentation, "(")
if !serverInfo.providerInstalled("password-policy", policy[0]) {
return fmt.Errorf("validation error: password-policy \"%s\" does not exist on the server, installed providers: %s", policy[0], serverInfo.getInstalledProvidersNames("password-policy"))
}
}
}

return nil
}

Expand Down
11 changes: 11 additions & 0 deletions provider/resource_keycloak_realm.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,11 @@ func resourceKeycloakRealm() *schema.Resource {
},
},
},
"password_policy": {
Type: schema.TypeString,
Description: "String that represents the passwordPolicies that are in place. Each policy is separated with \" and \". Supported policies can be found in the server-info providers page. example: \"upperCase(1) and length(8) and forceExpiredPasswordChange(365) and notUsername(undefined)\"",
Optional: true,
},
},
}
}
Expand Down Expand Up @@ -534,6 +539,10 @@ func getRealmFromData(data *schema.ResourceData) (*keycloak.Realm, error) {
} else {
setDefaultSecuritySettings(realm)
}

if passwordPolicy, ok := data.GetOk("password_policy"); ok {
realm.PasswordPolicy = passwordPolicy.(string)
}
return realm, nil
}

Expand Down Expand Up @@ -647,6 +656,8 @@ func setRealmData(data *schema.ResourceData, realm *keycloak.Realm) {
data.Set("security_defenses", []interface{}{securityDefensesSettings})
}
}

data.Set("password_policy", realm.PasswordPolicy)
}

func resourceKeycloakRealmCreate(data *schema.ResourceData, meta interface{}) error {
Expand Down
94 changes: 94 additions & 0 deletions provider/resource_keycloak_realm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,74 @@ func TestAccKeycloakRealm_securityDefenses(t *testing.T) {
})
}

func TestAccKeycloakRealm_passwordPolicy(t *testing.T) {
realmName := "terraform-" + acctest.RandString(10)
realmDisplayName := "terraform-" + acctest.RandString(10)
passwordPolicyStringValid1 := "upperCase(1) and length(8) and forceExpiredPasswordChange(365) and notUsername"
passwordPolicyStringValid2 := "upperCase(1) and length(8)"
passwordPolicyStringValid3 := "lowerCase(2)"

resource.Test(t, resource.TestCase{
Providers: testAccProviders,
PreCheck: func() { testAccPreCheck(t) },
CheckDestroy: testAccCheckKeycloakRealmDestroy(),
Steps: []resource.TestStep{
{
Config: testKeycloakRealm_basic(realmName, realmDisplayName),
Check: testAccCheckKeycloakRealmPasswordPolicy("keycloak_realm.realm", ""),
},
{
Config: testKeycloakRealm_passwordPolicy(realmName, realmDisplayName, passwordPolicyStringValid1),
Check: testAccCheckKeycloakRealmPasswordPolicy("keycloak_realm.realm", passwordPolicyStringValid1),
},
{
Config: testKeycloakRealm_passwordPolicy(realmName, realmDisplayName, passwordPolicyStringValid2),
Check: testAccCheckKeycloakRealmPasswordPolicy("keycloak_realm.realm", passwordPolicyStringValid2),
},
{
Config: testKeycloakRealm_passwordPolicy(realmName, realmDisplayName, passwordPolicyStringValid3),
Check: testAccCheckKeycloakRealmPasswordPolicy("keycloak_realm.realm", passwordPolicyStringValid3),
},
{
Config: testKeycloakRealm_basic(realmName, realmDisplayName),
Check: testAccCheckKeycloakRealmPasswordPolicy("keycloak_realm.realm", ""),
},
},
})
}

func TestAccKeycloakRealm_passwordPolicyInvalid(t *testing.T) {
realmName := "terraform-" + acctest.RandString(10)
realmDisplayName := "terraform-" + acctest.RandString(10)
passwordPolicyStringInvalid1 := "unknownpolicy(1) and length(8) and forceExpiredPasswordChange(365) and notUsername"
passwordPolicyStringInvalid2 := "lowerCase(1) and length(8) and unknownpolicy(365) and notUsername"
passwordPolicyStringInvalid3 := "unknownpolicy(2)"

resource.Test(t, resource.TestCase{
Providers: testAccProviders,
PreCheck: func() { testAccPreCheck(t) },
CheckDestroy: testAccCheckKeycloakRealmDestroy(),
Steps: []resource.TestStep{
{
Config: testKeycloakRealm_basic(realmName, realmDisplayName),
Check: testAccCheckKeycloakRealmPasswordPolicy("keycloak_realm.realm", ""),
},
{
Config: testKeycloakRealm_passwordPolicy(realmName, realmDisplayName, passwordPolicyStringInvalid1),
ExpectError: regexp.MustCompile("errors during apply: validation error: password-policy .+ does not exist on the server, installed providers: .+"),
},
{
Config: testKeycloakRealm_passwordPolicy(realmName, realmDisplayName, passwordPolicyStringInvalid2),
ExpectError: regexp.MustCompile("errors during apply: validation error: password-policy .+ does not exist on the server, installed providers: .+"),
},
{
Config: testKeycloakRealm_passwordPolicy(realmName, realmDisplayName, passwordPolicyStringInvalid3),
ExpectError: regexp.MustCompile("errors during apply: validation error: password-policy .+ does not exist on the server, installed providers: .+"),
},
},
})
}

func testKeycloakRealmLoginInfo(resourceName string, realm *keycloak.Realm) resource.TestCheckFunc {
return func(s *terraform.State) error {
realmFromState, err := getRealmFromState(s, resourceName)
Expand Down Expand Up @@ -651,6 +719,21 @@ func testAccCheckKeycloakRealmSecurityDefenses(resourceName, xFrameOptions strin
}
}

func testAccCheckKeycloakRealmPasswordPolicy(resourceName, passwordPolicy string) resource.TestCheckFunc {
return func(s *terraform.State) error {
realm, err := getRealmFromState(s, resourceName)
if err != nil {
return err
}

if realm.PasswordPolicy != passwordPolicy {
return fmt.Errorf("expected realm %s to have passwordPolicy %s, but was %s", realm.Realm, passwordPolicy, realm.PasswordPolicy)
}

return nil
}
}

func testKeycloakRealm_basic(realm, realmDisplayName string) string {
return fmt.Sprintf(`
resource "keycloak_realm" "realm" {
Expand Down Expand Up @@ -904,3 +987,14 @@ resource "keycloak_realm" "realm" {
}
`, realm, realmDisplayName, xFrameOptions)
}

func testKeycloakRealm_passwordPolicy(realm, realmDisplayName, passwordPolicy string) string {
return fmt.Sprintf(`
resource "keycloak_realm" "realm" {
realm = "%s"
enabled = true
display_name = "%s"
password_policy = "%s"
}
`, realm, realmDisplayName, passwordPolicy)
}

0 comments on commit 669ce05

Please sign in to comment.