Skip to content
Open
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
143 changes: 143 additions & 0 deletions content/en/docs/operations/oidc/self-signed-certificates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
---
title: "Self-Signed Certificates"
linkTitle: "Self-Signed Certificates"
description: "How to configure OIDC with self-signed certificates"
weight: 60
aliases:
- /docs/oidc/self-signed-certificates
---

This guide explains how to configure Kubernetes API server for OIDC authentication with Keycloak when using self-signed certificates (default in Cozystack).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not default in Cozystack. The default behavior is to issue certificates with LetsEncrypt, which is trusted by most system CA bundles.


## Prerequisites

- Cozystack cluster with OIDC enabled (see [Enable OIDC Server](../enable_oidc/))
- Talos Linux control plane nodes
- `talosctl` configured for your cluster
- `kubelogin` installed

## Step 1: Retrieve the Keycloak Certificate

Get the certificate from the ingress controller:

```bash
echo | openssl s_client -connect <KEYCLOAK_INGRESS_IP>:443 \
-servername keycloak.example.org 2>/dev/null | openssl x509
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To improve security, it's better to save the certificate directly to a file. This file can then be used with kubelogin in Step 3 to avoid insecurely skipping TLS verification.

Please also update the descriptive text on line 30 to reflect this change, explaining that the certificate is saved to keycloak-ca.crt and will be used later.

Suggested change
-servername keycloak.example.org 2>/dev/null | openssl x509
-servername keycloak.example.org 2>/dev/null | openssl x509 > keycloak-ca.crt

```

Replace `<KEYCLOAK_INGRESS_IP>` with your ingress controller IP address, and `keycloak.example.org` with your actual Keycloak domain.

Save the output (the certificate between `-----BEGIN CERTIFICATE-----` and `-----END CERTIFICATE-----`) for the next step.

## Step 2: Configure Talos Control Plane Nodes

For each control plane node, add the following to your machine configuration:

```yaml
machine:
network:
extraHostEntries:
- ip: <KEYCLOAK_INGRESS_IP>
aliases:
- keycloak.example.org
files:
- content: |
-----BEGIN CERTIFICATE-----
<YOUR_CERTIFICATE_CONTENT>
-----END CERTIFICATE-----
permissions: 0o644
path: /var/oidc-ca.crt
op: create

cluster:
apiServer:
extraArgs:
oidc-issuer-url: https://keycloak.example.org/realms/cozy
oidc-client-id: kubernetes
oidc-username-claim: preferred_username
oidc-groups-claim: groups
oidc-ca-file: /etc/kubernetes/oidc/ca.crt
extraVolumes:
- hostPath: /var/oidc-ca.crt
mountPath: /etc/kubernetes/oidc/ca.crt
```

Apply the configuration to each control plane node:

```bash
talosctl apply-config -n <NODE_IP> -f nodes/<node>.yaml
```

{{% alert color="info" %}}
The `extraHostEntries` configuration ensures that the Keycloak domain resolves correctly within the cluster, which is essential when using internal ingress IPs.
{{% /alert %}}

## Step 3: Configure kubelogin

Install kubelogin if you haven't already:

```bash
# Homebrew (macOS and Linux)
brew install int128/kubelogin/kubelogin

# Krew (macOS, Linux, Windows and ARM)
kubectl krew install oidc-login

# Chocolatey (Windows)
choco install kubelogin
```

Set up OIDC login (this will open a browser for authentication):

```bash
kubectl oidc-login setup \
--oidc-issuer-url=https://keycloak.example.org/realms/cozy \
--oidc-client-id=kubernetes \
--insecure-skip-tls-verify
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using --insecure-skip-tls-verify is insecure and should be avoided. Since the CA certificate is saved to a file in Step 1, it should be used here for a secure connection.

Suggested change
--insecure-skip-tls-verify
--oidc-ca-file=keycloak-ca.crt

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually, custom CAs like this are used in private enterprise networks, where, by default, users' workstations are configured to trust the custom CA. In this case passing the --insecure-skip-tls-verify flag is not necessary. This option should be documented as a workaround, not as the default approach.

```

Configure kubectl credentials:

```bash
kubectl config set-credentials oidc \
--exec-api-version=client.authentication.k8s.io/v1 \
--exec-interactive-mode=Never \
--exec-command=kubectl \
--exec-arg=oidc-login \
--exec-arg=get-token \
--exec-arg="--oidc-issuer-url=https://keycloak.example.org/realms/cozy" \
--exec-arg="--oidc-client-id=kubernetes" \
--exec-arg="--insecure-skip-tls-verify=true"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Similar to the setup command, --insecure-skip-tls-verify should be replaced with a path to the CA certificate for security.

Suggested change
--exec-arg="--insecure-skip-tls-verify=true"
--exec-arg="--oidc-ca-file=keycloak-ca.crt"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same consideration as in the comment above applies here.

Comment on lines +102 to +110
Copy link
Contributor

@coderabbitai coderabbitai bot Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

kubelogin exec-interactive-mode Never IfAvailable difference

💡 Result:

In a kubeconfig exec credential (including kubelogin), --exec-interactive-mode maps to Kubernetes’ user.exec.interactiveMode and controls whether stdin is passed to the exec plugin (and what happens when stdin isn’t available):

  • Never: The plugin is treated as non-interactive (it “never needs” stdin). It will run even if stdin isn’t available. Use this for CI/headless scenarios where prompting must not happen. [1]
  • IfAvailable: The plugin may use stdin (prompt) if it’s available, but it will still run even when stdin isn’t available. Use this when interactive login can help locally but you still want it to work in non-interactive contexts (as long as the plugin can fall back). [1]

(For completeness: Always requires stdin; otherwise kubectl errors.) [1]

Source: Kubernetes exec-credential interactiveMode definition (applies regardless of which exec plugin you use, including kubelogin). [1]

References
[1] Kubernetes Authentication docs – exec credential interactiveMode meanings (kubernetes.io)

Citations:


🌐 Web query:

kubelogin oidc-login exec-interactive-mode recommended kubernetes

💡 Result:

For kubelogin / kubectl oidc-login used as a Kubernetes exec credential plugin, the generally recommended setting is:

  • --exec-interactive-mode=IfAvailable for normal human use (lets the plugin use stdin when you’re in a terminal, but still works when stdin isn’t available). Kubernetes defines this exactly for plugins that prefer interaction but don’t require it. [1]

Notes:

  • If your kubeconfig uses client.authentication.k8s.io/v1beta1, interactiveMode is optional and defaults to IfAvailable when unset; for client.authentication.k8s.io/v1, it’s required. [2]
  • Use Never when you want to ensure it won’t try to read from stdin (often chosen for automation / locked-down setups; some guides explicitly set this). [1], [3]
  • Use Always only if the plugin must have stdin or it should fail. [1]

Sources: [1] Kubernetes authentication exec-plugin interactiveMode meanings, [2] kubeconfig ExecConfig interactiveMode defaults/requirements, [3] example guide setting --exec-interactive-mode=Never.


Clarify use case for --exec-interactive-mode=Never.

Never prevents stdin interaction and is appropriate for CI/automation. However, the Kubernetes-recommended setting for human users is IfAvailable, which allows browser/OIDC prompts when stdin is available while still working in non-interactive contexts. If this documentation targets typical human usage, use IfAvailable instead; if it's specifically for automation, document that context.

🤖 Prompt for AI Agents
In `@content/en/docs/operations/oidc/self-signed-certificates.md` around lines 102
- 110, The snippet uses --exec-interactive-mode=Never which disables stdin
prompts (suitable for CI); update the documentation and/or command so it uses
--exec-interactive-mode=IfAvailable for typical human usage (allowing
browser/OIDC prompts when stdin is present) or explicitly state that Never is
intentional for automation; locate the kubectl config set-credentials block and
either change --exec-interactive-mode=Never to
--exec-interactive-mode=IfAvailable or add a short note beside the
--exec-interactive-mode flag explaining that Never is recommended for
CI/automation while IfAvailable is recommended for interactive human users.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is correct. This option should not be passed at all.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lllamnyp, thank you for the clarification! I understand now that the --exec-interactive-mode option should be omitted entirely from the kubelogin configuration, rather than being set to a different value. I appreciate you correcting my earlier suggestion.


✏️ Learnings added
Learnt from: lllamnyp
Repo: cozystack/website PR: 416
File: content/en/docs/operations/oidc/self-signed-certificates.md:102-110
Timestamp: 2026-02-12T15:52:39.278Z
Learning: When configuring kubelogin with `kubectl config set-credentials`, the `--exec-interactive-mode` option should not be passed at all (should be omitted entirely).

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

```

Switch to the OIDC user and verify:

```bash
kubectl config set-context --current --user=oidc
kubectl get nodes
```

{{% alert color="warning" %}}
The `--insecure-skip-tls-verify` flag is used because kubelogin runs on your local machine, which doesn't have the self-signed certificate in its trust store. The API server itself uses the certificate file mounted in Step 2 for secure communication with Keycloak.
{{% /alert %}}
Comment on lines +120 to +122
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

With the proposed changes to use --oidc-ca-file, this warning about --insecure-skip-tls-verify is no longer necessary and can be removed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same consideration as in the comment above applies here too.


## Troubleshooting

### Check API Server OIDC Logs

```bash
kubectl logs -n kube-system -l component=kube-apiserver --tail=50 | grep oidc
```

### Verify OIDC Flags Are Applied

```bash
kubectl get pods -n kube-system -l component=kube-apiserver \
-o jsonpath='{.items[0].spec.containers[0].command}' | tr ',' '\n' | grep oidc
```

### Common Issues

- **Certificate not found**: Ensure the certificate file path in `extraVolumes` matches the path specified in `oidc-ca-file`.
- **Domain resolution fails**: Verify that `extraHostEntries` is correctly configured on all control plane nodes.
- **Authentication fails**: Check that the user exists in Keycloak and has the required group memberships (see [Users and Roles](../users_and_roles/)).