diff --git a/.eslintignore b/.eslintignore index 2d0c0644..9c3f2cfc 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,4 @@ dist/ node_modules/ coverage/ +*.test.ts \ No newline at end of file diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..b6a68c93 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1 @@ +!!! REMEMBER TO ADD A LABEL IN ORDER TO CREATE A RELEASE !!! (REMOVE THIS) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 00000000..8e13fe40 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,41 @@ +name: 'CodeQL' + +on: + push: + branches: ['main'] + pull_request: + branches: ['main'] + schedule: + - cron: '25 21 * * 1' + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }} + permissions: + security-events: write + packages: read + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + include: + - language: javascript-typescript + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: '/language:${{matrix.language}}' diff --git a/README.md b/README.md index 2c1384dd..0e208add 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,17 @@ [![OP Compliance Dashboard](https://img.shields.io/badge/OP%20Compliance%20Dashboard-click%20here-blue)](https://cydig.omegapoint.cloud/cydig)

-![Timestamp](https://img.shields.io/endpoint?url=https%3A%2F%2Ffunc-cydig-comp-state-prod.azurewebsites.net%2Fapi%2FReadToReadme%3Fcode%3DxaEvCDsaK01y2Z6SBivwOKndN4o915lpOTt1VkmULgsxgsjkml7u1DOhgULzmAPX%26teamName%3DCyDig%26teamProjectName%3DCyDig%26codeRepositoryName%3Dcydig-compliance-action%26stateType%3Dtimestamp)

-![threatModelingDate](https://img.shields.io/endpoint?url=https%3A%2F%2Ffunc-cydig-comp-state-prod.azurewebsites.net%2Fapi%2FReadToReadme%3Fcode%3DxaEvCDsaK01y2Z6SBivwOKndN4o915lpOTt1VkmULgsxgsjkml7u1DOhgULzmAPX%26teamName%3DCyDig%26teamProjectName%3DCyDig%26codeRepositoryName%3Dcydig-compliance-action%26stateType%3DthreatModelingDate)
-![numberOfReviewers](https://img.shields.io/endpoint?url=https%3A%2F%2Ffunc-cydig-comp-state-prod.azurewebsites.net%2Fapi%2FReadToReadme%3Fcode%3DxaEvCDsaK01y2Z6SBivwOKndN4o915lpOTt1VkmULgsxgsjkml7u1DOhgULzmAPX%26teamName%3DCyDig%26teamProjectName%3DCyDig%26codeRepositoryName%3Dcydig-compliance-action%26stateType%3DnumberOfReviewers)
-[![secureScore](https://img.shields.io/endpoint?url=https%3A%2F%2Ffunc-cydig-comp-state-prod.azurewebsites.net%2Fapi%2FReadToReadme%3Fcode%3DxaEvCDsaK01y2Z6SBivwOKndN4o915lpOTt1VkmULgsxgsjkml7u1DOhgULzmAPX%26teamName%3DCyDig%26teamProjectName%3DCyDig%26codeRepositoryName%3Dcydig-compliance-action%26stateType%3DsecureScore)](https://portal.azure.com/#view/Microsoft_Azure_Security/RecommendationsBladeV2/subscriptionIds~/%5B%2215c6235f-9e0f-4073-baf4-4fd0a7913d76%22%5D/source/SecurityPosture_ViewRecommendation)
-[![allowedLocationPolicy](https://img.shields.io/endpoint?url=https%3A%2F%2Ffunc-cydig-comp-state-prod.azurewebsites.net%2Fapi%2FReadToReadme%3Fcode%3DxaEvCDsaK01y2Z6SBivwOKndN4o915lpOTt1VkmULgsxgsjkml7u1DOhgULzmAPX%26teamName%3DCyDig%26teamProjectName%3DCyDig%26codeRepositoryName%3Dcydig-compliance-action%26stateType%3DallowedLocationPolicy)](https://portal.azure.com/#view/Microsoft_Azure_Policy/PolicyMenuBlade/~/Compliance)
-![pentestDate](https://img.shields.io/endpoint?url=https%3A%2F%2Ffunc-cydig-comp-state-prod.azurewebsites.net%2Fapi%2FReadToReadme%3Fcode%3DxaEvCDsaK01y2Z6SBivwOKndN4o915lpOTt1VkmULgsxgsjkml7u1DOhgULzmAPX%26teamName%3DCyDig%26teamProjectName%3DCyDig%26codeRepositoryName%3Dcydig-compliance-action%26stateType%3DpentestDate)
-![numberOfDeployedVMs](https://img.shields.io/endpoint?url=https%3A%2F%2Ffunc-cydig-comp-state-prod.azurewebsites.net%2Fapi%2FReadToReadme%3Fcode%3DxaEvCDsaK01y2Z6SBivwOKndN4o915lpOTt1VkmULgsxgsjkml7u1DOhgULzmAPX%26teamName%3DCyDig%26teamProjectName%3DCyDig%26codeRepositoryName%3Dcydig-compliance-action%26stateType%3DnumberOfDeployedVMs)
-![usersInProduction](https://img.shields.io/endpoint?url=https%3A%2F%2Ffunc-cydig-comp-state-prod.azurewebsites.net%2Fapi%2FReadToReadme%3Fcode%3DxaEvCDsaK01y2Z6SBivwOKndN4o915lpOTt1VkmULgsxgsjkml7u1DOhgULzmAPX%26teamName%3DCyDig%26teamProjectName%3DCyDig%26codeRepositoryName%3Dcydig-compliance-action%26stateType%3DusersInProduction)
- - +![Timestamp](https://img.shields.io/endpoint?url=https%3A%2F%2Ffunc-cydig-badge-service-prod.azurewebsites.net%2Fapi%2Fteams%2FCyDig%2Fsources%2FGitHub%2Fprojects%2Fnot-specified%2Frepositories%2Fcydig-compliance-action%2Fcontrols%2Ftimestamp%3Fcode%3DxaEvCDsaK01y2Z6SBivwOKndN4o915lpOTt1VkmULgsxgsjkml7u1DOhgULzmAPX)

+![threatModelingDate](https://img.shields.io/endpoint?url=https%3A%2F%2Ffunc-cydig-badge-service-prod.azurewebsites.net%2Fapi%2Fteams%2FCyDig%2Fsources%2FGitHub%2Fprojects%2Fnot-specified%2Frepositories%2Fcydig-compliance-action%2Fcontrols%2FthreatModelingDate%3Fcode%3DxaEvCDsaK01y2Z6SBivwOKndN4o915lpOTt1VkmULgsxgsjkml7u1DOhgULzmAPX)
+![numberOfReviewers](https://img.shields.io/endpoint?url=https%3A%2F%2Ffunc-cydig-badge-service-prod.azurewebsites.net%2Fapi%2Fteams%2FCyDig%2Fsources%2FGitHub%2Fprojects%2Fnot-specified%2Frepositories%2Fcydig-compliance-action%2Fcontrols%2FnumberOfReviewers%3Fcode%3DxaEvCDsaK01y2Z6SBivwOKndN4o915lpOTt1VkmULgsxgsjkml7u1DOhgULzmAPX)
+![scaTool](https://img.shields.io/endpoint?url=https%3A%2F%2Ffunc-cydig-badge-service-prod.azurewebsites.net%2Fapi%2Fteams%2FCyDig%2Fsources%2FGitHub%2Fprojects%2Fnot-specified%2Frepositories%2Fcydig-compliance-action%2Fcontrols%2FscaTool%3Fcode%3DxaEvCDsaK01y2Z6SBivwOKndN4o915lpOTt1VkmULgsxgsjkml7u1DOhgULzmAPX)
+![sastTool](https://img.shields.io/endpoint?url=https%3A%2F%2Ffunc-cydig-badge-service-prod.azurewebsites.net%2Fapi%2Fteams%2FCyDig%2Fsources%2FGitHub%2Fprojects%2Fnot-specified%2Frepositories%2Fcydig-compliance-action%2Fcontrols%2FsastTool%3Fcode%3DxaEvCDsaK01y2Z6SBivwOKndN4o915lpOTt1VkmULgsxgsjkml7u1DOhgULzmAPX)
+[![secureScore](https://img.shields.io/endpoint?url=https%3A%2F%2Ffunc-cydig-badge-service-prod.azurewebsites.net%2Fapi%2Fteams%2FCyDig%2Fsources%2FGitHub%2Fprojects%2Fnot-specified%2Frepositories%2Fcydig-compliance-action%2Fcontrols%2FsecureScore%3Fcode%3DxaEvCDsaK01y2Z6SBivwOKndN4o915lpOTt1VkmULgsxgsjkml7u1DOhgULzmAPX)](https://portal.azure.com/#view/Microsoft_Azure_Security/RecommendationsBladeV2/subscriptionIds~/%5B%22***%22%5D/source/SecurityPosture_ViewRecommendation)
+[![allowedLocationPolicy](https://img.shields.io/endpoint?url=https%3A%2F%2Ffunc-cydig-badge-service-prod.azurewebsites.net%2Fapi%2Fteams%2FCyDig%2Fsources%2FGitHub%2Fprojects%2Fnot-specified%2Frepositories%2Fcydig-compliance-action%2Fcontrols%2FallowedLocationPolicy%3Fcode%3DxaEvCDsaK01y2Z6SBivwOKndN4o915lpOTt1VkmULgsxgsjkml7u1DOhgULzmAPX)](https://portal.azure.com/#view/Microsoft_Azure_Policy/PolicyMenuBlade/~/Compliance)
+![pentestDate](https://img.shields.io/endpoint?url=https%3A%2F%2Ffunc-cydig-badge-service-prod.azurewebsites.net%2Fapi%2Fteams%2FCyDig%2Fsources%2FGitHub%2Fprojects%2Fnot-specified%2Frepositories%2Fcydig-compliance-action%2Fcontrols%2FpentestDate%3Fcode%3DxaEvCDsaK01y2Z6SBivwOKndN4o915lpOTt1VkmULgsxgsjkml7u1DOhgULzmAPX)
+![numberOfDeployedVMs](https://img.shields.io/endpoint?url=https%3A%2F%2Ffunc-cydig-badge-service-prod.azurewebsites.net%2Fapi%2Fteams%2FCyDig%2Fsources%2FGitHub%2Fprojects%2Fnot-specified%2Frepositories%2Fcydig-compliance-action%2Fcontrols%2FnumberOfDeployedVMs%3Fcode%3DxaEvCDsaK01y2Z6SBivwOKndN4o915lpOTt1VkmULgsxgsjkml7u1DOhgULzmAPX)
+![numberOfExposedSecrets](https://img.shields.io/endpoint?url=https%3A%2F%2Ffunc-cydig-badge-service-prod.azurewebsites.net%2Fapi%2Fteams%2FCyDig%2Fsources%2FGitHub%2Fprojects%2Fnot-specified%2Frepositories%2Fcydig-compliance-action%2Fcontrols%2FnumberOfExposedSecrets%3Fcode%3DxaEvCDsaK01y2Z6SBivwOKndN4o915lpOTt1VkmULgsxgsjkml7u1DOhgULzmAPX)
+![codeQualityTool](https://img.shields.io/endpoint?url=https%3A%2F%2Ffunc-cydig-badge-service-prod.azurewebsites.net%2Fapi%2Fteams%2FCyDig%2Fsources%2FGitHub%2Fprojects%2Fnot-specified%2Frepositories%2Fcydig-compliance-action%2Fcontrols%2FcodeQualityTool%3Fcode%3DxaEvCDsaK01y2Z6SBivwOKndN4o915lpOTt1VkmULgsxgsjkml7u1DOhgULzmAPX)
+![usersInProduction](https://img.shields.io/endpoint?url=https%3A%2F%2Ffunc-cydig-badge-service-prod.azurewebsites.net%2Fapi%2Fteams%2FCyDig%2Fsources%2FGitHub%2Fprojects%2Fnot-specified%2Frepositories%2Fcydig-compliance-action%2Fcontrols%2FusersInProduction%3Fcode%3DxaEvCDsaK01y2Z6SBivwOKndN4o915lpOTt1VkmULgsxgsjkml7u1DOhgULzmAPX)
+![userAccessToCode](https://img.shields.io/endpoint?url=https%3A%2F%2Ffunc-cydig-badge-service-prod.azurewebsites.net%2Fapi%2Fteams%2FCyDig%2Fsources%2FGitHub%2Fprojects%2Fnot-specified%2Frepositories%2Fcydig-compliance-action%2Fcontrols%2FentitiesInCode%3Fcode%3DxaEvCDsaK01y2Z6SBivwOKndN4o915lpOTt1VkmULgsxgsjkml7u1DOhgULzmAPX)
# CyDig Compliance Action @@ -19,8 +22,11 @@ This repository contains a action with compliance controls. The compliance controls that are currently available are listed below. * Number of reviewers on a pull request +* Number of exposed secrets * Date of latest threat modeling * Date of latest penetration test +* Implemented SCA tool +* Implemented SAST tool ## Development on already existing or new control. diff --git a/package-lock.json b/package-lock.json index 1c251b7d..60de0a99 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,8 +11,8 @@ "dependencies": { "@actions/core": "^1.10.0", "@actions/github": "^5.1.1", - "@octokit/plugin-retry": "^7.0.3", - "@octokit/rest": "^20.0.2", + "@octokit/plugin-retry": "^6.0.1", + "@octokit/rest": "^20.1.0", "@vercel/ncc": "^0.36.1", "azure-devops-node-api": "^12.4.0" }, @@ -278,82 +278,93 @@ } }, "node_modules/@octokit/auth-token": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.0.1.tgz", - "integrity": "sha512-RTmWsLfig8SBoiSdgvCht4BXl1CHU89Co5xiQ5JF19my/sIRDFCQ1RPrmK0exgqUZuNm39C/bV8+/83+MJEjGg==", - "peer": true, + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", + "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==", "engines": { "node": ">= 18" } }, "node_modules/@octokit/core": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.0.1.tgz", - "integrity": "sha512-MIpPQXu8Y8GjHwXM81JLveiV+DHJZtLMcB5nKekBGOl3iAtk0HT3i12Xl8Biybu+bCS1+k4qbuKEq5d0RxNRnQ==", - "peer": true, - "dependencies": { - "@octokit/auth-token": "^5.0.0", - "@octokit/graphql": "^8.0.0", - "@octokit/request": "^9.0.0", - "@octokit/request-error": "^6.0.1", - "@octokit/types": "^12.0.0", - "before-after-hook": "^3.0.2", - "universal-user-agent": "^7.0.0" + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.0.tgz", + "integrity": "sha512-1LFfa/qnMQvEOAdzlQymH0ulepxbxnCYAKJZfMci/5XJyIHWgEYnDmgnKakbTh7CH2tFQ5O60oYDvns4i9RAIg==", + "dependencies": { + "@octokit/auth-token": "^4.0.0", + "@octokit/graphql": "^7.1.0", + "@octokit/request": "^8.3.1", + "@octokit/request-error": "^5.1.0", + "@octokit/types": "^13.0.0", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" }, "engines": { "node": ">= 18" } }, - "node_modules/@octokit/core/node_modules/before-after-hook": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz", - "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==", - "peer": true + "node_modules/@octokit/core/node_modules/@octokit/openapi-types": { + "version": "22.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", + "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==" }, - "node_modules/@octokit/core/node_modules/universal-user-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz", - "integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==", - "peer": true + "node_modules/@octokit/core/node_modules/@octokit/types": { + "version": "13.5.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.5.0.tgz", + "integrity": "sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==", + "dependencies": { + "@octokit/openapi-types": "^22.2.0" + } }, "node_modules/@octokit/endpoint": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.0.0.tgz", - "integrity": "sha512-emBcNDxBdC1y3+knJonS5zhUB/CG6TihubxM2U1/pG/Z1y3a4oV0Gzz3lmkCvWWQI6h3tqBAX9MgCBFp+M68Jw==", - "peer": true, + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.5.tgz", + "integrity": "sha512-ekqR4/+PCLkEBF6qgj8WqJfvDq65RH85OAgrtnVp1mSxaXF03u2xW/hUdweGS5654IlC0wkNYC18Z50tSYTAFw==", "dependencies": { - "@octokit/types": "^12.0.0", - "universal-user-agent": "^7.0.2" + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" }, "engines": { "node": ">= 18" } }, - "node_modules/@octokit/endpoint/node_modules/universal-user-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz", - "integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==", - "peer": true + "node_modules/@octokit/endpoint/node_modules/@octokit/openapi-types": { + "version": "22.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", + "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==" + }, + "node_modules/@octokit/endpoint/node_modules/@octokit/types": { + "version": "13.5.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.5.0.tgz", + "integrity": "sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==", + "dependencies": { + "@octokit/openapi-types": "^22.2.0" + } }, "node_modules/@octokit/graphql": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.0.1.tgz", - "integrity": "sha512-lLDb6LhC1gBj2CxEDa5Xk10+H/boonhs+3Mi6jpRyetskDKNHe6crMeKmUE2efoLofMP8ruannLlCUgpTFmVzQ==", - "peer": true, + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.1.0.tgz", + "integrity": "sha512-r+oZUH7aMFui1ypZnAvZmn0KSqAUgE1/tUXIWaqUCa1758ts/Jio84GZuzsvUkme98kv0WFY8//n0J1Z+vsIsQ==", "dependencies": { - "@octokit/request": "^9.0.0", - "@octokit/types": "^12.0.0", - "universal-user-agent": "^7.0.0" + "@octokit/request": "^8.3.0", + "@octokit/types": "^13.0.0", + "universal-user-agent": "^6.0.0" }, "engines": { "node": ">= 18" } }, - "node_modules/@octokit/graphql/node_modules/universal-user-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz", - "integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==", - "peer": true + "node_modules/@octokit/graphql/node_modules/@octokit/openapi-types": { + "version": "22.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", + "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==" + }, + "node_modules/@octokit/graphql/node_modules/@octokit/types": { + "version": "13.5.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.5.0.tgz", + "integrity": "sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==", + "dependencies": { + "@octokit/openapi-types": "^22.2.0" + } }, "node_modules/@octokit/openapi-types": { "version": "20.0.0", @@ -384,6 +395,17 @@ "@octokit/openapi-types": "^12.11.0" } }, + "node_modules/@octokit/plugin-request-log": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-4.0.1.tgz", + "integrity": "sha512-GihNqNpGHorUrO7Qa9JbAl0dbLnqJVrV8OXe2Zm5/Y4wFkZQDfTreBzVmiRfJVfE4mClXdihHnbpyyO9FSX4HA==", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } + }, "node_modules/@octokit/plugin-rest-endpoint-methods": { "version": "5.16.2", "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.16.2.tgz", @@ -410,11 +432,11 @@ } }, "node_modules/@octokit/plugin-retry": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-7.0.3.tgz", - "integrity": "sha512-T9l5Z7XnDZ7dkyNmhJPSUq0YjbqUT/xn4yQbhcSuv4WGC/LqM73/mKwkl68VDPoLw20e8oz4L7qQopWt9v6sow==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-6.0.1.tgz", + "integrity": "sha512-SKs+Tz9oj0g4p28qkZwl/topGcb0k0qPNX/i7vBKmDsjoeqnVfFUquqrE/O9oJY7+oLzdCtkiWSXLpLjvl6uog==", "dependencies": { - "@octokit/request-error": "^6.0.0", + "@octokit/request-error": "^5.0.0", "@octokit/types": "^12.0.0", "bottleneck": "^2.15.3" }, @@ -422,100 +444,71 @@ "node": ">= 18" }, "peerDependencies": { - "@octokit/core": ">=6" + "@octokit/core": ">=5" } }, "node_modules/@octokit/request": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.0.1.tgz", - "integrity": "sha512-kL+cAcbSl3dctYLuJmLfx6Iku2MXXy0jszhaEIjQNaCp4zjHXrhVAHeuaRdNvJjW9qjl3u1MJ72+OuBP0YW/pg==", - "peer": true, + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.0.tgz", + "integrity": "sha512-9Bb014e+m2TgBeEJGEbdplMVWwPmL1FPtggHQRkV+WVsMggPtEkLKPlcVYm/o8xKLkpJ7B+6N8WfQMtDLX2Dpw==", "dependencies": { - "@octokit/endpoint": "^10.0.0", - "@octokit/request-error": "^6.0.1", - "@octokit/types": "^12.0.0", - "universal-user-agent": "^7.0.2" + "@octokit/endpoint": "^9.0.1", + "@octokit/request-error": "^5.1.0", + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" }, "engines": { "node": ">= 18" } }, "node_modules/@octokit/request-error": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.0.2.tgz", - "integrity": "sha512-WtRVpoHcNXs84+s9s/wqfHaxM68NGMg8Av7h59B50OVO0PwwMx+2GgQ/OliUd0iQBSNWgR6N8afi/KjSHbXHWw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.0.tgz", + "integrity": "sha512-GETXfE05J0+7H2STzekpKObFe765O5dlAKUTLNGeH+x47z7JjXHfsHKo5z21D/o/IOZTUEI6nyWyR+bZVP/n5Q==", "dependencies": { - "@octokit/types": "^12.0.0" + "@octokit/types": "^13.1.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" }, "engines": { "node": ">= 18" } }, - "node_modules/@octokit/request/node_modules/universal-user-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz", - "integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==", - "peer": true + "node_modules/@octokit/request-error/node_modules/@octokit/openapi-types": { + "version": "22.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", + "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==" }, - "node_modules/@octokit/rest": { - "version": "20.0.2", - "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-20.0.2.tgz", - "integrity": "sha512-Ux8NDgEraQ/DMAU1PlAohyfBBXDwhnX2j33Z1nJNziqAfHi70PuxkFYIcIt8aIAxtRE7KVuKp8lSR8pA0J5iOQ==", + "node_modules/@octokit/request-error/node_modules/@octokit/types": { + "version": "13.5.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.5.0.tgz", + "integrity": "sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==", "dependencies": { - "@octokit/core": "^5.0.0", - "@octokit/plugin-paginate-rest": "^9.0.0", - "@octokit/plugin-request-log": "^4.0.0", - "@octokit/plugin-rest-endpoint-methods": "^10.0.0" - }, - "engines": { - "node": ">= 18" + "@octokit/openapi-types": "^22.2.0" } }, - "node_modules/@octokit/rest/node_modules/@octokit/auth-token": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", - "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==", - "engines": { - "node": ">= 18" - } + "node_modules/@octokit/request/node_modules/@octokit/openapi-types": { + "version": "22.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", + "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==" }, - "node_modules/@octokit/rest/node_modules/@octokit/core": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.1.0.tgz", - "integrity": "sha512-BDa2VAMLSh3otEiaMJ/3Y36GU4qf6GI+VivQ/P41NC6GHcdxpKlqV0ikSZ5gdQsmS3ojXeRx5vasgNTinF0Q4g==", + "node_modules/@octokit/request/node_modules/@octokit/types": { + "version": "13.5.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.5.0.tgz", + "integrity": "sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==", "dependencies": { - "@octokit/auth-token": "^4.0.0", - "@octokit/graphql": "^7.0.0", - "@octokit/request": "^8.0.2", - "@octokit/request-error": "^5.0.0", - "@octokit/types": "^12.0.0", - "before-after-hook": "^2.2.0", - "universal-user-agent": "^6.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/rest/node_modules/@octokit/endpoint": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.4.tgz", - "integrity": "sha512-DWPLtr1Kz3tv8L0UvXTDP1fNwM0S+z6EJpRcvH66orY6Eld4XBMCSYsaWp4xIm61jTWxK68BrR7ibO+vSDnZqw==", - "dependencies": { - "@octokit/types": "^12.0.0", - "universal-user-agent": "^6.0.0" - }, - "engines": { - "node": ">= 18" + "@octokit/openapi-types": "^22.2.0" } }, - "node_modules/@octokit/rest/node_modules/@octokit/graphql": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.0.2.tgz", - "integrity": "sha512-OJ2iGMtj5Tg3s6RaXH22cJcxXRi7Y3EBqbHTBRq+PQAqfaS8f/236fUrWhfSn8P4jovyzqucxme7/vWSSZBX2Q==", + "node_modules/@octokit/rest": { + "version": "20.1.0", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-20.1.0.tgz", + "integrity": "sha512-STVO3itHQLrp80lvcYB2UIKoeil5Ctsgd2s1AM+du3HqZIR35ZH7WE9HLwUOLXH0myA0y3AGNPo8gZtcgIbw0g==", "dependencies": { - "@octokit/request": "^8.0.1", - "@octokit/types": "^12.0.0", - "universal-user-agent": "^6.0.0" + "@octokit/core": "^5.0.2", + "@octokit/plugin-paginate-rest": "^9.1.5", + "@octokit/plugin-request-log": "^4.0.0", + "@octokit/plugin-rest-endpoint-methods": "^10.2.0" }, "engines": { "node": ">= 18" @@ -535,17 +528,6 @@ "@octokit/core": "5" } }, - "node_modules/@octokit/rest/node_modules/@octokit/plugin-request-log": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-4.0.1.tgz", - "integrity": "sha512-GihNqNpGHorUrO7Qa9JbAl0dbLnqJVrV8OXe2Zm5/Y4wFkZQDfTreBzVmiRfJVfE4mClXdihHnbpyyO9FSX4HA==", - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": "5" - } - }, "node_modules/@octokit/rest/node_modules/@octokit/plugin-rest-endpoint-methods": { "version": "10.4.1", "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-10.4.1.tgz", @@ -560,33 +542,6 @@ "@octokit/core": "5" } }, - "node_modules/@octokit/rest/node_modules/@octokit/request": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.2.0.tgz", - "integrity": "sha512-exPif6x5uwLqv1N1irkLG1zZNJkOtj8bZxuVHd71U5Ftuxf2wGNvAJyNBcPbPC+EBzwYEbBDdSFb8EPcjpYxPQ==", - "dependencies": { - "@octokit/endpoint": "^9.0.0", - "@octokit/request-error": "^5.0.0", - "@octokit/types": "^12.0.0", - "universal-user-agent": "^6.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/rest/node_modules/@octokit/request-error": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.0.1.tgz", - "integrity": "sha512-X7pnyTMV7MgtGmiXBwmO6M5kIPrntOXdyKZLigNfQWSEQzVxR4a4vo49vJjTWX70mPndj8KhfT4Dx+2Ng3vnBQ==", - "dependencies": { - "@octokit/types": "^12.0.0", - "deprecation": "^2.0.0", - "once": "^1.4.0" - }, - "engines": { - "node": ">= 18" - } - }, "node_modules/@octokit/types": { "version": "12.6.0", "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", @@ -1042,12 +997,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -1614,9 +1569,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" diff --git a/package.json b/package.json index 13f0de36..a363d87d 100644 --- a/package.json +++ b/package.json @@ -25,8 +25,8 @@ "dependencies": { "@actions/core": "^1.10.0", "@actions/github": "^5.1.1", - "@octokit/plugin-retry": "^7.0.3", - "@octokit/rest": "^20.0.2", + "@octokit/plugin-retry": "^6.0.1", + "@octokit/rest": "^20.1.0", "@vercel/ncc": "^0.36.1", "azure-devops-node-api": "^12.4.0" }, diff --git a/src/AccessToCode/AccessToCodeService.ts b/src/AccessToCode/AccessToCodeService.ts new file mode 100644 index 00000000..fd4c3ff5 --- /dev/null +++ b/src/AccessToCode/AccessToCodeService.ts @@ -0,0 +1,62 @@ +import * as core from '@actions/core'; +import { Octokit } from '@octokit/rest'; +import { GetResponseDataTypeFromEndpointMethod, OctokitResponse } from '@octokit/types'; + +export class AccessToCodeService { + public static async setAccessToCodeFindings(octokit: Octokit, owner: string, repo: string): Promise { + try { + console.log('--- Access To CodeService Control ---'); + + type listCollaboratorsForRepoResponseDataType = GetResponseDataTypeFromEndpointMethod< + typeof octokit.repos.listCollaborators + >; + + // https://www.npmjs.com/package/octokit#pagination + const iterator: AsyncIterableIterator> = + octokit.paginate.iterator(octokit.repos.listCollaborators, { + owner: owner, + repo: repo, + per_page: 100, + affiliation: 'all', + }); + + let numberOfAdmins: number = 0; + let numberOfWriters: number = 0; + let numberOfReaders: number = 0; + + for await (const { data: page } of iterator) { + for (const user of page) { + if (user.permissions!.admin) { + numberOfAdmins++; + } else if (user.permissions!.maintain!) { + numberOfWriters++; + } else if (user.permissions!.push!) { + numberOfWriters++; + } else if (user.permissions!.triage!) { + numberOfReaders++; + } else if (user.permissions!.pull!) { + numberOfReaders++; + } + } + } + console.log('numberOfAdmins: ' + numberOfAdmins); + console.log('numberOfWriters: ' + numberOfWriters); + console.log('numberOfReaders: ' + numberOfReaders); + core.exportVariable('numberOfCodeAdmins', numberOfAdmins); + core.exportVariable('numberOfCodeWriters', numberOfWriters); + core.exportVariable('numberOfCodeReaders', numberOfReaders); + } catch (error) { + core.info('Failed to fetch access to code for repo'); + if (error.status === 401 || error.status === 403) { + // Removes link to REST API endpoint + const errorMessage: string = error.message.split('-')[0].trim(); + core.warning(errorMessage, { + title: 'Failed to fetch acces to code for repo', + }); + } else { + core.info(error.message); + } + } + console.log(); + } +} diff --git a/src/azuredevopsboard/AzureDevOpsBoardService.ts b/src/azuredevopsboard/AzureDevOpsBoardService.ts index 907ea6f1..569e5b0c 100644 --- a/src/azuredevopsboard/AzureDevOpsBoardService.ts +++ b/src/azuredevopsboard/AzureDevOpsBoardService.ts @@ -9,8 +9,7 @@ export class AzureDevOpsBoardService { public static async getStateOfAzureDevOpsBoards(cydigConfig: CyDigConfig): Promise { if (cydigConfig.azureDevOps.boards) { try { - console.log('\n Running Azure DevOps Boards control'); - + console.log('--- Azure DevOps Boards control ---'); const azureDevOpsConnection: AzureDevOpsConnection = new AzureDevOpsConnection( cydigConfig.azureDevOps.boards.organizationName, core.getInput('accessTokenAzureDevOps') @@ -45,8 +44,11 @@ export class AzureDevOpsBoardService { } } catch (error) { core.warning('Error getting tickets for Azure DevOps Board!'); - console.log('Error:', error.message); + console.log( + 'There is probably somethine wrong with your token, check that it has not expired or been revoked. Please check that you have the correct permissions (Work items: Read)' + ); } + console.log(); } } } diff --git a/src/branchprotection/BranchProtectionService.ts b/src/branchprotection/BranchProtectionService.ts index 5d904d45..4d143855 100644 --- a/src/branchprotection/BranchProtectionService.ts +++ b/src/branchprotection/BranchProtectionService.ts @@ -1,17 +1,13 @@ import * as core from '@actions/core'; -import * as github from '@actions/github'; -import { Endpoints } from '@octokit/types'; -import { GitHub } from '@actions/github/lib/utils'; +import { Octokit } from '@octokit/rest'; +import { BranchProtectionForRepoResponseDataType } from '../types/OctokitResponses'; + export class BranchProtectionService { - public static async getStateOfBranchProtection(): Promise { + public static async getStateOfBranchProtection(octokit: Octokit, owner: string, repo: string): Promise { try { - console.log('\n Running branch protection control'); - const { owner, repo }: { owner: string; repo: string } = github.context.repo; - const token: string = core.getInput('PAT-token'); + console.log('--- Branch protection control ---'); - const octokit: InstanceType = github.getOctokit(token); - type branchProtectionRepsponse = Endpoints['GET /repos/{owner}/{repo}/branches/{branch}/protection']['response']; - const response: branchProtectionRepsponse = await octokit.rest.repos.getBranchProtection({ + const response: BranchProtectionForRepoResponseDataType = await octokit.rest.repos.getBranchProtection({ owner, repo, branch: 'main', @@ -33,11 +29,41 @@ export class BranchProtectionService { core.exportVariable('numberOfReviewers', numberOfReviewers); } catch (error) { - core.warning('Error getting branch protection!'); - console.log('Error:', error.message); - if (error.status === 403) { - core.exportVariable('numberOfReviewers', 0); + const errorMessage: string = error.message.split('-')[0].trim(); + if (error.status === 401) { + core.info('Failed to get branch protection'); + // Removes link to REST API endpoint + core.warning(errorMessage, { + title: 'Branch protection control failed', + }); + } else if (error.status === 404) { + if (errorMessage === 'Branch not protected') { + core.notice(errorMessage, { + title: 'Branch protection control', + }); + core.exportVariable('numberOfReviewers', 0); + } else { + core.info('Failed to get branch protection'); + core.warning('Credentials probably lack necessary permissions', { + title: 'Branch protection control failed', + }); + } + } else { + switch (errorMessage) { + case 'Upgrade to GitHub Pro or make this repository public to enable this feature.': + console.log('Branch protection is not enabled for repository:', repo); + core.exportVariable('numberOfReviewers', 0); + break; + + default: + core.info('Failed to get branch protection'); + core.notice(errorMessage, { + title: 'Branch protection control failed', + }); + break; + } } } + console.log(); } } diff --git a/src/codequalitytools/CodeQualityService.ts b/src/codequalitytools/CodeQualityService.ts index 348846f2..b4f9f386 100644 --- a/src/codequalitytools/CodeQualityService.ts +++ b/src/codequalitytools/CodeQualityService.ts @@ -2,17 +2,18 @@ import * as core from '@actions/core'; export class CodeQualityService { public static async getStateOfCodeQualityTool(codeQualityTool: { nameOfTool: string }): Promise { - console.log('\n Running Code Quality control'); + console.log('--- Code Quality control ---'); if (process.env.codeQualityTool) { - console.log(`Code Quality Tool: ${process.env.codeQualityTool}`); + console.log(`Tool:`, `${process.env.codeQualityTool}`); core.exportVariable('codeQualityTool', process.env.codeQualityTool); } else { if (!codeQualityTool.nameOfTool || codeQualityTool.nameOfTool === 'name-of-tool') { core.warning('Code Quality Tool is not set!'); return; } - console.log(`Code Quality Tool: ${codeQualityTool.nameOfTool}`); + console.log(`Tool:`, `${codeQualityTool.nameOfTool}`); core.exportVariable('codeQualityTool', codeQualityTool.nameOfTool); } + console.log(); } } diff --git a/src/index.ts b/src/index.ts index 563d37f9..eeca892a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,35 +1,52 @@ -import * as core from '@actions/core'; +import { getInput, setFailed } from '@actions/core'; +import { context } from '@actions/github'; +import { retry } from '@octokit/plugin-retry'; +import { Octokit } from '@octokit/rest'; +import { AzureDevOpsBoardService } from './azuredevopsboard/AzureDevOpsBoardService'; import { BranchProtectionService } from './branchprotection/BranchProtectionService'; -import { CyDigConfig } from './types/CyDigConfig'; +import { CodeQualityService } from './codequalitytools/CodeQualityService'; import { getContentOfFile } from './helpfunctions/JsonService'; import { PentestService } from './pentest/PentestService'; -import { ThreatModelingService } from './threatmodeling/ThreatModelingService'; -import { AzureDevOpsBoardService } from './azuredevopsboard/AzureDevOpsBoardService'; -import { CodeQualityService } from './codequalitytools/CodeQualityService'; import { SastService } from './sasttools/SastService'; import { ScaService } from './scatools/ScaService'; +import { SecretScanningService } from './secretscanning/SecretScanningService'; +import { AccessToCodeService } from './AccessToCode/AccessToCodeService'; +import { ThreatModelingService } from './threatmodeling/ThreatModelingService'; +import { CyDigConfig } from './types/CyDigConfig'; + /** * The main function for the action. * @returns {Promise} Resolves when the action is complete. */ export async function run(): Promise { try { - console.log('\n Running controls on your repository'); - const cydigConfig: CyDigConfig = getContentOfFile(core.getInput('cydigConfigPath')); + console.log('Running compliance controls \n'); + const cydigConfig: CyDigConfig = getContentOfFile(getInput('cydigConfigPath')); + const { owner, repo }: { owner: string; repo: string } = context.repo; + const token: string = getInput('PAT-token'); + // eslint-disable-next-line + const OctokitRetry = Octokit.plugin(retry); + const octokit: Octokit = new OctokitRetry({ + auth: token, + }); await CodeQualityService.getStateOfCodeQualityTool(cydigConfig.codeQualityTool); - await SastService.getStateOfSastTool(cydigConfig.sastTool); - await ScaService.getStateOfScaTool(cydigConfig.scaTool); - - await BranchProtectionService.getStateOfBranchProtection(); - + await SastService.getStateOfSastTool(cydigConfig.sastTool.nameOfTool, octokit, owner, repo); + await ScaService.getStateOfScaTool(cydigConfig.scaTool.nameOfTool, octokit, owner, repo); + await SecretScanningService.getStateOfExposedSecrets( + cydigConfig.secretScanningTool?.nameOfTool, + octokit, + owner, + repo + ); + await BranchProtectionService.getStateOfBranchProtection(octokit, owner, repo); + await AccessToCodeService.setAccessToCodeFindings(octokit, owner, repo); await PentestService.getStateOfPentest(cydigConfig.pentest); await ThreatModelingService.getStateOfThreatModeling(cydigConfig.threatModeling); - await AzureDevOpsBoardService.getStateOfAzureDevOpsBoards(cydigConfig); } catch (error) { // Fail the workflow run if an error occurs - if (error instanceof Error) core.setFailed(error.message); + if (error instanceof Error) setFailed(error.message); } } diff --git a/src/pentest/PentestService.ts b/src/pentest/PentestService.ts index 7688a388..64c2e7db 100644 --- a/src/pentest/PentestService.ts +++ b/src/pentest/PentestService.ts @@ -2,14 +2,18 @@ import * as core from '@actions/core'; export class PentestService { public static async getStateOfPentest(pentest: { date: string; boardsTag?: string }): Promise { + console.log('--- Pentest control ---'); if (process.env.pentestDate) { + console.log('Pentest date was found'); core.exportVariable('pentestDate', process.env.pentestDate); } else { if (!pentest.date || pentest.date === 'date-of-pentest') { core.warning('Pentest Date is not set!'); return; } + console.log('Pentest date was found'); core.exportVariable('pentestDate', pentest.date); } + console.log(); } } diff --git a/src/sasttools/CodeQLService.ts b/src/sasttools/CodeQLService.ts new file mode 100644 index 00000000..ec11ba70 --- /dev/null +++ b/src/sasttools/CodeQLService.ts @@ -0,0 +1,77 @@ +import * as core from '@actions/core'; +import { Octokit } from '@octokit/rest'; +import GitHub_Tool_Severity_Level from '../types/GithubToolSeverityLevel'; +import { CodeScanningAlertsForRepoResponseDataType } from '../types/OctokitResponses'; + +export class CodeQLService { + public static async setCodeQLFindings( + nameOfTool: string, + octokit: Octokit, + owner: string, + repo: string + ): Promise { + try { + // https://www.npmjs.com/package/octokit#pagination + const iterator: AsyncIterableIterator = octokit.paginate.iterator( + octokit.codeScanning.listAlertsForRepo, + { + owner: owner, + repo: repo, + per_page: 100, + state: 'open', + tool_name: 'CodeQL', + } + ); + + let sastNumberOfSeverity1: number = 0; + let sastNumberOfSeverity2: number = 0; + let sastNumberOfSeverity3: number = 0; + let sastNumberOfSeverity4: number = 0; + + for await (const { data: alerts } of iterator) { + for (const alert of alerts) { + switch (alert.rule.security_severity_level) { + case GitHub_Tool_Severity_Level.LOW: + sastNumberOfSeverity1++; + break; + case GitHub_Tool_Severity_Level.MEDIUM: + sastNumberOfSeverity2++; + break; + case GitHub_Tool_Severity_Level.HIGH: + sastNumberOfSeverity3++; + break; + case GitHub_Tool_Severity_Level.CRITICAL: + sastNumberOfSeverity4++; + break; + default: + break; + } + } + } + + console.log('Low: ' + sastNumberOfSeverity1); + console.log('Medium: ' + sastNumberOfSeverity2); + console.log('High: ' + sastNumberOfSeverity3); + console.log('Critical: ' + sastNumberOfSeverity4); + + core.exportVariable('sastTool', nameOfTool); + core.exportVariable('SASTnumberOfSeverity1', sastNumberOfSeverity1); + core.exportVariable('SASTnumberOfSeverity2', sastNumberOfSeverity2); + core.exportVariable('SASTnumberOfSeverity3', sastNumberOfSeverity3); + core.exportVariable('SASTnumberOfSeverity4', sastNumberOfSeverity4); + } catch (error) { + core.info('Failed to get CodeQL severities'); + if (error.status === 401 || error.status === 403 || error.status === 404) { + // Removes link to REST API endpoint + const errorMessage: string = error.message.split('-')[0].trim(); + core.warning(errorMessage, { + title: 'SAST tool control failed', + }); + } else { + core.notice(error.message, { + title: 'SAST tool control failed', + }); + } + } + } +} diff --git a/src/sasttools/SastService.ts b/src/sasttools/SastService.ts index a17c4c7f..b92ac464 100644 --- a/src/sasttools/SastService.ts +++ b/src/sasttools/SastService.ts @@ -1,18 +1,33 @@ import * as core from '@actions/core'; +import { Octokit } from '@octokit/rest'; +import { CodeQLService } from './CodeQLService'; +import GitHub_Tools from '../types/GitHubTools'; export class SastService { - public static async getStateOfSastTool(sastTool: { nameOfTool: string }): Promise { - console.log('\n Running SAST control'); + public static async getStateOfSastTool( + nameOfTool: string, + octokit: Octokit, + owner: string, + repo: string + ): Promise { + console.log('--- SAST control ---'); + let sast: string = nameOfTool; if (process.env.sastTool) { - console.log(`SAST Tool: ${process.env.sastTool}`); - core.exportVariable('sastTool', process.env.sastTool); - } else { - if (!sastTool.nameOfTool || sastTool.nameOfTool === 'name-of-tool') { - core.warning('SAST Tool is not set!'); - return; - } - console.log(`SAST Tool: ${sastTool.nameOfTool}`); - core.exportVariable('sastTool', sastTool.nameOfTool); + sast = process.env.sastTool; } + if (!sast || sast === '' || sast === 'name-of-tool') { + core.warning('SAST Tool is not set!'); + return; + } + console.log(`Tool:`, `${sast}`); + switch (sast.toLowerCase()) { + case GitHub_Tools.CODEQL.toLowerCase(): + await CodeQLService.setCodeQLFindings(sast, octokit, owner, repo); + break; + default: + core.exportVariable('sastTool', sast); + break; + } + console.log(); } } diff --git a/src/scatools/DependabotService.ts b/src/scatools/DependabotService.ts index a6886d1c..80a4f6b4 100644 --- a/src/scatools/DependabotService.ts +++ b/src/scatools/DependabotService.ts @@ -1,20 +1,18 @@ import * as core from '@actions/core'; -import * as github from '@actions/github'; import { Octokit } from '@octokit/rest'; -import { OctokitResponse } from '@octokit/types'; +import GitHub_Tool_Severity_Level from '../types/GithubToolSeverityLevel'; +import { DependabotAlertsForRepoResponseDataType } from '../types/OctokitResponses'; export class DependabotService { - public static async setDependabotFindings(): Promise { + public static async setDependabotFindings( + nameOfTool: string, + octokit: Octokit, + owner: string, + repo: string + ): Promise { try { - const { owner, repo }: { owner: string; repo: string } = github.context.repo; - const token: string = core.getInput('PAT-token'); - - const octokit: Octokit = new Octokit({ - auth: token, - }); - // https://www.npmjs.com/package/octokit#pagination - const iterator: AsyncIterableIterator> = octokit.paginate.iterator( + const iterator: AsyncIterableIterator = octokit.paginate.iterator( octokit.dependabot.listAlertsForRepo, { owner: owner, @@ -32,37 +30,45 @@ export class DependabotService { for await (const { data: alerts } of iterator) { for (const alert of alerts) { switch (alert.security_vulnerability.severity) { - case 'low': + case GitHub_Tool_Severity_Level.LOW: scaNumberOfSeverity1++; break; - case 'medium': + case GitHub_Tool_Severity_Level.MEDIUM: scaNumberOfSeverity2++; break; - case 'high': + case GitHub_Tool_Severity_Level.HIGH: scaNumberOfSeverity3++; break; - case 'critical': + case GitHub_Tool_Severity_Level.CRITICAL: scaNumberOfSeverity4++; break; } } } - console.log('SCAnumberOfSeverityLow: ' + scaNumberOfSeverity1); - console.log('SCAnumberOfSeverityMedium: ' + scaNumberOfSeverity2); - console.log('SCAnumberOfSeverityHigh: ' + scaNumberOfSeverity3); - console.log('SCAnumberOfSeverityCritical: ' + scaNumberOfSeverity4); + console.log('Low: ' + scaNumberOfSeverity1); + console.log('Medium: ' + scaNumberOfSeverity2); + console.log('High: ' + scaNumberOfSeverity3); + console.log('Critical: ' + scaNumberOfSeverity4); + core.exportVariable('scaTool', nameOfTool); core.exportVariable('SCAnumberOfSeverity1', scaNumberOfSeverity1); core.exportVariable('SCAnumberOfSeverity2', scaNumberOfSeverity2); core.exportVariable('SCAnumberOfSeverity3', scaNumberOfSeverity3); core.exportVariable('SCAnumberOfSeverity4', scaNumberOfSeverity4); } catch (error) { - core.warning('Could not set Dependabot severities'); - core.exportVariable('SCAnumberOfSeverity1', 0); - core.exportVariable('SCAnumberOfSeverity2', 0); - core.exportVariable('SCAnumberOfSeverity3', 0); - core.exportVariable('SCAnumberOfSeverity4', 0); + core.info('Failed to get Dependabot severities'); + if (error.status === 401 || error.status === 403 || error.status === 404) { + // Removes link to REST API endpoint + const errorMessage: string = error.message.split('-')[0].trim(); + core.warning(errorMessage, { + title: 'SCA tool control failed', + }); + } else { + core.notice(error.message, { + title: 'SCA tool control failed', + }); + } } } } diff --git a/src/scatools/ScaService.ts b/src/scatools/ScaService.ts index 3334cefb..f83a58b5 100644 --- a/src/scatools/ScaService.ts +++ b/src/scatools/ScaService.ts @@ -1,23 +1,33 @@ import * as core from '@actions/core'; +import { Octokit } from '@octokit/rest'; import { DependabotService } from './DependabotService'; +import GitHub_Tools from '../types/GitHubTools'; export class ScaService { - public static async getStateOfScaTool(scaTool: { nameOfTool: string }): Promise { - console.log('\n Running SCA control'); - let sca: string = scaTool.nameOfTool; + public static async getStateOfScaTool( + nameOfTool: string, + octokit: Octokit, + owner: string, + repo: string + ): Promise { + console.log('--- SCA control ---'); + let sca: string = nameOfTool; if (process.env.scaTool) { sca = process.env.scaTool; } - console.log(`SCA Tool: ${sca}`); - core.exportVariable('scaTool', sca); - if (!sca || sca === '' || sca === 'name-of-tool') { core.warning('SCA Tool is not set!'); return; } - - if (sca.toLowerCase() === 'dependabot') { - DependabotService.setDependabotFindings(); + console.log(`Tool:`, `${sca}`); + switch (sca.toLowerCase()) { + case GitHub_Tools.DEPENDABOT.toLowerCase(): + await DependabotService.setDependabotFindings(sca, octokit, owner, repo); + break; + default: + core.exportVariable('scaTool', sca); + break; } + console.log(); } } diff --git a/src/secretscanning/GithubSecretScanningService.ts b/src/secretscanning/GithubSecretScanningService.ts new file mode 100644 index 00000000..cb75be84 --- /dev/null +++ b/src/secretscanning/GithubSecretScanningService.ts @@ -0,0 +1,62 @@ +import * as core from '@actions/core'; +import { Octokit } from '@octokit/rest'; +import { SecretAlertsForRepoResponseDataType } from '../types/OctokitResponses'; +import GitHub_Tools from '../types/GitHubTools'; + +export class GithubSecretScanningService { + public static async getStateOfExposedSecrets(octokit: Octokit, owner: string, repo: string): Promise { + try { + console.log('Tool: Github Secret Scanning'); + + // https://www.npmjs.com/package/octokit#pagination + const iterator: AsyncIterableIterator = octokit.paginate.iterator( + octokit.secretScanning.listAlertsForRepo, + { + owner: owner, + repo: repo, + per_page: 100, + state: 'open', + } + ); + + let numberOfExposedSecrets: number = 0; + + for await (const { data: alerts } of iterator) { + numberOfExposedSecrets += alerts.length; + } + + console.log('Exposed secrets:', numberOfExposedSecrets); + core.exportVariable('secretScanningTool', GitHub_Tools.GitHub_SECRET_SCANNING); + core.exportVariable('numberOfExposedSecrets', numberOfExposedSecrets); + } catch (error) { + core.info('Failed to get number of exposed secrets'); + // Removes link to REST API endpoint + const errorMessage: string = error.message.split('-')[0].trim(); + if (error.status === 401) { + core.warning(errorMessage, { + title: 'Number of exposed secrets control failed', + }); + } else if (error.status === 404) { + switch (errorMessage) { + case 'Secret scanning is disabled on this repository.': + core.warning(errorMessage, { + title: 'Number of exposed secrets control failed', + }); + break; + + default: + console.log(error); + core.warning('Credentials probably lack necessary permissions', { + title: 'Number of exposed secrets control failed', + }); + break; + } + } else { + core.notice(error.message, { + title: 'Number of exposed secrets control failed', + }); + } + } + console.log(); + } +} diff --git a/src/secretscanning/SecretScanningService.ts b/src/secretscanning/SecretScanningService.ts new file mode 100644 index 00000000..415025f9 --- /dev/null +++ b/src/secretscanning/SecretScanningService.ts @@ -0,0 +1,35 @@ +import * as core from '@actions/core'; +import { Octokit } from '@octokit/rest'; +import GitHub_Tools from '../types/GitHubTools'; +import { GithubSecretScanningService } from './GithubSecretScanningService'; + +export class SecretScanningService { + public static async getStateOfExposedSecrets( + nameOfTool: string, + octokit: Octokit, + owner: string, + repo: string + ): Promise { + console.log('--- Secret Scanning control ---'); + + if (nameOfTool === null || nameOfTool === undefined || nameOfTool === 'name-of-tool') { + core.warning('Secret Scanning Tool is not set! Will continue with GitHub Secret Scanning tool:'); + await GithubSecretScanningService.getStateOfExposedSecrets(octokit, owner, repo); + return; + } + + switch (nameOfTool.toLowerCase()) { + case GitHub_Tools.GitHub_SECRET_SCANNING.toLowerCase(): + await GithubSecretScanningService.getStateOfExposedSecrets(octokit, owner, repo); + break; + default: + core.notice('Given secret scanning tool is not implemented: ' + nameOfTool, { + title: 'Number of exposed secrets control failed', + }); + core.exportVariable('secretScanningTool', nameOfTool); + break; + } + + console.log(); + } +} diff --git a/src/threatmodeling/ThreatModelingService.ts b/src/threatmodeling/ThreatModelingService.ts index 5278c85c..9f0308f2 100644 --- a/src/threatmodeling/ThreatModelingService.ts +++ b/src/threatmodeling/ThreatModelingService.ts @@ -1,14 +1,18 @@ import * as core from '@actions/core'; export class ThreatModelingService { public static async getStateOfThreatModeling(threatModeling: { date: string; boardsTag?: string }): Promise { + console.log('--- Threat Modeling control ---'); if (process.env.threatModelingDate) { + console.log('Threat Modeling date was found'); core.exportVariable('threatModelingDate', process.env.threatModelingDate); } else { if (!threatModeling.date || threatModeling.date === 'date-of-threat-modeling') { core.warning('Threat Modeling Date is not set!'); return; } + console.log('Threat Modeling date was found'); core.exportVariable('threatModelingDate', threatModeling.date); } + console.log(); } } diff --git a/src/types/CyDigConfig.ts b/src/types/CyDigConfig.ts index 6ef8d191..4c41d8ac 100644 --- a/src/types/CyDigConfig.ts +++ b/src/types/CyDigConfig.ts @@ -5,6 +5,9 @@ export type CyDigConfig = { date: string; boardsTag: string; }; + secretScanningTool: { + nameOfTool: string; + }; pentest: { date: string; boardsTag: string; diff --git a/src/types/GitHubTools.ts b/src/types/GitHubTools.ts new file mode 100644 index 00000000..2d745a98 --- /dev/null +++ b/src/types/GitHubTools.ts @@ -0,0 +1,7 @@ +enum GitHub_Tools { + DEPENDABOT = 'Dependabot', + CODEQL = 'CodeQL', + GitHub_SECRET_SCANNING = 'GitHub', +} + +export default GitHub_Tools; diff --git a/src/types/GithubToolSeverityLevel.ts b/src/types/GithubToolSeverityLevel.ts new file mode 100644 index 00000000..dc0202e7 --- /dev/null +++ b/src/types/GithubToolSeverityLevel.ts @@ -0,0 +1,8 @@ +enum GitHub_Tool_Severity_Level { + LOW = 'low', + MEDIUM = 'medium', + HIGH = 'high', + CRITICAL = 'critical', +} + +export default GitHub_Tool_Severity_Level; diff --git a/src/types/OctokitResponses.ts b/src/types/OctokitResponses.ts new file mode 100644 index 00000000..e3a66377 --- /dev/null +++ b/src/types/OctokitResponses.ts @@ -0,0 +1,13 @@ +import { Endpoints } from '@octokit/types'; + +export type DependabotAlertsForRepoResponseDataType = + Endpoints['GET /repos/{owner}/{repo}/dependabot/alerts']['response']; + +export type CodeScanningAlertsForRepoResponseDataType = + Endpoints['GET /repos/{owner}/{repo}/code-scanning/alerts']['response']; + +export type SecretAlertsForRepoResponseDataType = + Endpoints['GET /repos/{owner}/{repo}/secret-scanning/alerts']['response']; + +export type BranchProtectionForRepoResponseDataType = + Endpoints['GET /repos/{owner}/{repo}/branches/{branch}/protection']['response']; diff --git a/tests/BranchProtectionService.test.ts b/tests/BranchProtectionService.test.ts new file mode 100644 index 00000000..7cafb8ab --- /dev/null +++ b/tests/BranchProtectionService.test.ts @@ -0,0 +1,104 @@ +import * as core from '@actions/core'; +import sinon, { SinonStub } from 'sinon'; +import { BranchProtectionService } from '../src/branchprotection/BranchProtectionService'; + +describe('BranchProtectionService', function () { + let warningStub: SinonStub; + let noticeStub: SinonStub; + let infoStub: SinonStub; + let exportVariableStub: SinonStub; + let logStub: SinonStub; + let getBranchProtectionStub: SinonStub; + const octokitMock: any = { + rest: { + repos: { + getBranchProtection() { + return; + }, + }, + }, + }; + + beforeEach(function () { + warningStub = sinon.stub(core, 'warning'); + noticeStub = sinon.stub(core, 'notice'); + infoStub = sinon.stub(core, 'info'); + exportVariableStub = sinon.stub(core, 'exportVariable'); + logStub = sinon.stub(console, 'log'); + getBranchProtectionStub = sinon.stub(octokitMock.rest.repos, 'getBranchProtection'); + }); + + afterEach(function () { + sinon.restore(); + }); + + it('should handle successful branch protection retrieval', async function () { + getBranchProtectionStub.returns({ + data: { + enforce_admins: { enabled: true }, + required_pull_request_reviews: { required_approving_review_count: 1 }, + }, + }); + + await BranchProtectionService.getStateOfBranchProtection(octokitMock, 'owner', 'repo'); + sinon.assert.calledOnceWithExactly(exportVariableStub, 'numberOfReviewers', 1); + sinon.assert.notCalled(warningStub); + }); + + it('should warn when admins can bypass branch protection rules', async function () { + getBranchProtectionStub.returns({ + data: { + enforce_admins: { enabled: false }, + required_pull_request_reviews: { required_approving_review_count: 1 }, + }, + }); + + await BranchProtectionService.getStateOfBranchProtection(octokitMock, 'owner', 'repo'); + sinon.assert.calledOnce(warningStub); + sinon.assert.calledOnceWithExactly(exportVariableStub, 'numberOfReviewers', 0); + }); + + it('should handle a 401 error', async function () { + getBranchProtectionStub.rejects({ + status: 401, + message: '401 error message', + }); + + await BranchProtectionService.getStateOfBranchProtection(octokitMock, 'owner', 'repo'); + sinon.assert.calledOnce(warningStub); + sinon.assert.notCalled(exportVariableStub); + }); + + it('should handle a 404 error (caused by branch protection not enabled)', async function () { + getBranchProtectionStub.rejects({ + status: 404, + message: 'Branch not protected', + }); + + await BranchProtectionService.getStateOfBranchProtection(octokitMock, 'owner', 'repo'); + sinon.assert.calledOnce(noticeStub); + sinon.assert.calledOnceWithExactly(exportVariableStub, 'numberOfReviewers', 0); + }); + + it('should handle a normal 404 error', async function () { + getBranchProtectionStub.rejects({ + status: 404, + message: 'Regular 404 error message', + }); + + await BranchProtectionService.getStateOfBranchProtection(octokitMock, 'owner', 'repo'); + sinon.assert.calledOnce(warningStub); + sinon.assert.notCalled(exportVariableStub); + }); + + it('should call warning when branch protection is enabled but receives 404 (status = 404)', async function () { + getBranchProtectionStub.rejects({ + status: 500, + message: 'Default error case', + }); + + await BranchProtectionService.getStateOfBranchProtection(octokitMock, 'owner', 'repo'); + sinon.assert.calledOnce(noticeStub); + sinon.assert.notCalled(exportVariableStub); + }); +}); diff --git a/tests/CodeQLService.test.ts b/tests/CodeQLService.test.ts new file mode 100644 index 00000000..318dda3b --- /dev/null +++ b/tests/CodeQLService.test.ts @@ -0,0 +1,115 @@ +import * as core from '@actions/core'; +import sinon, { SinonStub } from 'sinon'; +import { CodeQLService } from '../src/sasttools/CodeQLService'; +import GitHub_Tools from '../src/types/GitHubTools'; + +describe('CodeQLService', function () { + let warningStub: SinonStub; + let noticeStub: SinonStub; + let infoStub: SinonStub; + let exportVariableStub: SinonStub; + let logStub: SinonStub; + let iteratorStub: SinonStub; + const octokitMock: any = { + paginate: { + iterator() { + return; + }, + }, + codeScanning: { + listAlertsForRepo: '', + }, + }; + + beforeEach(() => { + warningStub = sinon.stub(core, 'warning'); + noticeStub = sinon.stub(core, 'notice'); + infoStub = sinon.stub(core, 'info'); + exportVariableStub = sinon.stub(core, 'exportVariable'); + logStub = sinon.stub(console, 'log'); + iteratorStub = sinon.stub(octokitMock.paginate, 'iterator'); + }); + + afterEach(() => { + sinon.restore(); + }); + + it('should handle successful code scanning retrieval', async function () { + iteratorStub.returns([ + { + data: [ + { + rule: { + security_severity_level: 'low', + }, + }, + { + rule: { + security_severity_level: 'medium', + }, + }, + { + rule: { + security_severity_level: 'high', + }, + }, + { + rule: { + security_severity_level: 'critical', + }, + }, + ], + }, + ]); + + await CodeQLService.setCodeQLFindings(GitHub_Tools.CODEQL, octokitMock, 'owner', 'repo'); + sinon.assert.callCount(exportVariableStub, 5); + sinon.assert.calledWithExactly(exportVariableStub, 'sastTool', GitHub_Tools.CODEQL); + sinon.assert.calledWithExactly(exportVariableStub, 'SASTnumberOfSeverity1', 1); + sinon.assert.calledWithExactly(exportVariableStub, 'SASTnumberOfSeverity2', 1); + sinon.assert.calledWithExactly(exportVariableStub, 'SASTnumberOfSeverity3', 1); + sinon.assert.calledWithExactly(exportVariableStub, 'SASTnumberOfSeverity4', 1); + sinon.assert.notCalled(warningStub); + }); + + it('should handle a 401 error', async function () { + iteratorStub.throws({ + status: 401, + message: '401 error message', + }); + + await CodeQLService.setCodeQLFindings(GitHub_Tools.CODEQL, octokitMock, 'owner', 'repo'); + sinon.assert.calledOnce(warningStub); + }); + + it('should handle a 403 error', async function () { + iteratorStub.throws({ + status: 403, + message: '403 error message', + }); + + await CodeQLService.setCodeQLFindings(GitHub_Tools.CODEQL, octokitMock, 'owner', 'repo'); + sinon.assert.calledOnce(warningStub); + }); + + it('should handle a 404 error', async function () { + iteratorStub.throws({ + status: 404, + message: '404 error message', + }); + + await CodeQLService.setCodeQLFindings(GitHub_Tools.CODEQL, octokitMock, 'owner', 'repo'); + sinon.assert.calledOnce(warningStub); + }); + + it('should handle error other than 401, 403, 404', async function () { + iteratorStub.throws({ + status: 500, + message: 'Default error case', + }); + + await CodeQLService.setCodeQLFindings(GitHub_Tools.CODEQL, octokitMock, 'owner', 'repo'); + sinon.assert.calledOnce(noticeStub); + sinon.assert.notCalled(warningStub); + }); +}); diff --git a/tests/DependabotService.test.ts b/tests/DependabotService.test.ts new file mode 100644 index 00000000..af9ab84a --- /dev/null +++ b/tests/DependabotService.test.ts @@ -0,0 +1,115 @@ +import * as core from '@actions/core'; +import sinon, { SinonStub } from 'sinon'; +import { DependabotService } from '../src/scatools/DependabotService'; +import GitHub_Tools from '../src/types/GitHubTools'; + +describe('CodeQLService', function () { + let warningStub: SinonStub; + let noticeStub: SinonStub; + let infoStub: SinonStub; + let exportVariableStub: SinonStub; + let logStub: SinonStub; + let iteratorStub: SinonStub; + const octokitMock: any = { + paginate: { + iterator() { + return; + }, + }, + dependabot: { + listAlertsForRepo: '', + }, + }; + + beforeEach(function () { + warningStub = sinon.stub(core, 'warning'); + noticeStub = sinon.stub(core, 'notice'); + infoStub = sinon.stub(core, 'info'); + exportVariableStub = sinon.stub(core, 'exportVariable'); + logStub = sinon.stub(console, 'log'); + iteratorStub = sinon.stub(octokitMock.paginate, 'iterator'); + }); + + afterEach(function () { + sinon.restore(); + }); + + it('should handle successful Dependabot alerts retrieval', async function () { + iteratorStub.returns([ + { + data: [ + { + security_vulnerability: { + severity: 'low', + }, + }, + { + security_vulnerability: { + severity: 'medium', + }, + }, + { + security_vulnerability: { + severity: 'high', + }, + }, + { + security_vulnerability: { + severity: 'critical', + }, + }, + ], + }, + ]); + + await DependabotService.setDependabotFindings(GitHub_Tools.DEPENDABOT, octokitMock, 'owner', 'repo'); + sinon.assert.callCount(exportVariableStub, 5); + sinon.assert.calledWithExactly(exportVariableStub, 'scaTool', GitHub_Tools.DEPENDABOT); + sinon.assert.calledWithExactly(exportVariableStub, 'SCAnumberOfSeverity1', 1); + sinon.assert.calledWithExactly(exportVariableStub, 'SCAnumberOfSeverity1', 1); + sinon.assert.calledWithExactly(exportVariableStub, 'SCAnumberOfSeverity1', 1); + sinon.assert.calledWithExactly(exportVariableStub, 'SCAnumberOfSeverity1', 1); + sinon.assert.notCalled(warningStub); + }); + + it('should handle a 401 error', async function () { + iteratorStub.throws({ + status: 401, + message: '401 error message', + }); + + await DependabotService.setDependabotFindings(GitHub_Tools.DEPENDABOT, octokitMock, 'owner', 'repo'); + sinon.assert.calledOnce(warningStub); + }); + + it('should handle a 403 error', async function () { + iteratorStub.throws({ + status: 403, + message: '403 error message', + }); + + await DependabotService.setDependabotFindings(GitHub_Tools.DEPENDABOT, octokitMock, 'owner', 'repo'); + sinon.assert.calledOnce(warningStub); + }); + + it('should handle a 404 error', async function () { + iteratorStub.throws({ + status: 404, + message: '404 error message', + }); + + await DependabotService.setDependabotFindings(GitHub_Tools.DEPENDABOT, octokitMock, 'owner', 'repo'); + sinon.assert.calledOnce(warningStub); + }); + + it('should handle error other than 401, 403, 404', async function () { + iteratorStub.throws({ + status: 500, + message: 'Default error case', + }); + + await DependabotService.setDependabotFindings(GitHub_Tools.DEPENDABOT, octokitMock, 'owner', 'repo'); + sinon.assert.calledOnce(noticeStub); + sinon.assert.notCalled(warningStub); + }); +}); diff --git a/tests/SastService.test.ts b/tests/SastService.test.ts new file mode 100644 index 00000000..caa25d74 --- /dev/null +++ b/tests/SastService.test.ts @@ -0,0 +1,38 @@ +import * as core from '@actions/core'; +import sinon, { SinonStub } from 'sinon'; +import { CodeQLService } from '../src/sasttools/CodeQLService'; +import { SastService } from '../src/sasttools/SastService'; +import GitHub_Tools from '../src/types/GitHubTools'; + +describe('SastService', function () { + let warningStub: SinonStub; + let noticeStub: SinonStub; + let infoStub: SinonStub; + let exportVariableStub: SinonStub; + let logStub: SinonStub; + let setCodeQLFindingsStub: SinonStub; + const octokitMock: any = {}; + + beforeEach(function () { + warningStub = sinon.stub(core, 'warning'); + noticeStub = sinon.stub(core, 'notice'); + infoStub = sinon.stub(core, 'info'); + exportVariableStub = sinon.stub(core, 'exportVariable'); + logStub = sinon.stub(console, 'log'); + setCodeQLFindingsStub = sinon.stub(CodeQLService, 'setCodeQLFindings'); + }); + + afterEach(function () { + sinon.restore(); + }); + + it('should run CodeQL suite if toolName is "CodeQl"', async function () { + await SastService.getStateOfSastTool(GitHub_Tools.CODEQL, octokitMock, 'owner', 'repo'); + sinon.assert.calledOnce(setCodeQLFindingsStub); + }); + + it('should only export variable "sastTool" if tool is not supported', async function () { + await SastService.getStateOfSastTool('unsupported tool', octokitMock, 'owner', 'repo'); + sinon.assert.calledOnceWithExactly(exportVariableStub, 'sastTool', 'unsupported tool'); + }); +}); diff --git a/tests/ScaService.test.ts b/tests/ScaService.test.ts new file mode 100644 index 00000000..41c4731c --- /dev/null +++ b/tests/ScaService.test.ts @@ -0,0 +1,38 @@ +import * as core from '@actions/core'; +import sinon, { SinonStub } from 'sinon'; +import { DependabotService } from '../src/scatools/DependabotService'; +import { ScaService } from '../src/scatools/ScaService'; +import GitHub_Tools from '../src/types/GitHubTools'; + +describe('DependabotService', function () { + let warningStub: SinonStub; + let noticeStub: SinonStub; + let infoStub: SinonStub; + let exportVariableStub: SinonStub; + let logStub: SinonStub; + let setDependabotFindingsStub: SinonStub; + const octokitMock: any = {}; + + beforeEach(function () { + warningStub = sinon.stub(core, 'warning'); + noticeStub = sinon.stub(core, 'notice'); + infoStub = sinon.stub(core, 'info'); + exportVariableStub = sinon.stub(core, 'exportVariable'); + logStub = sinon.stub(console, 'log'); + setDependabotFindingsStub = sinon.stub(DependabotService, 'setDependabotFindings'); + }); + + afterEach(function () { + sinon.restore(); + }); + + it('should run Dependabot suite if toolName is "Dependabot"', async function () { + await ScaService.getStateOfScaTool(GitHub_Tools.DEPENDABOT, octokitMock, 'owner', 'repo'); + sinon.assert.calledOnce(setDependabotFindingsStub); + }); + + it('should only export variable "scaTool" if tool is not supported', async function () { + await ScaService.getStateOfScaTool('unsupported tool', octokitMock, 'owner', 'repo'); + sinon.assert.calledOnceWithExactly(exportVariableStub, 'scaTool', 'unsupported tool'); + }); +}); diff --git a/tests/branchprotection.test.ts b/tests/branchprotection.test.ts deleted file mode 100644 index 5dc9c189..00000000 --- a/tests/branchprotection.test.ts +++ /dev/null @@ -1,79 +0,0 @@ -import * as core from '@actions/core'; -import * as github from '@actions/github'; -import sinon, { SinonSandbox, SinonStub } from 'sinon'; -import { expect } from 'chai'; -import { BranchProtectionService } from '../src/branchprotection/BranchProtectionService'; -describe('BranchProtectionService', () => { - let sandbox: SinonSandbox; - let warningStub: SinonStub; - let exportVariableStub: SinonStub; - let getOctokitStub: SinonStub; - - beforeEach(() => { - sandbox = sinon.createSandbox(); - warningStub = sandbox.stub(core, 'warning'); - exportVariableStub = sandbox.stub(core, 'exportVariable'); - getOctokitStub = sandbox.stub(github, 'getOctokit'); - sandbox.stub(github.context, 'repo').value({ - owner: 'owner', - repo: 'repo', - }); - }); - - afterEach(() => { - sandbox.restore(); - }); - - it('should handle successful branch protection retrieval', async () => { - getOctokitStub.returns({ - rest: { - repos: { - getBranchProtection: sinon.stub().resolves({ - data: { - enforce_admins: { enabled: true }, - required_pull_request_reviews: { required_approving_review_count: 1 }, - }, - }), - }, - }, - }); - - await BranchProtectionService.getStateOfBranchProtection(); - expect(warningStub.called).to.be.false; - expect(exportVariableStub.calledWith('numberOfReviewers', 1)).to.be.true; - }); - it('should call warning when admins can byypass branch protection rules', async () => { - getOctokitStub.returns({ - rest: { - repos: { - getBranchProtection: sinon.stub().resolves({ - data: { - enforce_admins: { enabled: false }, - required_pull_request_reviews: { required_approving_review_count: 1 }, - }, - }), - }, - }, - }); - - await BranchProtectionService.getStateOfBranchProtection(); - expect(warningStub.called).to.be.true; - expect(exportVariableStub.calledWith('numberOfReviewers', 0)).to.be.true; - }); - it('should call warning and set numberOfReviewers to 0 when github repo is private (status = 403)', async () => { - getOctokitStub.returns({ - rest: { - repos: { - getBranchProtection: sinon.stub().rejects({ - status: 403, - message: 'Forbidden', - }), - }, - }, - }); - - await BranchProtectionService.getStateOfBranchProtection(); - expect(warningStub.called).to.be.true; - expect(exportVariableStub.calledWith('numberOfReviewers', 0)).to.be.true; - }); -});