diff --git a/README.md b/README.md index 1d4f811..33d9d6f 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ Details: [Getting started — writable paths](./Studio/doc/getting-started-hck-c | Guide | When to use it | | --- | --- | | **[Getting started](./Studio/doc/getting-started-hck-cli.md)** | First run — `/data` + `/tmp` mounts, deployment profiles, quick commands | +| **[Image variants, preflight, arbitrary UID](./Studio/doc/image-variants.md)** | Ubuntu vs `-hardened` (DHI) tags, startup checks, OpenShift/K8s UID | | **[Offline pipeline example](./Studio/doc/example-offline-metadata-pipeline.md)** | **Full CI walkthrough** — image upgrade, offline license, revEng → compMod → DDL → docs | | **[Docker CLI how-to](./Studio/doc/docker-cli-howto.md)** | `docker run` without Compose (local and hardened) | | **[License validation](./Studio/doc/license-validation.md)** | Online / offline floating license | diff --git a/Studio/README.md b/Studio/README.md index f3f9b5b..22f56e8 100644 --- a/Studio/README.md +++ b/Studio/README.md @@ -23,6 +23,7 @@ Full breakdown: [getting-started-hck-cli.md](./doc/getting-started-hck-cli.md#wr | Guide | Description | | --- | --- | | **[getting-started-hck-cli.md](./doc/getting-started-hck-cli.md)** | Start here — `/data` + `/tmp` layout, profiles, quick start | +| **[image-variants.md](./doc/image-variants.md)** | Ubuntu vs `-hardened` (DHI) tags, startup preflight, arbitrary UID | | **[example-offline-metadata-pipeline.md](./doc/example-offline-metadata-pipeline.md)** | **Full worked example** — image upgrade, offline license, `version` / `showLicense` / `listLogs` / `showLogs`, revEng → compMod → forweng → genDoc | | **[docker-cli-howto.md](./doc/docker-cli-howto.md)** | `docker run` templates (local and hardened) | | **[license-validation.md](./doc/license-validation.md)** | Floating license — online and offline | @@ -41,7 +42,7 @@ Always use **`run --rm`** for one-off CLI jobs — without it, stopped `…-run- | File | Profile | | --- | --- | | [compose.yml](./compose.yml) | Local — `/data` + `/tmp`, writable rootfs | -| [compose.hardened.yml](./compose.hardened.yml) | **Hardened** (CI / production) — read-only rootfs, `cap_drop: ALL` | +| [compose.hardened.yml](./compose.hardened.yml) | **Runtime profile** (CI / production) — read-only rootfs, `cap_drop: ALL`. Works with the Ubuntu tag or `*-hardened` | | [k8s/](./k8s/) | Kubernetes — same mounts + Restricted Pod Security Standard | ## Custom builds (legacy) diff --git a/Studio/compose.hardened.yml b/Studio/compose.hardened.yml index 042da1c..c0ce27c 100644 --- a/Studio/compose.hardened.yml +++ b/Studio/compose.hardened.yml @@ -19,6 +19,11 @@ # - read-only root filesystem # - all capabilities dropped, no privilege escalation # - non-root user (1000:1001 by default) +# - group_add: ["0"] so an overridden user: still matches OpenShift (arbitrary UID, group 0) +# +# Image tag: hackolade/hck-cli: (Ubuntu 26.04 LTS as of 8.12.8) or +# -hardened (Docker Hardened Debian). The file name is the runtime +# profile, not the -hardened tag. # # Usage: # docker compose -f compose.hardened.yml run --rm hck-cli @@ -28,15 +33,20 @@ # For the same /data + /tmp layout without read-only rootfs, use compose.yml. # For Kubernetes manifests with a PVC, see k8s/hck-cli-job.yaml. # Documentation: doc/getting-started-hck-cli.md +# Image tags, preflight, arbitrary UID: doc/image-variants.md services: hck-cli: &hck-cli init: true - image: hackolade/hck-cli:8.12.7 + image: hackolade/hck-cli:8.12.8 command: ["version"] restart: 'no' read_only: true user: "1000:1001" + # OpenShift restricted-v2: arbitrary UID in group 0. Keep GID 0 as a supplemental group + # so overriding user: to ":" still matches the cluster (same as fsGroup: 0). + group_add: + - "0" cap_drop: - ALL security_opt: @@ -73,6 +83,8 @@ services: # OpenShift restricted-v2 assigns an arbitrary UID in the root supplementary group. # Proves nss_wrapper and group-0 ownership of /data work without a passwd entry. + # Primary GID may also be the assigned UID (user: ":"); group_add: ["0"] on + # the base service still grants write to /data. hck-cli-arbitrary-uid: <<: *hck-cli user: "31337:0" diff --git a/Studio/compose.yml b/Studio/compose.yml index 9d4b4a8..8413343 100644 --- a/Studio/compose.yml +++ b/Studio/compose.yml @@ -17,6 +17,7 @@ # on a writable rootfs, but use /data + /tmp for new setups (see getting-started-hck-cli.md). # # Same two paths in compose.hardened.yml and k8s/ — hardened adds read-only rootfs + caps. +# Image: hackolade/hck-cli: or -hardened (see doc/image-variants.md). # # Documentation: doc/getting-started-hck-cli.md # Custom builds on hackolade/studio: docker-compose.yml + doc/getting-started.md @@ -33,7 +34,7 @@ services: # --model '/data/models/MongoDB/Yelp Challenge dataset.hck.json' \ # --doc /data/output/doc-test --jsonSchema hck-cli: &hck-cli - image: hackolade/hck-cli:8.12.7 + image: hackolade/hck-cli:8.12.8 command: ["version"] restart: 'no' # network_mode: 'none' diff --git a/Studio/doc/docker-cli-howto.md b/Studio/doc/docker-cli-howto.md index 5a78046..cd5ba1b 100644 --- a/Studio/doc/docker-cli-howto.md +++ b/Studio/doc/docker-cli-howto.md @@ -8,7 +8,7 @@ Every example below includes both mounts. For a full end-to-end pipeline (offline license, revEng, compMod, forweng, genDoc), see **[example-offline-metadata-pipeline.md](./example-offline-metadata-pipeline.md)**. -Pin the image tag (example: `hackolade/hck-cli:8.12.7`). `latest` is not published. +Pin the image tag (example: `hackolade/hck-cli:8.12.8` or `hackolade/hck-cli:8.12.8-hardened`). `latest` is not published. Ubuntu vs Docker Hardened Image, startup preflight, and arbitrary UID: **[image-variants.md](./image-variants.md)**. ## Local profile @@ -21,26 +21,38 @@ docker run --rm \ -v hackolade-studio-data:/data \ -v "${PWD}/models:/data/models" \ --tmpfs /tmp:rw,size=1g,mode=1777 \ - hackolade/hck-cli:8.12.7 version + hackolade/hck-cli:8.12.8 version ``` Expect `Hackolade version: …` and an `Installed plugins (N):` list (name, version, commit, build date per plugin). ## Hardened profile (CI / production) -Read-only root filesystem, non-root, dropped capabilities — mirrors [`compose.hardened.yml`](../compose.hardened.yml): +Read-only root filesystem, non-root, dropped capabilities — mirrors [`compose.hardened.yml`](../compose.hardened.yml). `group_add` / `--group-add 0` keeps **group 0** so an overridden UID can still write `/data` (OpenShift `restricted-v2` does the same). Either image tag works: ```bash docker run --rm \ --init \ --read-only \ --user 1000:1001 \ + --group-add 0 \ --cap-drop ALL \ --security-opt no-new-privileges:true \ -v hackolade-studio-data:/data \ -v "${PWD}/models:/data/models" \ --tmpfs /tmp:rw,size=1g,mode=1777 \ - hackolade/hck-cli:8.12.7 version + hackolade/hck-cli:8.12.8 version +``` + +OpenShift-style arbitrary UID (no passwd entry, group 0): + +```bash +docker run --rm \ + --init --read-only --user 31337:0 --group-add 0 \ + --cap-drop ALL --security-opt no-new-privileges:true \ + -v hackolade-studio-data:/data \ + --tmpfs /tmp:rw,size=1g,mode=1777 \ + hackolade/hck-cli:8.12.8-hardened version ``` ## Diagnostics @@ -48,10 +60,10 @@ docker run --rm \ Wrapper commands — no full Studio session: ```bash -docker run --rm ... hackolade/hck-cli:8.12.7 showLicense -docker run --rm ... hackolade/hck-cli:8.12.7 showLicense --json -docker run --rm ... hackolade/hck-cli:8.12.7 listLogs -docker run --rm ... hackolade/hck-cli:8.12.7 showLogs --tail 50 --logfile re +docker run --rm ... hackolade/hck-cli:8.12.8 showLicense +docker run --rm ... hackolade/hck-cli:8.12.8 showLicense --json +docker run --rm ... hackolade/hck-cli:8.12.8 listLogs +docker run --rm ... hackolade/hck-cli:8.12.8 showLogs --tail 50 --logfile re ``` Replace `...` with the same volume and security flags as above. Details: [getting-started-hck-cli.md](./getting-started-hck-cli.md#inspecting-the-image-license-and-logs). @@ -60,12 +72,12 @@ Replace `...` with the same volume and security flags as above. Details: [gettin ```bash docker run --rm \ - --init --read-only --user 1000:1001 --cap-drop ALL \ + --init --read-only --user 1000:1001 --group-add 0 --cap-drop ALL \ --security-opt no-new-privileges:true \ -v hackolade-studio-data:/data \ -v "${PWD}/models:/data/models" \ --tmpfs /tmp:rw,size=1g,mode=1777 \ - hackolade/hck-cli:8.12.7 genDoc \ + hackolade/hck-cli:8.12.8 genDoc \ --format=HTML \ --model /data/models/my-model.hck.json \ --doc /data/output/doc @@ -79,11 +91,12 @@ docker run --rm --user root \ -v hackolade-studio-data:/data:ro \ -v "${PWD}/artifacts:/host" \ --entrypoint cp \ - hackolade/hck-cli:8.12.7 -r /data/output/. /host/ + hackolade/hck-cli:8.12.8 -r /data/output/. /host/ ``` ## See also - [Getting started with hck-cli](./getting-started-hck-cli.md) +- [Image variants, preflight, arbitrary UID](./image-variants.md) - [Example: offline metadata pipeline](./example-offline-metadata-pipeline.md) - [License validation](./license-validation.md) diff --git a/Studio/doc/example-offline-metadata-pipeline.md b/Studio/doc/example-offline-metadata-pipeline.md index 2e517b7..61254d2 100644 --- a/Studio/doc/example-offline-metadata-pipeline.md +++ b/Studio/doc/example-offline-metadata-pipeline.md @@ -4,7 +4,7 @@ End-to-end story using [`compose.hardened.yml`](../compose.hardened.yml): a new `hackolade/hck-cli` image is available, your runner has **no Internet**, and you want to refresh a production model, diff it against a baseline, then publish **DDL** and **documentation**. -Every step uses the hardened profile (read-only rootfs) with **consolidated `/data` + `/tmp` mounts** (recommended over legacy `/home/hackolade/…` paths). +Every step uses the hardened **runtime profile** (read-only rootfs) with **consolidated `/data` + `/tmp` mounts** (recommended over legacy `/home/hackolade/…` paths). Point `image:` at `hackolade/hck-cli:` or `-hardened` — [image-variants.md](./image-variants.md). ```bash cd /path/to/docker/Studio diff --git a/Studio/doc/getting-started-hck-cli.md b/Studio/doc/getting-started-hck-cli.md index 75eb515..8065117 100644 --- a/Studio/doc/getting-started-hck-cli.md +++ b/Studio/doc/getting-started-hck-cli.md @@ -9,6 +9,7 @@ Ready-to-use Docker image: Hackolade Studio CLI, all target plugins, no build st | Guide | Use when | | --- | --- | | **This page** | First run, deployment profiles, troubleshooting | +| **[Image variants, preflight, arbitrary UID](./image-variants.md)** | Ubuntu vs `-hardened` (DHI) tags, startup checks, OpenShift/K8s UID | | **[Offline pipeline example](./example-offline-metadata-pipeline.md)** | **Complete CI story** — new image tag, offline license, revEng → compMod → DDL → docs, plus `version`, `showLicense`, `listLogs`, `showLogs` | | **[Docker CLI how-to](./docker-cli-howto.md)** | You prefer **`docker run`** instead of Compose | | **[License validation](./license-validation.md)** | Online or offline floating license setup | @@ -35,7 +36,7 @@ This is the same rule in [`compose.yml`](../compose.yml), [`compose.hardened.yml | Profile | Compose / manifest | Read-only rootfs | Extra hardening | | --- | --- | --- | --- | | **Local** | [`compose.yml`](../compose.yml) | No | Same `/data` + `/tmp` mounts | -| **Hardened** (recommended for CI / production) | [`compose.hardened.yml`](../compose.hardened.yml) | Yes | `cap_drop: ALL`, non-root, no privilege escalation | +| **Hardened** (recommended for CI / production) | [`compose.hardened.yml`](../compose.hardened.yml) | Yes | `cap_drop: ALL`, non-root, no privilege escalation. Runtime profile — not the `-hardened` image tag | | **Kubernetes** | [`k8s/hck-cli-job.yaml`](../k8s/hck-cli-job.yaml) | Yes | Same as hardened Compose (Restricted Pod Security Standard) | ```bash @@ -44,12 +45,14 @@ This is the same rule in [`compose.yml`](../compose.yml), [`compose.hardened.yml docker compose -f compose.hardened.yml run --rm hck-cli ``` -OpenShift arbitrary UID: [`compose.hardened.yml`](../compose.hardened.yml) (`hck-cli-arbitrary-uid`) or [`k8s/hck-cli-job-openshift.yaml`](../k8s/hck-cli-job-openshift.yaml). +OpenShift arbitrary UID: [`compose.hardened.yml`](../compose.hardened.yml) (`hck-cli-arbitrary-uid`) or [`k8s/hck-cli-job-openshift.yaml`](../k8s/hck-cli-job-openshift.yaml). Details: [image-variants.md](./image-variants.md#arbitrary-uid). + +`compose.hardened.yml` is the **runtime profile** (read-only rootfs). The **`-hardened` image tag** is a different OS base (Docker Hardened Debian). Either tag works with either compose file — [image-variants.md](./image-variants.md). ## Before you start - **Floating license only** — workstation licenses do not work in Docker. -- **Pin a version tag** — `latest` is not published. Example: `hackolade/hck-cli:8.12.7`. Weekly plugin refreshes may appear as `8.12.7-YYYY-MM-DD` on the [current release only](https://hub.docker.com/r/hackolade/hck-cli/tags). +- **Pin a version tag** — `latest` is not published. Example: `hackolade/hck-cli:8.12.8` (**Ubuntu 26.04 LTS**) or `hackolade/hck-cli:8.12.8-hardened` (Docker Hardened Image). Weekly plugin refreshes may appear as `8.12.8-YYYY-MM-DD` on the [current release only](https://hub.docker.com/r/hackolade/hck-cli/tags). See [image tags](./image-variants.md#image-tags). - **Re-validate when the image tag changes** — license state is tied to the image UUID. - **Mount `/data` and `/tmp` on every run** — recommended consolidated layout; legacy `/home/hackolade/…` bind mounts may still work on a writable rootfs (see [Writable paths](#writable-paths-data-and-tmp-recommended)). - **Always use `docker compose run --rm`** — removes the one-off container when the command exits. Without `--rm`, stopped `…-run-…` containers accumulate and Compose warns about **orphan containers** on the next run. @@ -140,10 +143,11 @@ $COMPOSE run --rm hck-cli showLicense || exit 1 | Tag | Meaning | | --- | --- | -| `hackolade/hck-cli:8.12.7` | Current Hackolade Studio release | -| `hackolade/hck-cli:8.12.7-2026-08-07` | Example intermediate tag (plugin updates on the current release) | +| `hackolade/hck-cli:8.12.8` | Current Studio release (**Ubuntu 26.04 LTS** / Resolute Raccoon) | +| `hackolade/hck-cli:8.12.8-hardened` | Same release, [Docker Hardened Image](https://docs.docker.com/dhi/) (Debian) base | +| `hackolade/hck-cli:8.12.8-2026-08-07` | Example intermediate tag (plugin updates on the current release) | -Update the `image:` line in your compose file, then `docker compose pull`. +Update the `image:` line in your compose file, then `docker compose pull`. Ubuntu vs `-hardened`, startup preflight, and arbitrary UID: **[image-variants.md](./image-variants.md)**. ## Docker CLI (without Compose) @@ -153,7 +157,8 @@ See **[docker-cli-howto.md](./docker-cli-howto.md)** for local and hardened `doc | Problem | Check | | --- | --- | -| Permission denied on `./models` | `chown -R 1000:1001 ./models` | +| Permission denied on `./models` | `chown -R 1000:1001 ./models` (default user). For an OpenShift-style UID see [arbitrary UID](./image-variants.md#bind-mounted-host-folders) | +| `The container has no writable location` | Preflight: `/data` and `/tmp` must be writable by this UID. Mount both; keep **group 0** for an arbitrary UID (`group_add: ["0"]`, `user: ":0"`, or `fsGroup: 0`). Recreate a named volume stuck at `root:root` `755`. Details: [preflight](./image-variants.md#startup-preflight) | | Fails with read-only rootfs | Mount **`/data`** (volume) and **`/tmp`** (tmpfs) — with `read_only: true`, only these paths are writable | | `Read-only file system` / writes not landing on a legacy mount | Prefer **`/data/…`** — the image redirects runtime writes to `/data` and `/tmp`; legacy `/home/hackolade/…` bind mounts may still work on a writable rootfs but are not recommended | | License validation fails | Same image tag for UUID + validation; floating seat available | @@ -164,6 +169,7 @@ See **[docker-cli-howto.md](./docker-cli-howto.md)** for local and hardened `doc ## See also +- [Image variants, preflight, arbitrary UID](./image-variants.md) - [Offline metadata pipeline example](./example-offline-metadata-pipeline.md) — **full CI walkthrough** - [Docker CLI how-to](./docker-cli-howto.md) - [License validation](./license-validation.md) diff --git a/Studio/doc/image-variants.md b/Studio/doc/image-variants.md new file mode 100644 index 0000000..000a68b --- /dev/null +++ b/Studio/doc/image-variants.md @@ -0,0 +1,200 @@ +# Image variants, startup checks, and arbitrary UIDs + +**Part of:** [Getting started](./getting-started-hck-cli.md) · [All guides](../README.md#documentation) + +This page covers three things the other guides only mention in passing: + +1. **`hackolade/hck-cli:` vs `hackolade/hck-cli:-hardened`** — two OS bases, same CLI +2. **Startup preflight** — what `hck-cli` checks before Studio runs, and which failures are fatal +3. **Arbitrary UID** — what Kubernetes / OpenShift actually inject, and the constraints that follow + +## Two meanings of “hardened” + +| What | Where it shows up | What it changes | +| --- | --- | --- | +| **Image tag** `-hardened` | `hackolade/hck-cli:8.12.8-hardened` | OS **base**: [Docker Hardened Images](https://docs.docker.com/dhi/) (Debian) instead of Ubuntu 26.04 LTS | +| **Runtime profile** | [`compose.hardened.yml`](../compose.hardened.yml), [`k8s/`](../k8s/) | **How you run** the container: read-only rootfs, `cap_drop: ALL`, non-root, no-new-privileges | + +They combine independently. Use the Ubuntu 26.04 tag or the `-hardened` tag with `compose.yml`, `compose.hardened.yml`, or the Kubernetes Jobs. The CLI, plugins, volume layout, and preflight checks are the same. + +`compose.hardened.yml` is named for the **runtime profile**, not for the `-hardened` image tag. Point `image:` at whichever tag your policy requires. + +## Image tags + +`latest` is not published. Pin a Studio version. + +| Tag | Base OS | Meaning | +| --- | --- | --- | +| `hackolade/hck-cli:8.12.8` | **Ubuntu 26.04 LTS** (Resolute Raccoon) | Current Studio release (default) | +| `hackolade/hck-cli:8.12.8-hardened` | Docker Hardened Image (Debian) | Same Studio release and plugins, DHI base | +| `hackolade/hck-cli:8.12.8-2026-08-07` | Ubuntu 26.04 LTS | Intermediate tag (plugin refreshes on the current release) | +| `hackolade/hck-cli:8.12.8-2026-08-07-hardened` | Docker Hardened Image (Debian) | Same intermediate build on the DHI base | + +The un-suffixed **8.12.8** runtime reports: + +```text +PRETTY_NAME="Ubuntu 26.04 LTS" +NAME="Ubuntu" +VERSION_ID="26.04" +VERSION="26.04 LTS (Resolute Raccoon)" +VERSION_CODENAME=resolute +ID=ubuntu +``` + +(`cat /etc/os-release` inside the container.) Later Studio releases may move the default tag to a newer Ubuntu; pin the image and re-check `/etc/os-release` if the base OS matters for your policy. + +See [Docker Hub tags](https://hub.docker.com/r/hackolade/hck-cli/tags). Re-validate the floating license when the image tag (or digest) changes — license state is tied to the image UUID. + +### What is the same on both bases + +- Hackolade Studio CLI, bundled plugins, `hck-cli` entrypoint +- Numeric `USER 1000:1001` (`hackolade` / `data-modelers`) +- Writable roots: **`/data`** (persistent) and **`/tmp`** (scratch) +- `/data` owned **`1000:0` mode 2775** (setgid) so an OpenShift-style arbitrary UID in **group 0** can write +- `libnss-wrapper` so a UID with no `/etc/passwd` entry still resolves for Studio +- Startup preflight and license flow + +### What differs on `-hardened` + +The Docker Hardened Image base is a minimal Debian image. Apt **recommends are disabled**, extra packages (passwd, ssh, sudo, systemd) are stripped after build-time user setup, and the default account is the DHI `nonroot` user renamed to `hackolade` (Ubuntu tags rename `ubuntu`). + +Functionally you still run the same CLI. Choose `-hardened` when policy requires a DHI-based image; choose the un-suffixed tag when you want **Ubuntu 26.04 LTS**. + +```bash +# Default (Ubuntu 26.04 LTS) +image: hackolade/hck-cli:8.12.8 + +# DHI Debian base — same compose / Kubernetes files +image: hackolade/hck-cli:8.12.8-hardened +``` + +## Startup preflight + +On the official `hackolade/hck-cli` image, `hck-cli` prepares the Linux/Electron stack **before** Studio starts. Typical log lines: + +```text +[hackolade-cli-image/linux] Runtime detected (hackolade-cli-image/linux) +[hackolade-cli-image/linux] Checking writable mounts +[hackolade-cli-image/linux] Checking volume mounts +[hackolade-cli-image/linux] Ensuring Docker log directory (/data/logs) +Activating license key... +``` + +| Step | Severity | What it requires | +| --- | --- | --- | +| Runtime detected | Info | Official CLI image | +| **Checking writable mounts** | **Fatal** if any required path cannot be written | `/data/app` (license / Electron userData), `/data/logs`, `/tmp`, and the XDG runtime dir under `/tmp` | +| Checking volume mounts | Warning | License and logs should sit on a **volume** so they survive `docker compose run --rm`. Skipped for `version`, `help`, `getComputerId`, `showLicense`, `listLogs`, `showLogs`, `warm-cache` | +| Ensuring Docker log directory | Info | Creates `/data/logs` when the volume is writable | +| Passwd for the current UID | Silent unless the UID is unknown | `libnss-wrapper` synthesizes a passwd entry on the tmpfs (OpenShift random UID) | +| D-Bus / gnome-keyring / safeStorage | Fatal only if those daemons fail later | `/tmp` must be writable (tmpfs recommended) | +| Activating license | Depends on online/offline setup | See [license-validation.md](./license-validation.md) | + +Writable-mount checks run **after** env alignment, so a legacy bind mount at `/home/hackolade/.config` still counts as the config home on a writable rootfs. With `read_only: true`, only `/data` and `/tmp` can succeed. + +### Fatal: no writable location + +```text +The container has no writable location for data it must produce. + +Running as uid=31337 gid=31337 groups=31337. + +Not writable: + /data/app (license state and Electron userData) + /data/logs (CLI run logs) +``` + +Usual causes: + +1. **`/data` or `/tmp` not mounted** (or tmpfs mode wrong — Compose long-form `tmpfs.mode: 1777` is decimal; use `1023`, or the short form `tmpfs: ["/tmp:rw,size=1g,mode=1777"]`). +2. **Arbitrary UID without group 0** — see [below](#arbitrary-uid). +3. **Named volume created as `root:root` `755`** — first mount of an empty volume should copy the image’s `1000:0` `2775` layout. If the volume already existed with the wrong owner, recreate it (`docker volume rm …`) or set Kubernetes `fsGroup` so the kubelet can chown it. +4. **Host bind** under `/data/models` (or similar) not writable by the container UID/GID. + +## Arbitrary UID + +The image is built so **Kubernetes and OpenShift can assign a random UID**. That is an arbitrary **UID**, not an arbitrary **primary GID**. + +### What the orchestrator injects + +| Runtime | UID | Group 0 | +| --- | --- | --- | +| Default image `USER` | `1000` | Primary GID is **`1001`** (`data-modelers`). UID 1000 still writes `/data` as **owner**. | +| OpenShift `restricted-v2` | Random (no passwd entry) | **Group 0** as primary or supplemental. `runAsUser` is omitted; SCC assigns the UID. | +| Kubernetes Restricted | You set `runAsUser` (or keep 1000) | Add group 0 with **`fsGroup: 0`** and/or `runAsGroup: 0`. | +| Compose / `docker run` | You set `user:` | Keep group 0 with **`group_add: ["0"]`** (already in [`compose.hardened.yml`](../compose.hardened.yml)) or `user: ":0"`. | + +`nss-wrapper` only invents a passwd line so `os.userInfo()` and gnome-keyring do not crash. It does **not** chmod the volume. Writability comes from: + +- `/data` in the image: owner `1000`, group `0`, directories `2775` (setgid) +- `/tmp`: tmpfs `mode=1777` (any UID) + +Copying the image `USER` as `":1001"` or `":"` **without** group 0 is not what OpenShift does, and `/data` will not be writable. + +### Compose + +[`compose.hardened.yml`](../compose.hardened.yml) already sets `group_add: ["0"]` on the base service, so overriding `user:` keeps group 0: + +```bash +# OpenShift-style primary GID 0 (service hck-cli-arbitrary-uid) +docker compose -f compose.hardened.yml run --rm hck-cli-arbitrary-uid + +# Fully arbitrary primary GID — still works because group_add: ["0"] +docker compose -f compose.hardened.yml run --rm --user 31337:31337 hck-cli +``` + +If you omit `group_add` in your own compose file: + +```yaml +user: "31337:0" # primary GID 0 +# or +user: "31337:31337" +group_add: + - "0" +``` + +### Kubernetes / OpenShift + +Use [`k8s/hck-cli-job.yaml`](../k8s/hck-cli-job.yaml) for a fixed UID `1000:1001` with `fsGroup: 0`. + +Use [`k8s/hck-cli-job-openshift.yaml`](../k8s/hck-cli-job-openshift.yaml) for `restricted-v2`: + +```yaml +securityContext: + runAsNonRoot: true + # runAsUser omitted — SCC assigns an arbitrary UID + runAsGroup: 0 + fsGroup: 0 +``` + +Some clusters assign `fsGroup` from the namespace range (not `0`). That still works: the kubelet chowns the PVC to that group and adds it as a supplemental group. Image ownership of `/data` then matters less than `fsGroup` on the volume. + +### `docker run` + +```bash +docker run --rm \ + --init --read-only --user 31337:0 --group-add 0 \ + --cap-drop ALL --security-opt no-new-privileges:true \ + -v hackolade-studio-data:/data \ + --tmpfs /tmp:rw,size=1g,mode=1777 \ + hackolade/hck-cli:8.12.8-hardened version +``` + +`--user 31337` (UID only, no GID) typically defaults GID to `0` when the UID is not in `/etc/passwd`. `--user 31337:31337` needs `--group-add 0`. + +### Bind-mounted host folders + +Host paths under `/data` (models, output) must be writable by the **container** UID, or by group 0 if you rely on the OpenShift group. For the default user: + +```bash +chown -R 1000:1001 ./models +``` + +For an OpenShift-style UID, prefer a PVC with `fsGroup` rather than a hostPath, or chown the host tree so group 0 can write (`chown -R 1000:0` and `chmod g+w`). + +## See also + +- [Getting started](./getting-started-hck-cli.md) — `/data` + `/tmp`, deployment profiles, quick start +- [Docker CLI how-to](./docker-cli-howto.md) — `docker run` templates +- [Kubernetes examples](../k8s/README.md) +- [License validation](./license-validation.md) diff --git a/Studio/k8s/README.md b/Studio/k8s/README.md index 61ec846..44d78de 100644 --- a/Studio/k8s/README.md +++ b/Studio/k8s/README.md @@ -13,16 +13,18 @@ Legacy `/home/hackolade/…` bind mounts from older Docker setups are not used h Same rule as [`compose.yml`](../compose.yml) and [`compose.hardened.yml`](../compose.hardened.yml). These manifests add the **hardened profile**: read-only root filesystem, non-root, dropped capabilities (Kubernetes **Restricted** Pod Security Standard). -Pin the image tag in each manifest (default: `hackolade/hck-cli:8.12.7`). +Pin the image tag in each manifest. Append `-hardened` for the Docker Hardened Image (Debian) base — same Jobs. Ubuntu vs `-hardened`, preflight, and arbitrary UID: [image-variants.md](../doc/image-variants.md). ## Manifests | File | Purpose | | --- | --- | -| [`hck-cli-job.yaml`](./hck-cli-job.yaml) | Smoke test — runs `version` | -| [`hck-cli-job-openshift.yaml`](./hck-cli-job-openshift.yaml) | Same, for OpenShift `restricted-v2` (arbitrary UID) | +| [`hck-cli-job.yaml`](./hck-cli-job.yaml) | Smoke test — runs `version` (`runAsUser: 1000`, `runAsGroup: 1001`, `fsGroup: 0`) | +| [`hck-cli-job-openshift.yaml`](./hck-cli-job-openshift.yaml) | OpenShift `restricted-v2`: omit `runAsUser` (SCC assigns an arbitrary UID), `runAsGroup: 0`, `fsGroup: 0` | | [`hck-cli-gendoc-job.yaml`](./hck-cli-gendoc-job.yaml) | Example `genDoc` using a model on the PVC | +OpenShift injects a **random UID** in **group 0**. The image owns `/data` as `1000:0` mode `2775` so that UID can write; `nss-wrapper` supplies a passwd entry. Do not set `runAsUser: ` with `runAsGroup: 1001` (or a random primary GID) without group 0 — `/data` will fail the writable-mount preflight. Full contract: [arbitrary UID](../doc/image-variants.md#arbitrary-uid). + ## Quick test ```bash @@ -40,5 +42,6 @@ kubectl logs job/hck-cli-version ## See also - [Getting started with hck-cli](../doc/getting-started-hck-cli.md) — runtime model and deployment profiles +- [Image variants, preflight, arbitrary UID](../doc/image-variants.md) - [License validation](../doc/license-validation.md) - [Custom TLS certificates](../doc/custom-certificates.md)