diff --git a/.github/workflows/scan-with-codeql.yml b/.github/workflows/scan-with-codeql.yml index 2cebc709..95858976 100644 --- a/.github/workflows/scan-with-codeql.yml +++ b/.github/workflows/scan-with-codeql.yml @@ -1,47 +1,50 @@ -# Name of the GitHub Actions workflow -name: CodeQL Analysis for JavaScript +name: JavaScript CodeQL Analysis -# Define when the workflow should be triggered on: - push: + pull_request: branches: - - development # Trigger when code is pushed to the 'development' branch - - main # Trigger when code is pushed to the 'main' branch + - development + - main -# Define the jobs to be executed within the workflow jobs: - build: - name: Scan JavaScript code with CodeQL - runs-on: [ 'ubuntu-latest' ] # Use the latest version of Ubuntu + analyze: + name: Analyze JavaScript code with CodeQL + runs-on: ubuntu-latest + needs: init permissions: actions: read contents: read security-events: write - strategy: - fail-fast: false - matrix: - language: [ 'javascript' ] - # CodeQL supports [ 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' ] - # Use only 'java-kotlin' to analyze code written in Java, Kotlin or both - # Use only 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both - # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support steps: - name: Checkout code uses: actions/checkout@v4 - # Action to check out the code from the repository - # This step fetches the codebase from the GitHub repository + if: github.event.pull_request.head.sha == github.event.pull_request.base.sha - name: Initialize CodeQL + id: initialize-codeql uses: github/codeql-action/init@v3 with: languages: javascript - # Action to initialize the CodeQL environment - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 + if: github.event.pull_request.head.sha == github.event.pull_request.base.sha with: - # Specify a category to distinguish between multiple analyses - # for the same tool and ref. If you don't use `category` in your workflow, - # GitHub will generate a default category name for you - category: "Scan-JavaScript-code-with-CodeQL" - \ No newline at end of file + category: "JavaScript CodeQL Analysis" + + init: + name: Initialize CodeQL environment + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Initialize CodeQL + id: initialize-codeql + uses: github/codeql-action/init@v3 + with: + languages: javascript + + outputs: + needs: initialize-codeql + codeql-version: ${{ steps.initialize-codeql.outputs.codeql-version }} diff --git a/.github/workflows/scan-with-owasp-dependency-check.yml b/.github/workflows/scan-with-owasp-dependency-check.yml index 783e27c3..e2dd4847 100644 --- a/.github/workflows/scan-with-owasp-dependency-check.yml +++ b/.github/workflows/scan-with-owasp-dependency-check.yml @@ -1,19 +1,24 @@ # Name of the GitHub Actions workflow name: Scan with OWASP Dependency Check +# Define environment variables +env: + PROJECT_NAME: Mutillidae + OUTPUT_DIR: . + # Define when the workflow should be triggered on: push: branches: - - development # Trigger when code is pushed to the 'development' branch - - main # Trigger when code is pushed to the 'main' branch + - development + - main # Define the job(s) to be executed within the workflow jobs: depchecktest: name: Scan with OWASP Dependency Check - - runs-on: [ 'ubuntu-latest' ] # Use the latest version of Ubuntu + runs-on: ubuntu-latest + timeout-minutes: 30 # Define permissions for specific actions permissions: @@ -24,33 +29,21 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 - # Step: Checkout code - # Action to check out the code from the repository - # This step fetches the codebase from the GitHub repository - name: Scan with OWASP Dependency Check + id: owasp-dependency-check uses: dependency-check/Dependency-Check_Action@main - id: Depcheck with: - project: 'Mutillidae' # Project name for Dependency Check - path: '.' # Scan all files in the current directory - format: 'SARIF' # Output format for scan results - out: '.' # Output directory (default is the current directory) + project: $PROJECT_NAME + path: ${{ env.OUTPUT_DIR }} + format: SARIF + out: ${{ env.OUTPUT_DIR }} args: > - --enableRetired # Additional scan arguments for Dependency Check + --enableRetired continue-on-error: true - # Step: Scan with OWASP Dependency Check - # Action to run OWASP Dependency Check to scan dependencies - # It identifies vulnerabilities in project dependencies - name: Upload results from OWASP Dependency Check to GitHub Code Scanning uses: github/codeql-action/upload-sarif@main with: - sarif_file: dependency-check-report.sarif - # Step: Upload results to GitHub Code Scanning - # Action to upload the results of the OWASP Dependency Check scan in SARIF format - # This allows viewing and analyzing the scan results in the GitHub repository + sarif_file: ${{ env.OUTPUT_DIR }}/dependency-check-report.sarif category: "Scan-dependencies-code-with-OWASP-Dependency-Check" - # Specify a category to distinguish between multiple analyses - # for the same tool and ref. If you don't use `category` in your workflow, - # GitHub will generate a default category name for you \ No newline at end of file diff --git a/.github/workflows/scan-with-semgrep.yml b/.github/workflows/scan-with-semgrep.yml index ba9f18c4..d85796ab 100644 --- a/.github/workflows/scan-with-semgrep.yml +++ b/.github/workflows/scan-with-semgrep.yml @@ -1,73 +1,78 @@ -# Name of this GitHub Actions workflow. name: Scan Application Code with Semgrep SAST on: - # Trigger the workflow on the following events: - - # Scan changed files in Pull Requests (diff-aware scanning). pull_request: {} - - # Trigger the workflow on-demand through the GitHub Actions interface. - workflow_dispatch: {} - - # Scan mainline branches (main and development) and report all findings. + workflow_dispatch: + inputs: + xss_config: + description: 'Path to Semgrep configuration file' + required: true + xss_output: + description: 'Path to Semgrep output file' + required: true + ci_config: + description: 'Path to Semgrep configuration file' + required: true + ci_output: + description: 'Path to Semgrep output file' + required: true push: branches: ["main", "development"] jobs: semgrep: - # User definable name of this GitHub Actions job. name: Scan Application Code with Semgrep SAST - - # Specify the runner environment. Use the latest version of Ubuntu. runs-on: ubuntu-latest - - # Define permissions for specific GitHub Actions. permissions: - actions: read # Permission to read GitHub Actions. - contents: read # Permission to read repository contents. - security-events: write # Permission to write security events. - + actions: read + contents: read + security-events: write container: - # Use a Docker image with Semgrep installed. Do not change this. - image: returntocorp/semgrep + image: returntocorp/semgrep:latest - # Skip any Pull Request created by the Dependabot to avoid permission issues. - if: (github.actor != 'dependabot[bot]') + if: github.actor != 'dependabot[bot]' steps: - - name: Checkout code - uses: actions/checkout@v4 - # Step: Checkout code - # Action to check out the code from the repository. - # This step fetches the codebase from the GitHub repository. + - name: Checkout code + uses: actions/checkout@v4 + + # Step to set the environment variables dynamically based on the event + - name: Set Config and Output Paths + id: vars + run: | + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + echo "xss_config=${{ github.event.inputs.xss_config }}" >> $GITHUB_ENV + echo "xss_output=${{ github.event.inputs.xss_output }}" >> $GITHUB_ENV + echo "ci_config=${{ github.event.inputs.ci_config }}" >> $GITHUB_ENV + echo "ci_output=${{ github.event.inputs.ci_output }}" >> $GITHUB_ENV + else + echo "xss_config=.github/semgrep/xss-config.yml" >> $GITHUB_ENV + echo "xss_output=xss-output.sarif" >> $GITHUB_ENV + echo "ci_config=.github/semgrep/ci-config.yml" >> $GITHUB_ENV + echo "ci_output=ci-output.sarif" >> $GITHUB_ENV + fi + shell: bash - - name: Run Semgrep XSS Scan - run: semgrep --config p/xss --sarif --output=semgrep-xss-results.sarif - continue-on-error: true - # Execute Semgrep to scan the code for XSS (Cross-Site Scripting) vulnerabilities using the p/xss configuration. - # Save the results in SARIF format to semgrep-xss-results.sarif. - # Continue the workflow even if there are errors during the scan. + # Run Semgrep XSS Scan using the dynamically set environment variables + - name: Run Semgrep XSS Scan + run: semgrep --config "$xss_config" --sarif --output="$xss_output" + continue-on-error: true - - name: Run Semgrep High-Confidence SAST Scan - run: semgrep --config p/ci --sarif --output=semgrep-ci-results.sarif - continue-on-error: true - # Execute Semgrep to scan the code for XSS (Cross-Site Scripting) vulnerabilities using the p/xss configuration. - # Save the results in SARIF format to semgrep-xss-results.sarif. - # Continue the workflow even if there are errors during the scan. + # Run Semgrep High-Confidence SAST Scan using the dynamically set environment variables + - name: Run Semgrep High-Confidence SAST Scan + run: semgrep --config "$ci_config" --sarif --output="$ci_output" + continue-on-error: true - - name: Upload XSS SARIF file for GitHub Advanced Security Dashboard - uses: github/codeql-action/upload-sarif@main - with: - sarif_file: semgrep-xss-results.sarif - category: "Semgrep XSS Scan" - if: always() - # Upload the SARIF file with scan results to the GitHub Advanced Security Dashboard. + # Upload the XSS SARIF file + - name: Upload XSS SARIF file + uses: github/codeql-action/upload-sarif@main + with: + sarif_file: "$xss_output" + category: "Semgrep XSS Scan" - - name: Upload CI SARIF file for GitHub Advanced Security Dashboard - uses: github/codeql-action/upload-sarif@main - with: - sarif_file: semgrep-ci-results.sarif - category: "Semgrep High-Confidence SAST Scan" - if: always() - # Upload the SARIF file with scan results to the GitHub Advanced Security Dashboard. + # Upload the High-Confidence SAST SARIF file + - name: Upload CI SARIF file + uses: github/codeql-action/upload-sarif@main + with: + sarif_file: "$ci_output" + category: "Semgrep High-Confidence SAST Scan" diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml new file mode 100644 index 00000000..0374d3d4 --- /dev/null +++ b/.github/workflows/sonarcloud.yml @@ -0,0 +1,32 @@ +name: SonarCloud Analysis + +on: + # Trigger analysis when pushing to your main branches, and when creating a pull request. + push: + branches: + - main + - master + - development + - 'releases/**' + pull_request: + types: [opened, synchronize, reopened] + +jobs: + sonar-scan: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + java-package: 'jdk' + + - name: SonarCloud Scan + uses: sonarsource/sonarcloud-github-action@v3 # Ex: v2.1.0, See the latest version at https://github.com/marketplace/actions/sonarcloud-scan + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index 5e0a3e4e..11720a42 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -1,3 +1,5 @@ +name: SonarQube Analysis + on: # Trigger analysis when pushing to your main branches, and when creating a pull request. push: @@ -9,35 +11,31 @@ on: pull_request: types: [opened, synchronize, reopened] -name: Mutillidae II SonarQube Workflow - jobs: - sonarqube: + sonar-scan: runs-on: ubuntu-latest - + steps: - - name: Checkout Code - uses: actions/checkout@v4 - with: - # Disabling shallow clones is recommended for improving the relevancy of reporting - fetch-depth: 0 + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Docker Compose + run: sudo apt-get install docker-compose + + - name: Build and Run SonarQube with Docker Compose + # Pass the SONAR_TOKEN from GitHub Secrets as an environment variable + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: docker-compose up -d sonar-server - - name: Set up JDK 11 (Required for SonarQube) - uses: actions/setup-java@v4 - with: - java-version: '11' - distribution: 'temurin' - java-package: 'jdk' + - name: Wait for SonarQube to be ready + run: sleep 180 # Adjust this time if needed, depending on your server startup time - - name: SonarQube Scan - uses: sonarsource/sonarqube-scan-action@v2.3.0 # Latest version of SonarQube scan action - env: - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} # SonarQube authentication token - SONAR_HOST_URL: ${{ vars.SONAR_HOST_URL }} # SonarQube URL + - name: Run SonarScanner with the environment variable + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: docker-compose run sonar-scanner - - name: Report Quality Gate Status - if: always() # Ensure the status is reported even if the scan fails - uses: sonarsource/sonarqube-quality-gate-action@master - env: - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - SONAR_HOST_URL: ${{ vars.SONAR_HOST_URL }} + - name: Tear down Docker Compose + if: always() + run: docker-compose down diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..3851b075 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,29 @@ +# Stage 1: Build the application +FROM php:7.4-apache AS build +WORKDIR /var/www/html +COPY . /var/www/html/ +RUN composer install --no-dev --prefer-dist + +# Stage 2: Create the final image +FROM php:7.4-apache +RUN groupadd -r www-data && useradd -r -g www-data -G www-data -d /var/www/html www-data +RUN chown -R www-data:www-data /var/www/html +COPY --from=build /var/www/html/public /var/www/html/ +COPY --from=build /var/www/html/.htaccess /var/www/html/ +COPY --from=build /var/www/html/index.php /var/www/html/ + +# Install the required PHP extensions +RUN apt-get update && apt-get install -y --no-install-recommends libapache2-mod-php7.4 php7.4-mysql php7.4-curl + +# Configure Apache to serve the Mutillidae-II application +RUN a2enmod rewrite +RUN sed -i 's/DocumentRoot \/var\/www\/html/DocumentRoot \/var\/www\/html\/public/g' /etc/apache2/sites-available/000-default.conf + +# Expose the Apache port +EXPOSE 80 + +# Switch to the non-root user +USER www-data + +# Start Apache when the container is launched +CMD ["apache2-foreground"] diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 00000000..a6861493 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,82 @@ +pipeline { + agent any + + tools { + // Define tools if needed (optional for PHP) + } + + environment { + SONAR_TOKEN = credentials('SONAR_TOKEN') // SonarQube token from Jenkins credentials + MYSQL_ROOT_PASSWORD = 'root' // MySQL root password for the MySQL container + } + + stages { + stage('Start MySQL Service') { + steps { + script { + // Start MySQL as a service container + sh ''' + docker run --name mysql-server -e MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD} -d mysql:5.7 + ''' + } + } + } + + stage('SonarQube Analysis') { + steps { + withCredentials([string(credentialsId: 'SONAR_TOKEN', variable: 'SONAR_TOKEN')]) { + script { + // Run SonarScanner for PHP with the correct SonarQube server URL and project key + sh ''' + sonar-scanner \ + -Dsonar.projectKey=Mutillidae-II \ + -Dsonar.sources=. \ + -Dsonar.host.url=http://localhost:9000 \ + -Dsonar.login=$SONAR_TOKEN \ + -Dsonar.php.tests.reportPath=./test-reports/unit-report.xml \ + -Dsonar.php.coverage.reportPaths=./test-reports/coverage.xml + ''' + } + } + } + } + + stage('Build PHP Application Docker Image') { + steps { + withDockerRegistry([credentialsId: "dockerlogin", url: ""]) { + script { + // Build the PHP application Docker image + app = docker.build("angel3/mutillidae:latest") + } + } + } + } + } + + stage('Quality Gate') { + steps { + script { + // Wait for the quality gate result from SonarQube + timeout(time: 10, unit: 'MINUTES') { + def qg = waitForQualityGate() + if (qg.status != 'OK') { + error "Pipeline failed due to SonarQube quality gate failure: ${qg.status}" + } + } + } + } + } + + post { + always { + stage('Tear Down') { + steps { + script { + // Stop MySQL container after use + sh 'docker stop mysql-server && docker rm mysql-server' + } + } + } + } + } +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..a4075835 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,44 @@ +services: + sonar-server: + image: sonarqube:latest + container_name: sonar-server + ports: + - "9000:9000" # Expose port 9000 for SonarQube + networks: + - sonarnet + environment: + - SONAR_JDBC_URL=jdbc:postgresql://db:5432/sonarqube + - SONAR_JDBC_USERNAME=sonar + - SONAR_JDBC_PASSWORD=sonar + + db: + image: postgres:latest + container_name: sonar-db + networks: + - sonarnet + environment: + - POSTGRES_USER=sonar + - POSTGRES_PASSWORD=sonar + - POSTGRES_DB=sonarqube + healthcheck: + test: ["CMD-SHELL", "curl -f http://localhost:9000 || exit 1"] + interval: 30s + timeout: 10s + retries: 10 + + sonar-scanner: + image: sonarsource/sonar-scanner-cli:latest + depends_on: + - sonar-server + networks: + - sonarnet + environment: + - SONAR_HOST_URL=http://sonar-server:9000 + - SONAR_TOKEN=${SONAR_TOKEN} + volumes: + - .:/usr/src + entrypoint: ["/bin/bash", "-c", "sonar-scanner -X -Dsonar.projectKey=Mutillidae-II-2 -Dsonar.sources=."] + +networks: + sonarnet: + driver: bridge # This defines the 'sonarnet' network with the bridge driver diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 00000000..13b0ade0 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,10 @@ +# Required project settings +sonar.organization=meleksabit +sonar.projectKey=meleksabit_mutillidae +sonar.projectName=mutillidae +sonar.host.url=https://sonarcloud.io + +# Optional configuration +sonar.sources=. +sonar.language=php # (or your project's primary language) +sonar.sourceEncoding=UTF-8