|
1 | 1 | name: dependency-checks |
2 | 2 |
|
3 | 3 | on: |
| 4 | + pull_request: |
4 | 5 | workflow_call: |
5 | 6 |
|
| 7 | +permissions: |
| 8 | + pull-requests: write |
| 9 | + contents: read |
| 10 | + |
6 | 11 | jobs: |
7 | 12 | dependency-scan: |
8 | | - name: Run internal dependency script |
| 13 | + name: Run dependencies analysis |
9 | 14 | runs-on: ${{ github.repository_visibility != 'public' && 'centreon-security' || 'ubuntu-24.04' }} |
10 | 15 |
|
11 | 16 | steps: |
12 | 17 | - name: Checkout Repository |
13 | 18 | uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 |
14 | | - with: |
15 | | - fetch-depth: 0 |
16 | 19 |
|
17 | | - - name: Run dependency scan |
| 20 | + - name: Check dependencies type and lockfiles version |
18 | 21 | run: | |
19 | | - if [ -f compromised-packages.txt ]; then rm -f compromised-packages.txt; fi |
20 | | - wget https://raw.githubusercontent.com/centreon/security-tools/main/blacklist/compromised-packages.txt |
| 22 | + # Check override |
| 23 | + if [ "${{ vars.OVERRIDE_DEPENDENCY_SCAN }}" == "true" ]; then |
| 24 | + echo "[INFO] - Scan override enabled" |
| 25 | + echo "fail_the_build=false" >> "$GITHUB_ENV" |
| 26 | + cat $GITHUB_ENV |
| 27 | + return 0 |
| 28 | + fi |
| 29 | +
|
| 30 | + # Check date |
| 31 | + current_timestamp=$(date +%s) |
| 32 | + DUE_DATE="${{ vars.OVERRIDE_DEPENDENCY_ENFORCEMENT_DATE }}" |
| 33 | + input_timestamp=$(date -d "$DUE_DATE" +%s) |
| 34 | +
|
| 35 | + # Setup vars |
21 | 36 | ERROR_LOG="error_log.txt" |
22 | | - DEP_LIST="compromised-packages.txt" |
23 | | - LOCKFILES=($(find ./ -name "package-lock.json" -o -name "pnpm-lock.yaml" -o -name "yarn.lock")) |
| 37 | + touch "$ERROR_LOG" |
| 38 | + FORCE_FAIL="false" |
| 39 | + ENFORCEMENT="false" |
| 40 | + SKIP="false" |
| 41 | + FAIL_THE_BUILD="false" |
| 42 | + if [ "$current_timestamp" -ge "$input_timestamp" ]; then |
| 43 | + echo "[INFO]: Deadline passed." |
| 44 | + ENFORCEMENT="true" |
| 45 | + fi |
24 | 46 |
|
25 | | - function checkPnpmLockfile() { |
26 | | - # Find dependency formated as |
27 | | - # "name@version:" |
28 | | - if grep -qF "$NAME@$VERSION" "$LOCKFILE"; then |
29 | | - echo "$NAME:$VERSION was found in $LOCKFILE" |
30 | | - echo "::error:: $NAME:$VERSION was found in $LOCKFILE" >> "$ERROR_LOG" |
| 47 | + ## |
| 48 | + # Scan manifests compliance |
| 49 | + ## |
| 50 | +
|
| 51 | + function message_type() { |
| 52 | + MSG="$1" |
| 53 | +
|
| 54 | + if [ "$ENFORCEMENT" == "true" ]; then |
| 55 | + echo "[ERROR] : $MSG" |
| 56 | + FORCE_FAIL="true" |
31 | 57 | else |
32 | | - echo -n "." |
| 58 | + echo "[WARNING] : $MSG" |
33 | 59 | fi |
| 60 | + echo "$MSG" >> "$ERROR_LOG" |
34 | 61 | } |
35 | 62 |
|
36 | | - function checkNpmLockfile() { |
37 | | - # Find dependencies formated as |
38 | | - # "@accordproject/concerto-linter-default-ruleset": { |
39 | | - # "version": "3.24.1", |
40 | | - local package="$1" |
41 | | - local version="$2" |
42 | | - local extractedDep |
43 | | -
|
44 | | - extractedDep=$(awk -v name="$package" -v version="$version" ' |
45 | | - /"dependencies": *{/ { in_deps=1; next } |
46 | | - in_deps && /"[^"]+": *{/ { |
47 | | - match($0, /"([^"]+)": *{/, arr) |
48 | | - dep = arr[1] |
49 | | - getline |
50 | | - if ($0 ~ /"version":/) { |
51 | | - match($0, /"version": *"([^"]+)"/, ver) |
52 | | - if (dep == name && ver[1] == version) { |
53 | | - print dep " " ver[1] |
54 | | - } |
55 | | - } |
56 | | - } |
57 | | - ' "$LOCKFILE" |
58 | | - ) |
59 | | - if [[ "$extractedDep" == "$package $version" ]]; then |
60 | | - echo "$package:$version" "Was found in $LOCKFILE" |
61 | | - echo "::error:: $package:$version Was found in $LOCKFILE" >> "$ERROR_LOG" |
| 63 | + # Compare pnpm version used in lockfile |
| 64 | + function compare_version() { |
| 65 | + SKIP="false" |
| 66 | + MIN_LOCKFILE_VERSION="8.9.9" |
| 67 | + LOCKFILE_VERSION=$(grep -E '^lockfileVersion:' "$LOC_FILE" \ |
| 68 | + | awk '{print $2}' \ |
| 69 | + | tr -d "'\"") |
| 70 | + # Compare versions |
| 71 | + version_ge() { |
| 72 | + [ "$(printf '%s\n' "$1" "$2" | sort -V | head -n1)" = "$2" ] |
| 73 | + } |
| 74 | + echo "[DEBUG] - MIN_LOCKFILE_VERSION = $MIN_LOCKFILE_VERSION and LOCKFILE_VERSION = $LOCKFILE_VERSION" |
| 75 | + if version_ge "$MIN_LOCKFILE_VERSION" "$LOCKFILE_VERSION"; then |
| 76 | + message_type "PNPM lockfile update is required : lockfileVersion $LOCKFILE_VERSION < $MIN_LOCKFILE_VERSION found in **$LOC_FILE**" |
62 | 77 | else |
63 | | - echo -n "." |
| 78 | + SKIP="true" |
64 | 79 | fi |
65 | 80 | } |
66 | | -
|
67 | | - function checkYarnLockfile() { |
68 | | - # Find dependencies formated as |
69 | | - # "@aashutoshrathi/word-wrap@^1.2.3": |
70 | | - # version "1.2.6" |
71 | | - local package="$1" |
72 | | - local version="$2" |
73 | | - local extractedDep |
74 | 81 | |
75 | | - extractedDep=$(awk -v pkg="$package" ' |
76 | | - /^".*":$/ { |
77 | | - split($0, arr, ",") |
78 | | - dep=arr[1] |
79 | | - gsub(/"/, "", dep) |
80 | | - sub(/@[^@]*$/, "", dep) |
81 | | - current_pkg=dep |
82 | | - } |
83 | | - /version "/ { |
84 | | - match($0, /"([^"]+)"/, v) |
85 | | - if(current_pkg==pkg) print current_pkg, v[1] |
86 | | - } |
87 | | - ' "$LOCKFILE") |
88 | | - if [[ "$extractedDep" == "$package $version" ]]; then |
89 | | - echo "$package:$version Was found in $LOCKFILE" |
90 | | - echo "::error:: $package:$version Was found in $LOCKFILE" >> "$ERROR_LOG" |
| 82 | + # Scan manifests compliance |
| 83 | + echo "[INFO] - Scan manifests compliance" |
| 84 | + DEP_FILES=($(find ./ -type f -name "package.json")) |
| 85 | + for DEP_FILE in ${DEP_FILES[@]}; do |
| 86 | + DEP_DIR=$(dirname $DEP_FILE) |
| 87 | + echo "[INFO] - Scanning $DEP_FILE" |
| 88 | + LOC_FILES=($(find $DEP_DIR -maxdepth 1 -type f -name "package-lock.json" -o -name "pnpm-lock.yaml" -o -name "yarn.lock")) |
| 89 | + COUNT=0 |
| 90 | +
|
| 91 | + for LOC_FILE in ${LOC_FILES[@]}; do |
| 92 | + COUNT=$((COUNT+1)) |
| 93 | + LOC_TYPE=$(basename $LOC_FILE) |
| 94 | + LOC_DIR=$(dirname $LOC_FILE) |
| 95 | + echo "[DEBUG] - COUNT = $COUNT / LocFile = $LOC_FILE" |
| 96 | +
|
| 97 | + case $LOC_TYPE in |
| 98 | + "yarn.lock") |
| 99 | + message_type "YARN is no longer allowed. Kindly replace the lockfile using PNPM. Found in **$LOC_FILE**" |
| 100 | + ;; |
| 101 | + "package-lock.json") |
| 102 | + message_type "NPM is no longer allowed. Kindly replace the lockfile using PNPM. Found in **$LOC_FILE**" |
| 103 | + ;; |
| 104 | + "pnpm-lock.yaml") |
| 105 | + SKIP=$(compare_version) |
| 106 | + if [ "$SKIP" == "true" ] ; then continue; fi |
| 107 | + ;; |
| 108 | + "") |
| 109 | + message_type "A lockfile is required. No lockfile found in **$LOC_DIR**" |
| 110 | + ;; |
| 111 | + esac |
| 112 | + done |
| 113 | + if [[ $COUNT -gt 1 ]]; then |
| 114 | + message_type "$COUNT lockfiles were found. Kindly keep only the lockfile generated with PNPM. Found in **$LOC_DIR**" |
| 115 | + fi |
| 116 | + done |
| 117 | +
|
| 118 | + ## |
| 119 | + # Scan manifests for blacklisted dependencies |
| 120 | + ## |
| 121 | +
|
| 122 | + function checkPnpmLockfile() { |
| 123 | + # Find dependency formated as |
| 124 | + # "name@version:" |
| 125 | + if grep -qF "$NAME@$VERSION" "$LOCKFILE"; then |
| 126 | + echo "$NAME:$VERSION was found in $LOCKFILE" |
| 127 | + echo "[ERROR] - $NAME:$VERSION was found in $LOCKFILE" >> "$ERROR_LOG" |
91 | 128 | else |
92 | | - echo -n "." |
| 129 | + echo -n "." |
93 | 130 | fi |
94 | 131 | } |
95 | 132 |
|
96 | 133 | function checkManifest() { |
97 | 134 | COUNT=0 |
98 | | - echo "::info:: Testing manifest $LOCKFILE" |
| 135 | + echo "[INFO] - Testing manifest $LOCKFILE" |
99 | 136 | manifest_type=$(basename "$LOCKFILE") |
100 | 137 |
|
101 | 138 | while IFS=':' read -r NAME VERSION; do |
102 | 139 | # ignore empty and commented lines |
103 | 140 | [[ -z "${NAME// }" ]] && continue |
104 | 141 | [[ "$NAME" =~ ^# ]] && continue |
105 | | - #echo "DEBUG To check $NAME $VERSION" |
| 142 | +
|
106 | 143 | case "$manifest_type" in |
107 | 144 | "pnpm-lock.yaml") |
108 | 145 | checkPnpmLockfile |
109 | 146 | ;; |
110 | | - "yarn.lock") |
111 | | - checkYarnLockfile "$NAME" "$VERSION" |
112 | | - ;; |
113 | | - "package-lock.json") |
114 | | - checkNpmLockfile "$NAME" "$VERSION" |
115 | | - ;; |
116 | 147 | "*") |
117 | | - echo "KO manifest not managed" |
118 | | - exit 1 |
| 148 | + message_type "Dependency manager not managed. Found in $LOCKFILE" >> "$ERROR_LOG" |
119 | 149 | esac |
| 150 | +
|
120 | 151 | COUNT=$((COUNT+1)) |
121 | 152 | done < "$DEP_LIST" |
122 | | - echo "Scanned $COUNT IOC" |
| 153 | + echo "[INFO] - Scanned $COUNT IOC" |
123 | 154 | } |
124 | 155 |
|
125 | | - touch "$ERROR_LOG" |
| 156 | + # Check blacklist |
| 157 | + echo "[INFO] - Scan manifests for blacklisted dependencies" |
| 158 | + DEP_LIST="compromised-packages.txt" |
| 159 | + wget https://raw.githubusercontent.com/centreon/security-tools/main/blacklist/"$DEP_LIST" |
| 160 | +
|
| 161 | + LOCKFILES=($(find ./ -type f -name "pnpm-lock.yaml")) |
126 | 162 | for LOCKFILE in "${LOCKFILES[@]}"; do |
127 | 163 | checkManifest "$LOCKFILE" |
128 | 164 | done |
| 165 | +
|
| 166 | + # Quality gate |
129 | 167 | if [ -s "$ERROR_LOG" ]; then |
130 | | - echo -e "\nFATAL Breaking the run as following dependencies were found:" |
131 | | - cat "$ERROR_LOG" |
132 | | - exit 1 |
| 168 | + if [ "$FORCE_FAIL" == "true" ]; then |
| 169 | + echo "[ERROR] - Breaking the run. Kindly, check the comment" |
| 170 | + FAIL_THE_BUILD="true" |
| 171 | + fi |
133 | 172 | else |
134 | | - echo "OK nothing found" |
| 173 | + echo "[OK] - Great, nothing found !" |
135 | 174 | fi |
136 | | -
|
| 175 | + echo "fail_the_build=$FAIL_THE_BUILD" >> "$GITHUB_ENV" |
| 176 | + cat $GITHUB_ENV |
137 | 177 | shell: bash |
| 178 | + |
| 179 | + - name: comment_PR |
| 180 | + continue-on-error: true |
| 181 | + uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2.9.4 |
| 182 | + with: |
| 183 | + recreate: true |
| 184 | + ignore_empty: true |
| 185 | + path: "error_log.txt" |
| 186 | + |
| 187 | + - name: Fail job if previous step failed |
| 188 | + if: env.fail_the_build == 'true' |
| 189 | + run: | |
| 190 | + echo "[ERROR] - Breaking the run. Kindly, check the comment and the dependency analysis log above" |
| 191 | + exit 1 |
0 commit comments