Skip to content

Commit

Permalink
resource to enable the token exchange idp permission (#318)
Browse files Browse the repository at this point in the history
* resource to enable the token excahnge idp permission + auto create of client policy

* fmt

* Update provider/resource_keycloak_identity_provider_token_exchange_scope_permission.go

Co-authored-by: Michael Parker <michael@parker.gg>

* improved docs+ IsError409 helper method + better policy_type validation method + improved policy name creator logic

* fmt

Co-authored-by: Tom Rutsaert <tom.rutsaert@axians.com>
Co-authored-by: Michael Parker <michael@parker.gg>
  • Loading branch information
3 people authored Jun 29, 2020
1 parent 6e58f05 commit 731c4cc
Show file tree
Hide file tree
Showing 8 changed files with 1,006 additions and 64 deletions.
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ services:
- 8389:389
keycloak:
image: jboss/keycloak:10.0.2
command: -b 0.0.0.0 -Dkeycloak.profile.feature.upload_scripts=enabled
command: -b 0.0.0.0 -Dkeycloak.profile.feature.upload_scripts=enabled -Dkeycloak.profile.feature.admin_fine_grained_authz=enabled -Dkeycloak.profile.feature.token_exchange=enabled
depends_on:
- postgres
- openldap
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# keycloak_identity_provider_token_exchange_scope_permission

Allows you to manage Identity Provider "Token exchange" Scope Based Permissions.

This is part of a preview keycloak feature. You need to enable this feature to be able to use this resource.
More information about enabling the preview feature can be found here: https://www.keycloak.org/docs/latest/securing_apps/index.html#_token-exchange

When enabling Identity Provider Permissions, Keycloak does several things automatically:
1. Enable Authorization on build-in realm-management client
1. Create a "token-exchange" scope
1. Create a resource representing the identity provider
1. Create a scope based permission for the "token-exchange" scope and identity provider resource

The only thing that is missing is a policy set on the permission.
As the policy lives within the context of the realm-management client, you cannot create a policy resource and link to from with your _.tf_ file. This would also cause an implicit cycle dependency.
Thus, the only way to manage this in terraform is to create and manage the policy internally from within this terraform resource itself.
At the moment only a client policy type is supported. The client policy will automatically be created for the clients parameter.

### Example Usage

```hcl
resource "keycloak_realm" "token-exchange_realm" {
realm = "token-exchange_destination_realm"
enabled = true
}
resource keycloak_oidc_identity_provider token-exchange_my_oidc_idp {
realm = keycloak_realm.token-exchange_realm.id
alias = "myIdp"
authorization_url = "http://localhost:8080/auth/realms/someRealm/protocol/openid-connect/auth"
token_url = "http://localhost:8080/auth/realms/someRealm/protocol/openid-connect/token"
client_id = "clientId"
client_secret = "secret"
default_scopes = "openid"
}
resource "keycloak_openid_client" "token-exchange_webapp_client" {
realm_id = keycloak_realm.token-exchange_realm.id
name = "webapp_client"
client_id = "webapp_client"
client_secret = "secret"
description = "a webapp client on the destination realm"
access_type = "CONFIDENTIAL"
standard_flow_enabled = true
valid_redirect_uris = [
"http://localhost:8080/*",
]
}
//relevant part
resource "keycloak_identity_provider_token_exchange_scope_permission" "oidc_idp_permission" {
realm_id = keycloak_realm.token-exchange_realm.id
provider_alias = keycloak_oidc_identity_provider.token-exchange_my_oidc_idp.alias
policy_type = "client"
clients = [keycloak_openid_client.token-exchange_webapp_client.id]
}
```

### Argument Reference

The following arguments are supported:

- `realm_id` - (Required) The realm this group exists in.
- `provider_alias` - (Required) Alias of the identity provider.
- `policy_type` - (Optional) Defaults to "client" This is also the only value policy type supported by this provider.
- `clients` - (Required) Ids of the clients for which a policy will be created and set on scope based token exchange permission.

### Attributes Reference

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

- `policy_id` - Policy id that will be set on the scope based token exchange permission automatically created by enabling permissions on the reference identity provider.
- `authorization_resource_server_id` - Resource server id representing the realm management client on which this permission is managed.
- `authorization_idp_resource_id` - Resource id representing the identity provider, this automatically created by keycloak.
- `authorization_token_exchange_scope_permission_id` - Permission id representing the Permission with scope 'Token Exchange' and the resource 'authorization_idp_resource_id', this automatically created by keycloak, the policy id will be set on this permission.


### Import

This resource can be imported using the format
`{{realm_id}}/{{provider_alias}}`, where `provider_alias` is the alias that you assign to the identity provider upon creation.

Example:

```bash
$ terraform import keycloak_identity_provider_token_exchange_scope_permission.my_permission my-realm/my_idp
```

57 changes: 57 additions & 0 deletions example/external_token_exchange_example.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
resource "keycloak_realm" "token-exchange_source_realm" {
realm = "token-exchange_source_realm"
enabled = true
}

resource "keycloak_openid_client" "token-exchange_destination_client" {
realm_id = keycloak_realm.token-exchange_source_realm.id
name = "destination_client"
client_id = "destination_client"
client_secret = "secret"
description = "a client used by the destination realm"
access_type = "CONFIDENTIAL"
standard_flow_enabled = true
valid_redirect_uris = [
"http://localhost:8080/*",
]
}

resource "keycloak_realm" "token-exchange_destination_realm" {
realm = "token-exchange_destination_realm"
enabled = true
}

resource keycloak_oidc_identity_provider token-exchange_source_oidc_idp {
realm = keycloak_realm.token-exchange_destination_realm.id
alias = "source"
authorization_url = "http://localhost:8080/auth/realms/${keycloak_realm.token-exchange_source_realm.id}/protocol/openid-connect/auth"
token_url = "http://localhost:8080/auth/realms/${keycloak_realm.token-exchange_source_realm.id}/protocol/openid-connect/token"
user_info_url = "http://localhost:8080/auth/realms/${keycloak_realm.token-exchange_source_realm.id}/protocol/openid-connect/userinfo"
jwks_url = "http://localhost:8080/auth/realms/${keycloak_realm.token-exchange_source_realm.id}/protocol/openid-connect/certs"
validate_signature = true
client_id = keycloak_openid_client.token-exchange_destination_client.client_id
client_secret = keycloak_openid_client.token-exchange_destination_client.client_secret
default_scopes = "openid"
}

resource "keycloak_openid_client" "token-exchange_webapp_client" {
realm_id = keycloak_realm.token-exchange_destination_realm.id
name = "webapp_client"
client_id = "webapp_client"
client_secret = "secret"
description = "a webapp client on the destination realm"
access_type = "CONFIDENTIAL"
standard_flow_enabled = true
valid_redirect_uris = [
"http://localhost:8080/*",
]
}

//token exchange feature enabler
resource "keycloak_identity_provider_token_exchange_scope_permission" "source_oidc_idp_permission" {
realm_id = keycloak_realm.token-exchange_destination_realm.id
provider_alias = keycloak_oidc_identity_provider.token-exchange_source_oidc_idp.alias
policy_type = "client"
clients = [keycloak_openid_client.token-exchange_webapp_client.id]
}

6 changes: 6 additions & 0 deletions keycloak/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,9 @@ func ErrorIs404(err error) bool {

return ok && keycloakError != nil && keycloakError.Code == http.StatusNotFound
}

func ErrorIs409(err error) bool {
keycloakError, ok := errwrap.GetType(err, &ApiError{}).(*ApiError)

return ok && keycloakError != nil && keycloakError.Code == http.StatusConflict
}
46 changes: 46 additions & 0 deletions keycloak/identity_provider_permissions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package keycloak

import (
"fmt"
)

type IdentityProviderPermissionsInput struct {
Enabled bool `json:"enabled"`
}

type IdentityProviderPermissions struct {
RealmId string `json:"-"`
ProviderAlias string `json:"-"`
Enabled bool `json:"enabled"`
Resource string `json:"resource"`
ScopePermissions map[string]interface{} `json:"scopePermissions"`
}

func (keycloakClient *KeycloakClient) EnableIdentityProviderPermissions(realmId, providerAlias string) error {
return keycloakClient.put(fmt.Sprintf("/realms/%s/identity-provider/instances/%s/management/permissions", realmId, providerAlias), IdentityProviderPermissionsInput{Enabled: true})
}

func (keycloakClient *KeycloakClient) DisableIdentityProviderPermissions(realmId, providerAlias string) error {
return keycloakClient.put(fmt.Sprintf("/realms/%s/identity-provider/instances/%s/management/permissions", realmId, providerAlias), IdentityProviderPermissionsInput{Enabled: false})
}

func (keycloakClient *KeycloakClient) GetIdentityProviderPermissions(realmId, providerAlias string) (*IdentityProviderPermissions, error) {
var identityProviderPermissions IdentityProviderPermissions
identityProviderPermissions.RealmId = realmId
identityProviderPermissions.ProviderAlias = providerAlias

err := keycloakClient.get(fmt.Sprintf("/realms/%s/identity-provider/instances/%s/management/permissions", realmId, providerAlias), &identityProviderPermissions, nil)
if err != nil {
return nil, err
}

return &identityProviderPermissions, nil
}

func (identityProviderPermissions *IdentityProviderPermissions) GetTokenExchangeScopedPermissionId() (string, error) {
if identityProviderPermissions.Enabled {
return identityProviderPermissions.ScopePermissions["token-exchange"].(string), nil
} else {
return "", fmt.Errorf("identity provider permissions are not enabled, thus can not return the linked 'token-exchange' scope based permission")
}
}
Loading

0 comments on commit 731c4cc

Please sign in to comment.