-
Notifications
You must be signed in to change notification settings - Fork 49
Keycloak - Directory Sync Integration #2402
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
Merged
Merged
Changes from 9 commits
Commits
Show all changes
36 commits
Select commit
Hold shift + click to select a range
4321630
WIP : directory sync | keycloak integration
manab-pr f137325
Merge branch 'main' of https://github.com/manab-pr/core into feature/…
manab-pr 1b18454
Merge branch 'main' of https://github.com/manab-pr/core into feature/…
manab-pr 2a5ca09
feat : added keycloak integration files
manab-pr 1768d78
Merge branch 'main' of https://github.com/manab-pr/core into feature/…
manab-pr 87ab01d
post merge changes
manab-pr 199835d
feat : containerized
manab-pr 11f1c6c
Merge branch 'main' into feature/keycloak
manab-pr ac35234
Merge branch 'main' into feature/keycloak
manab-pr 86d8e31
updated
manab-pr 694d4df
Merge branch 'main' of https://github.com/manab-pr/core into feature/…
manab-pr 9a61690
Merge branch 'feature/keycloak' of https://github.com/manab-pr/core i…
manab-pr 1e96a13
fix : requested changes
manab-pr 37ed112
Merge branch 'main' of https://github.com/manab-pr/core into feature/…
manab-pr 0d448ae
fix : test file
manab-pr 24d789a
Merge branch 'main' of https://github.com/manab-pr/core into feature/…
manab-pr 5d6e878
Update internal/integrations/definitions/keycloak/builder.go
manab-pr 8ed4736
Update internal/integrations/definitions/keycloak/doc.go
manab-pr 0ec1e1e
Apply suggestion from @golanglemonade
manab-pr 1964b30
Merge branch 'feature/keycloak' of https://github.com/manab-pr/core i…
manab-pr 815d7da
added permissions, last login time
manab-pr eb2b76a
Merge branch 'main' of https://github.com/manab-pr/core into feature/…
manab-pr f909b69
fix : conflicts
manab-pr 11bc578
Update docker/docker-compose-keycloak.yml
manab-pr 62e206d
Merge branch 'main' of https://github.com/manab-pr/core into feature/…
manab-pr dc9746d
Merge branch 'main' into feature/keycloak
manab-pr 4bba065
fix : ssl required NONE
manab-pr 2910547
Merge branch 'main' of https://github.com/manab-pr/core into feature/…
manab-pr 46539ef
Merge branch 'feature/keycloak' of https://github.com/manab-pr/core i…
manab-pr 1edda28
fix : keycloak compose file
manab-pr b4fde76
Merge branch 'theopenlane:main' into feature/keycloak
manab-pr 146667b
Merge branch 'main' of https://github.com/manab-pr/core into feature/…
manab-pr 2adf037
Merge branch 'feature/keycloak' of https://github.com/manab-pr/core i…
manab-pr c9d7239
Update internal/integrations/definitions/keycloak/builder.go
manab-pr 1cb3224
Update internal/integrations/definitions/keycloak/types.go
manab-pr 95e7756
fix : added missing logs, and fixed the type
manab-pr File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| KC_BOOTSTRAP_ADMIN_USERNAME=admin | ||
| KC_BOOTSTRAP_ADMIN_PASSWORD=change-me | ||
| KC_DB_PASSWORD=password |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| services: | ||
| keycloak: | ||
| image: quay.io/keycloak/keycloak:26.2.4 | ||
| command: start-dev | ||
| env_file: | ||
| - ${PWD}/docker/configs/keycloak/.env-example | ||
| environment: | ||
| KC_DB: postgres | ||
| KC_DB_URL: jdbc:postgresql://postgres:5432/keycloak | ||
| KC_DB_USERNAME: postgres | ||
| KC_HOSTNAME: localhost | ||
| KC_HTTP_PORT: 8180 | ||
| ports: | ||
| - 8180:8180 | ||
| depends_on: | ||
| postgres: | ||
| condition: service_healthy | ||
| networks: | ||
| - default | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| package keycloak | ||
|
|
||
| import ( | ||
| "github.com/theopenlane/core/internal/ent/integrationgenerated" | ||
| "github.com/theopenlane/core/internal/integrations/registry" | ||
| "github.com/theopenlane/core/internal/integrations/types" | ||
| "github.com/theopenlane/core/pkg/jsonx" | ||
| ) | ||
|
|
||
| // Builder returns the Keycloak definition builder | ||
| func Builder() registry.Builder { | ||
| return registry.Builder(func() (types.Definition, error) { | ||
| return types.Definition{ | ||
| DefinitionSpec: types.DefinitionSpec{ | ||
| ID: definitionID.ID(), | ||
| Family: "Keycloak", | ||
| DisplayName: "Keycloak", | ||
| Description: "Collect Keycloak realm users, groups, and memberships for identity posture and access governance.", | ||
| Category: "identity", | ||
| DocsURL: "https://docs.theopenlane.io/docs/platform/integrations/keycloak/overview", | ||
| Tags: []string{"directory"}, | ||
| Active: false, | ||
| Visible: false, | ||
|
manab-pr marked this conversation as resolved.
Outdated
|
||
| }, | ||
| UserInput: &types.UserInputRegistration{ | ||
| Schema: jsonx.SchemaFrom[UserInput](), | ||
| }, | ||
| CredentialRegistrations: []types.CredentialRegistration{ | ||
| { | ||
| Ref: keycloakCredential.ID(), | ||
| Name: "Keycloak Credential", | ||
| Description: "Client credentials used to access Keycloak realm data.", | ||
| Schema: keycloakCredentialSchema, | ||
| }, | ||
| }, | ||
| Connections: []types.ConnectionRegistration{ | ||
| { | ||
| CredentialRef: keycloakCredential.ID(), | ||
| Name: "Keycloak Client Credentials", | ||
| Description: "Configure Keycloak access using client credentials from your realm.", | ||
| CredentialRefs: []types.CredentialSlotID{keycloakCredential.ID()}, | ||
| ClientRefs: []types.ClientID{keycloakClient.ID()}, | ||
| ValidationOperation: healthCheckOperation.Name(), | ||
| Integration: integration.Registration(), | ||
| Disconnect: &types.DisconnectRegistration{ | ||
| CredentialRef: keycloakCredential.ID(), | ||
| Description: "Removes the stored client credentials from Openlane. If the client is no longer needed, disable or delete it in your Keycloak admin console under Clients.", | ||
| }, | ||
| }, | ||
| }, | ||
| Clients: []types.ClientRegistration{ | ||
| { | ||
| Ref: keycloakClient.ID(), | ||
| CredentialRefs: []types.CredentialSlotID{keycloakCredential.ID()}, | ||
| Description: "Keycloak API client", | ||
| Build: Client{}.Build, | ||
| }, | ||
| }, | ||
| Operations: []types.OperationRegistration{ | ||
| { | ||
| Name: healthCheckOperation.Name(), | ||
| Description: "Call Keycloak realm API to verify client credentials and realm connectivity", | ||
| Topic: definitionID.OperationTopic(healthCheckOperation.Name()), | ||
| ClientRef: keycloakClient.ID(), | ||
| Policy: types.ExecutionPolicy{Inline: true}, | ||
| ConfigSchema: healthCheckSchema, | ||
| Handle: HealthCheck{}.Handle(), | ||
| }, | ||
| { | ||
| Name: directorySyncOperation.Name(), | ||
|
manab-pr marked this conversation as resolved.
|
||
| Description: "Collect Keycloak realm users, groups, and memberships as directory accounts", | ||
| Topic: definitionID.OperationTopic(directorySyncOperation.Name()), | ||
| ClientRef: keycloakClient.ID(), | ||
| ConfigSchema: directorySyncSchema, | ||
| Policy: types.ExecutionPolicy{Reconcile: true}, | ||
| SkipDefaultLookback: true, | ||
| Ingest: []types.IngestContract{ | ||
| { | ||
| Schema: integrationgenerated.IntegrationMappingSchemaDirectoryAccount, | ||
| }, | ||
| { | ||
| Schema: integrationgenerated.IntegrationMappingSchemaDirectoryGroup, | ||
| }, | ||
| { | ||
| Schema: integrationgenerated.IntegrationMappingSchemaDirectoryMembership, | ||
| }, | ||
| }, | ||
| IngestHandle: DirectorySync{}.IngestHandle(), | ||
| }, | ||
| }, | ||
| Mappings: keycloakMappings(), | ||
| }, nil | ||
| }) | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| package keycloak | ||
|
|
||
| import ( | ||
| "context" | ||
| "time" | ||
|
|
||
| gocloak "github.com/Nerzal/gocloak/v13" | ||
|
|
||
| "github.com/theopenlane/core/internal/integrations/types" | ||
| ) | ||
|
|
||
| const ( | ||
| // keycloakRequestTimeout is the per-request timeout for Keycloak API calls | ||
| keycloakRequestTimeout = 30 * time.Second | ||
| // keycloakDefaultPageSize is the number of records requested per Keycloak API page | ||
| keycloakDefaultPageSize = 100 | ||
| ) | ||
|
|
||
| // Client builds Keycloak API clients for one installation | ||
| type Client struct{} | ||
|
|
||
| // Build constructs the Keycloak API client for one installation | ||
| func (Client) Build(_ context.Context, req types.ClientBuildRequest) (any, error) { | ||
| cred, err := resolveCredential(req.Credentials) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| if cred.BaseURL == "" { | ||
| return nil, ErrBaseURLMissing | ||
| } | ||
|
|
||
| if cred.Realm == "" { | ||
| return nil, ErrRealmMissing | ||
| } | ||
|
|
||
| if cred.ClientID == "" { | ||
| return nil, ErrClientIDMissing | ||
| } | ||
|
|
||
| if cred.ClientSecret == "" { | ||
| return nil, ErrClientSecretMissing | ||
| } | ||
|
|
||
| gc := gocloak.NewClient(cred.BaseURL) | ||
| gc.RestyClient().SetTimeout(keycloakRequestTimeout) | ||
|
|
||
| return gc, nil | ||
| } | ||
|
|
||
| // resolveCredential extracts the CredentialSchema from the provided credential bindings | ||
| func resolveCredential(bindings types.CredentialBindings) (CredentialSchema, error) { | ||
| cred, ok, err := keycloakCredential.Resolve(bindings) | ||
| if err != nil { | ||
| return CredentialSchema{}, ErrCredentialDecode | ||
| } | ||
|
|
||
| if !ok { | ||
| return CredentialSchema{}, ErrCredentialDecode | ||
| } | ||
|
|
||
| return cred, nil | ||
| } | ||
|
|
||
| // derefString safely dereferences a string pointer returning empty string if nil | ||
| func derefString(s *string) string { | ||
|
manab-pr marked this conversation as resolved.
Outdated
|
||
| if s == nil { | ||
| return "" | ||
| } | ||
|
|
||
| return *s | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| // Package keycloak provides an integration definition for Keycloak | ||
| // (https://www.keycloak.org), an open-source identity and access management | ||
| // solution designed for self-hosted and on-premise deployments. | ||
| // | ||
| // # Overview | ||
| // | ||
| // This integration connects a Keycloak instance to OpenLane using OAuth2 | ||
|
manab-pr marked this conversation as resolved.
Outdated
|
||
| // client credentials authentication, enabling directory sync of users, groups, | ||
| // and group memberships for identity posture and access governance workflows. | ||
| // | ||
| // # Authentication | ||
| // | ||
| // The integration authenticates using OAuth2 client credentials (client ID and | ||
| // client secret) scoped to a specific realm. An access token is acquired at | ||
| // connection time and automatically refreshed before expiry during long-running | ||
| // sync operations. | ||
| // | ||
| // # Directory Sync | ||
| // | ||
| // The directory sync operation pulls the following resources from the connected | ||
| // Keycloak realm and normalizes them into OpenLane's internal directory schemas: | ||
| // | ||
| // - DirectoryAccount: Keycloak users within the configured realm | ||
| // - DirectoryGroup: Keycloak groups with full representation | ||
| // - DirectoryMembership: Group membership relationships between accounts and groups | ||
| // | ||
| // Service accounts (identified by the presence of serviceAccountClientId) are | ||
| // mapped to the SERVICE account type. Group sync can be disabled via the | ||
| // DisableGroupSync option in UserInput. | ||
| // | ||
| // # Configuration | ||
| // | ||
| // The following options are available when connecting a Keycloak instance: | ||
| // | ||
| // - BaseURL: The base URL of the Keycloak instance (e.g. https://keycloak.mycompany.com) | ||
| // - Realm: The Keycloak realm to sync (e.g. master or your-organization-realm) | ||
| // - ClientID: The client ID with permissions to read realm users and groups | ||
| // - ClientSecret: The client secret for the configured client | ||
| // - DisableGroupSync: When true, only users are synced; groups and memberships are skipped | ||
| // - PrimaryDirectory: Marks this installation as the authoritative source for identity holder enrichment | ||
| // - FilterExpr: An optional CEL expression applied to records before ingestion | ||
| // | ||
| // # SDK | ||
| // | ||
| // This integration uses the gocloak library (github.com/Nerzal/gocloak/v13), | ||
| // a well-maintained Go client for the Keycloak Admin REST API. | ||
| package keycloak | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.