Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
33 changes: 33 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache-dependency-path: go.sum

- name: Verify dependencies
run: go mod verify

- name: Build
run: CGO_ENABLED=0 go build ./...

- name: Test
run: go test ./...

- name: Build Docker image
uses: docker/build-push-action@v6
with:
context: .
push: false
125 changes: 125 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
name: Release

on:
push:
tags:
- "v*"

jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
packages: write

steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}

- name: Log in to ghcr.io
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract version from tag
id: version
run: echo "version=${GITHUB_REF_NAME#v}" >> $GITHUB_OUTPUT

- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: |
ghcr.io/kleffio/idp-authentik:${{ steps.version.outputs.version }}
ghcr.io/kleffio/idp-authentik:latest

- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: true

- name: Generate app token
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.KLEFF_APP_ID }}
private-key: ${{ secrets.KLEFF_APP_PRIVATE_KEY }}
owner: kleffio
repositories: plugin-registry

- name: Open PR to plugin-registry
uses: actions/github-script@v7
with:
github-token: ${{ steps.app-token.outputs.token }}
script: |
const version = "${{ steps.version.outputs.version }}";
const branch = `update/idp-authentik-${version}`;

// Get current plugins.json from registry repo
const { data: file } = await github.rest.repos.getContent({
owner: "kleffio",
repo: "plugin-registry",
path: "plugins.json",
});

const plugins = JSON.parse(Buffer.from(file.content, "base64").toString());
const idx = plugins.findIndex(p => p.id === "idp-authentik");
if (idx !== -1) plugins[idx].version = version;

// Create branch and commit
const { data: ref } = await github.rest.git.getRef({
owner: "kleffio",
repo: "plugin-registry",
ref: "heads/main",
});

// Create or reset branch
try {
await github.rest.git.createRef({
owner: "kleffio",
repo: "plugin-registry",
ref: `refs/heads/${branch}`,
sha: ref.object.sha,
});
} catch (e) {
if (e.status === 422) {
await github.rest.git.updateRef({
owner: "kleffio",
repo: "plugin-registry",
ref: `heads/${branch}`,
sha: ref.object.sha,
force: true,
});
} else throw e;
}

await github.rest.repos.createOrUpdateFileContents({
owner: "kleffio",
repo: "plugin-registry",
path: "plugins.json",
message: `chore: bump idp-authentik to v${version}`,
content: Buffer.from(JSON.stringify(plugins, null, 2) + "\n").toString("base64"),
branch,
sha: file.sha,
});

const { data: pr } = await github.rest.pulls.create({
owner: "kleffio",
repo: "plugin-registry",
title: `chore: bump idp-authentik to v${version}`,
head: branch,
base: "main",
body: `Automated release PR from \`kleffio/authentik-plugin\` tag \`v${version}\`.`,
});

// Auto-merge the PR
await github.rest.pulls.merge({
owner: "kleffio",
repo: "plugin-registry",
pull_number: pr.number,
merge_method: "squash",
});
18 changes: 18 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
FROM golang:1.25-alpine AS builder
WORKDIR /build

COPY go.mod ./
RUN go mod download 2>/dev/null || true

COPY . .
RUN go mod tidy && CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o /plugin ./cmd/plugin

# ── Runtime image ─────────────────────────────────────────────────────────────
FROM alpine:3.20
RUN apk add --no-cache ca-certificates tzdata

COPY --from=builder /plugin /plugin

ENV PLUGIN_PORT=50051
EXPOSE 50051
ENTRYPOINT ["/plugin"]
102 changes: 102 additions & 0 deletions cmd/plugin/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Command plugin is the entrypoint for the idp-authentik Kleff plugin.
// It wires the hexagonal layers together and starts the gRPC server.
// All Authentik setup (realm, client, admin user) is performed automatically —
// no manual configuration is required.
package main

import (
"context"
"log/slog"
"net"
"os"
"os/signal"
"syscall"
"time"

pluginsv1 "github.com/kleffio/plugin-sdk-go/v1"
authentikadapter "github.com/kleffio/idp-authentik/internal/adapters/authentik"
grpcadapter "github.com/kleffio/idp-authentik/internal/adapters/grpc"
"github.com/kleffio/idp-authentik/internal/core/application"
"google.golang.org/grpc"
)

func main() {
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))

// ── Infrastructure (outbound adapter) ─────────────────────────────────────
provider := authentikadapter.New(authentikadapter.Config{
BaseURL: env("AUTHENTIK_URL", "http://authentik-server:9000"),
PublicURL: env("AUTHENTIK_PUBLIC_URL", "http://localhost:9000"),
BootstrapToken: env("AUTHENTIK_BOOTSTRAP_TOKEN", ""),
AppSlug: env("AUTHENTIK_APP_SLUG", "kleff"),
AdminEmail: env("AUTHENTIK_ADMIN_EMAIL", "admin@localhost"),
AdminPassword: env("AUTHENTIK_ADMIN_PASSWORD", "admin"),
AuthMode: "headless",
})

// ── Application layer ──────────────────────────────────────────────────────
svc := application.New(provider)

// ── Inbound adapter (gRPC) ─────────────────────────────────────────────────
srv := grpcadapter.New(svc,
env("AUTHENTIK_PUBLIC_URL", "http://localhost:9000"),
env("AUTHENTIK_APP_SLUG", "kleff"),
)

gs := grpc.NewServer()
pluginsv1.RegisterIdentityPluginServer(gs, srv)
pluginsv1.RegisterPluginHealthServer(gs, srv)
pluginsv1.RegisterPluginUIServer(gs, srv)

port := env("PLUGIN_PORT", "50051")
lis, err := net.Listen("tcp", ":"+port)
if err != nil {
logger.Error("listen failed", "error", err)
os.Exit(1)
}

// Start gRPC immediately so the platform can dial while setup is in progress.
go func() {
logger.Info("plugin listening", "port", port)
if err := gs.Serve(lis); err != nil {
logger.Error("gRPC server error", "error", err)
os.Exit(1)
}
}()

// ── Auto-configure Authentik in the background ─────────────────────────────
// Retries indefinitely — the Authentik companion containers take time to
// start, and this is safe to call multiple times (idempotent).
// srv.SetReady() is called once setup succeeds so GetOIDCConfig starts
// returning a valid config and the platform's ready check fires.
go func() {
for {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
err := provider.EnsureSetup(ctx)
cancel()
if err == nil {
logger.Info("Authentik configured",
"base", env("AUTHENTIK_URL", "http://authentik-server:9000"),
"app", env("AUTHENTIK_APP_SLUG", "kleff"),
)
srv.SetReady()
return
}
logger.Warn("waiting for Authentik...", "error", err)
time.Sleep(5 * time.Second)
}
}()

stop := make(chan os.Signal, 1)
signal.Notify(stop, syscall.SIGTERM, syscall.SIGINT)
<-stop
logger.Info("shutting down")
gs.GracefulStop()
}

func env(key, def string) string {
if v := os.Getenv(key); v != "" {
return v
}
return def
}
16 changes: 16 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module github.com/kleffio/idp-authentik

go 1.25.0

require (
github.com/kleffio/plugin-sdk-go v0.1.2
google.golang.org/grpc v1.64.0
)

require (
golang.org/x/net v0.26.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/text v0.16.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 // indirect
google.golang.org/protobuf v1.34.2 // indirect
)
16 changes: 16 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/kleffio/plugin-sdk-go v0.1.2 h1:hY27rPzcFPba+NSRVUy+Cqy3YQGCaTBIfLGBkOdh0Bw=
github.com/kleffio/plugin-sdk-go v0.1.2/go.mod h1:QSUqnkbslgBShcN7/y3vl7Q+cVo5BLoweRtbc9rMB24=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 h1:Di6ANFilr+S60a4S61ZM00vLdw0IrQOSMS2/6mrnOU0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
Loading
Loading