Compare commits

..

35 commits
v1.0.0 ... main

Author SHA1 Message Date
df0eaf0a21 Add kubectx and kubens support to Dockerfile and bash completion scripts
All checks were successful
Build k8s-mgmt-pod image / Build & Push (push) Successful in 1m23s
2026-06-29 13:40:34 +02:00
d582311917 Update ASCII art in motd for improved branding and consistency
All checks were successful
Build k8s-mgmt-pod image / Build & Push (push) Successful in 1m59s
2026-06-29 10:54:20 +02:00
e4f9f06674 Fix formatting in the motd file for consistency 2026-06-29 10:51:20 +02:00
4f57393103 Merge pull request 'Update dependency int128/kubelogin to v1.36.2' (#13) from renovate/int128-kubelogin-1.x into main
All checks were successful
Build k8s-mgmt-pod image / Build & Push (push) Successful in 1m16s
Reviewed-on: #13
2026-06-29 08:23:43 +00:00
1b3fa2c228 Merge pull request 'Update dependency janosmiko/lfk to v0.14.18' (#12) from renovate/janosmiko-lfk-0.x into main
All checks were successful
Build k8s-mgmt-pod image / Build & Push (push) Successful in 2m13s
Reviewed-on: #12
2026-06-29 08:20:03 +00:00
998012fdff Add locale support and environment variables to Dockerfile
All checks were successful
Build k8s-mgmt-pod image / Build & Push (push) Successful in 1m49s
2026-06-29 09:42:12 +02:00
47fe90296e Create cache directory and set ownership/permissions for kubeconfig
All checks were successful
Build k8s-mgmt-pod image / Build & Push (push) Successful in 44s
2026-06-29 09:26:47 +02:00
Renovate Bot
fba15a73c0 Update dependency int128/kubelogin to v1.36.2
All checks were successful
Build k8s-mgmt-pod image / Build & Push (pull_request) Successful in 1m3s
2026-06-29 07:24:11 +00:00
Renovate Bot
62d68bea0c Update dependency janosmiko/lfk to v0.14.18
All checks were successful
Build k8s-mgmt-pod image / Build & Push (pull_request) Successful in 1m52s
2026-06-29 07:24:07 +00:00
b6f7d1e1b7 Update kubeconfig handling in entrypoint script for better clarity
All checks were successful
Build k8s-mgmt-pod image / Build & Push (push) Successful in 53s
2026-06-29 09:14:20 +02:00
68c6789ac4 Add OIDC login support and unzip utility to Dockerfile
All checks were successful
Build k8s-mgmt-pod image / Build & Push (push) Successful in 1m50s
2026-06-29 08:52:08 +02:00
0613ebca66 Add apt-get clean to Dockerfile to optimize image size
All checks were successful
Build k8s-mgmt-pod image / Build & Push (push) Successful in 1m10s
2026-06-24 15:00:17 +02:00
91e9afc355 Add apt-get clean to Dockerfile to reduce image size 2026-06-24 14:59:41 +02:00
d26bf1a78a Merge pull request 'Update dependency derailed/k9s to v0.51.0' (#2) from renovate/derailed-k9s-0.x into main
All checks were successful
Build k8s-mgmt-pod image / Build & Push (push) Successful in 1m4s
Reviewed-on: #2
2026-06-24 11:46:55 +00:00
bd11364dfb Merge pull request 'Update dependency helm/helm to v3.21.2' (#4) from renovate/helm-helm-3.x into main
Some checks failed
Build k8s-mgmt-pod image / Build & Push (push) Has been cancelled
Reviewed-on: #4
2026-06-24 11:46:47 +00:00
9904442bda Merge pull request 'Update dependency janosmiko/lfk to v0.14.15' (#1) from renovate/janosmiko-lfk-0.x into main
All checks were successful
Build k8s-mgmt-pod image / Build & Push (push) Successful in 5m17s
Reviewed-on: #1
2026-06-24 11:40:15 +00:00
44abce08fd Merge pull request 'Update dependency kubernetes/kubernetes to v1.36.2' (#5) from renovate/kubernetes-kubernetes-1.x into main
Some checks failed
Build k8s-mgmt-pod image / Build & Push (push) Has been cancelled
Reviewed-on: #5
2026-06-24 11:39:49 +00:00
Renovate Bot
32292556d4 Update dependency kubernetes/kubernetes to v1.36.2
All checks were successful
Build k8s-mgmt-pod image / Build & Push (pull_request) Successful in 2m4s
2026-06-24 11:27:27 +00:00
Renovate Bot
93b5f89e59 Update dependency helm/helm to v3.21.2
All checks were successful
Build k8s-mgmt-pod image / Build & Push (pull_request) Successful in 3m49s
2026-06-24 11:27:22 +00:00
Renovate Bot
335b11f411 Update dependency derailed/k9s to v0.51.0
All checks were successful
Build k8s-mgmt-pod image / Build & Push (pull_request) Successful in 1m39s
2026-06-24 11:27:17 +00:00
Renovate Bot
a7532c9292 Update dependency janosmiko/lfk to v0.14.15
All checks were successful
Build k8s-mgmt-pod image / Build & Push (pull_request) Successful in 1m45s
2026-06-24 11:27:11 +00:00
d4e935c470 Add renovate comments for dependency tracking in Dockerfile
All checks were successful
Build k8s-mgmt-pod image / Build & Push (push) Successful in 1m50s
2026-06-24 13:26:14 +02:00
0553a4815c reverting back to working example
All checks were successful
Build k8s-mgmt-pod image / Build & Push (push) Successful in 1m48s
2026-06-24 13:22:08 +02:00
709dd6d0b6 revert f0e72c69a0
Some checks failed
Build k8s-mgmt-pod image / Build & Push (push) Has been cancelled
revert Add locales support and set UTF-8 environment variables in Dockerfile
2026-06-24 11:18:15 +00:00
89c1e900b8 Fix typo in Dockerfile ARG comment for kubectl datasource
Some checks failed
Build k8s-mgmt-pod image / Build & Push (push) Failing after 1m54s
2026-06-24 12:58:49 +02:00
60c4ed3c02 Refactor Dockerfile ARG declarations for better organization and add renovate configuration for automated dependency updates
Some checks failed
Build k8s-mgmt-pod image / Build & Push (push) Failing after 1m40s
2026-06-24 12:42:34 +02:00
f0e72c69a0 Add locales support and set UTF-8 environment variables in Dockerfile
All checks were successful
Build k8s-mgmt-pod image / Build & Push (push) Successful in 2m1s
2026-06-24 11:17:12 +02:00
43aa568452 Update base images in Dockerfile from bookworm to trixie
All checks were successful
Build k8s-mgmt-pod image / Build & Push (push) Successful in 2m24s
2026-06-24 11:11:10 +02:00
e48f76441d Add PersistentVolumeClaim for pod state persistence and update README
All checks were successful
Build k8s-mgmt-pod image / Build & Push (push) Successful in 28s
2026-06-24 11:01:13 +02:00
95281d36e1 Refactor RBAC configuration to use ClusterRole and ClusterRoleBinding for k8s-mgmt-pod with expanded resource permissions, while retaining security.
All checks were successful
Build k8s-mgmt-pod image / Build & Push (push) Successful in 2m23s
2026-06-24 09:12:27 +02:00
41d4b36345 Enhance entrypoint and deployment configurations for Kubernetes management pod
All checks were successful
Build k8s-mgmt-pod image / Build & Push (push) Successful in 44s
- Add K8S_RUNTIME_ENV_PROFILE and KUBERNETES_SECRETS_DIR variables in entrypoint.sh
- Implement service account kubeconfig installation and environment variable persistence
- Update deployment.yaml to include fsGroup and fsGroupChangePolicy in securityContext
- Modify container ports and resource limits for better management
2026-06-23 16:37:14 +02:00
795d3844dc Add SYS_CHROOT capability to container security context
All checks were successful
Build k8s-mgmt-pod image / Build & Push (push) Successful in 28s
2026-06-23 16:00:46 +02:00
5584dc1f29 Update SSH port from 22 to 2222 in Dockerfile, entrypoint, and Kubernetes configurations
All checks were successful
Build k8s-mgmt-pod image / Build & Push (push) Successful in 1m35s
2026-06-23 15:48:15 +02:00
b715b4bb59 Remove paths filter from push event in build workflow
All checks were successful
Build k8s-mgmt-pod image / Build & Push (push) Successful in 40s
2026-06-23 15:31:09 +02:00
edfe160b4f Update entrypoint.sh to prevent failure on missing password entry and modify deployment.yaml to use latest image tag with Always pull policy 2026-06-23 15:29:06 +02:00
16 changed files with 405 additions and 70 deletions

View file

@ -5,9 +5,6 @@ on:
- cron: '0 3 * * 6'
push:
branches: ["main"]
paths:
- "Dockerfile"
- ".github/workflows/build-workflow.yaml"
tags: ["v*.*.*"]
pull_request:
branches: ["main"]

View file

@ -1,8 +1,9 @@
ARG NODE_IMAGE=node:22-bookworm-slim
ARG NODE_IMAGE=node:22-trixie-slim
FROM ${NODE_IMAGE} AS webssh2-builder
ENV DEBIAN_FRONTEND=noninteractive
# renovate: datasource=github-releases depName=billchurch/webssh2
ARG WEBSSH2_VERSION=5.0.1
RUN apt-get update \
@ -19,19 +20,31 @@ RUN apt-get update \
&& rm -f "/tmp/webssh2-${WEBSSH2_VERSION}.tar.gz" "/tmp/webssh2-${WEBSSH2_VERSION}.tar.gz.sha256" \
&& npm cache clean --force
FROM debian:bookworm-slim
FROM debian:trixie-slim
# renovate: datasource=github-releases depName=janosmiko/lfk
ARG LFK_VERSION=0.14.18
# renovate: datasource=github-releases depName=kubernetes/kubernetes
ARG KUBECTL_VERSION=1.36.2
# renovate: datasource=github-releases depName=helm/helm
ARG HELM_VERSION=3.21.2
# renovate: datasource=github-releases depName=derailed/k9s
ARG K9S_VERSION=0.51.0
# renovate: datasource=github-releases depName=int128/kubelogin
ARG OIDC_LOGIN_VERSION=1.36.2
# renovate: datasource=github-tags depName=ahmetb/kubectx
ARG KUBECTX_VERSION=0.9.5
ARG LFK_VERSION=0.14.9
ARG KUBECTL_VERSION=1.31.0
ARG HELM_VERSION=3.16.2
ARG K9S_VERSION=0.40.10
ARG WEBSSH2_VERSION=5.0.1
ENV DEBIAN_FRONTEND=noninteractive \
LANG=en_US.UTF-8 \
LANGUAGE=en_US:en \
LC_ALL=en_US.UTF-8 \
WEB_SSH_PORT=3000 \
WEBSSH2_LISTEN_PORT=3001 \
WEBSSH2_SSH_HOST=127.0.0.1 \
WEBSSH2_SSH_PORT=22 \
WEBSSH2_SSH_PORT=2222 \
SSH_PORT=2222 \
WEBSSH2_HEADER_TEXT=k8s-mgmt-pod \
WEBSSH2_SSH_HOSTKEY_ENABLED=false
@ -43,7 +56,12 @@ RUN apt-get update \
bash-completion \
ca-certificates \
curl \
jq \
locales \
unzip \
tar \
vim \
yq \
nginx-light \
netcat-openbsd \
openssh-client \
@ -75,10 +93,23 @@ RUN apt-get update \
&& curl -fsSLo "/tmp/k9s_Linux_amd64.tar.gz" "https://github.com/derailed/k9s/releases/download/v${K9S_VERSION}/k9s_Linux_amd64.tar.gz" \
&& tar -xzf "/tmp/k9s_Linux_amd64.tar.gz" -C /tmp \
&& install -m 0755 /tmp/k9s /usr/local/bin/k9s \
&& curl -fsSLo "/tmp/kubelogin_linux_amd64.zip" "https://github.com/int128/kubelogin/releases/download/v${OIDC_LOGIN_VERSION}/kubelogin_linux_amd64.zip" \
&& curl -fsSLo "/tmp/kubelogin_linux_amd64.zip.sha256" "https://github.com/int128/kubelogin/releases/download/v${OIDC_LOGIN_VERSION}/kubelogin_linux_amd64.zip.sha256" \
&& sed 's#\*kubelogin_linux_amd64.zip$#*/tmp/kubelogin_linux_amd64.zip#' /tmp/kubelogin_linux_amd64.zip.sha256 | sha256sum -c - \
&& unzip -p /tmp/kubelogin_linux_amd64.zip kubelogin > /usr/local/bin/kubectl-oidc_login \
&& chmod 0755 /usr/local/bin/kubectl-oidc_login \
&& curl -fsSLo /usr/local/bin/kubectx "https://raw.githubusercontent.com/ahmetb/kubectx/v${KUBECTX_VERSION}/kubectx" \
&& curl -fsSLo /usr/local/bin/kubens "https://raw.githubusercontent.com/ahmetb/kubectx/v${KUBECTX_VERSION}/kubens" \
&& chmod 0755 /usr/local/bin/kubectx /usr/local/bin/kubens \
&& mkdir -p /etc/bash_completion.d \
&& curl -fsSLo /etc/bash_completion.d/kubectx "https://raw.githubusercontent.com/ahmetb/kubectx/v${KUBECTX_VERSION}/completion/kubectx.bash" \
&& curl -fsSLo /etc/bash_completion.d/kubens "https://raw.githubusercontent.com/ahmetb/kubectx/v${KUBECTX_VERSION}/completion/kubens.bash" \
&& kubectl completion bash > /etc/bash_completion.d/kubectl \
&& helm completion bash > /etc/bash_completion.d/helm \
&& chmod 0644 /etc/bash_completion.d/kubectl /etc/bash_completion.d/helm \
&& chmod 0644 /etc/bash_completion.d/kubectl /etc/bash_completion.d/helm /etc/bash_completion.d/kubectx /etc/bash_completion.d/kubens \
&& sed -i 's/^# *en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen \
&& locale-gen en_US.UTF-8 \
&& update-locale LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 \
&& rm -f /tmp/kubectl.sha256 \
&& rm -rf /tmp/linux-amd64 \
&& rm -f \
@ -87,8 +118,12 @@ RUN apt-get update \
/tmp/k9s \
/tmp/README.md \
/tmp/LICENSE \
/tmp/kubelogin_linux_amd64.zip \
/tmp/kubelogin_linux_amd64.zip.sha256 \
/tmp/k9s_Linux_amd64.tar.gz \
&& rm -f /etc/ssh/ssh_host_*
&& rm -f /etc/ssh/ssh_host_* \
&& apt-get clean
COPY docker/sshd/10-lfk-base.conf /etc/ssh/sshd_config.d/10-lfk-base.conf
COPY docker/nginx/webssh.conf /etc/nginx/conf.d/webssh.conf
@ -99,9 +134,9 @@ COPY entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod 0755 /usr/local/bin/entrypoint.sh \
&& chmod 0644 /etc/profile.d/20-k8s-tools-completion.sh
EXPOSE 22 3000
EXPOSE 2222 3000
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
CMD nc -z 127.0.0.1 22 || exit 1
CMD nc -z 127.0.0.1 "${SSH_PORT:-2222}" || exit 1
ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/entrypoint.sh"]

View file

@ -6,8 +6,10 @@
- `kubectl` preinstalled from a pinned upstream binary release
- `helm` preinstalled from a pinned upstream binary release
- `k9s` preinstalled from a pinned GitHub release tarball
- `kubectl` and `helm` bash autocompletion enabled for both `admin` and `user`
- local `openssh-server` on port `22`
- `kubectx` and `kubens` preinstalled from a pinned upstream release
- `jq`, `yq`, and `vim` preinstalled from Debian packages
- `kubectl`, `helm`, `kubectx`, and `kubens` bash autocompletion enabled for both `admin` and `user`
- local `openssh-server` on port `2222`
- a browser-based SSH client on port `3000`
- a custom ASCII-art MOTD on login
- two provisioned users: `admin` and `user`
@ -20,7 +22,7 @@ One caveat is worth stating clearly: browser-based SSH tools do not keep the pri
## Ports
- `22`: native SSH into the container's local `sshd`
- `2222`: native SSH into the container's local `sshd`
- `3000`: browser UI entrypoint; `/` and `/ssh` are forced to a fixed local WebSSH2 session for `127.0.0.1` and rewritten back to that host if changed in the URL
## Runtime environment variables
@ -71,6 +73,8 @@ The included [docker-compose.yaml](docker-compose.yaml) enables kubeconfig boots
That bootstrap is explicitly opt-in and defaults to off in the container itself, so it does not run in Kubernetes unless you deliberately enable it.
When both are available, the bootstrap kubeconfig is applied after the service-account kubeconfig and therefore takes precedence.
Start it with:
```bash
@ -113,7 +117,7 @@ If `${HOME}/.kube/config` is missing on the host, the container logs that bootst
```bash
docker run --rm \
-p 2222:22 \
-p 2222:2222 \
-p 3000:3000 \
-e ADMIN_SSH_PUBKEY="$(cat ~/.ssh/id_ed25519.pub)" \
-e USER_SSH_PUBKEY="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIexample user@example" \
@ -132,7 +136,7 @@ Or in a browser at `http://localhost:3000/`.
```bash
docker run --rm \
-p 2222:22 \
-p 2222:2222 \
-p 3000:3000 \
-e ADMIN_PASSWORD='change-this-admin-password' \
-e USER_PASSWORD='change-this-user-password' \
@ -143,7 +147,7 @@ If that fails with a message like `unable to upgrade to tcp, received 500`, the
```bash
docker run -d --name k8s-mgmt-pod \
-p 2222:22 \
-p 2222:2222 \
-p 3000:3000 \
-e ADMIN_PASSWORD='change-this-admin-password' \
-e USER_PASSWORD='change-this-user-password' \
@ -156,7 +160,7 @@ If Docker reports `bind: address already in use`, change the host-side ports or
```bash
docker run --rm \
-p 127.0.0.1::22 \
-p 127.0.0.1::2222 \
-p 127.0.0.1::3000 \
-e ADMIN_PASSWORD='change-this-admin-password' \
-e USER_PASSWORD='change-this-user-password' \
@ -167,7 +171,7 @@ docker run --rm \
```bash
docker run --rm \
-p 2222:22 \
-p 2222:2222 \
-p 3000:3000 \
-e ADMIN_SSH_PUBKEY="$(cat ~/.ssh/id_ed25519.pub)" \
-e ADMIN_PASSWORD='change-this-admin-password' \
@ -184,7 +188,7 @@ If you omit both the public-key and password variables for a user, the container
```bash
docker run --rm \
-p 2222:22 \
-p 2222:2222 \
-p 3000:3000 \
-e ADMIN_SSH_PUBKEY="$(cat ~/.ssh/id_ed25519.pub)" \
k8s-mgmt-pod:local

View file

@ -6,7 +6,7 @@ services:
dockerfile: Dockerfile
container_name: k8s-mgmt-pod
ports:
- "${HOST_SSH_PORT:-2222}:22"
- "${HOST_SSH_PORT:-2222}:2222"
- "${HOST_WEBSSH_PORT:-3000}:3000"
environment:
ADMIN_PASSWORD: ${ADMIN_PASSWORD:-change-this-admin-password}

View file

@ -1,12 +1,12 @@
╔════════════════════════════════════════════════════════════════╗
║ ║
██╗ ███████╗██╗ ██╗ ██╗ ██╗ █████╗ ███████╗
██║ ██╔════╝██║ ██╔╝ ██║ ██╔╝██╔══██╗██╔════╝
██║ █████╗ █████╔╝ █████╔╝ ╚█████╔╝███████╗
██║ ██╔══╝ ██╔═██╗ ██╔═██╗ ██╔══██╗╚════██║
███████╗██║ ██║ ██╗ ██║ ██╗╚█████╔╝███████║
╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚════╝ ╚══════╝
_ __ ___ ____ __ __ ____ __ __ _____
| |/ / / _ \ / ___| | \/ | / ___|| \/ ||_ _|
| ' / | (_) |\___ \ | |\/| || | _ | |\/| | | |
| . \ \__, | ___) | | | | || |_| || | | | | |
|_|\_\ /_/ |____/ |_| |_| \____||_| |_| |_|
║ Kubernetes Management Pod - Jump Box ║
║ ║
╠════════════════════════════════════════════════════════════════╣
@ -15,7 +15,7 @@
║ - lfk : LFK kubernetes management ║
║ - k9s : interactive cluster navigation ║
║ - helm : chart install, upgrade, and rollback ║
║ - plus : kubectx, kubens, jq, yq, and more
║ - plus : kubectx, kubens, jq, yq, vim, and more ║
║ ║
╠════════════════════════════════════════════════════════════════╣
║ Quick start ║

View file

@ -13,6 +13,14 @@ if [ -n "${BASH_VERSION:-}" ]; then
if [ -r /etc/bash_completion.d/helm ]; then
. /etc/bash_completion.d/helm
fi
if [ -r /etc/bash_completion.d/kubectx ]; then
. /etc/bash_completion.d/kubectx
fi
if [ -r /etc/bash_completion.d/kubens ]; then
. /etc/bash_completion.d/kubens
fi
fi
alias k=kubectl

View file

@ -1,4 +1,3 @@
Port 22
Protocol 2
PermitRootLogin no
PubkeyAuthentication yes

View file

@ -4,12 +4,20 @@ set -Eeuo pipefail
STATE_DIR="/var/lib/k8s-mgmt-pod"
GENERATED_PASSWORDS_FILE="${STATE_DIR}/generated-passwords.env"
SSH_DYNAMIC_CONFIG="/etc/ssh/sshd_config.d/20-k8s-mgmt-pod-auth.conf"
K8S_RUNTIME_ENV_PROFILE="/etc/profile.d/30-kubernetes-runtime-env.sh"
PASSWORD_AUTH_REQUIRED=0
SSHD_PID=""
WEBSSH2_PID=""
NGINX_PID=""
BOOTSTRAP_KUBECONFIG="${BOOTSTRAP_KUBECONFIG:-/bootstrap/kubeconfig/config}"
ENABLE_BOOTSTRAP_KUBECONFIG="${ENABLE_BOOTSTRAP_KUBECONFIG:-false}"
KUBERNETES_SECRETS_DIR="${KUBERNETES_SECRETS_DIR:-/var/run/kubernetes.io}"
SSH_PORT="${SSH_PORT:-2222}"
if [[ -z "${WEBSSH2_SSH_PORT:-}" ]]; then
WEBSSH2_SSH_PORT="${SSH_PORT}"
fi
export WEBSSH2_SSH_PORT
ensure_host_keys() {
if [[ ! -f "${STATE_DIR}/ssh_host_rsa_key" ]]; then
@ -70,7 +78,8 @@ store_generated_password() {
read_generated_password() {
local username="$1"
grep -E "^${username}=" "${GENERATED_PASSWORDS_FILE}" | tail -n 1 | cut -d '=' -f 2-
# Return an empty string if no entry exists; never fail under `set -euo pipefail`.
awk -F '=' -v username="${username}" '$1 == username { value = substr($0, index($0, "=") + 1) } END { if (value != "") print value }' "${GENERATED_PASSWORDS_FILE}"
}
write_authorized_keys() {
@ -89,12 +98,87 @@ write_authorized_keys() {
install_kubeconfig_for_user() {
local username="$1"
local source_kubeconfig="$2"
local home_dir="/home/${username}"
local kube_dir="${home_dir}/.kube"
local kube_config="${kube_dir}/config"
mkdir -p "${kube_dir}"
install -m 0600 -o "${username}" -g "${username}" "${BOOTSTRAP_KUBECONFIG}" "${kube_config}"
mkdir -p "${kube_dir}/cache"
chown -R "${username}:${username}" "${kube_dir}"
chmod 0700 "${kube_dir}"
install -m 0600 -o "${username}" -g "${username}" "${source_kubeconfig}" "${kube_config}"
}
resolve_kubernetes_secrets_dir() {
local configured_dir="${KUBERNETES_SECRETS_DIR}"
local fallback_dir="/var/run/secrets/kubernetes.io/serviceaccount"
if [[ -f "${configured_dir}/token" && -f "${configured_dir}/ca.crt" && -f "${configured_dir}/namespace" ]]; then
printf '%s\n' "${configured_dir}"
return 0
fi
if [[ "${configured_dir}" != "${fallback_dir}" ]] \
&& [[ -f "${fallback_dir}/token" && -f "${fallback_dir}/ca.crt" && -f "${fallback_dir}/namespace" ]]; then
printf '%s\n' "${fallback_dir}"
return 0
fi
return 1
}
install_serviceaccount_kubeconfig() {
local sa_dir=""
local token_file=""
local ca_file=""
local namespace_file=""
local namespace=""
local kubernetes_host="${KUBERNETES_SERVICE_HOST:-kubernetes.default.svc}"
local kubernetes_port="${KUBERNETES_SERVICE_PORT_HTTPS:-443}"
local server=""
local generated_kubeconfig="${STATE_DIR}/incluster.kubeconfig"
if ! sa_dir="$(resolve_kubernetes_secrets_dir)"; then
log "Skipping in-cluster kubeconfig generation because no service-account mount was found in ${KUBERNETES_SECRETS_DIR} or /var/run/secrets/kubernetes.io/serviceaccount."
return
fi
token_file="${sa_dir}/token"
ca_file="${sa_dir}/ca.crt"
namespace_file="${sa_dir}/namespace"
namespace="$(tr -d '\n' < "${namespace_file}")"
if [[ -z "${namespace}" ]]; then
namespace="default"
fi
server="https://${kubernetes_host}:${kubernetes_port}"
cat > "${generated_kubeconfig}" <<EOF
apiVersion: v1
kind: Config
clusters:
- name: in-cluster
cluster:
certificate-authority: ${ca_file}
server: ${server}
contexts:
- name: in-cluster
context:
cluster: in-cluster
user: service-account
namespace: ${namespace}
current-context: in-cluster
users:
- name: service-account
user:
tokenFile: ${token_file}
EOF
chmod 0600 "${generated_kubeconfig}"
install_kubeconfig_for_user admin "${generated_kubeconfig}"
install_kubeconfig_for_user user "${generated_kubeconfig}"
log "Installed generated in-cluster kubeconfig for admin and user from ${sa_dir}."
}
install_bootstrap_kubeconfig() {
@ -107,8 +191,8 @@ install_bootstrap_kubeconfig() {
return
fi
install_kubeconfig_for_user admin
install_kubeconfig_for_user user
install_kubeconfig_for_user admin "${BOOTSTRAP_KUBECONFIG}"
install_kubeconfig_for_user user "${BOOTSTRAP_KUBECONFIG}"
log "Installed mounted kubeconfig for admin and user."
}
@ -186,15 +270,38 @@ render_sshd_auth_config() {
fi
cat > "${SSH_DYNAMIC_CONFIG}" <<EOF
Port ${SSH_PORT}
PasswordAuthentication ${password_value}
KbdInteractiveAuthentication ${keyboard_interactive_value}
EOF
}
persist_kubernetes_login_env() {
local kubernetes_host="${KUBERNETES_SERVICE_HOST:-kubernetes.default.svc}"
local kubernetes_https_port="${KUBERNETES_SERVICE_PORT_HTTPS:-443}"
local kubernetes_port="${KUBERNETES_SERVICE_PORT:-${kubernetes_https_port}}"
cat > "${K8S_RUNTIME_ENV_PROFILE}" <<EOF
# Generated at container start so SSH login shells see Kubernetes API environment values.
export KUBERNETES_SERVICE_HOST='${kubernetes_host}'
export KUBERNETES_SERVICE_PORT='${kubernetes_port}'
export KUBERNETES_SERVICE_PORT_HTTPS='${kubernetes_https_port}'
EOF
chmod 0644 "${K8S_RUNTIME_ENV_PROFILE}"
}
prepare_runtime() {
mkdir -p /var/run/sshd
ensure_host_keys
if [[ "${ENABLE_BOOTSTRAP_KUBECONFIG}" == "true" ]]; then
log "Kubeconfig mode: bootstrap-only (serviceaccount kubeconfig generation disabled)."
install_bootstrap_kubeconfig
else
log "Kubeconfig mode: in-cluster serviceaccount."
install_serviceaccount_kubeconfig
fi
persist_kubernetes_login_env
configure_user_auth admin ADMIN_SSH_PUBKEY ADMIN_PASSWORD
configure_user_auth user USER_SSH_PUBKEY USER_PASSWORD
render_sshd_auth_config
@ -211,7 +318,7 @@ start_services() {
nginx -g 'daemon off;' &
NGINX_PID=$!
log "sshd started on port 22."
log "sshd started on port ${SSH_PORT}."
log "WebSSH2 started on port ${WEBSSH2_LISTEN_PORT}."
log "nginx redirect/proxy started on port ${WEB_SSH_PORT}."

View file

@ -7,6 +7,7 @@ These manifests deploy `k8s-mgmt-pod` as a single-replica bastion deployment ins
1. Build and publish the image.
2. Replace the placeholder image in `deployment.yaml` with a pinned tag or digest.
3. Create the real `k8s-mgmt-pod-auth` Secret instead of applying `secret.yaml.example`.
4. The pod state directory is now backed by a PVC, so SSH host keys and generated fallback passwords survive pod recreation until you delete the claim.
## Create the Secret
@ -62,7 +63,7 @@ If you prefer individual files, apply `namespace.yaml`, `serviceaccount.yaml`, `
Port-forward the service instead of exposing it externally by default:
```bash
kubectl -n k8s-mgmt-pod port-forward svc/k8s-mgmt-pod 2222:22 3000:3000
kubectl -n k8s-mgmt-pod port-forward svc/k8s-mgmt-pod 2222:2222 3000:3000
```
Then connect with:

View file

@ -16,20 +16,24 @@ spec:
spec:
serviceAccountName: k8s-mgmt-pod
automountServiceAccountToken: true
securityContext:
seccompProfile:
type: RuntimeDefault
fsGroup: 1000
fsGroupChangePolicy: OnRootMismatch
containers:
- name: k8s-mgmt-pod
image: git.alcatrash.net/michael/k8s-mgmt-pod/k8s-mgmt-pod:v1.0.0
imagePullPolicy: IfNotPresent
image: git.alcatrash.net/michael/k8s-mgmt-pod/k8s-mgmt-pod:latest
imagePullPolicy: Always
ports:
- name: ssh
containerPort: 22
protocol: TCP
containerPort: 2222
- name: webssh
containerPort: 3000
protocol: TCP
env:
- name: ADMIN_SSH_PUBKEY
valueFrom:
@ -55,6 +59,7 @@ spec:
name: k8s-mgmt-pod-auth
key: USER_PASSWORD
optional: true
resources:
requests:
cpu: 100m
@ -62,30 +67,42 @@ spec:
limits:
cpu: 500m
memory: 512Mi
readinessProbe:
tcpSocket:
port: 22
port: 2222
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
tcpSocket:
port: 22
port: 2222
initialDelaySeconds: 15
periodSeconds: 20
volumeMounts:
- name: state
mountPath: /var/lib/k8s-mgmt-pod
securityContext:
# sshd still runs as root here because it needs to bind port 22 and switch to the authenticated user session.
# The tradeoff is deliberate: keep the container functional, then narrow capabilities instead of forcing a fragile rootless sshd setup.
runAsUser: 0
runAsGroup: 0
runAsNonRoot: false
allowPrivilegeEscalation: false
readOnlyRootFilesystem: false
capabilities:
drop: ["ALL"]
add: ["CHOWN", "DAC_OVERRIDE", "FOWNER", "NET_BIND_SERVICE", "SETGID", "SETUID"]
drop:
- ALL
add:
- AUDIT_WRITE
- CHOWN
- DAC_OVERRIDE
- FOWNER
- SETGID
- SETUID
- SYS_CHROOT
volumes:
- name: state
emptyDir: {}
persistentVolumeClaim:
claimName: k8s-mgmt-pod-state

View file

@ -5,6 +5,7 @@ resources:
- namespace.yaml
- serviceaccount.yaml
- rbac.yaml
- pvc.yaml
- deployment.yaml
- service.yaml
- networkpolicy.yaml

View file

@ -19,6 +19,6 @@ spec:
k8s-mgmt-pod-access: "true"
ports:
- protocol: TCP
port: 22
port: 2222
- protocol: TCP
port: 3000

10
k8s/pvc.yaml Normal file
View file

@ -0,0 +1,10 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: k8s-mgmt-pod-state
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi

View file

@ -1,49 +1,189 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
kind: ClusterRole
metadata:
name: k8s-mgmt-pod
rules:
# This is intentionally broader than the strict minimum so lfk and kubectl are usable.
# Scope it down to the smallest verb and resource set your operators actually need.
- apiGroups: [""]
resources:
- configmaps
- endpoints
- events
- limitranges
- namespaces
- nodes
- persistentvolumeclaims
- persistentvolumes
- pods
- pods/exec
- pods/log
- secrets
- pods/portforward
- pods/proxy
- replicationcontrollers
- replicationcontrollers/scale
- resourcequotas
- serviceaccounts
- services
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- services/proxy
verbs: ["*"]
- apiGroups: ["apps"]
resources:
- controllerrevisions
- daemonsets
- deployments
- deployments/scale
- replicasets
- replicasets/scale
- statefulsets
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- statefulsets/scale
verbs: ["*"]
- apiGroups: ["batch"]
resources:
- cronjobs
- jobs
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
verbs: ["*"]
- apiGroups: ["autoscaling"]
resources:
- horizontalpodautoscalers
verbs: ["*"]
- apiGroups: ["networking.k8s.io"]
resources:
- ingressclasses
- ingresses
- networkpolicies
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
verbs: ["*"]
- apiGroups: ["extensions"]
resources:
- ingresses
verbs: ["*"]
- apiGroups: ["policy"]
resources:
- poddisruptionbudgets
verbs: ["*"]
- apiGroups: ["coordination.k8s.io"]
resources:
- leases
verbs: ["*"]
- apiGroups: ["storage.k8s.io"]
resources:
- csidrivers
- csinodes
- csistoragecapacities
- storageclasses
- volumeattachments
verbs: ["*"]
- apiGroups: ["apiextensions.k8s.io"]
resources:
- customresourcedefinitions
verbs: ["*"]
- apiGroups: ["admissionregistration.k8s.io"]
resources:
- mutatingadmissionpolicies
- mutatingwebhookconfigurations
- validatingadmissionpolicies
- validatingwebhookconfigurations
verbs: ["*"]
- apiGroups: ["metrics.k8s.io"]
resources:
- nodes
- pods
verbs: ["get", "list", "watch"]
- apiGroups: ["gateway.networking.k8s.io"]
resources:
- gatewayclasses
- gateways
- httproutes
- referencegrants
- tcproutes
- tlsroutes
- udproutes
verbs: ["*"]
- apiGroups: ["cert-manager.io"]
resources:
- certificates
- certificaterequests
- clusterissuers
- issuers
verbs: ["*"]
- apiGroups: ["monitoring.coreos.com"]
resources:
- alertmanagers
- podmonitors
- prometheuses
- prometheusrules
- servicemonitors
- thanosrulers
verbs: ["*"]
- apiGroups: ["argoproj.io"]
resources:
- applications
- appprojects
verbs: ["*"]
- apiGroups: ["traefik.containo.us", "traefik.io"]
resources:
- ingressroutes
- middlewares
- traefikservices
verbs: ["*"]
- apiGroups: ["k8s.cni.cncf.io"]
resources:
- network-attachment-definitions
verbs: ["*"]
- apiGroups: ["kubevirt.io"]
resources:
- virtualmachineinstances
- virtualmachines
verbs: ["*"]
- apiGroups: ["subresources.kubevirt.io"]
resources:
- "*"
verbs: ["*"]
- apiGroups: ["instancetype.kubevirt.io"]
resources:
- "*"
verbs: ["*"]
- apiGroups: ["cdi.kubevirt.io"]
resources:
- "*"
verbs: ["*"]
- apiGroups: ["pool.kubevirt.io"]
resources:
- "*"
verbs: ["*"]
- apiGroups: ["cluster.x-k8s.io"]
resources:
- clusters
- machinedeployments
- machinepools
- machines
- machinesets
verbs: ["*"]
- apiGroups: ["controlplane.cluster.x-k8s.io"]
resources:
- kubeadmcontrolplanes
verbs: ["*"]
- apiGroups: ["bootstrap.cluster.x-k8s.io"]
resources:
- kubeadmconfigs
- kubeadmconfigtemplates
verbs: ["*"]
- apiGroups: ["infrastructure.cluster.x-k8s.io"]
resources:
- kubevirtclusters
- kubevirtmachinetemplates
verbs: ["*"]
- apiGroups: ["addons.cluster.x-k8s.io"]
resources:
- clusterresourcesets
verbs: ["*"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
kind: ClusterRoleBinding
metadata:
name: k8s-mgmt-pod
name: k8s-mgmt-pod-access
subjects:
- kind: ServiceAccount
name: k8s-mgmt-pod
namespace: k8s-mgmt-pod
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
kind: ClusterRole
name: k8s-mgmt-pod

View file

@ -8,7 +8,7 @@ spec:
app.kubernetes.io/name: k8s-mgmt-pod
ports:
- name: ssh
port: 22
port: 2222
targetPort: ssh
protocol: TCP
- name: webssh
@ -27,7 +27,7 @@ spec:
# app.kubernetes.io/name: k8s-mgmt-pod
# ports:
# - name: ssh
# port: 22
# port: 2222
# targetPort: ssh
# - name: webssh
# port: 3000

16
renovate.json Normal file
View file

@ -0,0 +1,16 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended"
],
"customManagers": [
{
"customType": "regex",
"managerFilePatterns": ["/^Dockerfile$/", "/Dockerfile.*/"],
"matchStrings": [
"# renovate: datasource=(?<datasource>.*?) depName=(?<depName>.*?)\\s+ARG .*?_VERSION=(?<currentValue>.*)\\s"
],
"datasourceTemplate": "{{{datasource}}}"
}
]
}