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 saml client installation provider #263

Merged
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
56 changes: 56 additions & 0 deletions docs/data_sources/keycloak_saml_client_installation_provider.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# keycloak_saml_client_installation_provider data source

This data source can be used to retrieve Installation Provider
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"
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"
}


resource "aws_iam_saml_provider" "default" {
name = "myprovider"
saml_metadata_document = 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. 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

In addition to the arguments listed above, the following computed attributes are exported:

- `id` - The hash of the value
- `value` The returned XML document needed for SAML installation
16 changes: 10 additions & 6 deletions keycloak/keycloak_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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) {
Expand Down
5 changes: 5 additions & 0 deletions keycloak/saml_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,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"
Expand Down
57 changes: 57 additions & 0 deletions provider/data_source_keycloak_saml_client_installation_provider.go
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -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_sp_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-sp-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_sp_descriptor" {
realm_id = "${keycloak_realm.realm.id}"
client_id = "${keycloak_saml_client.saml_client.id}"
provider_id = "saml-sp-descriptor"
}
`, realm, clientId)
}
1 change: 1 addition & 0 deletions provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down