Skip to content
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
a6e8a40
feat : WIP authentik integration errors and types are added
manab-pr May 3, 2026
c28e43a
added authentik , fulfilling all the requirements
manab-pr May 7, 2026
35f75df
response struct changes and one api replaced as well
manab-pr May 8, 2026
02a4435
Merge branch 'main' into feature/authentik
manab-pr May 8, 2026
d917e74
using go-client instead of http-client and added more identity as well
manab-pr May 9, 2026
4c6412c
Merge branch 'feature/authentik' of https://github.com/manab-pr/core …
manab-pr May 9, 2026
7e0d3f2
Merge branch 'main' into feature/authentik
manab-pr May 9, 2026
75f6431
added uid instead of pk for directory ac and members
manab-pr May 9, 2026
3968a73
Merge branch 'feature/authentik' of https://github.com/manab-pr/core …
manab-pr May 9, 2026
a2bfc86
Both extractHost and extractScheme now use url.Parse and propagate e…
manab-pr May 9, 2026
832fe47
listDirectoryUsers now accept lastRunAt ,SkipDefaultLookback: true ad…
manab-pr May 9, 2026
a49aabc
Merge branch 'main' into feature/authentik
manab-pr May 9, 2026
7dc6b63
Merge branch 'main' into feature/authentik
manab-pr May 10, 2026
ec4db77
Merge branch 'main' into feature/authentik
manab-pr May 11, 2026
cd364b4
added user,group,members test json files as mappingtest suggests and …
manab-pr May 11, 2026
4838e29
Update internal/integrations/definitions/authentik/mappings.go
manab-pr May 11, 2026
19abcd8
Merge branch 'main' into feature/authentik
manab-pr May 11, 2026
2e0678a
fixed linting
manab-pr May 12, 2026
d7b1413
Merge branch 'feature/authentik' of https://github.com/manab-pr/core …
manab-pr May 12, 2026
e9ed521
Merge branch 'main' into feature/authentik
manab-pr May 12, 2026
3ed015c
fixed mapping , moved compose file to docker directory and added task…
manab-pr May 15, 2026
c2759fd
the IntegrationMappingDirectoryAccountAccountType is written in one line
manab-pr May 15, 2026
c56f7d1
Merge branch 'main' into feature/authentik
manab-pr May 15, 2026
0b56e51
Update internal/integrations/definitions/authentik/operation_director…
manab-pr May 15, 2026
d4589a1
Update internal/integrations/definitions/authentik/operation_director…
manab-pr May 15, 2026
3766015
Apply suggestions from code review
manab-pr May 15, 2026
0b883e9
Update internal/integrations/definitions/authentik/types.go
manab-pr May 15, 2026
e647435
using defualt postgre container now , user need not to provide eenvs …
manab-pr May 16, 2026
3d67413
Merge branch 'main' into feature/authentik
manab-pr May 16, 2026
a407c38
lint issue fixed
manab-pr May 16, 2026
20035a8
Merge branch 'feature/authentik' of https://github.com/manab-pr/core …
manab-pr May 16, 2026
50a7f1a
test bug removed
manab-pr May 16, 2026
c2b3acb
test failing fix
manab-pr May 16, 2026
3056032
Merge branch 'main' into feature/authentik
manab-pr May 16, 2026
bc5f719
yml modification and mapping modification as well
manab-pr May 16, 2026
461428c
Merge branch 'feature/authentik' of https://github.com/manab-pr/core …
manab-pr May 16, 2026
292e98f
added missing env
manab-pr May 17, 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 @@ -249,3 +249,29 @@ tasks:
cmds:
- task: mailhog
- task: mailhog:open

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

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

authentik:open:
dir: ..
desc: opens the authentik web interface in a browser
cmds:
- 'open "http://localhost:9000"'

authentik:up:
dir: ..
desc: brings the authentik compose environment up and opens the web interface
aliases: [authentikup]
cmds:
- task: authentik
- task: authentik:open
71 changes: 71 additions & 0 deletions docker/docker-compose-authentik.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
services:
Comment thread
manab-pr marked this conversation as resolved.
server:
command: server
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
environment:
AUTHENTIK_POSTGRESQL__HOST: postgres
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_POSTGRESQL__PASSWORD: password
AUTHENTIK_POSTGRESQL__USER: postgres
AUTHENTIK_SECRET_KEY: authentik-dev-secret-key-do-not-use-in-production
AUTHENTIK_REDIS__HOST: redis
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2026.2.2}
ports:
- ${COMPOSE_PORT_HTTP:-9000}:9000
- ${COMPOSE_PORT_HTTPS:-9443}:9443
restart: unless-stopped
shm_size: 512mb
volumes:
- authentik-data:/data
- authentik-custom-templates:/templates
networks:
- default

worker:
command: worker
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
environment:
AUTHENTIK_POSTGRESQL__HOST: postgres
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_POSTGRESQL__PASSWORD: password
AUTHENTIK_POSTGRESQL__USER: postgres
AUTHENTIK_SECRET_KEY: authentik-dev-secret-key-do-not-use-in-production
AUTHENTIK_REDIS__HOST: redis
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2026.2.2}
restart: unless-stopped
shm_size: 512mb
user: root
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- authentik-data:/data
- authentik-certs:/certs
- authentik-custom-templates:/templates
networks:
- default

redis:
image: redis:7.4.9-alpine
restart: unless-stopped
networks:
- default

volumes:
authentik-data:
driver: local
authentik-certs:
driver: local
authentik-custom-templates:
driver: local

networks:
default:
external: true
name: docker_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 @@ -13,3 +13,6 @@ GRANT ALL PRIVILEGES ON DATABASE jobs TO postgres;
CREATE DATABASE windmill;

GRANT ALL PRIVILEGES ON DATABASE windmill TO postgres;

CREATE DATABASE authentik;
GRANT ALL PRIVILEGES ON DATABASE authentik TO postgres;
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ require (
github.com/xeipuuv/gojsonschema v1.2.0
github.com/yuin/goldmark v1.8.2
github.com/zitadel/oidc/v3 v3.47.5
goauthentik.io/api/v3 v3.2026020.16
gocloud.dev v0.45.0
golang.org/x/crypto v0.51.0
golang.org/x/mod v0.36.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -857,6 +857,8 @@ go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
go.yaml.in/yaml/v4 v4.0.0-rc.2 h1:/FrI8D64VSr4HtGIlUtlFMGsm7H7pWTbj6vOLVZcA6s=
go.yaml.in/yaml/v4 v4.0.0-rc.2/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
goauthentik.io/api/v3 v3.2026020.16 h1:sEqcVRXYSJTYaSdU5PzSEdFUWDqCONm5BeL62F5k+58=
goauthentik.io/api/v3 v3.2026020.16/go.mod h1:82lqAz4jxzl6Cg0YDbhNtvvTG2rm6605ZhdJFnbbsl8=
gocloud.dev v0.45.0 h1:WknIK8IbRdmynDvara3Q7G6wQhmEiOGwpgJufbM39sY=
gocloud.dev v0.45.0/go.mod h1:0kXKmkCLG6d31N7NyLZWzt7jDSQura9zD/mWgiB6THI=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
Expand Down
94 changes: 94 additions & 0 deletions internal/integrations/definitions/authentik/builder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package authentik

import (
"github.com/theopenlane/core/internal/ent/integrationgenerated"
"github.com/theopenlane/core/internal/integrations/providerkit"
"github.com/theopenlane/core/internal/integrations/registry"
"github.com/theopenlane/core/internal/integrations/types"
)

// Builder returns the Authentik definition builder
func Builder() registry.Builder {
return registry.Builder(func() (types.Definition, error) {
return types.Definition{
DefinitionSpec: types.DefinitionSpec{
ID: definitionID.ID(),
Family: "Authentik",
DisplayName: "Authentik",
Description: "Collect Authentik directory users, groups, and memberships for identity posture and access governance.",
Category: "identity",
DocsURL: "https://docs.theopenlane.io/docs/platform/integrations/authentik/overview",
Tags: []string{"directory"},
Active: false,
Visible: false,
},
UserInput: &types.UserInputRegistration{
Schema: providerkit.SchemaFrom[UserInput](),
},
CredentialRegistrations: []types.CredentialRegistration{
{
Ref: authentikCredential.ID(),
Name: "Authentik Credential",
Description: "API token used to access Authentik instance data.",
Schema: authentikCredentialSchema,
},
},
Connections: []types.ConnectionRegistration{
{
CredentialRef: authentikCredential.ID(),
Name: "Authentik API Token",
Description: "Configure Authentik access using an API token from your instance.",
CredentialRefs: []types.CredentialSlotID{authentikCredential.ID()},
ClientRefs: []types.ClientID{authentikClient.ID()},
ValidationOperation: healthCheckOperation.Name(),
Integration: integration.Registration(),
Disconnect: &types.DisconnectRegistration{
CredentialRef: authentikCredential.ID(),
Description: "Removes the stored API token from Openlane. If the token is no longer needed, revoke it in your Authentik admin panel under Directory > Tokens.",
},
},
},
Clients: []types.ClientRegistration{
{
Ref: authentikClient.ID(),
CredentialRefs: []types.CredentialSlotID{authentikCredential.ID()},
Description: "Authentik API client",
Build: Client{}.Build,
},
},
Operations: []types.OperationRegistration{
{
Name: healthCheckOperation.Name(),
Description: "Call Authentik API to verify token and instance connectivity",
Topic: definitionID.OperationTopic(healthCheckOperation.Name()),
ClientRef: authentikClient.ID(),
Policy: types.ExecutionPolicy{Inline: true},
ConfigSchema: healthCheckSchema,
Handle: HealthCheck{}.Handle(),
},
{
Name: directorySyncOperation.Name(),
Description: "Collect Authentik directory users, groups, and memberships as directory accounts",
Topic: definitionID.OperationTopic(directorySyncOperation.Name()),
ClientRef: authentikClient.ID(),
ConfigSchema: directorySyncSchema,
Policy: types.ExecutionPolicy{Reconcile: true},
SkipDefaultLookback: true,
Ingest: []types.IngestContract{
Comment thread
manab-pr marked this conversation as resolved.
{
Schema: integrationgenerated.IntegrationMappingSchemaDirectoryAccount,
},
{
Schema: integrationgenerated.IntegrationMappingSchemaDirectoryGroup,
},
{
Schema: integrationgenerated.IntegrationMappingSchemaDirectoryMembership,
},
},
IngestHandle: DirectorySync{}.IngestHandle(),
},
},
Mappings: authentikMappings(),
}, nil
})
}
88 changes: 88 additions & 0 deletions internal/integrations/definitions/authentik/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package authentik

import (
"context"
"net/http"
"net/url"
"time"

authentikSDK "goauthentik.io/api/v3"

"github.com/theopenlane/core/internal/integrations/types"
)

const (
// authentikRequestTimeout is the per-request timeout for Authentik API calls
authentikRequestTimeout = 30 * time.Second
)

// Client builds Authentik API clients for one installation
type Client struct{}

// Build constructs the Authentik 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.Token == "" {
return nil, ErrAPITokenMissing
}

if cred.BaseURL == "" {
return nil, ErrBaseURLMissing
}

host, err := extractHost(cred.BaseURL)
if err != nil {
return nil, err
}

scheme, err := extractScheme(cred.BaseURL)
if err != nil {
return nil, err
}

cfg := authentikSDK.NewConfiguration()
cfg.Host = host
cfg.Scheme = scheme
cfg.HTTPClient = &http.Client{Timeout: authentikRequestTimeout}
cfg.AddDefaultHeader("Authorization", "Bearer "+cred.Token)

return authentikSDK.NewAPIClient(cfg), nil
}

// resolveCredential extracts the CredentialSchema from the provided credential bindings
func resolveCredential(bindings types.CredentialBindings) (CredentialSchema, error) {
cred, ok, err := authentikCredential.Resolve(bindings)
if err != nil {
return CredentialSchema{}, ErrCredentialDecode
}

if !ok {
return CredentialSchema{}, ErrCredentialDecode
}

return cred, nil
}

// extractHost extracts the host from a base URL
func extractHost(baseURL string) (string, error) {
u, err := url.Parse(baseURL)
if err != nil {
return "", err
}

return u.Host, nil
}

// extractScheme extracts the scheme from a base URL
func extractScheme(baseURL string) (string, error) {
u, err := url.Parse(baseURL)
if err != nil {
return "", err
}

return u.Scheme, nil
}
48 changes: 48 additions & 0 deletions internal/integrations/definitions/authentik/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Package authentik provides an integration definition for Authentik
// (https://goauthentik.io), an open-source identity provider designed for
// self-hosted and on-premise deployments.
//
// # Overview
//
// This integration connects an Authentik instance to OpenLane using API token
// authentication, enabling directory sync of users, groups, and group
// memberships for identity posture and access governance workflows.
//
// # Authentication
//
// The integration authenticates using a static API token generated from the
// Authentik admin panel. The token is scoped to a service account and passed
// as a Bearer token on every API request. No OAuth2 flow or token refresh is
// required.
//
// # Directory Sync
//
// The directory sync operation pulls the following resources from the connected
// Authentik instance and normalizes them into OpenLane's internal directory
// schemas:
//
// - DirectoryAccount: Authentik users (internal and external types)
// - DirectoryGroup: Authentik groups with classification derived from is_superuser
// - DirectoryMembership: Group membership relationships between accounts and groups
//
// Users of type service_account and internal_service_account are included in
// the sync but 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 an Authentik instance:
//
// - BaseURL: The base URL of the Authentik instance (e.g. https://authentik.mycompany.com)
// - Token: A static API token generated from the Authentik admin panel
// - 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 official Authentik Go SDK (goauthentik.io/api/v3),
// which is generated from Authentik's OpenAPI schema and versioned per Authentik
// release. This ensures breaking API changes are caught at compile time when the
// SDK version is updated.
package authentik
30 changes: 30 additions & 0 deletions internal/integrations/definitions/authentik/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package authentik

import "errors"

var (
// ErrAPITokenMissing indicates the API token is missing from the credential
ErrAPITokenMissing = errors.New("authentik: api token missing")
// ErrBaseURLMissing indicates the Authentik base URL is missing from the credential
ErrBaseURLMissing = errors.New("authentik: base url missing")
// ErrClientType indicates the provided client is not the expected authentik client type
ErrClientType = errors.New("authentik: unexpected client type")
// ErrClientConfigInvalid indicates the Authentik client configuration is invalid
ErrClientConfigInvalid = errors.New("authentik: client config invalid")
// ErrCredentialEncode indicates the credential could not be serialized
ErrCredentialEncode = errors.New("authentik: credential encode failed")
// ErrCredentialDecode indicates the credential could not be deserialized
ErrCredentialDecode = errors.New("authentik: credential decode failed")
// ErrHealthCheckFailed indicates the health check request failed
ErrHealthCheckFailed = errors.New("authentik: health check failed")
// ErrDirectoryUsersFetchFailed indicates the users listing failed
ErrDirectoryUsersFetchFailed = errors.New("authentik: directory users fetch failed")
// ErrDirectoryGroupsFetchFailed indicates the groups listing failed
ErrDirectoryGroupsFetchFailed = errors.New("authentik: directory groups fetch failed")
// ErrDirectoryGroupMembersFetchFailed indicates the group members listing failed
ErrDirectoryGroupMembersFetchFailed = errors.New("authentik: directory group members fetch failed")
// ErrPayloadEncode indicates a provider payload could not be serialized
ErrPayloadEncode = errors.New("authentik: payload encode failed")
// ErrResultEncode indicates an operation result could not be serialized
ErrResultEncode = errors.New("authentik: result encode failed")
)
Loading