Add OpenShift manifests for in-cluster build and runtime
- build chain: project, Gitea basic-auth source secret, output ImageStream, BuildConfig (Docker strategy, ubuntu:24.04 base), optional base-image ImageStream and Docker Hub pull secret - runtime: ServiceAccount + least-privilege SCC and its RoleBinding (VShell needs UID 0), PVC for config/certificate, Deployment with ImageStream trigger, ClusterIP Service, passthrough HTTPS Route - all manifests verified with 'oc create --dry-run=client' against the OpenShift 4.22 client scheme (12/12 decode cleanly)
This commit is contained in:
1 parent
a6f8e67976
commit
9dbe3749ba
14 files changed
+503
No files matched your search
@@ -96,6 +96,13 @@ user-namespace chroot on high ports and probes every listener from outside:
|
||||
SSH banner, FTPS banner, HTTPS/TLS handshake, implicit FTPS handshake and the
|
||||
HTTP→HTTPS redirect, after checking `-version`, `-test_config` and `-license`.
|
||||
|
||||
## OpenShift
|
||||
|
||||
`openshift/` holds drop-in manifests that build this image in-cluster
|
||||
(`BuildConfig` + `ImageStream` + Gitea source secret) and, optionally, run it
|
||||
(custom SCC, PVC, Deployment, Service, passthrough Route). See
|
||||
[openshift/README.md](openshift/README.md) for the apply order.
|
||||
|
||||
## Licensing
|
||||
|
||||
VShell is commercial, closed-source software distributed under the VanDyke
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# Project for the VShell image build and (optional) runtime objects.
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: vshell
|
||||
labels:
|
||||
app.kubernetes.io/name: vshell
|
||||
annotations:
|
||||
openshift.io/display-name: VShell
|
||||
openshift.io/description: VShell 5.0.1-3985 container image build (VanDyke SSH2/SFTP/FTPS/HTTPS server)
|
||||
@@ -0,0 +1,23 @@
|
||||
# Git credentials for the private Gitea repository that holds the Dockerfile,
|
||||
# the build tooling and the vendored .deb.
|
||||
#
|
||||
# The password is the Gitea access token (see ~/.dsh/AGENTS.md on the bastion).
|
||||
# Do not commit a real token here - create the secret from the command line:
|
||||
#
|
||||
# oc create secret generic gitea-vshell-source -n vshell \
|
||||
# --type=kubernetes.io/basic-auth \
|
||||
# --from-literal=username=mward4 \
|
||||
# --from-literal=password=<gitea-token>
|
||||
#
|
||||
# This file is the equivalent manifest if you prefer to apply it directly.
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: gitea-vshell-source
|
||||
namespace: vshell
|
||||
labels:
|
||||
app.kubernetes.io/name: vshell
|
||||
type: kubernetes.io/basic-auth
|
||||
stringData:
|
||||
username: mward4
|
||||
password: REPLACE_WITH_GITEA_TOKEN
|
||||
@@ -0,0 +1,18 @@
|
||||
# Output image stream for the built image. The build pushes vshell:latest here
|
||||
# and the runtime Deployment pulls from it.
|
||||
apiVersion: image.openshift.io/v1
|
||||
kind: ImageStream
|
||||
metadata:
|
||||
name: vshell
|
||||
namespace: vshell
|
||||
labels:
|
||||
app.kubernetes.io/name: vshell
|
||||
spec:
|
||||
lookupPolicy:
|
||||
local: true
|
||||
tags:
|
||||
- name: latest
|
||||
importPolicy:
|
||||
scheduled: false
|
||||
referencePolicy:
|
||||
type: Local
|
||||
@@ -0,0 +1,49 @@
|
||||
# In-cluster build of the VShell image.
|
||||
#
|
||||
# The repository already vendors the vendor .deb, so the only things the build
|
||||
# pod fetches from the network are:
|
||||
# - gitea.milesward.dev (this git repo, authenticated by gitea-vshell-source)
|
||||
# - registry-1.docker.io (base image ubuntu:24.04)
|
||||
# - archive.ubuntu.com (apt-get install openssl in the Dockerfile)
|
||||
# On an air-gapped cluster, mirror ubuntu:24.04 and openssl into an internal
|
||||
# registry first; see 04-base-imagestream.yaml and openshift/README.md.
|
||||
apiVersion: build.openshift.io/v1
|
||||
kind: BuildConfig
|
||||
metadata:
|
||||
name: vshell
|
||||
namespace: vshell
|
||||
labels:
|
||||
app.kubernetes.io/name: vshell
|
||||
spec:
|
||||
runPolicy: Serial
|
||||
source:
|
||||
type: Git
|
||||
git:
|
||||
uri: https://gitea.milesward.dev/mward4/vshell.git
|
||||
ref: main
|
||||
sourceSecret:
|
||||
name: gitea-vshell-source
|
||||
strategy:
|
||||
type: Docker
|
||||
dockerStrategy:
|
||||
dockerfilePath: Dockerfile
|
||||
from:
|
||||
kind: DockerImage
|
||||
name: docker.io/library/ubuntu:24.04
|
||||
# Pin the base by digest instead of tag when you need reproducible builds:
|
||||
# name: docker.io/library/ubuntu@sha256:a60ef8ec184edb5ed91f5983f00261359fb2fe20f603e8d488b9477d32fcad70
|
||||
output:
|
||||
to:
|
||||
kind: ImageStreamTag
|
||||
name: vshell:latest
|
||||
resources:
|
||||
requests:
|
||||
cpu: 500m
|
||||
memory: 1Gi
|
||||
limits:
|
||||
cpu: "2"
|
||||
memory: 4Gi
|
||||
successfulBuildsHistoryLimit: 5
|
||||
failedBuildsHistoryLimit: 3
|
||||
triggers:
|
||||
- type: ConfigChange
|
||||
@@ -0,0 +1,37 @@
|
||||
# Optional: keep the Ubuntu base image inside the cluster instead of pulling it
|
||||
# from Docker Hub on every build (rate limits, air-gapped clusters, or an
|
||||
# ImageDigestMirrorSet that rewrites docker.io).
|
||||
#
|
||||
# Import the tag once:
|
||||
# oc import-image ubuntu:24.04 -n vshell \
|
||||
# --from=docker.io/library/ubuntu:24.04 --confirm
|
||||
#
|
||||
# Then point the build at it in 03-buildconfig.yaml:
|
||||
# strategy:
|
||||
# dockerStrategy:
|
||||
# from:
|
||||
# kind: ImageStreamTag
|
||||
# name: ubuntu:24.04
|
||||
#
|
||||
# Equivalent one-shot without this file:
|
||||
# oc tag docker.io/library/ubuntu:24.04 vshell/ubuntu:24.04 --scheduled
|
||||
apiVersion: image.openshift.io/v1
|
||||
kind: ImageStream
|
||||
metadata:
|
||||
name: ubuntu
|
||||
namespace: vshell
|
||||
labels:
|
||||
app.kubernetes.io/name: vshell
|
||||
app.kubernetes.io/component: base-image
|
||||
spec:
|
||||
lookupPolicy:
|
||||
local: true
|
||||
tags:
|
||||
- name: "24.04"
|
||||
from:
|
||||
kind: DockerImage
|
||||
name: docker.io/library/ubuntu:24.04
|
||||
importPolicy:
|
||||
scheduled: false
|
||||
referencePolicy:
|
||||
type: Local
|
||||
@@ -0,0 +1,28 @@
|
||||
# Optional: Docker Hub credentials for the build's base-image pull.
|
||||
# Anonymous pulls of docker.io/library/ubuntu are rate limited and builds fail
|
||||
# with "toomanyrequests" on a busy cluster.
|
||||
#
|
||||
# Create it with your own credentials (do not commit them):
|
||||
#
|
||||
# oc create secret docker-registry dockerhub-pull -n vshell \
|
||||
# --docker-server=docker.io \
|
||||
# --docker-username=<dockerhub-user> \
|
||||
# --docker-password=<dockerhub-token-or-password>
|
||||
#
|
||||
# # let build pods use it for pulls
|
||||
# oc secrets link builder dockerhub-pull -n vshell --for=pull
|
||||
#
|
||||
# If the cluster already has a global pull secret, linking it is enough:
|
||||
# oc secrets link builder <global-pull-secret> -n vshell --for=pull
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: dockerhub-pull
|
||||
namespace: vshell
|
||||
labels:
|
||||
app.kubernetes.io/name: vshell
|
||||
app.kubernetes.io/component: base-image
|
||||
type: kubernetes.io/dockerconfigjson
|
||||
stringData:
|
||||
.dockerconfigjson: |
|
||||
{"auths":{"https://index.docker.io/v1/":{"username":"REPLACE","password":"REPLACE","auth":"REPLACE_BASE64_USER_COLON_PASSWORD"}}}
|
||||
@@ -0,0 +1,83 @@
|
||||
# OpenShift manifests for VShell
|
||||
|
||||
Everything needed to build `vshell:latest` inside an OpenShift project and, if you
|
||||
want, to run it there. Write-tested against the OpenShift 4.22 client scheme and
|
||||
matching the 4.21 cluster described in `/workspace/openshift-win11/FINDINGS.md`.
|
||||
|
||||
## Build (required)
|
||||
|
||||
| File | What it does |
|
||||
|---|---|
|
||||
| [`00-namespace.yaml`](00-namespace.yaml) | Project `vshell`. |
|
||||
| [`01-gitea-source-secret.yaml`](01-gitea-source-secret.yaml) | `kubernetes.io/basic-auth` secret for the private Gitea repo. |
|
||||
| [`02-vshell-imagestream.yaml`](02-vshell-imagestream.yaml) | Output `ImageStream/vshell` (`latest`). |
|
||||
| [`03-buildconfig.yaml`](03-buildconfig.yaml) | `BuildConfig/vshell` — Docker strategy, git source, `ubuntu:24.04` base. |
|
||||
| [`04-base-imagestream.yaml`](04-base-imagestream.yaml) | Optional: keep the Ubuntu base inside the cluster instead of pulling Docker Hub on every build. |
|
||||
| [`05-dockerhub-pull-secret.yaml`](05-dockerhub-pull-secret.yaml) | Optional: Docker Hub credentials so base pulls are not rate limited. |
|
||||
|
||||
```sh
|
||||
oc apply -f 00-namespace.yaml
|
||||
|
||||
# password = the Gitea token from ~/.dsh/AGENTS.md (do not commit it into 01-*.yaml)
|
||||
oc create secret generic gitea-vshell-source -n vshell \
|
||||
--type=kubernetes.io/basic-auth \
|
||||
--from-literal=username=mward4 \
|
||||
--from-literal=password=<gitea-token>
|
||||
|
||||
oc apply -f 02-vshell-imagestream.yaml
|
||||
oc apply -f 03-buildconfig.yaml
|
||||
|
||||
oc start-build vshell -n vshell --follow
|
||||
oc get is vshell -n vshell
|
||||
```
|
||||
|
||||
The repository vendors the vendor `.deb`, so the build pod only needs egress to
|
||||
`gitea.milesward.dev`, `registry-1.docker.io` and `archive.ubuntu.com`
|
||||
(`apt-get install openssl`). Air-gapped clusters: import `ubuntu:24.04` with
|
||||
`04-base-imagestream.yaml`, point `strategy.dockerStrategy.from` at
|
||||
`ImageStreamTag ubuntu:24.04`, and add an internal apt mirror.
|
||||
|
||||
`replicas: 1` is not a build concern but a runtime one: the daemons own fixed
|
||||
ports and a single host key, so never scale the Deployment.
|
||||
|
||||
## Runtime (optional)
|
||||
|
||||
| File | What it does |
|
||||
|---|---|
|
||||
| [`runtime/00-serviceaccount.yaml`](runtime/00-serviceaccount.yaml) | `ServiceAccount/vshell`. |
|
||||
| [`runtime/01-scc.yaml`](runtime/01-scc.yaml) | Least-privilege SCC that allows UID 0 + Role/RoleBinding so only this SA may use it. |
|
||||
| [`runtime/02-pvc.yaml`](runtime/02-pvc.yaml) | `PersistentVolumeClaim/vshell-data` for config + TLS certificate. |
|
||||
| [`runtime/03-deployment.yaml`](runtime/03-deployment.yaml) | Deployment, auto-updated by the `image.openshift.io/triggers` annotation. |
|
||||
| [`runtime/04-service.yaml`](runtime/04-service.yaml) | ClusterIP service for ports 22/21/990/80/443. |
|
||||
| [`runtime/05-route.yaml`](runtime/05-route.yaml) | HTTPS Route, **passthrough** termination. |
|
||||
|
||||
```sh
|
||||
oc apply -f runtime/
|
||||
oc get route vshell-https -n vshell
|
||||
```
|
||||
|
||||
`restricted-v2` forbids UID 0, so the SCC (or `oc adm policy add-scc-to-user anyuid -z vshell -n vshell`)
|
||||
is required — VShell reads `/etc/shadow` through PAM, writes `/var/run/*.pid`
|
||||
and creates root-owned host keys. The custom SCC grants no extra capabilities
|
||||
and no host access.
|
||||
|
||||
The Route is passthrough because `vshell-httpsd` terminates TLS itself with the
|
||||
PKCS#12 certificate the entrypoint generated; clients therefore see a
|
||||
self-signed certificate until you mount a real one at `$VSHELL_TLS_P12`. SSH
|
||||
(22), explicit FTPS (21) and implicit FTPS (990) cannot go through a Route —
|
||||
expose the Service with a NodePort/LoadBalancer or an external load balancer.
|
||||
|
||||
The Deployment points `VSHELL_CONFIG` and `VSHELL_TLS_P12` at the PVC-mounted
|
||||
`/data`, which keeps the vendor defaults and the `HttpsFiles/` web assets
|
||||
visible. The SSH host key stays at `/usr/local/etc/vshelld_hostkey` unless you
|
||||
set `HostKeyFilenames /data/vshelld_hostkey` in the generated config.
|
||||
|
||||
## Caveats
|
||||
|
||||
- Builds run as root inside the build pod by default, which the Dockerfile's
|
||||
`apt-get` and `dpkg -i` need. Unprivileged builds (`BUILD_PRIVILEGED=false`)
|
||||
are an opt-in Developer Preview feature and would break this Dockerfile.
|
||||
- VShell runs under its 60-day evaluation license (expires 2026-12-04) until a
|
||||
license file is mounted and `LicenseFilePath` is set.
|
||||
- The image and repository are private because VShell is commercial software
|
||||
distributed under the VanDyke Software EULA.
|
||||
@@ -0,0 +1,10 @@
|
||||
# Service account the VShell pod runs as. It is bound to the custom SCC in
|
||||
# 01-scc.yaml, because VShell must run as root (PAM reads /etc/shadow, the
|
||||
# daemons write /var/run/*.pid and generate host keys owned by root).
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: vshell
|
||||
namespace: vshell
|
||||
labels:
|
||||
app.kubernetes.io/name: vshell
|
||||
@@ -0,0 +1,73 @@
|
||||
# OpenShift runs pods under restricted-v2 by default, which forbids UID 0.
|
||||
# VShell needs root, so grant a narrow, dedicated SCC instead of anyuid.
|
||||
#
|
||||
# Quick alternative (broader, uses the stock anyuid SCC):
|
||||
# oc adm policy add-scc-to-user anyuid -z vshell -n vshell
|
||||
#
|
||||
# This SCC is the least-privilege version: no privileged containers, no host
|
||||
# namespaces, no hostPath, no extra capabilities, but RunAsAny so the image's
|
||||
# root user is allowed.
|
||||
apiVersion: security.openshift.io/v1
|
||||
kind: SecurityContextConstraints
|
||||
metadata:
|
||||
name: vshell-root
|
||||
labels:
|
||||
app.kubernetes.io/name: vshell
|
||||
allowHostDirVolumePlugin: false
|
||||
allowHostIPC: false
|
||||
allowHostNetwork: false
|
||||
allowHostPID: false
|
||||
allowHostPorts: false
|
||||
allowPrivilegedContainer: false
|
||||
allowPrivilegeEscalation: true
|
||||
allowedCapabilities: []
|
||||
defaultAddCapabilities: []
|
||||
requiredDropCapabilities: []
|
||||
readOnlyRootFilesystem: false
|
||||
runAsUser:
|
||||
type: RunAsAny
|
||||
seLinuxContext:
|
||||
type: MustRunAs
|
||||
fsGroup:
|
||||
type: RunAsAny
|
||||
supplementalGroups:
|
||||
type: RunAsAny
|
||||
seccompProfiles:
|
||||
- runtime/default
|
||||
volumes:
|
||||
- configMap
|
||||
- downwardAPI
|
||||
- emptyDir
|
||||
- persistentVolumeClaim
|
||||
- projected
|
||||
- secret
|
||||
---
|
||||
# Namespaced RBAC so only the vshell service account may use that SCC.
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: vshell-scc-use
|
||||
namespace: vshell
|
||||
rules:
|
||||
- apiGroups:
|
||||
- security.openshift.io
|
||||
resources:
|
||||
- securitycontextconstraints
|
||||
resourceNames:
|
||||
- vshell-root
|
||||
verbs:
|
||||
- use
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: vshell-scc-use
|
||||
namespace: vshell
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: vshell-scc-use
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: vshell
|
||||
namespace: vshell
|
||||
@@ -0,0 +1,20 @@
|
||||
# Persistence for the generated server configuration and the self-signed TLS
|
||||
# certificate. The entrypoint is pointed at this volume with VSHELL_CONFIG and
|
||||
# VSHELL_TLS_P12 in the Deployment, so the vendor defaults and the HttpsFiles/
|
||||
# web assets under /usr/local/etc stay visible.
|
||||
#
|
||||
# The SSH host key path is not redirected here; set HostKeyFilenames in
|
||||
# vshelld_config to /data/vshelld_hostkey if you want it to survive restarts too.
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: vshell-data
|
||||
namespace: vshell
|
||||
labels:
|
||||
app.kubernetes.io/name: vshell
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
@@ -0,0 +1,81 @@
|
||||
# VShell runtime. Single replica: the daemons own fixed ports and one host key,
|
||||
# so they are not horizontally scalable.
|
||||
#
|
||||
# The image tag is kept in sync by the image.openshift.io/triggers annotation,
|
||||
# which fires whenever a new vshell:latest build finishes.
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: vshell
|
||||
namespace: vshell
|
||||
labels:
|
||||
app.kubernetes.io/name: vshell
|
||||
annotations:
|
||||
image.openshift.io/triggers: |
|
||||
[{"from":{"kind":"ImageStreamTag","name":"vshell:latest"},"fieldPath":"spec.template.spec.containers[0].image"}]
|
||||
spec:
|
||||
replicas: 1
|
||||
strategy:
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: vshell
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: vshell
|
||||
spec:
|
||||
serviceAccountName: vshell
|
||||
securityContext:
|
||||
runAsUser: 0
|
||||
runAsGroup: 0
|
||||
fsGroup: 0
|
||||
containers:
|
||||
- name: vshell
|
||||
image: vshell:latest
|
||||
imagePullPolicy: Always
|
||||
env:
|
||||
- name: VSHELL_CONFIG
|
||||
value: /data/vshelld_config
|
||||
- name: VSHELL_TLS_P12
|
||||
value: /data/vshell-https.p12
|
||||
ports:
|
||||
- name: ssh
|
||||
containerPort: 22
|
||||
protocol: TCP
|
||||
- name: ftps
|
||||
containerPort: 21
|
||||
protocol: TCP
|
||||
- name: ftps-implicit
|
||||
containerPort: 990
|
||||
protocol: TCP
|
||||
- name: http
|
||||
containerPort: 80
|
||||
protocol: TCP
|
||||
- name: https
|
||||
containerPort: 443
|
||||
protocol: TCP
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /data
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: ssh
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: ssh
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 20
|
||||
resources:
|
||||
requests:
|
||||
cpu: 250m
|
||||
memory: 512Mi
|
||||
limits:
|
||||
cpu: "2"
|
||||
memory: 2Gi
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: vshell-data
|
||||
@@ -0,0 +1,37 @@
|
||||
# Cluster-internal entry point for all four VShell listeners.
|
||||
#
|
||||
# Only 80/443 can be exposed through an OpenShift Route. SSH (22), explicit FTPS
|
||||
# (21) and implicit FTPS (990) need a NodePort/LoadBalancer service or an
|
||||
# external load balancer in front of this ClusterIP service.
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: vshell
|
||||
namespace: vshell
|
||||
labels:
|
||||
app.kubernetes.io/name: vshell
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app.kubernetes.io/name: vshell
|
||||
ports:
|
||||
- name: ssh
|
||||
port: 22
|
||||
targetPort: ssh
|
||||
protocol: TCP
|
||||
- name: ftps
|
||||
port: 21
|
||||
targetPort: ftps
|
||||
protocol: TCP
|
||||
- name: ftps-implicit
|
||||
port: 990
|
||||
targetPort: ftps-implicit
|
||||
protocol: TCP
|
||||
- name: http
|
||||
port: 80
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
- name: https
|
||||
port: 443
|
||||
targetPort: https
|
||||
protocol: TCP
|
||||
@@ -0,0 +1,27 @@
|
||||
# HTTPS access through the OpenShift router.
|
||||
#
|
||||
# Termination is passthrough: vshell-httpsd does its own TLS with the PKCS#12
|
||||
# certificate the entrypoint generated, so the router must not decrypt. Clients
|
||||
# therefore see the container's self-signed certificate unless you mount a real
|
||||
# certificate at $VSHELL_TLS_P12.
|
||||
#
|
||||
# HTTP is not routed: VShell's port-80 listener only redirects to https://<host>:443
|
||||
# and the redirect target uses the container's own hostname.
|
||||
apiVersion: route.openshift.io/v1
|
||||
kind: Route
|
||||
metadata:
|
||||
name: vshell-https
|
||||
namespace: vshell
|
||||
labels:
|
||||
app.kubernetes.io/name: vshell
|
||||
spec:
|
||||
to:
|
||||
kind: Service
|
||||
name: vshell
|
||||
weight: 100
|
||||
port:
|
||||
targetPort: https
|
||||
tls:
|
||||
termination: passthrough
|
||||
insecureEdgeTerminationPolicy: None
|
||||
wildcardPolicy: None
|
||||
Reference in new issue
Block a user