Compare commits
35 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| df0eaf0a21 | |||
| d582311917 | |||
| e4f9f06674 | |||
| 4f57393103 | |||
| 1b3fa2c228 | |||
| 998012fdff | |||
| 47fe90296e | |||
|
|
fba15a73c0 | ||
|
|
62d68bea0c | ||
| b6f7d1e1b7 | |||
| 68c6789ac4 | |||
| 0613ebca66 | |||
| 91e9afc355 | |||
| d26bf1a78a | |||
| bd11364dfb | |||
| 9904442bda | |||
| 44abce08fd | |||
|
|
32292556d4 | ||
|
|
93b5f89e59 | ||
|
|
335b11f411 | ||
|
|
a7532c9292 | ||
| d4e935c470 | |||
| 0553a4815c | |||
| 709dd6d0b6 | |||
| 89c1e900b8 | |||
| 60c4ed3c02 | |||
| f0e72c69a0 | |||
| 43aa568452 | |||
| e48f76441d | |||
| 95281d36e1 | |||
| 41d4b36345 | |||
| 795d3844dc | |||
| 5584dc1f29 | |||
| b715b4bb59 | |||
| edfe160b4f |
16 changed files with 405 additions and 70 deletions
3
.github/workflows/build-workflow.yaml
vendored
3
.github/workflows/build-workflow.yaml
vendored
|
|
@ -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"]
|
||||
|
|
|
|||
59
Dockerfile
59
Dockerfile
|
|
@ -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"]
|
||||
22
README.md
22
README.md
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
16
docker/motd
16
docker/motd
|
|
@ -1,13 +1,13 @@
|
|||
|
||||
╔════════════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ ██╗ ███████╗██╗ ██╗ ██╗ ██╗ █████╗ ███████╗ ║
|
||||
║ ██║ ██╔════╝██║ ██╔╝ ██║ ██╔╝██╔══██╗██╔════╝ ║
|
||||
║ ██║ █████╗ █████╔╝ █████╔╝ ╚█████╔╝███████╗ ║
|
||||
║ ██║ ██╔══╝ ██╔═██╗ ██╔═██╗ ██╔══██╗╚════██║ ║
|
||||
║ ███████╗██║ ██║ ██╗ ██║ ██╗╚█████╔╝███████║ ║
|
||||
║ ╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚════╝ ╚══════╝ ║
|
||||
║ Kubernetes Management Pod - Jump Box ║
|
||||
║ ║
|
||||
║ _ __ ___ ____ __ __ ____ __ __ _____ ║
|
||||
║ | |/ / / _ \ / ___| | \/ | / ___|| \/ ||_ _| ║
|
||||
║ | ' / | (_) |\___ \ | |\/| || | _ | |\/| | | | ║
|
||||
║ | . \ \__, | ___) | | | | || |_| || | | | | | ║
|
||||
║ |_|\_\ /_/ |____/ |_| |_| \____||_| |_| |_| ║
|
||||
║ Kubernetes Management Pod - Jump Box ║
|
||||
║ ║
|
||||
╠════════════════════════════════════════════════════════════════╣
|
||||
║ Toolbelt ║
|
||||
|
|
@ -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 ║
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
Port 22
|
||||
Protocol 2
|
||||
PermitRootLogin no
|
||||
PubkeyAuthentication yes
|
||||
|
|
|
|||
119
entrypoint.sh
119
entrypoint.sh
|
|
@ -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
|
||||
install_bootstrap_kubeconfig
|
||||
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}."
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -5,6 +5,7 @@ resources:
|
|||
- namespace.yaml
|
||||
- serviceaccount.yaml
|
||||
- rbac.yaml
|
||||
- pvc.yaml
|
||||
- deployment.yaml
|
||||
- service.yaml
|
||||
- networkpolicy.yaml
|
||||
|
|
|
|||
|
|
@ -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
10
k8s/pvc.yaml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: k8s-mgmt-pod-state
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
166
k8s/rbac.yaml
166
k8s/rbac.yaml
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
16
renovate.json
Normal 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}}}"
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue