From b1d5c3c65eda6bd8acff6af33ae0d750a71e59f8 Mon Sep 17 00:00:00 2001 From: Leechael Yim Date: Tue, 8 Sep 2026 15:30:51 +0800 Subject: [PATCH 1/4] fix: disable sshd StrictModes on dstack guests dstack 0.6 guest rootfs has / as 0777. sshd StrictModes walks every component of AuthorizedKeysFile and refuses the key with "bad ownership or modes for directory /", so pubkey login always fails even when the key file itself is 0600. The generated sshd_config now sets StrictModes no. The check does not buy anything here: rootfs modes are an image property the installer cannot fix, and the filesystem already sits inside the CVM trust boundary. Fixes https://github.com/Dstack-TEE/dstack-openssh-installer/issues/3 --- scripts/install-openssh.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/install-openssh.sh b/scripts/install-openssh.sh index acc24ba..d25fe60 100644 --- a/scripts/install-openssh.sh +++ b/scripts/install-openssh.sh @@ -304,6 +304,8 @@ AuthorizedKeysFile ${SSH_HOME_DIR}/%u/.ssh/authorized_keys PasswordAuthentication no PermitEmptyPasswords no KbdInteractiveAuthentication no +# dstack guest rootfs has / as 0777; sshd StrictModes would refuse AuthorizedKeysFile +StrictModes no # Security X11Forwarding no From 2421ce2a8757658a72ac1c0eb1e4c9a2e59e7a67 Mon Sep 17 00:00:00 2001 From: Leechael Yim Date: Tue, 8 Sep 2026 15:34:06 +0800 Subject: [PATCH 2/4] feat: accept comma-separated SSH_GITHUB_USER list Previously only a single GitHub username was fetched. Operators with several accounts had to pre-merge github.com/.keys into SSH_PUBKEY. SSH_GITHUB_USER now splits on commas, trims whitespace, and imports keys from each account into the managed authorized_keys block. A failed fetch for one user is logged and skipped so other accounts still land. --- README.md | 9 ++++++++- scripts/install-openssh.sh | 40 ++++++++++++++++++++++++-------------- 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index f10333e..74c04ed 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,13 @@ docker run --rm --privileged --pid=host --net=host -v /:/host \ $NS/dstack-openssh-installer:latest ``` +**Import ssh public keys from multiple GitHub usernames:** +```bash +docker run --rm --privileged --pid=host --net=host -v /:/host \ + -e SSH_GITHUB_USER="alice,bob" \ + $NS/dstack-openssh-installer:latest +``` + **Custom port:** ```bash docker run --rm --privileged --pid=host --net=host -v /:/host \ @@ -44,7 +51,7 @@ docker run --rm --privileged --pid=host --net=host -v /:/host \ |----------|---------|-------------| | `SSH_PORT` | `22` | SSH listening port | | `SSH_PUBKEY` | - | SSH public key for root login | -| `SSH_GITHUB_USER` | - | GitHub username to import public keys from | +| `SSH_GITHUB_USER` | - | GitHub username(s) to import public keys from (comma-separated) | | `SSH_PERMIT_ROOT_LOGIN` | `prohibit-password` | Root login policy (`yes`, `no`, `prohibit-password`) | ## Usage After Installation diff --git a/scripts/install-openssh.sh b/scripts/install-openssh.sh index d25fe60..520b3ec 100644 --- a/scripts/install-openssh.sh +++ b/scripts/install-openssh.sh @@ -199,26 +199,36 @@ setup_authorized_keys() { keys_added=1 fi - # Fetch keys from GitHub user + # Fetch keys from GitHub user(s). SSH_GITHUB_USER accepts a comma-separated list. if [[ -n "${SSH_GITHUB_USER}" ]]; then - local github_url="https://github.com/${SSH_GITHUB_USER}.keys" - log_info "Fetching public keys from GitHub user: ${SSH_GITHUB_USER}..." - local fetched_keys - if fetched_keys=$(wget -qO- "${github_url}" 2>/dev/null); then - if [[ -n "${fetched_keys}" ]]; then - if [[ -n "${managed_keys}" ]]; then - managed_keys="${managed_keys}"$'\n'"${fetched_keys}" + local github_users + IFS=',' read -r -a github_users <<< "${SSH_GITHUB_USER}" + local github_user + for github_user in "${github_users[@]}"; do + github_user="${github_user//[[:space:]]/}" + if [[ -z "${github_user}" ]]; then + continue + fi + + local github_url="https://github.com/${github_user}.keys" + log_info "Fetching public keys from GitHub user: ${github_user}..." + local fetched_keys + if fetched_keys=$(wget -qO- "${github_url}" 2>/dev/null); then + if [[ -n "${fetched_keys}" ]]; then + if [[ -n "${managed_keys}" ]]; then + managed_keys="${managed_keys}"$'\n'"${fetched_keys}" + else + managed_keys="${fetched_keys}" + fi + keys_added=1 + log_success "Public keys imported from GitHub (${github_user})" else - managed_keys="${fetched_keys}" + log_warning "No keys found for GitHub user: ${github_user}" fi - keys_added=1 - log_success "Public keys imported from GitHub (${SSH_GITHUB_USER})" else - log_warning "No keys found for GitHub user: ${SSH_GITHUB_USER}" + log_error "Failed to fetch keys from GitHub for user: ${github_user}" fi - else - log_error "Failed to fetch keys from GitHub for user: ${SSH_GITHUB_USER}" - fi + done fi # Update authorized_keys file with markers From b2a8ddc5a42e9471b39a770a2dc3a7de065376ab Mon Sep 17 00:00:00 2001 From: Leechael Yim Date: Tue, 8 Sep 2026 15:45:36 +0800 Subject: [PATCH 3/4] ci: add GHCR image build workflow Publish linux/amd64 images to ghcr.io// from Actions so builds do not depend on Docker Hub credentials. PRs build only. Pushes to main, v* tags, and workflow_dispatch log in with GITHUB_TOKEN and push. Tags include latest on the default branch, semver from v* tags, branch names, and short SHA. workflow_dispatch also stamps a UTC timestamp tag. --- .github/workflows/ghcr.yml | 59 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 .github/workflows/ghcr.yml diff --git a/.github/workflows/ghcr.yml b/.github/workflows/ghcr.yml new file mode 100644 index 0000000..59fadbb --- /dev/null +++ b/.github/workflows/ghcr.yml @@ -0,0 +1,59 @@ +name: GHCR + +on: + push: + branches: [main] + tags: + - 'v*' + pull_request: + workflow_dispatch: + +permissions: + contents: read + packages: write + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Log in to GHCR + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ github.repository }} + tags: | + type=raw,value=latest,enable={{is_default_branch}} + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=ref,event=branch + type=sha + type=raw,value={{date 'YYYYMMDDHHmmss'}},enable=${{ github.event_name == 'workflow_dispatch' }} + + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: . + file: docker/Dockerfile + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + platforms: linux/amd64 + provenance: false + build-args: | + DSTACK_REV=${{ github.sha }} + cache-from: type=gha + cache-to: type=gha,mode=max From a38b21d5e73964c1de668420a3fb99885295bf88 Mon Sep 17 00:00:00 2001 From: Leechael Yim Date: Tue, 8 Sep 2026 15:57:30 +0800 Subject: [PATCH 4/4] fix: accept static-pie sshd in image build check The builder image did not install file(1), and alpine:latest now reports a -static -pie sshd as "static-pie linked" rather than "statically linked". The verify step failed grep and aborted the GHCR build. Install file and match both strings so the static-link check still holds. --- docker/Dockerfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index d39e2e8..3eb2be0 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -10,7 +10,8 @@ RUN apk add --no-cache \ openssl-libs-static \ zlib-dev \ zlib-static \ - wget + wget \ + file WORKDIR /build @@ -40,7 +41,7 @@ RUN wget https://cdn.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-${OPENSSH_ /build/install/usr/libexec/sshd-session # Verify static linking -RUN file /build/install/usr/sbin/sshd | grep -q "statically linked" && \ +RUN file /build/install/usr/sbin/sshd | grep -Eq "statically linked|static-pie linked" && \ echo "sshd is statically linked" # Final image