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

new resource: keycloak_saml_client #82

Merged
merged 12 commits into from
Jan 24, 2019
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ services:
openldap:
image: osixia/openldap:1.2.2
ports:
- 8389:8389
- 8389:389
keycloak:
image: jboss/keycloak:4.2.1.Final
depends_on:
Expand Down
64 changes: 64 additions & 0 deletions docs/resources/keycloak_saml_client.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# keycloak_saml_client

Allows for creating and managing Keycloak clients that use the SAML protocol.

Clients are entities that can use Keycloak for user authentication. Typically,
clients are applications that redirect users to Keycloak for authentication
in order to take advantage of Keycloak's user sessions for SSO.

### Example Usage

```hcl
resource "keycloak_realm" "realm" {
realm = "my-realm"
enabled = true
}

resource "keycloak_saml_client" "saml_client" {
realm_id = "${keycloak_realm.realm.id}"
client_id = "test-saml-client"
name = "test-saml-client"

sign_documents = false
sign_assertions = true
include_authn_statement = true

signing_certificate = "${file("saml-cert.pem")}"
signing_private_key = "${file("saml-key.pem")}"
}
```

### Argument Reference

The following arguments are supported:

- `realm_id` - (Required) The realm this client is attached to.
- `client_id` - (Required) The unique ID of this client, referenced in the URI during authentication and in issued tokens.
- `name` - (Optional) The display name of this client in the GUI.
- `enabled` - (Optional) When false, this client will not be able to initiate a login or obtain access tokens. Defaults to `true`.
- `description` - (Optional) The description of this client in the GUI.
- `include_authn_statement` - (Optional) When `true`, an `AuthnStatement` will be included in the SAML response.
- `sign_documents` - (Optional) When `true`, the SAML document will be signed by Keycloak using the realm's private key.
- `sign_assertions` - (Optional) When `true`, the SAML assertions will be signed by Keycloak using the realm's private key, and embedded within the SAML XML Auth response.
- `client_signature_required` - (Optional) When `true`, Keycloak will expect that documents originating from a client will be signed using the certificate and/or key configured via `signing_certificate` and `signing_private_key`.
- `force_post_binding` - (Optional) When `true`, Keycloak will always respond to an authentication request via the SAML POST Binding.
- `front_channel_logout` - (Optional) When `true`, this client will require a browser redirect in order to perform a logout.
- `name_id_format` - (Optional) Sets the Name ID format for the subject.
- `root_url` - (Optional) When specified, this value is prepended to all relative URLs.
- `valid_redirect_uris` - (Optional) When specified, Keycloak will use this list to validate given Assertion Consumer URLs specified in the authentication request.
- `base_url` - (Optional) When specified, this URL will be used whenever Keycloak needs to link to this client.
- `master_saml_processing_url` - (Optional) When specified, this URL will be used for all SAML requests.
- `signing_certificate` - (Optional) If documents or assertions from the client are signed, this certificate will be used to verify the signature.
- `signing_private_key` - (Optional) If documents or assertions from the client are signed, this private key will be used to verify the signature.


### Import

Clients can be imported using the format `{{realm_id}}/{{client_keycloak_id}}`, where `client_keycloak_id` is the unique ID that Keycloak
assigns to the client upon creation. This value can be found in the URI when editing this client in the GUI, and is typically a GUID.

Example:

```bash
$ terraform import keycloak_saml_client.saml_client my-realm/dcbc4c73-e478-4928-ae2e-d5e420223352
```
68 changes: 45 additions & 23 deletions example/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,16 @@ resource "keycloak_user" "another_user" {
}

resource "keycloak_user" "user_with_password" {
realm_id = "${keycloak_realm.test.id}"
username = "user-with-password"
realm_id = "${keycloak_realm.test.id}"
username = "user-with-password"

email = "[email protected]"
first_name = "Testy"
last_name = "Tester"
email = "[email protected]"
first_name = "Testy"
last_name = "Tester"
initial_password {
value = "my password"
value = "my password"
temporary = false
}
}
}


Expand All @@ -73,15 +73,19 @@ resource "keycloak_group_memberships" "foo_members" {
}

resource "keycloak_openid_client" "test_client" {
client_id = "test-client"
name = "test-client"
realm_id = "${keycloak_realm.test.id}"
description = "a test client"
client_id = "test-openid-client"
name = "test-openid-client"
realm_id = "${keycloak_realm.test.id}"
description = "a test openid client"

access_type = "CONFIDENTIAL"
valid_redirect_uris = [
"http://localhost:8080/callback"
standard_flow_enabled = true

access_type = "CONFIDENTIAL"
valid_redirect_uris = [
"http://localhost:5555/callback"
]

client_secret = "secret"
}

resource "keycloak_openid_client_scope" "test_client_scope" {
Expand All @@ -93,8 +97,8 @@ resource "keycloak_openid_client_scope" "test_client_scope" {
}

resource "keycloak_openid_client_default_scopes" "default_client_scopes" {
realm_id = "${keycloak_realm.test.id}"
client_id = "${keycloak_openid_client.test_client.id}"
realm_id = "${keycloak_realm.test.id}"
client_id = "${keycloak_openid_client.test_client.id}"

default_scopes = [
"profile",
Expand All @@ -105,9 +109,10 @@ resource "keycloak_openid_client_default_scopes" "default_client_scopes" {

resource "keycloak_ldap_user_federation" "openldap" {
name = "openldap"
realm_id = "master"
realm_id = "${keycloak_realm.test.id}"

enabled = true
import_enabled = false

username_ldap_attribute = "cn"
rdn_ldap_attribute = "cn"
Expand All @@ -123,15 +128,19 @@ resource "keycloak_ldap_user_federation" "openldap" {

connection_timeout = "5s"
read_timeout = "10s"

cache_policy = "NO_CACHE"
}

resource "keycloak_ldap_user_attribute_mapper" "attr_mapper" {
name = "test mapper"
resource "keycloak_ldap_user_attribute_mapper" "description_attr_mapper" {
name = "description-mapper"
realm_id = "${keycloak_ldap_user_federation.openldap.realm_id}"
ldap_user_federation_id = "${keycloak_ldap_user_federation.openldap.id}"

user_model_attribute = "foo"
ldap_attribute = "bar"
user_model_attribute = "description"
ldap_attribute = "description"

always_read_value_from_ldap = false
}

resource "keycloak_ldap_group_mapper" "group_mapper" {
Expand Down Expand Up @@ -177,8 +186,8 @@ resource "keycloak_openid_user_attribute_protocol_mapper" "map_user_attributes_c
name = "tf-test-open-id-user-attribute-protocol-mapper-client"
realm_id = "${keycloak_realm.test.id}"
client_id = "${keycloak_openid_client.test_client.id}"
user_attribute = "foo"
claim_name = "bar"
user_attribute = "description"
claim_name = "description"
}

resource "keycloak_openid_user_attribute_protocol_mapper" "map_user_attributes_client_scope" {
Expand Down Expand Up @@ -248,3 +257,16 @@ resource "keycloak_openid_hardcoded_claim_protocol_mapper" "hardcoded_claim_clie
claim_name = "foo"
claim_value = "bar"
}

resource "keycloak_saml_client" "saml_client" {
realm_id = "${keycloak_realm.test.id}"
client_id = "test-saml-client"
name = "test-saml-client"

sign_documents = false
sign_assertions = true
include_authn_statement = true

signing_certificate = "${file("../provider/misc/saml-cert.pem")}"
signing_private_key = "${file("../provider/misc/saml-key.pem")}"
}
76 changes: 76 additions & 0 deletions keycloak/saml_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package keycloak

import (
"fmt"
)

type SamlClientAttributes struct {
IncludeAuthnStatement *string `json:"saml.authnstatement"`
SignDocuments *string `json:"saml.server.signature"`
SignAssertions *string `json:"saml.assertion.signature"`
ClientSignatureRequired *string `json:"saml.client.signature"`
ForcePostBinding *string `json:"saml.force.post.binding"`
// attributes above are actually booleans, but the Keycloak API expects strings
NameIdFormat string `json:"saml_name_id_format"`
SigningCertificate *string `json:"saml.signing.certificate,omitempty"`
SigningPrivateKey *string `json:"saml.signing.private.key"`
}

type SamlClient struct {
Id string `json:"id,omitempty"`
ClientId string `json:"clientId"`
RealmId string `json:"-"`
Name string `json:"name"`
Protocol string `json:"protocol"` // always saml for this resource
ClientAuthenticatorType string `json:"clientAuthenticatorType"` // always client-secret

Enabled bool `json:"enabled"`
Description string `json:"description"`

FrontChannelLogout bool `json:"frontchannelLogout"`

RootUrl string `json:"rootUrl"`
ValidRedirectUris []string `json:"redirectUris"`
BaseUrl string `json:"baseUrl"`
MasterSamlProcessingUrl string `json:"adminUrl"`

Attributes *SamlClientAttributes `json:"attributes"`
}

func (keycloakClient *KeycloakClient) NewSamlClient(client *SamlClient) error {
client.Protocol = "saml"
client.ClientAuthenticatorType = "client-secret"

location, err := keycloakClient.post(fmt.Sprintf("/realms/%s/clients", client.RealmId), client)
if err != nil {
return err
}

client.Id = getIdFromLocationHeader(location)

return nil
}

func (keycloakClient *KeycloakClient) GetSamlClient(realmId, id string) (*SamlClient, error) {
var client SamlClient

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

client.RealmId = realmId

return &client, nil
}

func (keycloakClient *KeycloakClient) UpdateSamlClient(client *SamlClient) error {
client.Protocol = "saml"
client.ClientAuthenticatorType = "client-secret"

return keycloakClient.put(fmt.Sprintf("/realms/%s/clients/%s", client.RealmId, client.Id), client)
}

func (keycloakClient *KeycloakClient) DeleteSamlClient(realmId, id string) error {
return keycloakClient.delete(fmt.Sprintf("/realms/%s/clients/%s", realmId, id))
}
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ nav:
- keycloak_group: resources/keycloak_group.md
- keycloak_group_memberships: resources/keycloak_group_memberships.md
- keycloak_openid_client: resources/keycloak_openid_client.md
- keycloak_saml_client: resources/keycloak_saml_client.md
- keycloak_openid_client_scope: resources/keycloak_openid_client_scope.md
- keycloak_openid_client_default_scopes: resources/keycloak_openid_client_default_scopes.md
- keycloak_openid_user_attribute_protocol_mapper: resources/keycloak_openid_user_attribute_protocol_mapper.md
Expand Down
Loading