-
Notifications
You must be signed in to change notification settings - Fork 49
Directory Sync : authentik integration #2347
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 7 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 c28e43a
added authentik , fulfilling all the requirements
manab-pr 35f75df
response struct changes and one api replaced as well
manab-pr 02a4435
Merge branch 'main' into feature/authentik
manab-pr d917e74
using go-client instead of http-client and added more identity as well
manab-pr 4c6412c
Merge branch 'feature/authentik' of https://github.com/manab-pr/core …
manab-pr 7e0d3f2
Merge branch 'main' into feature/authentik
manab-pr 75f6431
added uid instead of pk for directory ac and members
manab-pr 3968a73
Merge branch 'feature/authentik' of https://github.com/manab-pr/core …
manab-pr a2bfc86
Both extractHost and extractScheme now use url.Parse and propagate e…
manab-pr 832fe47
listDirectoryUsers now accept lastRunAt ,SkipDefaultLookback: true ad…
manab-pr a49aabc
Merge branch 'main' into feature/authentik
manab-pr 7dc6b63
Merge branch 'main' into feature/authentik
manab-pr ec4db77
Merge branch 'main' into feature/authentik
manab-pr cd364b4
added user,group,members test json files as mappingtest suggests and …
manab-pr 4838e29
Update internal/integrations/definitions/authentik/mappings.go
manab-pr 19abcd8
Merge branch 'main' into feature/authentik
manab-pr 2e0678a
fixed linting
manab-pr d7b1413
Merge branch 'feature/authentik' of https://github.com/manab-pr/core …
manab-pr e9ed521
Merge branch 'main' into feature/authentik
manab-pr 3ed015c
fixed mapping , moved compose file to docker directory and added task…
manab-pr c2759fd
the IntegrationMappingDirectoryAccountAccountType is written in one line
manab-pr c56f7d1
Merge branch 'main' into feature/authentik
manab-pr 0b56e51
Update internal/integrations/definitions/authentik/operation_director…
manab-pr d4589a1
Update internal/integrations/definitions/authentik/operation_director…
manab-pr 3766015
Apply suggestions from code review
manab-pr 0b883e9
Update internal/integrations/definitions/authentik/types.go
manab-pr e647435
using defualt postgre container now , user need not to provide eenvs …
manab-pr 3d67413
Merge branch 'main' into feature/authentik
manab-pr a407c38
lint issue fixed
manab-pr 20035a8
Merge branch 'feature/authentik' of https://github.com/manab-pr/core …
manab-pr 50a7f1a
test bug removed
manab-pr c2b3acb
test failing fix
manab-pr 3056032
Merge branch 'main' into feature/authentik
manab-pr bc5f719
yml modification and mapping modification as well
manab-pr 461428c
Merge branch 'feature/authentik' of https://github.com/manab-pr/core …
manab-pr 292e98f
added missing env
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
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,93 @@ | ||
| 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}, | ||
| Ingest: []types.IngestContract{ | ||
| { | ||
| Schema: integrationgenerated.IntegrationMappingSchemaDirectoryAccount, | ||
| }, | ||
| { | ||
| Schema: integrationgenerated.IntegrationMappingSchemaDirectoryGroup, | ||
| }, | ||
| { | ||
| Schema: integrationgenerated.IntegrationMappingSchemaDirectoryMembership, | ||
| }, | ||
| }, | ||
| IngestHandle: DirectorySync{}.IngestHandle(), | ||
| }, | ||
| }, | ||
| Mappings: authentikMappings(), | ||
| }, 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,75 @@ | ||
| package authentik | ||
|
|
||
| import ( | ||
| "context" | ||
| "net/http" | ||
| "strings" | ||
| "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 | ||
| } | ||
|
|
||
| cfg := authentikSDK.NewConfiguration() | ||
| cfg.Host = extractHost(cred.BaseURL) | ||
| cfg.Scheme = extractScheme(cred.BaseURL) | ||
| 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 { | ||
| host := strings.TrimPrefix(baseURL, "https://") | ||
| host = strings.TrimPrefix(host, "http://") | ||
|
|
||
| return strings.TrimRight(host, "/") | ||
| } | ||
|
|
||
| // extractScheme extracts the scheme from a base URL | ||
| func extractScheme(baseURL string) string { | ||
| if strings.HasPrefix(baseURL, "https://") { | ||
| return "https" | ||
| } | ||
|
|
||
| return "http" | ||
| } | ||
|
manab-pr marked this conversation as resolved.
Outdated
|
||
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,4 @@ | ||
| // Package authentik provides an integration definition for Authentik, | ||
| // an open-source identity provider. It supports directory sync of users, | ||
| // groups, and memberships via the Authentik REST API using API token authentication. | ||
| package authentik |
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,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") | ||
| ) |
38 changes: 38 additions & 0 deletions
38
internal/integrations/definitions/authentik/installation.go
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,38 @@ | ||
| package authentik | ||
|
|
||
| import ( | ||
| "context" | ||
|
|
||
| "github.com/theopenlane/core/internal/integrations/types" | ||
| authentikSDK "goauthentik.io/api/v3" | ||
| ) | ||
|
|
||
| // resolveInstallationMetadata derives Authentik instance metadata from the persisted credential | ||
| func resolveInstallationMetadata(ctx context.Context, req types.InstallationRequest) (InstallationMetadata, bool, error) { | ||
| cred, err := resolveCredential(req.Credentials) | ||
| if err != nil { | ||
| return InstallationMetadata{}, false, ErrCredentialDecode | ||
| } | ||
|
|
||
| if cred.Token == "" || cred.BaseURL == "" { | ||
| return InstallationMetadata{}, false, nil | ||
| } | ||
|
|
||
| client, err := Client{}.Build(ctx, types.ClientBuildRequest{Credentials: req.Credentials}) | ||
| if err != nil { | ||
| return InstallationMetadata{}, false, err | ||
| } | ||
|
manab-pr marked this conversation as resolved.
|
||
|
|
||
| apiClient := client.(*authentikSDK.APIClient) | ||
|
|
||
| info, _, err := apiClient.AdminApi.AdminSystemRetrieve(ctx).Execute() | ||
| if err != nil { | ||
| return InstallationMetadata{}, false, ErrHealthCheckFailed | ||
| } | ||
|
manab-pr marked this conversation as resolved.
|
||
|
|
||
| return InstallationMetadata{ | ||
| Brand: info.GetBrand(), | ||
| Host: info.GetHttpHost(), | ||
| BaseURL: cred.BaseURL, | ||
| }, true, 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,66 @@ | ||
| package authentik | ||
|
|
||
| import ( | ||
| "github.com/theopenlane/core/internal/ent/integrationgenerated" | ||
| "github.com/theopenlane/core/internal/integrations/providerkit" | ||
| "github.com/theopenlane/core/internal/integrations/types" | ||
| ) | ||
|
|
||
| // mapExprDirectoryAccount is the CEL mapping expression for Authentik user payloads mapped to DirectoryAccount | ||
| var mapExprDirectoryAccount = providerkit.CelMapExpr([]providerkit.CelMapEntry{ | ||
|
manab-pr marked this conversation as resolved.
|
||
| {Key: integrationgenerated.IntegrationMappingDirectoryAccountExternalID, Expr: `'pk' in payload ? string(payload.pk) : ""`}, | ||
| {Key: integrationgenerated.IntegrationMappingDirectoryAccountCanonicalEmail, Expr: `'email' in payload && payload.email != null && payload.email != "" ? payload.email : ""`}, | ||
|
manab-pr marked this conversation as resolved.
Outdated
|
||
| {Key: integrationgenerated.IntegrationMappingDirectoryAccountDisplayName, Expr: `'name' in payload && payload.name != null && payload.name != "" ? payload.name : ('username' in payload ? payload.username : "")`}, | ||
| {Key: integrationgenerated.IntegrationMappingDirectoryAccountStatus, Expr: `dyn('is_active' in payload ? (payload.is_active ? "ACTIVE" : "INACTIVE") : "INACTIVE")`}, | ||
| {Key: integrationgenerated.IntegrationMappingDirectoryAccountAccountType, Expr: `'type' in payload && payload.type != null ? payload.type : ""`}, | ||
| {Key: integrationgenerated.IntegrationMappingDirectoryAccountAddedAt, Expr: `'date_joined' in payload && payload.date_joined != null ? payload.date_joined : null`}, | ||
| {Key: integrationgenerated.IntegrationMappingDirectoryAccountLastSeenAt, Expr: `'last_login' in payload && payload.last_login != null ? payload.last_login : null`}, | ||
| {Key: integrationgenerated.IntegrationMappingDirectoryAccountObservedAt, Expr: `'last_updated' in payload && payload.last_updated != null ? payload.last_updated : null`}, | ||
|
manab-pr marked this conversation as resolved.
Outdated
|
||
| {Key: integrationgenerated.IntegrationMappingDirectoryAccountMetadata, Expr: `'attributes' in payload ? payload.attributes : {}`}, | ||
| {Key: integrationgenerated.IntegrationMappingDirectoryAccountProfile, Expr: "payload"}, | ||
| }) | ||
|
|
||
| // mapExprDirectoryGroup is the CEL mapping expression for Authentik group payloads mapped to DirectoryGroup | ||
| var mapExprDirectoryGroup = providerkit.CelMapExpr([]providerkit.CelMapEntry{ | ||
|
golanglemonade marked this conversation as resolved.
|
||
| {Key: integrationgenerated.IntegrationMappingDirectoryGroupExternalID, Expr: `'pk' in payload ? payload.pk : ""`}, | ||
| {Key: integrationgenerated.IntegrationMappingDirectoryGroupDisplayName, Expr: `'name' in payload && payload.name != null ? payload.name : ""`}, | ||
| {Key: integrationgenerated.IntegrationMappingDirectoryGroupClassification, Expr: `dyn('is_superuser' in payload && payload.is_superuser ? "ADMIN" : "TEAM")`}, | ||
| {Key: integrationgenerated.IntegrationMappingDirectoryGroupStatus, Expr: `dyn("ACTIVE")`}, | ||
| {Key: integrationgenerated.IntegrationMappingDirectoryGroupMetadata, Expr: `'attributes' in payload ? payload.attributes : {}`}, | ||
| {Key: integrationgenerated.IntegrationMappingDirectoryGroupProfile, Expr: "payload"}, | ||
| }) | ||
|
|
||
| // mapExprDirectoryMembership is the CEL mapping expression for Authentik membership payloads mapped to DirectoryMembership | ||
| var mapExprDirectoryMembership = providerkit.CelMapExpr([]providerkit.CelMapEntry{ | ||
| {Key: integrationgenerated.IntegrationMappingDirectoryMembershipDirectoryAccountID, Expr: `'pk' in payload ? string(payload.pk) : ""`}, | ||
| {Key: integrationgenerated.IntegrationMappingDirectoryMembershipDirectoryGroupID, Expr: `resource != "" ? resource : ""`}, | ||
| {Key: integrationgenerated.IntegrationMappingDirectoryMembershipRole, Expr: `dyn("MEMBER")`}, | ||
| {Key: integrationgenerated.IntegrationMappingDirectoryMembershipMetadata, Expr: "payload"}, | ||
| }) | ||
|
|
||
| // authentikMappings returns the built-in Authentik ingest mappings | ||
| func authentikMappings() []types.MappingRegistration { | ||
| return []types.MappingRegistration{ | ||
| { | ||
| Schema: integrationgenerated.IntegrationMappingSchemaDirectoryAccount, | ||
| Spec: types.MappingOverride{ | ||
| FilterExpr: "true", | ||
| MapExpr: mapExprDirectoryAccount, | ||
| }, | ||
| }, | ||
| { | ||
| Schema: integrationgenerated.IntegrationMappingSchemaDirectoryGroup, | ||
| Spec: types.MappingOverride{ | ||
| FilterExpr: "true", | ||
| MapExpr: mapExprDirectoryGroup, | ||
| }, | ||
| }, | ||
| { | ||
| Schema: integrationgenerated.IntegrationMappingSchemaDirectoryMembership, | ||
| Spec: types.MappingOverride{ | ||
| FilterExpr: "true", | ||
| MapExpr: mapExprDirectoryMembership, | ||
| }, | ||
| }, | ||
| } | ||
| } | ||
26 changes: 26 additions & 0 deletions
26
internal/integrations/definitions/authentik/mappings_test.go
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,26 @@ | ||
| package authentik | ||
|
|
||
| import ( | ||
|
manab-pr marked this conversation as resolved.
|
||
| "testing" | ||
|
|
||
| "gotest.tools/v3/assert" | ||
|
|
||
| "github.com/theopenlane/core/internal/integrations/providerkit" | ||
| ) | ||
|
|
||
| func TestMappingExpressionsValid(t *testing.T) { | ||
| for _, m := range authentikMappings() { | ||
| name := m.Schema | ||
| if m.Variant != "" { | ||
| name += "/" + m.Variant | ||
| } | ||
|
|
||
| t.Run(name+"/filter", func(t *testing.T) { | ||
| assert.NilError(t, providerkit.ValidateExpr(m.Spec.FilterExpr)) | ||
| }) | ||
|
|
||
| t.Run(name+"/map", func(t *testing.T) { | ||
| assert.NilError(t, providerkit.ValidateExpr(m.Spec.MapExpr)) | ||
| }) | ||
| } | ||
| } | ||
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.