Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ RUN set -ex \

WORKDIR /opt

COPY --from=lina /opt/lina /opt/lina
COPY --from=luna /opt/luna /opt/luna
ARG VERSION
ARG PAM_AGENT_VERSION
COPY pam-agent.sh pam-agent-version.txt /tmp/
RUN PAM_AGENT_VERSION="${PAM_AGENT_VERSION}" sh /tmp/pam-agent.sh \
&& rm -f /tmp/pam-agent.sh /tmp/pam-agent-version.txt
COPY --from=lina /opt/lina /opt/lina
COPY --from=luna /opt/luna /opt/luna
ARG VERSION
COPY versions.txt /opt/download/versions.txt
COPY client-version.txt /opt/download/client-version.txt
COPY nginx.conf /etc/nginx/nginx.conf
Expand Down
41 changes: 30 additions & 11 deletions client.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
: "${CLIENT_NAME:?CLIENT_NAME is required}"
: "${WEBLITE_VERSION:?WEBLITE_VERSION is required}"

RELEASE_URL=${CLIENT_RELEASE_URL:-https://github.com/jumpserver/luna/releases/download/client-v${CLIENT_VERSION}}
WEBLITE_RELEASE_URL=${WEBLITE_RELEASE_URL:-https://github.com/jumpserver/luna/releases/download/weblite-v${WEBLITE_VERSION}}
OFFICIAL_RELEASE_URL=https://github.com/jumpserver/luna/releases/download/client-v${CLIENT_VERSION}
RELEASE_URL=${CLIENT_RELEASE_URL:-${OFFICIAL_RELEASE_URL}}
OFFICIAL_WEBLITE_RELEASE_URL=https://github.com/jumpserver/luna/releases/download/weblite-v${WEBLITE_VERSION}
WEBLITE_RELEASE_URL=${WEBLITE_RELEASE_URL:-${OFFICIAL_WEBLITE_RELEASE_URL}}
DOWNLOAD_DIR=${CLIENT_DOWNLOAD_DIR:-/opt/download/public}
WEBLITE_DOWNLOAD_DIR=${WEBLITE_DOWNLOAD_DIR:-/opt/download/applets}

Expand All @@ -21,15 +23,32 @@ windows_file="${CLIENT_NAME}Client_${CLIENT_VERSION}_x64-setup.exe"
macos_arm64_file="${CLIENT_NAME}Client_${CLIENT_VERSION}_aarch64.dmg"
weblite_file="JumpServer-WebLite-${WEBLITE_VERSION}-x64.msi"

wget --https-only \
--output-document="${windows_file}" \
"${RELEASE_URL}/${CLIENT_NAME}-${CLIENT_VERSION}-Setup.exe"
wget --https-only \
--output-document="${macos_arm64_file}" \
"${RELEASE_URL}/${CLIENT_NAME}-${CLIENT_VERSION}-arm64.dmg"
wget --https-only \
--output-document="${WEBLITE_DOWNLOAD_DIR}/${weblite_file}" \
"${WEBLITE_RELEASE_URL}/${weblite_file}"
download_release() {
file=$1 url=$2 official_url=$3
if wget --https-only --timeout=30 --tries=1 --output-document="${file}" "${url}"; then
return 0
else
status=$?
fi
# Match PAM downloads: retry only a network failure against the same official
# URL. Certificate, authentication and HTTP failures retain their error status.
if [ "${status}" -ne 4 ] || [ "${url}" != "${official_url}" ]; then
return "${status}"
fi
printf '%s\n' "Client download network failure; retrying once with TLS 1.2" >&2
wget --https-only --timeout=30 --tries=1 --secure-protocol=TLSv1_2 \
--output-document="${file}" "${url}"
}

download_release "${windows_file}" \
"${RELEASE_URL}/${CLIENT_NAME}-${CLIENT_VERSION}-Setup.exe" \
"${OFFICIAL_RELEASE_URL}/${CLIENT_NAME}-${CLIENT_VERSION}-Setup.exe"
download_release "${macos_arm64_file}" \
"${RELEASE_URL}/${CLIENT_NAME}-${CLIENT_VERSION}-arm64.dmg" \
"${OFFICIAL_RELEASE_URL}/${CLIENT_NAME}-${CLIENT_VERSION}-arm64.dmg"
download_release "${WEBLITE_DOWNLOAD_DIR}/${weblite_file}" \
"${WEBLITE_RELEASE_URL}/${weblite_file}" \
"${OFFICIAL_WEBLITE_RELEASE_URL}/${weblite_file}"

ln -s "${windows_file}" "Client_${CLIENT_VERSION}_x64-setup.exe"
ln -s "${macos_arm64_file}" "Client_${CLIENT_VERSION}_aarch64.dmg"
4 changes: 2 additions & 2 deletions https_server.conf
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ server {
proxy_buffering off;
proxy_request_buffering off;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Host $http_host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
# 默认作为第一层;仅显式 USE_LB=0 时由启动脚本改为信任上游代理头。
Expand Down Expand Up @@ -55,7 +55,7 @@ server {
proxy_buffering off;
proxy_request_buffering off;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Host $http_host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
# 默认作为第一层;仅显式 USE_LB=0 时由启动脚本改为信任上游代理头。
Expand Down
17 changes: 17 additions & 0 deletions includes/chen.conf
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
# Go SQL workbench: request/response over HTTP and one session event stream.
# Keep this prefix separate from legacy Chen WebSocket routes.
location /chen/api/v1/ {
proxy_pass http://chen:8082;
proxy_http_version 1.1;
proxy_buffering off;
proxy_cache off;
proxy_request_buffering off;
proxy_set_header Connection "";
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_connect_timeout 30;
proxy_send_timeout 600;
proxy_read_timeout 600;
send_timeout 600;
}

location /chen/ {
proxy_pass http://chen:8082;
proxy_buffering off;
Expand Down
18 changes: 16 additions & 2 deletions pam-agent.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,27 @@ PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)

: "${PAM_AGENT_VERSION:?PAM_AGENT_VERSION must name a published JumpServer release}"
RELEASE_TAG="v${PAM_AGENT_VERSION#v}"
RELEASE_URL=${PAM_AGENT_RELEASE_URL:-https://github.com/jumpserver/pam-clients/releases/download/${RELEASE_TAG}}
OFFICIAL_RELEASE_URL=https://github.com/jumpserver/pam-clients/releases/download/${RELEASE_TAG}
RELEASE_URL=${PAM_AGENT_RELEASE_URL:-${OFFICIAL_RELEASE_URL}}
DOWNLOAD_DIR=${PAM_AGENT_DOWNLOAD_DIR:-/opt/download/pam}

mkdir -p "${DOWNLOAD_DIR}"
cd "${DOWNLOAD_DIR}"
for filename in jms-pam-agent-linux-amd64 jms-pam-agent-linux-arm64 SHA256SUMS; do
wget --https-only --output-document="${filename}" "${RELEASE_URL}/${filename}"
if wget --https-only --timeout=30 --tries=1 --output-document="${filename}" "${RELEASE_URL}/${filename}"; then
continue
else
status=$?
fi
# Some GnuTLS/proxy combinations fail with automatic TLS negotiation. Retry
# network errors once with TLS 1.2, only for the same official release URL.
# Certificate, authentication, HTTP and checksum errors must still fail.
if [ "${status}" -ne 4 ] || [ "${RELEASE_URL}" != "${OFFICIAL_RELEASE_URL}" ]; then
exit "${status}"
fi
printf '%s\n' "PAM download network failure; retrying ${filename} once with TLS 1.2" >&2
wget --https-only --timeout=30 --tries=1 --secure-protocol=TLSv1_2 \
--output-document="${filename}" "${RELEASE_URL}/${filename}"
done
sha256sum --check SHA256SUMS
chmod 0755 jms-pam-agent-linux-amd64 jms-pam-agent-linux-arm64
76 changes: 76 additions & 0 deletions tests/test_client_download.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import json
import os
from pathlib import Path
import shutil
import subprocess
import tempfile
import unittest


ROOT = Path(__file__).resolve().parents[1]


class ClientDownloadTest(unittest.TestCase):
def run_download(self, failure=0, custom_url=False):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
for name in ("client.sh", "client-version.txt"):
shutil.copyfile(ROOT / name, root / name)
executable = root / "wget"
executable.write_text(
"#!/usr/bin/env python3\n"
"import json, os, sys\n"
"from pathlib import Path\n"
"log = Path(os.environ['DOWNLOAD_TEST_LOG'])\n"
"calls = json.loads(log.read_text()) if log.exists() else []\n"
"calls.append(sys.argv[1:]); log.write_text(json.dumps(calls))\n"
"if len(calls) == 1: sys.exit(int(os.environ['DOWNLOAD_TEST_FAILURE']))\n"
)
executable.chmod(0o755)
env = dict(os.environ)
env.update(
PATH=str(root) + os.pathsep + env["PATH"],
DOWNLOAD_TEST_LOG=str(root / "calls.json"),
DOWNLOAD_TEST_FAILURE=str(failure),
CLIENT_DOWNLOAD_DIR=str(root / "public"),
WEBLITE_DOWNLOAD_DIR=str(root / "applets"),
)
env.pop("CLIENT_RELEASE_URL", None)
env.pop("WEBLITE_RELEASE_URL", None)
if custom_url:
env["CLIENT_RELEASE_URL"] = "https://mirror.example.invalid/releases"
process = subprocess.run(["sh", str(root / "client.sh")], env=env, capture_output=True)
return process.returncode, json.loads((root / "calls.json").read_text())

def test_normal_downloads_have_no_tls_override(self):
code, calls = self.run_download()
self.assertEqual(code, 0)
self.assertEqual(len(calls), 3)
self.assertTrue(all("--https-only" in call for call in calls))
self.assertFalse(any("--secure-protocol=TLSv1_2" in call for call in calls))

def test_network_retry_keeps_exact_official_url_and_target(self):
code, calls = self.run_download(4)
self.assertEqual(code, 0)
self.assertEqual(len(calls), 4)
self.assertIn("--secure-protocol=TLSv1_2", calls[1])
self.assertEqual(calls[0][-1], calls[1][-1])
target = lambda call: next(arg for arg in call if arg.startswith("--output-document="))
self.assertEqual(target(calls[0]), target(calls[1]))
self.assertNotIn("--no-check-certificate", calls[1])

def test_certificate_authentication_and_http_failures_are_not_retried(self):
for failure in (5, 6, 8):
with self.subTest(failure=failure):
code, calls = self.run_download(failure)
self.assertEqual(code, failure)
self.assertEqual(len(calls), 1)

def test_custom_mirror_does_not_get_tls_fallback(self):
code, calls = self.run_download(4, custom_url=True)
self.assertEqual(code, 4)
self.assertEqual(len(calls), 1)


if __name__ == "__main__":
unittest.main()
Loading