diff --git a/jenkins/Jenkinsfile.preview-deployer b/jenkins/Jenkinsfile.preview-deployer index 6591da10f..3d187edc4 100644 --- a/jenkins/Jenkinsfile.preview-deployer +++ b/jenkins/Jenkinsfile.preview-deployer @@ -47,6 +47,10 @@ pipeline { steps { sh ''' set -euo pipefail + ACTION="${ACTION:-}" + SOURCE_BRANCH="${SOURCE_BRANCH:-}" + COMMIT_HASH="${COMMIT_HASH:-}" + DEPLOYMENT_ID="${DEPLOYMENT_ID:-}" case "${ACTION}" in DEPLOY|STATUS|UNINSTALL) ;; *) exit 1 ;; esac if [ -n "${SOURCE_BRANCH}" ]; then git check-ref-format --branch "${SOURCE_BRANCH}" >/dev/null @@ -89,13 +93,13 @@ pipeline { withCredentials([sshUserPrivateKey(credentialsId: env.GIT_REMOTE_CREDENTIAL_ID, keyFileVariable: 'GENARRATIVE_GIT_SSH_KEY')]) { sh ''' set -euo pipefail - SOURCE_BRANCH="${SOURCE_BRANCH}" \ - COMMIT_HASH="${COMMIT_HASH}" \ - GIT_REMOTE_URL="${GIT_REMOTE_URL}" \ + SOURCE_BRANCH="${SOURCE_BRANCH:-}" \ + COMMIT_HASH="${COMMIT_HASH:-}" \ + GIT_REMOTE_URL="${GIT_REMOTE_URL:-}" \ SOURCE_COMMIT_FILE=".jenkins-source-commit" \ GENARRATIVE_JENKINS_REUSE_EXISTING_CHECKOUT="true" \ GIT_SSH_COMMAND="ssh -i ${GENARRATIVE_GIT_SSH_KEY} -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new" \ - "${WORKSPACE}/scripts/jenkins-checkout-source.sh" + bash "${WORKSPACE}/scripts/jenkins-checkout-source.sh" ''' } } diff --git a/jenkins/preview-deployer-job-config.xml b/jenkins/preview-deployer-job-config.xml index a2317950f..a0cbd4247 100644 --- a/jenkins/preview-deployer-job-config.xml +++ b/jenkins/preview-deployer-job-config.xml @@ -5,7 +5,37 @@ false - + + + ACTION + 部署、查询状态或卸载分支预览容器 + + + DEPLOY + STATUS + UNINSTALL + + + + + SOURCE_BRANCH + 源码分支;DEPLOY 和 UNINSTALL 必填 + + true + + + COMMIT_HASH + 可选;必须是 SOURCE_BRANCH 历史中的 commit + + true + + + DEPLOYMENT_ID + 预览实例稳定 ID + + true + + diff --git a/scripts/check-preview-deployer.mjs b/scripts/check-preview-deployer.mjs index 230052052..472f472fa 100644 --- a/scripts/check-preview-deployer.mjs +++ b/scripts/check-preview-deployer.mjs @@ -70,6 +70,21 @@ assertIncludes( 'disableConcurrentBuilds()', 'Jenkins Job 必须禁止并发构建。', ); +assertIncludes( + jenkinsfile, + 'COMMIT_HASH="${COMMIT_HASH:-}"', + 'Jenkins Checkout 阶段必须允许 COMMIT_HASH 未导出或为空。', +); +assertIncludes( + jenkinsfile, + 'bash "${WORKSPACE}/scripts/jenkins-checkout-source.sh"', + 'Jenkins Checkout 阶段必须显式使用 bash 执行源码检出脚本。', +); +assertExcludes( + jenkinsfile, + 'COMMIT_HASH="${COMMIT_HASH}"', + 'Jenkins Checkout 阶段不得在 set -u 下直接展开未导出的 COMMIT_HASH。', +); assertIncludes(deployer, 'flock -x 9', '部署脚本必须使用跨进程独占锁。'); assertIncludes( deployer, @@ -132,6 +147,11 @@ assertIncludes( 'jenkins/Jenkinsfile.preview-deployer', 'Jenkins Job 必须从受保护 master 读取固定流水线。', ); +assertIncludes( + jobConfig, + '', + 'Jenkins Job 首次触发前必须已经声明 buildWithParameters 参数。', +); assertIncludes( nginx, 'location ^~ /api/preview-deployer/', diff --git a/server-rs/crates/preview-deployer-server/src/jenkins.rs b/server-rs/crates/preview-deployer-server/src/jenkins.rs index fb4f45437..840c4a0f0 100644 --- a/server-rs/crates/preview-deployer-server/src/jenkins.rs +++ b/server-rs/crates/preview-deployer-server/src/jenkins.rs @@ -274,11 +274,27 @@ impl JenkinsClient { } fn resolve_trusted_url(&self, value: &str) -> Result { - let url = Url::parse(value) + let returned = Url::parse(value) .or_else(|_| self.job_url.join(value)) .map_err(|_| "Jenkins 返回了无效 URL".to_string())?; - self.ensure_same_origin(&url)?; - Ok(url) + if !returned.username().is_empty() + || returned.password().is_some() + || !returned.path().starts_with(self.root_url.path()) + { + return Err("Jenkins 返回了非受信源 URL".to_string()); + } + let relative_path = returned + .path() + .strip_prefix(self.root_url.path()) + .ok_or_else(|| "Jenkins 返回了非受信路径".to_string())?; + let mut trusted = self + .root_url + .join(relative_path) + .map_err(|_| "Jenkins 返回了无效 URL".to_string())?; + trusted.set_query(returned.query()); + trusted.set_fragment(None); + self.ensure_same_origin(&trusted)?; + Ok(trusted) } fn ensure_same_origin(&self, url: &Url) -> Result<(), String> { diff --git a/server-rs/crates/preview-deployer-server/src/tests.rs b/server-rs/crates/preview-deployer-server/src/tests.rs index cbb117687..d78e065e2 100644 --- a/server-rs/crates/preview-deployer-server/src/tests.rs +++ b/server-rs/crates/preview-deployer-server/src/tests.rs @@ -95,7 +95,7 @@ async fn mock_trigger( async fn mock_queue(axum::extract::Path(id): axum::extract::Path) -> Json { Json( - json!({"cancelled": false, "executable": {"url": format!("/jenkins/job/shared/job/Genarrative-Preview-Deployer/{id}/")}}), + json!({"cancelled": false, "executable": {"url": format!("http://192.168.35.82:8080/jenkins/job/shared/job/Genarrative-Preview-Deployer/{id}/")}}), ) }