initial commit
All checks were successful
Build k8s-mgmt-pod image / Build & Push (push) Successful in 2m21s
All checks were successful
Build k8s-mgmt-pod image / Build & Push (push) Successful in 2m21s
This commit is contained in:
commit
4dbcde381f
20 changed files with 1151 additions and 0 deletions
82
k8s/README.md
Normal file
82
k8s/README.md
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
# Kubernetes deployment
|
||||
|
||||
These manifests deploy `k8s-mgmt-pod` as a single-replica bastion deployment inside its own namespace.
|
||||
|
||||
## Before you apply
|
||||
|
||||
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`.
|
||||
|
||||
## Create the Secret
|
||||
|
||||
Public-key path:
|
||||
|
||||
```bash
|
||||
kubectl -n k8s-mgmt-pod create secret generic k8s-mgmt-pod-auth \
|
||||
--from-literal=ADMIN_SSH_PUBKEY="$(cat ~/.ssh/id_ed25519.pub)" \
|
||||
--from-literal=USER_SSH_PUBKEY="$(cat ~/.ssh/id_ed25519.pub)" \
|
||||
--from-literal=ADMIN_PASSWORD='' \
|
||||
--from-literal=USER_PASSWORD=''
|
||||
```
|
||||
|
||||
Password path:
|
||||
|
||||
```bash
|
||||
kubectl -n k8s-mgmt-pod create secret generic k8s-mgmt-pod-auth \
|
||||
--from-literal=ADMIN_SSH_PUBKEY='' \
|
||||
--from-literal=USER_SSH_PUBKEY='' \
|
||||
--from-literal=ADMIN_PASSWORD='change-this-admin-password' \
|
||||
--from-literal=USER_PASSWORD='change-this-user-password'
|
||||
```
|
||||
|
||||
Mixed key-plus-password path:
|
||||
|
||||
```bash
|
||||
kubectl -n k8s-mgmt-pod create secret generic k8s-mgmt-pod-auth \
|
||||
--from-literal=ADMIN_SSH_PUBKEY="$(cat ~/.ssh/id_ed25519.pub)" \
|
||||
--from-literal=USER_SSH_PUBKEY="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIexample user@example" \
|
||||
--from-literal=ADMIN_PASSWORD='change-this-admin-password' \
|
||||
--from-literal=USER_PASSWORD='change-this-user-password'
|
||||
```
|
||||
|
||||
With that configuration, both `admin` and `user` can authenticate with either their SSH key or their password.
|
||||
|
||||
Resolution order is per user:
|
||||
|
||||
1. If a public key is supplied, key-based login is enabled.
|
||||
2. If a password is also supplied, password login is enabled too.
|
||||
3. If only a public key is supplied, password login is locked for that user.
|
||||
4. If neither is supplied, the container generates and logs a random password for that user.
|
||||
|
||||
## Apply
|
||||
|
||||
```bash
|
||||
kubectl apply -k k8s/
|
||||
```
|
||||
|
||||
If you prefer individual files, apply `namespace.yaml`, `serviceaccount.yaml`, `rbac.yaml`, `deployment.yaml`, `service.yaml`, and `networkpolicy.yaml` after creating the real Secret.
|
||||
|
||||
## Safe local access
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
Then connect with:
|
||||
|
||||
```bash
|
||||
ssh admin@localhost -p 2222
|
||||
```
|
||||
|
||||
Or open `http://localhost:3000/ssh` in a browser.
|
||||
|
||||
## RBAC note
|
||||
|
||||
The included Role is the most security-sensitive part of this deployment. It is namespace-scoped and avoids `cluster-admin`, but it still grants broad read/write access to common namespaced resources so `lfk` remains usable as a management tool. In particular, access to `secrets`, `pods/exec`, and workload mutation verbs may be too broad for your environment and should be reduced wherever possible.
|
||||
|
||||
## Bastion note
|
||||
|
||||
This pod is a privileged operational entry point into the cluster. Treat it like a bastion: restrict who can reach it, monitor access, rotate credentials, and avoid using it as a general-purpose workstation.
|
||||
92
k8s/deployment.yaml
Normal file
92
k8s/deployment.yaml
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: k8s-mgmt-pod
|
||||
spec:
|
||||
replicas: 1
|
||||
strategy:
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: k8s-mgmt-pod
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: k8s-mgmt-pod
|
||||
spec:
|
||||
serviceAccountName: k8s-mgmt-pod
|
||||
automountServiceAccountToken: true
|
||||
securityContext:
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: k8s-mgmt-pod
|
||||
# Replace this placeholder with a pinned tag or digest in production.
|
||||
image: ghcr.io/<org>/<repo>/k8s-mgmt-pod:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- name: ssh
|
||||
containerPort: 22
|
||||
protocol: TCP
|
||||
- name: webssh
|
||||
containerPort: 3000
|
||||
protocol: TCP
|
||||
env:
|
||||
- name: ADMIN_SSH_PUBKEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: k8s-mgmt-pod-auth
|
||||
key: ADMIN_SSH_PUBKEY
|
||||
optional: true
|
||||
- name: USER_SSH_PUBKEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: k8s-mgmt-pod-auth
|
||||
key: USER_SSH_PUBKEY
|
||||
optional: true
|
||||
- name: ADMIN_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: k8s-mgmt-pod-auth
|
||||
key: ADMIN_PASSWORD
|
||||
optional: true
|
||||
- name: USER_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: k8s-mgmt-pod-auth
|
||||
key: USER_PASSWORD
|
||||
optional: true
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 256Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: 22
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: 22
|
||||
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"]
|
||||
volumes:
|
||||
- name: state
|
||||
emptyDir: {}
|
||||
10
k8s/kustomization.yaml
Normal file
10
k8s/kustomization.yaml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: k8s-mgmt-pod
|
||||
resources:
|
||||
- namespace.yaml
|
||||
- serviceaccount.yaml
|
||||
- rbac.yaml
|
||||
- deployment.yaml
|
||||
- service.yaml
|
||||
- networkpolicy.yaml
|
||||
7
k8s/namespace.yaml
Normal file
7
k8s/namespace.yaml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: k8s-mgmt-pod
|
||||
labels:
|
||||
app.kubernetes.io/name: k8s-mgmt-pod
|
||||
k8s-mgmt-pod-access: "false"
|
||||
24
k8s/networkpolicy.yaml
Normal file
24
k8s/networkpolicy.yaml
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: k8s-mgmt-pod
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: k8s-mgmt-pod
|
||||
policyTypes:
|
||||
- Ingress
|
||||
ingress:
|
||||
# Starting point only. Adjust these selectors to the namespaces or pods that should reach the bastion.
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: k8s-mgmt-pod
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
k8s-mgmt-pod-access: "true"
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 22
|
||||
- protocol: TCP
|
||||
port: 3000
|
||||
49
k8s/rbac.yaml
Normal file
49
k8s/rbac.yaml
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
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
|
||||
- persistentvolumeclaims
|
||||
- pods
|
||||
- pods/exec
|
||||
- pods/log
|
||||
- secrets
|
||||
- serviceaccounts
|
||||
- services
|
||||
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
|
||||
- apiGroups: ["apps"]
|
||||
resources:
|
||||
- daemonsets
|
||||
- deployments
|
||||
- replicasets
|
||||
- statefulsets
|
||||
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
|
||||
- apiGroups: ["batch"]
|
||||
resources:
|
||||
- cronjobs
|
||||
- jobs
|
||||
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
|
||||
- apiGroups: ["networking.k8s.io"]
|
||||
resources:
|
||||
- ingresses
|
||||
- networkpolicies
|
||||
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: k8s-mgmt-pod
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: k8s-mgmt-pod
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: k8s-mgmt-pod
|
||||
15
k8s/secret.yaml.example
Normal file
15
k8s/secret.yaml.example
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# Example only. Do not commit a real Secret object with live credentials or keys.
|
||||
# Create the real secret out-of-band with kubectl, Sealed Secrets, External Secrets,
|
||||
# or your preferred secret manager.
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: k8s-mgmt-pod-auth
|
||||
type: Opaque
|
||||
stringData:
|
||||
ADMIN_SSH_PUBKEY: |
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIexample admin@example
|
||||
USER_SSH_PUBKEY: |
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIexample user@example
|
||||
ADMIN_PASSWORD: "change-this-admin-password"
|
||||
USER_PASSWORD: "change-this-user-password"
|
||||
34
k8s/service.yaml
Normal file
34
k8s/service.yaml
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: k8s-mgmt-pod
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app.kubernetes.io/name: k8s-mgmt-pod
|
||||
ports:
|
||||
- name: ssh
|
||||
port: 22
|
||||
targetPort: ssh
|
||||
protocol: TCP
|
||||
- name: webssh
|
||||
port: 3000
|
||||
targetPort: webssh
|
||||
protocol: TCP
|
||||
|
||||
# Expose externally only if you explicitly want SSH and web SSH reachable from outside the cluster.
|
||||
# apiVersion: v1
|
||||
# kind: Service
|
||||
# metadata:
|
||||
# name: k8s-mgmt-pod-external
|
||||
# spec:
|
||||
# type: LoadBalancer
|
||||
# selector:
|
||||
# app.kubernetes.io/name: k8s-mgmt-pod
|
||||
# ports:
|
||||
# - name: ssh
|
||||
# port: 22
|
||||
# targetPort: ssh
|
||||
# - name: webssh
|
||||
# port: 3000
|
||||
# targetPort: webssh
|
||||
4
k8s/serviceaccount.yaml
Normal file
4
k8s/serviceaccount.yaml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: k8s-mgmt-pod
|
||||
Loading…
Add table
Add a link
Reference in a new issue