Skip to content
Merged
Show file tree
Hide file tree
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 May 15, 2026
f137325
Merge branch 'main' of https://github.com/manab-pr/core into feature/…
manab-pr May 19, 2026
1b18454
Merge branch 'main' of https://github.com/manab-pr/core into feature/…
manab-pr May 23, 2026
2a5ca09
feat : added keycloak integration files
manab-pr May 27, 2026
1768d78
Merge branch 'main' of https://github.com/manab-pr/core into feature/…
manab-pr May 27, 2026
87ab01d
post merge changes
manab-pr May 27, 2026
199835d
feat : containerized
manab-pr May 28, 2026
11f1c6c
Merge branch 'main' into feature/keycloak
manab-pr May 28, 2026
ac35234
Merge branch 'main' into feature/keycloak
manab-pr May 28, 2026
86d8e31
updated
manab-pr Jun 4, 2026
694d4df
Merge branch 'main' of https://github.com/manab-pr/core into feature/…
manab-pr Jun 4, 2026
9a61690
Merge branch 'feature/keycloak' of https://github.com/manab-pr/core i…
manab-pr Jun 4, 2026
1e96a13
fix : requested changes
manab-pr Jun 4, 2026
37ed112
Merge branch 'main' of https://github.com/manab-pr/core into feature/…
manab-pr Jun 13, 2026
0d448ae
fix : test file
manab-pr Jun 14, 2026
24d789a
Merge branch 'main' of https://github.com/manab-pr/core into feature/…
manab-pr Jun 17, 2026
5d6e878
Update internal/integrations/definitions/keycloak/builder.go
manab-pr Jun 17, 2026
8ed4736
Update internal/integrations/definitions/keycloak/doc.go
manab-pr Jun 17, 2026
0ec1e1e
Apply suggestion from @golanglemonade
manab-pr Jun 17, 2026
1964b30
Merge branch 'feature/keycloak' of https://github.com/manab-pr/core i…
manab-pr Jun 17, 2026
815d7da
added permissions, last login time
manab-pr Jun 17, 2026
eb2b76a
Merge branch 'main' of https://github.com/manab-pr/core into feature/…
manab-pr Jun 17, 2026
f909b69
fix : conflicts
manab-pr Jun 20, 2026
11bc578
Update docker/docker-compose-keycloak.yml
manab-pr Jun 23, 2026
62e206d
Merge branch 'main' of https://github.com/manab-pr/core into feature/…
manab-pr Jun 23, 2026
dc9746d
Merge branch 'main' into feature/keycloak
manab-pr Jun 24, 2026
4bba065
fix : ssl required NONE
manab-pr Jul 2, 2026
2910547
Merge branch 'main' of https://github.com/manab-pr/core into feature/…
manab-pr Jul 2, 2026
46539ef
Merge branch 'feature/keycloak' of https://github.com/manab-pr/core i…
manab-pr Jul 2, 2026
1edda28
fix : keycloak compose file
manab-pr Jul 2, 2026
b4fde76
Merge branch 'theopenlane:main' into feature/keycloak
manab-pr Jul 7, 2026
146667b
Merge branch 'main' of https://github.com/manab-pr/core into feature/…
manab-pr Jul 7, 2026
2adf037
Merge branch 'feature/keycloak' of https://github.com/manab-pr/core i…
manab-pr Jul 7, 2026
c9d7239
Update internal/integrations/definitions/keycloak/builder.go
manab-pr Jul 7, 2026
1cb3224
Update internal/integrations/definitions/keycloak/types.go
manab-pr Jul 7, 2026
95e7756
fix : added missing logs, and fixed the type
manab-pr Jul 7, 2026
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
26 changes: 26 additions & 0 deletions docker/Taskfile.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -275,3 +275,29 @@ tasks:
cmds:
- task: authentik
- task: authentik:open

keycloak:
dir: ..
desc: brings up the compose environment for keycloak
cmds:
- "docker compose -f ./docker/docker-compose-pg.yml -f ./docker/docker-compose-keycloak.yml up -d"

keycloak:down:
dir: ..
desc: brings the keycloak compose environment down
cmds:
- "docker compose -f ./docker/docker-compose-pg.yml -f ./docker/docker-compose-keycloak.yml down"

keycloak:open:
dir: ..
desc: opens the keycloak admin console in a browser
cmds:
- 'open "http://localhost:8180"'

keycloak:up:
dir: ..
desc: brings the keycloak compose environment up and opens the admin console
aliases: [keycloakup]
cmds:
- task: keycloak
- task: keycloak:open
3 changes: 3 additions & 0 deletions docker/configs/keycloak/.env-example
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
19 changes: 19 additions & 0 deletions docker/docker-compose-keycloak.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
services:
keycloak:
image: quay.io/keycloak/keycloak:26.2.4
Comment thread
manab-pr marked this conversation as resolved.
Outdated
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
3 changes: 3 additions & 0 deletions docker/pg-init-scripts/init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@ GRANT ALL PRIVILEGES ON DATABASE windmill TO postgres;

CREATE DATABASE authentik;
GRANT ALL PRIVILEGES ON DATABASE authentik TO postgres;

Comment thread
golanglemonade marked this conversation as resolved.
Outdated
CREATE DATABASE keycloak;
GRANT ALL PRIVILEGES ON DATABASE keycloak TO postgres;
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.1
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/security/armsecurity v0.14.0
github.com/Nerzal/gocloak/v13 v13.9.0
github.com/alicebob/miniredis/v2 v2.38.0
github.com/alitto/pond/v2 v2.7.1
github.com/aws/aws-sdk-go-v2 v1.41.7
Expand Down Expand Up @@ -135,6 +136,7 @@ require (
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
github.com/PuerkitoBio/goquery v1.12.0 // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/go-resty/resty/v2 v2.7.0 // indirect
github.com/google/go-github/v87 v87.0.0 // indirect
github.com/hhrutter/lzw v1.0.0 // indirect
github.com/hhrutter/pkcs7 v0.2.2 // indirect
Expand All @@ -150,11 +152,13 @@ require (
github.com/microsoftgraph/msgraph-sdk-go-core v1.4.1 // indirect
github.com/moby/moby/api v1.54.2 // indirect
github.com/moby/moby/client v0.4.1 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/patrickmn/go-cache v0.0.0-20180815053127-5633e0862627 // indirect
github.com/pb33f/ordered-map/v2 v2.3.1 // indirect
github.com/philhofer/fwd v1.2.0 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
github.com/segmentio/ksuid v1.0.4 // indirect
github.com/sendgrid/rest v2.6.9+incompatible // indirect
github.com/sendgrid/sendgrid-go v3.16.1+incompatible // indirect
github.com/shopspring/decimal v1.4.0 // indirect
Expand Down
11 changes: 11 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/Nerzal/gocloak/v13 v13.9.0 h1:YWsJsdM5b0yhM2Ba3MLydiOlujkBry4TtdzfIzSVZhw=
github.com/Nerzal/gocloak/v13 v13.9.0/go.mod h1:YYuDcXZ7K2zKECyVP7pPqjKxx2AzYSpKDj8d6GuyM10=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
github.com/PuerkitoBio/goquery v1.12.0 h1:pAcL4g3WRXekcB9AU/y1mbKez2dbY2AajVhtkO8RIBo=
Expand Down Expand Up @@ -292,6 +294,8 @@ github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-redis/redismock/v8 v8.0.6 h1:rtuijPgGynsRB2Y7KDACm09WvjHWS4RaG44Nm7rcj4Y=
github.com/go-redis/redismock/v8 v8.0.6/go.mod h1:sDIF73OVsmaKzYe/1FJXGiCQ4+oHYbzjpaL9Vor0sS4=
github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
Expand Down Expand Up @@ -582,6 +586,8 @@ github.com/openfga/language/pkg/go v0.2.1 h1:nmVJTPfjvaJC2EWGcy8HrUyL15KkIfjjnmB
github.com/openfga/language/pkg/go v0.2.1/go.mod h1:wg+EuPmYIaM855F2uPygT1hJoWcoUxAoecgYC5akXsw=
github.com/openfga/openfga v1.16.0 h1:pKimibHNP0eWybRt676icCACmC77XiDcFhDZ6n99mVQ=
github.com/openfga/openfga v1.16.0/go.mod h1:qLSJJo2RtXkIGaMuu2LnUzv/eoypUlmeSHWrtaRLolo=
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA=
github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs=
github.com/patrickmn/go-cache v0.0.0-20180815053127-5633e0862627 h1:pSCLCl6joCFRnjpeojzOpEYs4q7Vditq8fySFG5ap3Y=
Expand Down Expand Up @@ -665,6 +671,8 @@ github.com/scim2/filter-parser/v2 v2.2.0 h1:QGadEcsmypxg8gYChRSM2j1edLyE/2j72j+h
github.com/scim2/filter-parser/v2 v2.2.0/go.mod h1:jWnkDToqX/Y0ugz0P5VvpVEUKcWcyHHj+X+je9ce5JA=
github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0=
github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c=
github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE=
github.com/sendgrid/rest v2.6.9+incompatible h1:1EyIcsNdn9KIisLW50MKwmSRSK+ekueiEMJ7NEoxJo0=
github.com/sendgrid/rest v2.6.9+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE=
github.com/sendgrid/sendgrid-go v3.16.1+incompatible h1:zWhTmB0Y8XCDzeWIm2/BIt1GjJohAA0p6hVEaDtHWWs=
Expand Down Expand Up @@ -889,6 +897,7 @@ golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
Expand Down Expand Up @@ -916,6 +925,7 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand Down Expand Up @@ -944,6 +954,7 @@ golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
Expand Down
2 changes: 2 additions & 0 deletions internal/integrations/definitions/catalog/catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/theopenlane/core/internal/integrations/definitions/gcpscc"
"github.com/theopenlane/core/internal/integrations/definitions/githubapp"
"github.com/theopenlane/core/internal/integrations/definitions/googleworkspace"
"github.com/theopenlane/core/internal/integrations/definitions/keycloak"
"github.com/theopenlane/core/internal/integrations/definitions/microsoftteams"
"github.com/theopenlane/core/internal/integrations/definitions/oidclocal"
"github.com/theopenlane/core/internal/integrations/definitions/okta"
Expand All @@ -32,6 +33,7 @@ func Builders(cfg Config, devMode bool) []registry.Builder {
gcpscc.Builder(),
githubapp.Builder(cfg.GitHubApp),
googleworkspace.Builder(cfg.GoogleWorkspace),
keycloak.Builder(),
microsoftteams.Builder(cfg.MicrosoftTeams),
oidclocal.Builder(cfg.OIDCLocal),
okta.Builder(),
Expand Down
94 changes: 94 additions & 0 deletions internal/integrations/definitions/keycloak/builder.go
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,
Comment thread
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(),
Comment thread
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
})
}
72 changes: 72 additions & 0 deletions internal/integrations/definitions/keycloak/client.go
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 {
Comment thread
manab-pr marked this conversation as resolved.
Outdated
if s == nil {
return ""
}

return *s
}
47 changes: 47 additions & 0 deletions internal/integrations/definitions/keycloak/doc.go
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
Comment thread
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
Loading