External Secrets with Authentik
I use Authentik for SSO in my homelab and External-Secrets for 99% of my secrets so that I do not have to store them encrypted in the repo. It also makes it easier to rotate credentials etc. One thing that was a little irritating is that when I created a new application in Authentik I had to copy the client-id and client-secret to my external-secret for the application, duplicating the data. But no more!
External-Secrets has a webhook provider that is compatible with Authentik! I’ll outline the steps to integrate it real quick.
Authentik API Token
Go to Directory » Tokens and App passwords and click Create.
- Identifier: external-secrets
- User: <Some Admin User>
- Intent: API Token
- Description: Token for accessing app credentials via external-secrets
- Expiring: true (or false if you are less risk averse)
- Expires On: <Whatever date works for your threat model>
Now copy the token.
Creating the Webhook Provider
Create a secret with the token from the previous step. I’m putting everything in the external-secrets namespace. Since this is bootstrapping the External-Secrets system I would use SOPS to encrypt this at rest. You could probably use a regular SecretStore to pull it from some other external provider too.
apiVersion: v1
kind: Secret
metadata:
name: authentik-secrets
namespace: external-secrets
labels:
external-secrets.io/type: webhook
type: Opaque
stringData:
AUTHENTIK_BOOTSTRAP_TOKEN: <TOKEN>
Create the External-Secrets webhook provider:
apiVersion: external-secrets.io/v1
kind: ClusterSecretStore
metadata:
name: authentik-oauth
namespace: external-secrets
spec:
provider:
webhook:
# This should be your in-cluster url for the authentik-server service
url: "http://authentik-server.authentik.svc/api/v3/providers/oauth2/?name={{ .remoteRef.key }}"
headers:
Content-Type: application/json
Authorization: Bearer {{ .auth.AUTHENTIK_BOOTSTRAP_TOKEN }}
result:
jsonPath: "$.results[0]"
secrets:
- name: auth
secretRef:
name: authentik-secrets
namespace: external-secrets
Using the Provider
Now that the ClusterSecretStore is created, we can pull our client credentials like so:
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: app1-oauth-secret
spec:
secretStoreRef:
kind: ClusterSecretStore
name: authentik-oauth
target:
name: app1-oauth-secret
template:
data:
client_id: "{{ .client_id }}"
client_secret: "{{ .client_secret }}"
dataFrom:
- extract:
# The name of the Provider in Authentik
key: Provider for App1
Conclusion
This is greatly simplifying how I manage my service credentials. Hopefully it helps others too. Perhaps when Authentik 5675 gets implemented this won’t be necessary, but its definitely an improvement until then.