From bbfb2b12c06741dc4f960367b1bb421b0618ab54 Mon Sep 17 00:00:00 2001 From: Romain Philibert Date: Thu, 26 Mar 2020 16:10:22 +0100 Subject: [PATCH 1/4] feat: add saml client installation provider --- ...cloak_saml_client_installation_provider.md | 52 +++++++++++++++++ keycloak/keycloak_client.go | 16 ++++-- keycloak/saml_client.go | 5 ++ ...cloak_saml_client_installation_provider.go | 57 +++++++++++++++++++ provider/provider.go | 1 + 5 files changed, 125 insertions(+), 6 deletions(-) create mode 100644 docs/data_sources/keycloak_saml_client_installation_provider.md create mode 100644 provider/data_source_keycloak_saml_client_installation_provider.go diff --git a/docs/data_sources/keycloak_saml_client_installation_provider.md b/docs/data_sources/keycloak_saml_client_installation_provider.md new file mode 100644 index 000000000..d55245ca5 --- /dev/null +++ b/docs/data_sources/keycloak_saml_client_installation_provider.md @@ -0,0 +1,52 @@ +# keycloak_saml_client_installation_provider data source + +This data source can be used to retrieve Installation Provider +of a SAML Client. + +### 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")}" +} + +data "keycloak_saml_client_installation_provider" "saml_idp_descriptor" { + realm_id = "${keycloak_realm.realm.id}" + client_id = "${keycloak_saml_client.saml_client}" + provider_id = "saml-idp-descriptor" +} + +output "xml"{ + value = data.keycloak_saml_client_installation_provider.saml_idp_descriptor.value +} + +``` + +### Argument Reference + +The following arguments are supported: + +- `realm_id` - (Required) The realm this group exists within. +- `client_id` - (Required) The name of the saml client +- `provider_id` - (Required) Could be one of `saml-idp-descriptor`, `keycloak-saml`, `saml-sp-descriptor`, `keycloak-saml-subsystem`, `mod-auth-mellon` + +### Attributes Reference + +In addition to the arguments listed above, the following computed attributes are exported: + +- `id` - The hash of the value +- `value` The returned document needed for SAML installation diff --git a/keycloak/keycloak_client.go b/keycloak/keycloak_client.go index b2c04ea9e..570b1ddbb 100644 --- a/keycloak/keycloak_client.go +++ b/keycloak/keycloak_client.go @@ -296,11 +296,19 @@ func (keycloakClient *KeycloakClient) sendRequest(request *http.Request) ([]byte } func (keycloakClient *KeycloakClient) get(path string, resource interface{}, params map[string]string) error { + body, err := keycloakClient.getRaw(path, params) + if err != nil { + return err + } + return json.Unmarshal(body, resource) +} + +func (keycloakClient *KeycloakClient) getRaw(path string, params map[string]string) ([]byte, error) { resourceUrl := keycloakClient.baseUrl + apiUrl + path request, err := http.NewRequest(http.MethodGet, resourceUrl, nil) if err != nil { - return err + return nil, err } if params != nil { @@ -312,11 +320,7 @@ func (keycloakClient *KeycloakClient) get(path string, resource interface{}, par } body, _, err := keycloakClient.sendRequest(request) - if err != nil { - return err - } - - return json.Unmarshal(body, resource) + return body, err } func (keycloakClient *KeycloakClient) post(path string, requestBody interface{}) ([]byte, string, error) { diff --git a/keycloak/saml_client.go b/keycloak/saml_client.go index b713dfe2d..a7fc00bd9 100644 --- a/keycloak/saml_client.go +++ b/keycloak/saml_client.go @@ -72,6 +72,11 @@ func (keycloakClient *KeycloakClient) GetSamlClient(realmId, id string) (*SamlCl return &client, nil } +func (keycloakClient *KeycloakClient) GetSamlClientInstallationProvider(realmId, id string, providerId string) ([]byte, error) { + value, err := keycloakClient.getRaw(fmt.Sprintf("/realms/%s/clients/%s/installation/providers/%s", realmId, id, providerId), nil) + return value, err +} + func (keycloakClient *KeycloakClient) UpdateSamlClient(client *SamlClient) error { client.Protocol = "saml" client.ClientAuthenticatorType = "client-secret" diff --git a/provider/data_source_keycloak_saml_client_installation_provider.go b/provider/data_source_keycloak_saml_client_installation_provider.go new file mode 100644 index 000000000..d97ae5522 --- /dev/null +++ b/provider/data_source_keycloak_saml_client_installation_provider.go @@ -0,0 +1,57 @@ +package provider + +import ( + "crypto/sha1" + "encoding/base64" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/mrparkers/terraform-provider-keycloak/keycloak" +) + +func dataSourceKeycloakSamlClientInstallationProvider() *schema.Resource { + return &schema.Resource{ + Read: dataSourceKeycloakSamlClientInstallationProviderRead, + Schema: map[string]*schema.Schema{ + "realm_id": { + Type: schema.TypeString, + Required: true, + }, + "client_id": { + Type: schema.TypeString, + Required: true, + }, + "provider_id": { + Type: schema.TypeString, + Required: true, + }, + "value": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func dataSourceKeycloakSamlClientInstallationProviderRead(data *schema.ResourceData, meta interface{}) error { + keycloakClient := meta.(*keycloak.KeycloakClient) + + realmId := data.Get("realm_id").(string) + cliendId := data.Get("client_id").(string) + providerId := data.Get("provider_id").(string) + + value, err := keycloakClient.GetSamlClientInstallationProvider(realmId, cliendId, providerId) + if err != nil { + return err + } + + h := sha1.New() + h.Write(value) + id := base64.URLEncoding.EncodeToString(h.Sum(nil)) + + data.SetId(id) + data.Set("realm_id", realmId) + data.Set("client_id", cliendId) + data.Set("provider_id", providerId) + data.Set("value", string(value)) + + return nil +} diff --git a/provider/provider.go b/provider/provider.go index 14153444b..77972900d 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -15,6 +15,7 @@ func KeycloakProvider() *schema.Provider { "keycloak_realm": dataSourceKeycloakRealm(), "keycloak_realm_keys": dataSourceKeycloakRealmKeys(), "keycloak_role": dataSourceKeycloakRole(), + "keycloak_saml_client_installation_provider": dataSourceKeycloakSamlClientInstallationProvider(), }, ResourcesMap: map[string]*schema.Resource{ "keycloak_realm": resourceKeycloakRealm(), From 673fc098dd1d1dfc6de21766757a3039afeaab26 Mon Sep 17 00:00:00 2001 From: Romain Philibert Date: Mon, 6 Apr 2020 13:48:39 +0200 Subject: [PATCH 2/4] update doc with aws_iam_saml_provider example --- .../keycloak_saml_client_installation_provider.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/data_sources/keycloak_saml_client_installation_provider.md b/docs/data_sources/keycloak_saml_client_installation_provider.md index d55245ca5..f656a97f2 100644 --- a/docs/data_sources/keycloak_saml_client_installation_provider.md +++ b/docs/data_sources/keycloak_saml_client_installation_provider.md @@ -5,6 +5,9 @@ of a SAML Client. ### Example Usage +In the example below, we extract the SAML metadata IDPSSODescriptor +to pass it to the AWS IAM SAML Provider. + ```hcl resource "keycloak_realm" "realm" { realm = "my-realm" @@ -30,10 +33,11 @@ data "keycloak_saml_client_installation_provider" "saml_idp_descriptor" { provider_id = "saml-idp-descriptor" } -output "xml"{ - value = data.keycloak_saml_client_installation_provider.saml_idp_descriptor.value -} +resource "aws_iam_saml_provider" "default" { + name = "myprovider" + saml_metadata_document = data.keycloak_saml_client_installation_provider.saml_idp_descriptor.value +} ``` ### Argument Reference @@ -49,4 +53,4 @@ The following arguments are supported: In addition to the arguments listed above, the following computed attributes are exported: - `id` - The hash of the value -- `value` The returned document needed for SAML installation +- `value` The returned XML document needed for SAML installation From f97a9b7e8390ef4817ccabb97d61df2c1e74b64a Mon Sep 17 00:00:00 2001 From: Romain Philibert Date: Mon, 6 Apr 2020 15:51:33 +0200 Subject: [PATCH 3/4] add tests --- ...cloak_saml_client_installation_provider.md | 2 +- ..._saml_client_installation_provider_test.go | 73 +++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 provider/data_source_keycloak_saml_client_installation_provider_test.go diff --git a/docs/data_sources/keycloak_saml_client_installation_provider.md b/docs/data_sources/keycloak_saml_client_installation_provider.md index f656a97f2..51ec9e635 100644 --- a/docs/data_sources/keycloak_saml_client_installation_provider.md +++ b/docs/data_sources/keycloak_saml_client_installation_provider.md @@ -45,7 +45,7 @@ resource "aws_iam_saml_provider" "default" { The following arguments are supported: - `realm_id` - (Required) The realm this group exists within. -- `client_id` - (Required) The name of the saml client +- `client_id` - (Required) The name of the saml client. Not the id of the client. - `provider_id` - (Required) Could be one of `saml-idp-descriptor`, `keycloak-saml`, `saml-sp-descriptor`, `keycloak-saml-subsystem`, `mod-auth-mellon` ### Attributes Reference diff --git a/provider/data_source_keycloak_saml_client_installation_provider_test.go b/provider/data_source_keycloak_saml_client_installation_provider_test.go new file mode 100644 index 000000000..4bd44c0d3 --- /dev/null +++ b/provider/data_source_keycloak_saml_client_installation_provider_test.go @@ -0,0 +1,73 @@ +package provider + +import ( + "encoding/xml" + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" +) + +func TestAccKeycloakSamlClientInstallationProvider_basic(t *testing.T) { + realmName := "terraform-" + acctest.RandString(10) + clientId := "terraform-" + acctest.RandString(10) + + resourceName := "keycloak_saml_client.saml_client" + dataSourceName := "data.keycloak_saml_client_installation_provider.saml_idp_descriptor" + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckKeycloakSamlClientDestroy(), + Steps: []resource.TestStep{ + { + Config: testDataSourceKeycloakSamlClientInstallationProvider_basic(realmName, clientId), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair(dataSourceName, "realm_id", resourceName, "realm_id"), + resource.TestCheckResourceAttrPair(dataSourceName, "client_id", resourceName, "id"), + resource.TestCheckResourceAttr(dataSourceName, "provider_id", "saml-idp-descriptor"), + testAccCheckDataKeycloakSamlClientInstallationProvider(dataSourceName), + ), + }, + }, + }) +} + +func testAccCheckDataKeycloakSamlClientInstallationProvider(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("resource not found: %s", resourceName) + } + + value := rs.Primary.Attributes["value"] + + err := xml.Unmarshal([]byte(value), new(interface{})) + if err != nil { + return fmt.Errorf("invalid XML: %s\n%s", err, value) + } + + return nil + } +} + +func testDataSourceKeycloakSamlClientInstallationProvider_basic(realm, clientId string) string { + return fmt.Sprintf(` +resource "keycloak_realm" "realm" { + realm = "%s" +} + +resource "keycloak_saml_client" "saml_client" { + client_id = "%s" + realm_id = "${keycloak_realm.realm.id}" +} + +data "keycloak_saml_client_installation_provider" "saml_idp_descriptor" { + realm_id = "${keycloak_realm.realm.id}" + client_id = "${keycloak_saml_client.saml_client.id}" + provider_id = "saml-idp-descriptor" +} + `, realm, clientId) +} From db253690e4262c6b864757171a0a2b61403c72ff Mon Sep 17 00:00:00 2001 From: Romain Philibert Date: Mon, 6 Apr 2020 17:05:04 +0200 Subject: [PATCH 4/4] fix tests --- ...rce_keycloak_saml_client_installation_provider_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/provider/data_source_keycloak_saml_client_installation_provider_test.go b/provider/data_source_keycloak_saml_client_installation_provider_test.go index 4bd44c0d3..dfa76c518 100644 --- a/provider/data_source_keycloak_saml_client_installation_provider_test.go +++ b/provider/data_source_keycloak_saml_client_installation_provider_test.go @@ -15,7 +15,7 @@ func TestAccKeycloakSamlClientInstallationProvider_basic(t *testing.T) { clientId := "terraform-" + acctest.RandString(10) resourceName := "keycloak_saml_client.saml_client" - dataSourceName := "data.keycloak_saml_client_installation_provider.saml_idp_descriptor" + dataSourceName := "data.keycloak_saml_client_installation_provider.saml_sp_descriptor" resource.Test(t, resource.TestCase{ Providers: testAccProviders, @@ -27,7 +27,7 @@ func TestAccKeycloakSamlClientInstallationProvider_basic(t *testing.T) { Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttrPair(dataSourceName, "realm_id", resourceName, "realm_id"), resource.TestCheckResourceAttrPair(dataSourceName, "client_id", resourceName, "id"), - resource.TestCheckResourceAttr(dataSourceName, "provider_id", "saml-idp-descriptor"), + resource.TestCheckResourceAttr(dataSourceName, "provider_id", "saml-sp-descriptor"), testAccCheckDataKeycloakSamlClientInstallationProvider(dataSourceName), ), }, @@ -64,10 +64,10 @@ resource "keycloak_saml_client" "saml_client" { realm_id = "${keycloak_realm.realm.id}" } -data "keycloak_saml_client_installation_provider" "saml_idp_descriptor" { +data "keycloak_saml_client_installation_provider" "saml_sp_descriptor" { realm_id = "${keycloak_realm.realm.id}" client_id = "${keycloak_saml_client.saml_client.id}" - provider_id = "saml-idp-descriptor" + provider_id = "saml-sp-descriptor" } `, realm, clientId) }