diff --git a/.gitignore b/.gitignore index 3622f498..3f62eaee 100644 --- a/.gitignore +++ b/.gitignore @@ -27,7 +27,7 @@ replay_pid* ### IntelliJ IDEA ### -.idea +.idea/* *.iws *.iml *.ipr!/.flattened-pom.xml diff --git a/modules/module-ci-common-pipeline/src/main/java/cd/casic/ci/common/pipeline/req/pipeline/PipelineCreateReq.java b/modules/module-ci-common-pipeline/src/main/java/cd/casic/ci/common/pipeline/req/pipeline/PipelineCreateReq.java index 8a996ed9..835e21cf 100644 --- a/modules/module-ci-common-pipeline/src/main/java/cd/casic/ci/common/pipeline/req/pipeline/PipelineCreateReq.java +++ b/modules/module-ci-common-pipeline/src/main/java/cd/casic/ci/common/pipeline/req/pipeline/PipelineCreateReq.java @@ -58,7 +58,7 @@ public class PipelineCreateReq { */ private String templateId; - private String targetId; + private String targetVersionId; private String targetType; diff --git a/modules/module-ci-common-pipeline/src/main/java/cd/casic/ci/common/pipeline/req/pipeline/PipelineReq.java b/modules/module-ci-common-pipeline/src/main/java/cd/casic/ci/common/pipeline/req/pipeline/PipelineReq.java index 800d1237..adceb099 100644 --- a/modules/module-ci-common-pipeline/src/main/java/cd/casic/ci/common/pipeline/req/pipeline/PipelineReq.java +++ b/modules/module-ci-common-pipeline/src/main/java/cd/casic/ci/common/pipeline/req/pipeline/PipelineReq.java @@ -68,7 +68,7 @@ public class PipelineReq { // */ // private String templateId; // -// private String targetId; +// private String targetVersionId; // // private String targetType; // diff --git a/modules/module-ci-common-pipeline/src/main/java/cd/casic/ci/common/pipeline/req/pipeline/PipelineUpdateReq.java b/modules/module-ci-common-pipeline/src/main/java/cd/casic/ci/common/pipeline/req/pipeline/PipelineUpdateReq.java index 0bef309f..a7329291 100644 --- a/modules/module-ci-common-pipeline/src/main/java/cd/casic/ci/common/pipeline/req/pipeline/PipelineUpdateReq.java +++ b/modules/module-ci-common-pipeline/src/main/java/cd/casic/ci/common/pipeline/req/pipeline/PipelineUpdateReq.java @@ -68,7 +68,7 @@ public class PipelineUpdateReq { */ private String templateId; - private String targetId; + private String targetVersionId; private String targetType; diff --git a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/engine/configinfo/ScaSbomConfigInfo.java b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/engine/configinfo/ScaSbomConfigInfo.java new file mode 100644 index 00000000..762f1445 --- /dev/null +++ b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/engine/configinfo/ScaSbomConfigInfo.java @@ -0,0 +1,59 @@ +package cd.casic.ci.process.engine.configinfo; + +import lombok.Data; + +/** + * @author HopeLi + * @version v1.0 + * @ClassName ScaSbomConfigInfo + * @Date: 2025/5/21 19:25 + * @Description: + */ +@Data +public class ScaSbomConfigInfo { + + /** + * 机器id + */ + private long machineId; + + /** + * 项目名称 + */ + private String projectName; + + /** + * 应用名称 + */ + private String applicationName; + + /** + * 应用版本 + */ + private String applicationVersion; + + /** + * 应用描述,可选 + */ + private String applicationDescription; + + /** + * 是否开启恶意组件分析,可选 + */ + private String enablePoison; + + /** + * 节点保存返回的任务id + */ + private Integer scaTaskId; + + /** + * 节点保存返回的任务id + */ + private Integer applicationId; + + /** + * 目标版本id --用于比较目标版本是否改变 + */ + private String targetVersionId; +} diff --git a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/engine/worker/HttpWorker.java b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/engine/worker/HttpWorker.java new file mode 100644 index 00000000..b5972dff --- /dev/null +++ b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/engine/worker/HttpWorker.java @@ -0,0 +1,55 @@ +package cd.casic.ci.process.engine.worker; + +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.conn.ssl.TrustSelfSignedStrategy; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.ssl.SSLContextBuilder; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; + +import javax.net.ssl.SSLContext; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; + +/** + * @author HopeLi + * @version v1.0 + * @ClassName httpWorker + * @Date: 2025/5/21 20:13 + * @Description: + */ +public abstract class HttpWorker extends BaseWorker{ + public static RestTemplate getRestTemplateWithoutSANCheck() throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException { + // 创建一个信任所有证书的 SSL 上下文 + SSLContext sslContext = new SSLContextBuilder() + .loadTrustMaterial(null, new TrustSelfSignedStrategy()) + .build(); + + // 创建一个不验证主机名的主机名验证器 + CloseableHttpClient httpClient = HttpClients.custom() + .setSSLContext(sslContext) + .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE) + .build(); + + // 创建一个自定义的请求工厂 + HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(); + requestFactory.setHttpClient(httpClient); + + return new RestTemplate(requestFactory); + } + + public static void main(String[] args) { + try { + RestTemplate restTemplate = getRestTemplateWithoutSANCheck(); + // 示例 POST 请求 + String url = "https://175.6.27.252:30002/openapi/v1/sbom/detect-file"; + String requestBody = "{\"key\": \"value\"}"; + String response = restTemplate.postForObject(url, requestBody, String.class); + System.out.println("Response: " + response); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/engine/worker/ScaSbomWorker.java b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/engine/worker/ScaSbomWorker.java new file mode 100644 index 00000000..5bb3d0e1 --- /dev/null +++ b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/engine/worker/ScaSbomWorker.java @@ -0,0 +1,253 @@ +package cd.casic.ci.process.engine.worker; + +import cd.casic.ci.common.pipeline.annotation.Plugin; +import cd.casic.ci.process.engine.configinfo.ScaSbomConfigInfo; +import cd.casic.ci.process.engine.runContext.BaseRunContext; +import cd.casic.ci.process.engine.runContext.TaskRunContext; +import cd.casic.ci.process.process.dataObject.base.PipBaseElement; +import cd.casic.ci.process.process.dataObject.pipeline.PipPipeline; +import cd.casic.ci.process.process.dataObject.target.TargetVersion; +import cd.casic.ci.process.process.dataObject.task.PipTask; +import cd.casic.ci.process.process.service.pipeline.PipelineService; +import cd.casic.ci.process.process.service.target.impl.TargetVersionServiceImpl; +import cd.casic.ci.process.process.service.task.impl.TaskServiceImpl; +import cd.casic.framework.commons.exception.ServiceException; +import cd.casic.framework.commons.exception.enums.GlobalErrorCodeConstants; +import cn.hutool.core.util.ObjectUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.BeanUtils; +import org.springframework.core.io.FileSystemResource; +import org.springframework.http.*; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +import java.io.File; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.util.*; + +/** + * @author HopeLi + * @version v1.0 + * @ClassName ScaSbomWorker + * @Date: 2025/5/21 9:30 + * @Description: + */ +@Slf4j +@Plugin(taskType = "ScaSbom") +public class ScaSbomWorker extends BaseWorker{ + + private static final int POLLING_INTERVAL = 5000; // 轮询间隔,单位:毫秒 + private static final int MAX_POLLING_TIMES = 100; // 最大退出次数 + + @Resource + private PipelineService pipelineService; + + @Resource + private TargetVersionServiceImpl targetVersionService; + + @Resource + private TaskServiceImpl pipelineNodeInfoService; + + + public String work(BaseRunContext workerParam) { + int statusCode = 0; + + PipBaseElement contextDef = workerParam.getContextDef(); + log.info("================SCA-SBOM节点执行==================="); + if (ObjectUtil.isEmpty(contextDef)) { + log.error("未查询到节点[{}]配置,taskType = ScaSbom"); + return "-1"; + } + if (ObjectUtil.isEmpty(contextDef)) { + log.error("未查询到节点[{}]配置,taskType = ScaSbom"); + return "-1"; + } + + String filePath = ""; + if (contextDef instanceof PipTask pipTask){ + // 查询并下载目标文件 + String pipelineId = pipTask.getPipelineId(); + //根据流水线id查询流水线信息 + PipPipeline pipeline = pipelineService.getById(pipelineId); + //根据目标id查询目标信息 + if (StringUtils.isEmpty(pipeline.getTargetVersionId())){ + throw new ServiceException(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR.getCode(),"目标文件不存在") + } + TargetVersion targetVersion = targetVersionService.getById(pipeline.getTargetVersionId()); + filePath = targetVersion.getFilePath(); + + try { + + + File file = new File(filePath); + if (!file.exists() || !file.canRead()) { + log.error("目标文件不存在或不可读"); +// nodeLogger.appendErrorNow("目标文件不存在或不可读"); + return "-1"; + } + + handleUpload(workerParam, contextDef, pipTask.getTaskProperties(), file); + }catch (Exception e){ + throw new ServiceException(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR.getCode(),"SCA-SBOM节点执行失败") + } + } + + + return statusCode + ""; + } + + private void handleUpload(BaseRunContext workerParam, PipBaseElement pipelineNodeConfigInfo, + Map scaSbomConfigInfo, File file) throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + RestTemplate restTemplate = getRestTemplateWithoutSANCheck(); + String scaUploadUrl = ConstantContextHolder.getScaIp() + "/openapi/v1/sbom/detect-file"; + MultiValueMap body = buildRequestBody(scaSbomConfigInfo, file); + HttpHeaders headers = createHeaders(); + headers.setContentType(MediaType.MULTIPART_FORM_DATA); + headers.add("OpenApiUserToken", ConstantContextHolder.getScaToken()); + HttpEntity> requestEntity = new HttpEntity<>(body, headers); + + log.info("SCA上传接口:" + scaUploadUrl); + JSONObject response = restTemplate.postForObject(scaUploadUrl, requestEntity, JSONObject.class); + String message = response.getString("message"); + + if (message.equals("success")) { + nodeLogger.appendNow("==================SCA上传成功================="); + JSONObject data = response.getJSONObject("data"); + Integer scaTaskId = data.getInteger("scaTaskId"); + Integer applicationId = data.getInteger("applicationId"); + + scaSbomConfigInfo.setScaTaskId(scaTaskId); + scaSbomConfigInfo.setApplicationId(applicationId); + //更新到节点json数据 + String nodeJson = JSON.toJSONString(scaSbomConfigInfo); + pipelineNodeConfigInfo.setInfo(nodeJson); + PipelineNodeInfoParam pipelineNodeInfoParam = new PipelineNodeInfoParam(); + BeanUtils.copyProperties(pipelineNodeConfigInfo, pipelineNodeInfoParam); + pipelineNodeInfoService.update(pipelineNodeInfoParam); + workerParam.getPipelineNodeConfigInfo().setInfo(nodeJson); + + pollTaskStatus(restTemplate, scaTaskId); + } else if (message.equals("应用已经存在")) { + Integer oldScaTaskId = scaSbomConfigInfo.getScaTaskId(); + Integer oldApplicationId = scaSbomConfigInfo.getApplicationId(); + int restartResult = reStartTask(restTemplate, oldApplicationId); + if (restartResult != 0) { + return; + } + pollTaskStatus(restTemplate, oldScaTaskId); + } else { + nodeLogger.appendNow("==================SCA接口异常,调用失败================="); + } + } + + private MultiValueMap buildRequestBody(ScaSbomConfigInfo scaSbomConfigInfo, File file) { + MultiValueMap body = new LinkedMultiValueMap<>(); + body.add("file", new FileSystemResource(file)); + body.add("projectName", scaSbomConfigInfo.getProjectName()); + body.add("applicationName", scaSbomConfigInfo.getApplicationName()); + body.add("applicationVersion", scaSbomConfigInfo.getApplicationVersion()); + body.add("applicationDescription", scaSbomConfigInfo.getApplicationDescription()); + return body; + } + + /** + * 创建请求头 + * + * @return HttpHeaders + */ + private HttpHeaders createHeaders() { + HttpHeaders headers = new HttpHeaders(); + headers.add("OpenApiUserToken", ConstantContextHolder.getScaToken()); + return headers; + } + + /** + * 轮询请求任务状态 + * + * @param restTemplate + * @param scaTaskId + */ + public void pollTaskStatus(RestTemplate restTemplate, Integer scaTaskId) { + int currentPollingTimes = 0; + while (currentPollingTimes < MAX_POLLING_TIMES) { + try { + HttpHeaders headers = new HttpHeaders(); + headers.add("OpenApiUserToken", ConstantContextHolder.getScaToken()); + HttpEntity requestEntity = new HttpEntity<>(null, headers); + String scaStatusUrl = ConstantContextHolder.getScaIp() + "/openapi/v1/task/" + scaTaskId; + ResponseEntity response = restTemplate.exchange(scaStatusUrl, HttpMethod.GET, requestEntity, JSONObject.class); + + if (Objects.requireNonNull(response.getBody()).getString("message").equals("success")) { + //"status": 5, //状态 0-未审计 1-未检测 2-排队中 3-检测中 4-检测暂停 5-检测完成 6-检测超时 7-手动停止 8-检测异常 9-已删除 10-拉取中 11-停止中 12-下载中 + int status = response.getBody().getJSONObject("data").getInteger("status"); + log.info("当前任务状态: " + status); + if (status == 5) { + System.out.println("任务已完成,停止轮询。"); + log.info("任务已完成,停止轮询。"); + break; + } + } else { + log.error("获取任务状态失败: " + response.getBody().getString("message")); + break; + } + } catch (Exception e) { + log.error("获取任务状态时发生错误: " + e.getMessage()); + } + try { + // 轮询间隔 5 秒 + Thread.sleep(POLLING_INTERVAL); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.error("轮询被中断: " + e.getMessage()); + } + currentPollingTimes++; + } + System.out.println("停止轮询"); + } + + /** + * 重新检测接口 + * + * @param restTemplate + * @param applicationId + */ + public int reStartTask(RestTemplate restTemplate, Integer applicationId) { + try { + String url = ConstantContextHolder.getScaIp() + "/openapi/v1/task/batch/detect"; + HttpHeaders headers = new HttpHeaders(); + headers.add("OpenApiUserToken", ConstantContextHolder.getScaToken()); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.add("Accept", MediaType.APPLICATION_JSON.toString()); + Map> param = new HashMap<>(); + param.put("applicationIds", Arrays.asList(applicationId)); + String s = JSON.toJSONString(param); + HttpEntity formEntry = new HttpEntity<>(s, headers); + + JSONObject res = restTemplate.postForObject(url, formEntry, JSONObject.class); + if (res.getString("message").equals("success")) { + log.info("重新检测成功"); + return 0; + } else { + log.error("重新检测失败"); + log.error(res.getString("message")); + return -1; + } + } catch (Exception e) { + log.error("重新检测失败"); + log.error(e.getMessage()); + } + return 0; + } + + @Override + public void execute(TaskRunContext context) { + + } +} diff --git a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dataObject/pipeline/PipPipeline.java b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dataObject/pipeline/PipPipeline.java index 84b65401..fb0cb6bf 100644 --- a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dataObject/pipeline/PipPipeline.java +++ b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dataObject/pipeline/PipPipeline.java @@ -1,8 +1,5 @@ package cd.casic.ci.process.process.dataObject.pipeline; -import cd.casic.ci.process.process.dataObject.base.PipBaseElement; -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableId; import cd.casic.ci.process.process.dataObject.base.PipBaseElement; import lombok.Data; import lombok.EqualsAndHashCode; @@ -52,7 +49,7 @@ public class PipPipeline extends PipBaseElement { /** * 目标id */ - private String targetId; + private String targetVersionId; /** * 目标类型