diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3d7fd4b..c14dd26 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,4 @@ -name: build Maven +name: CI/CD Pipeline with DevSecOps on: pull_request: @@ -10,34 +10,101 @@ on: - reopened jobs: - test_and_build: + # Build and Test + build_and_test: runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Set up Java + uses: actions/setup-java@v4 + with: + java-version: '23' + distribution: 'temurin' + + - name: Build and Test with Maven + run: | + mvn clean install + mvn test + - name: Create JAR File + run: mvn clean package + + - name: Upload Build Artifact + uses: actions/upload-artifact@v3 + with: + name: build-artifact + path: target/*.jar + + static_code_analysis: + runs-on: ubuntu-latest + needs: build_and_test steps: - - name: Checkout the repository - uses: actions/checkout@v4 + - name: Checkout Repository + uses: actions/checkout@v4 - - name: Set up Java 23 - uses: actions/setup-java@v4 - with: - java-version: '23' - distribution: 'temurin' + - name: Set up Java + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '23' - - name: Install dependencies and run tests - run: mvn clean install + - name: Build the project + run: mvn clean install -DskipTests - - name: Run tests with Maven - run: mvn test + - name: Cache SonarQube packages + uses: actions/cache@v4 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + - name: Cache Maven packages + uses: actions/cache@v4 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + - name: Build and analyze + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: mvn -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=yashsahsani_Learning-SpringBoot - - name: create jar file - run: mvn clean package - - create-docker: + + # Security Scanning + security_scanning: runs-on: ubuntu-latest - needs: test_and_build + needs: build_and_test + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Snyk Dependency Scan + uses: snyk/actions/setup@master + - name: Run Snyk Test + run: snyk test --all-projects --severity-threshold=high + env: + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + + - name: Monitor the project on Snyk + run: snyk monitor --all-projects + env: + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + + - name: Scan for Vulnerable Dependencies with Trivy + uses: aquasecurity/trivy-action@master + with: + scan-type: 'fs' + severity: 'CRITICAL,HIGH' + ignore-unfixed: true + # Docker Image Build and Scan + docker_build_and_scan: + runs-on: ubuntu-latest + needs: [build_and_test, security_scanning,static_code_analysis] steps: - - name: Checkout the repository + - name: Checkout Repository uses: actions/checkout@v4 - name: Set up Docker Buildx @@ -48,9 +115,24 @@ jobs: with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - - name: Build Docker image + + - name: Build Docker Image run: docker build -t ${{ secrets.DOCKER_USERNAME }}/spring-learning:${{ github.sha }} . - - name: Push Docker image to Docker Hub - run: | - docker push ${{ secrets.DOCKER_USERNAME }}/spring-learning:${{ github.sha }} \ No newline at end of file + # - name: Snyk Docker Image Scan + # uses: snyk/actions/docker@master + # with: + # image: ${{ secrets.DOCKER_USERNAME }}/spring-learning:${{ github.sha }} + # args: --severity-threshold=high + # env: + # SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + + - name: Scan Docker Image with Trivy + uses: aquasecurity/trivy-action@master + with: + image-ref: ${{ secrets.DOCKER_USERNAME }}/spring-learning:${{ github.sha }} + severity: 'CRITICAL,HIGH' + ignore-unfixed: true + + - name: Push Docker Image to Docker Hub + run: docker push ${{ secrets.DOCKER_USERNAME }}/spring-learning:${{ github.sha }} diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml new file mode 100644 index 0000000..14e192d --- /dev/null +++ b/.github/workflows/sonar.yml @@ -0,0 +1,37 @@ +name: SonarQube +on: + push: + branches: + - master + pull_request: + types: [opened, synchronize, reopened] +jobs: + build: + name: Build and analyze + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + - name: Set up JDK 23 + uses: actions/setup-java@v4 + with: + java-version: 23 + distribution: 'temurin' # Alternative distribution options are available. + - name: Cache SonarQube packages + uses: actions/cache@v4 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + - name: Cache Maven packages + uses: actions/cache@v4 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + - name: Build and analyze + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: mvn -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=yashsahsani_Learning-SpringBoot \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index a3a3c82..800d502 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ COPY . . RUN mvn clean package # Stage 2: Run the application -FROM openjdk:23-jdk-slim +FROM openjdk:24-jdk-slim WORKDIR /app COPY --from=builder /app/target/learning-spring.jar app.jar EXPOSE 8080 diff --git a/kubernetes/configMap.yaml b/kubernetes/configMap.yaml new file mode 100644 index 0000000..4ff2b71 --- /dev/null +++ b/kubernetes/configMap.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: spring-boot-app-config + namespace: spring +data: + DATASOURCE_URL: jdbc:mysql://db-service:3306/learning_spring + SPRING_PROFILES_ACTIVE: prod \ No newline at end of file diff --git a/kubernetes/db-deploy.yaml b/kubernetes/db-deploy.yaml index 0072091..7932588 100644 --- a/kubernetes/db-deploy.yaml +++ b/kubernetes/db-deploy.yaml @@ -6,7 +6,7 @@ metadata: labels: app: db spec: - replicas: 1 + replicas: 2 selector: matchLabels: app: db @@ -20,16 +20,19 @@ spec: image: mysql:latest env: - name: MYSQL_ROOT_PASSWORD - value: "prodroot" + valueFrom: + secretKeyRef: + name: db-secret + key: MYSQL_ROOT_PASSWORD - name: MYSQL_DATABASE value: "learning_spring" ports: - containerPort: 3306 - # volumeMounts: - # - name: mysql-data - # mountPath: /var/lib/mysql - # volumes: - # - name: mysql-data - # persistentVolumeClaim: - # claimName: mysql-pvc + volumeMounts: + - name: mysql-data + mountPath: /var/lib/mysql + volumes: + - name: mysql-data + persistentVolumeClaim: + claimName: mysql-pvc \ No newline at end of file diff --git a/kubernetes/db-secrets.yaml b/kubernetes/db-secrets.yaml new file mode 100644 index 0000000..0c98f1d --- /dev/null +++ b/kubernetes/db-secrets.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Secret +metadata: + name: db-secrets + namespace: spring +type: Opaque +data: + MYSQL_ROOT_PASSWORD: cGFzc3dvcmQ= \ No newline at end of file diff --git a/kubernetes/secrets.yaml b/kubernetes/secrets.yaml new file mode 100644 index 0000000..a2f8742 --- /dev/null +++ b/kubernetes/secrets.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Secret +metadata: + name: spring-boot-app-secret + namespace: spring +type: Opaque +data: + DATASOURCE_USERNAME: cm9vdA== + DATASOURCE_PASSWORD: cHJvZHJvb3Q= diff --git a/kubernetes/spring-deploy.yml b/kubernetes/spring-deploy.yml index b549036..6e80097 100644 --- a/kubernetes/spring-deploy.yml +++ b/kubernetes/spring-deploy.yml @@ -6,7 +6,7 @@ metadata: labels: app: spring-boot-app spec: - replicas: 1 + replicas: 4 selector: matchLabels: app: spring-boot-app @@ -18,14 +18,31 @@ spec: containers: - name: spring-boot-app image: yashsahsani/spring-learning:latest + securityContext: + readOnlyRootFilesystem: true + runAsNonRoot: true + allowPrivilegeEscalation: false env: - name: DATASOURCE_URL - value: jdbc:mysql://db-service:3306/learning_spring + valueFrom: + configMapKeyRef: + name: spring-boot-app-config + key: DATASOURCE_URL - name: DATASOURCE_USERNAME - value: root + valueFrom: + secretKeyRef: + name: spring-boot-app-secret + key: DATASOURCE_USERNAME - name: DATASOURCE_PASSWORD - value: prodroot + valueFrom: + secretKeyRef: + name: spring-boot-app-secret + key: DATASOURCE_PASSWORD - name: SPRING_PROFILES_ACTIVE - value: prod + valueFrom: + configMapKeyRef: + name: spring-boot-app-config + key: SPRING_PROFILES_ACTIVE ports: - - containerPort: 8080 \ No newline at end of file + - containerPort: 8080 + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 60aa55d..1d7d280 100644 --- a/pom.xml +++ b/pom.xml @@ -28,6 +28,8 @@ 23 + yashsahsani + https://sonarcloud.io @@ -37,15 +39,20 @@ - + + + org.apache.tomcat.embed + tomcat-embed-core + 11.0.2 + org.springframework.boot spring-boot-starter-web + 3.4.1 diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000..8666443 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,5 @@ +sonar.projectKey=yashsashani_Learning-SpringBoot +sonar.organization=yashsahsani +sonar.host.url=https://sonarcloud.io +sonar.sources=. +sonar.java.binaries=target/classes diff --git a/src/main/java/com/github/learning_spring/controller/DepartmentController.java b/src/main/java/com/github/learning_spring/controller/DepartmentController.java index b3d6867..c0d689e 100644 --- a/src/main/java/com/github/learning_spring/controller/DepartmentController.java +++ b/src/main/java/com/github/learning_spring/controller/DepartmentController.java @@ -52,7 +52,8 @@ public String updateDepartmentById(@PathVariable("id") Long departmentId,@Valid } @DeleteMapping("/deleteDepartment/{id}") - public String deleteMethodName(@PathVariable String id) { + public String deleteMethodName(@Valid @PathVariable String id) { + return departmentService.deleteDepartment(id); }