Skip to content

Commit 4ff8975

Browse files
authored
ci(secu): dependency compliance analysis (#21)
1 parent f011ace commit 4ff8975

File tree

3 files changed

+141
-84
lines changed

3 files changed

+141
-84
lines changed

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* @centreon/owners-security
Lines changed: 138 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,137 +1,191 @@
11
name: dependency-checks
22

33
on:
4+
pull_request:
45
workflow_call:
56

7+
permissions:
8+
pull-requests: write
9+
contents: read
10+
611
jobs:
712
dependency-scan:
8-
name: Run internal dependency script
13+
name: Run dependencies analysis
914
runs-on: ${{ github.repository_visibility != 'public' && 'centreon-security' || 'ubuntu-24.04' }}
1015

1116
steps:
1217
- name: Checkout Repository
1318
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
14-
with:
15-
fetch-depth: 0
1619

17-
- name: Run dependency scan
20+
- name: Check dependencies type and lockfiles version
1821
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
2136
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
2446
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"
3157
else
32-
echo -n "."
58+
echo "[WARNING] : $MSG"
3359
fi
60+
echo "$MSG" >> "$ERROR_LOG"
3461
}
3562
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**"
6277
else
63-
echo -n "."
78+
SKIP="true"
6479
fi
6580
}
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
7481
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"
91128
else
92-
echo -n "."
129+
echo -n "."
93130
fi
94131
}
95132
96133
function checkManifest() {
97134
COUNT=0
98-
echo "::info:: Testing manifest $LOCKFILE"
135+
echo "[INFO] - Testing manifest $LOCKFILE"
99136
manifest_type=$(basename "$LOCKFILE")
100137
101138
while IFS=':' read -r NAME VERSION; do
102139
# ignore empty and commented lines
103140
[[ -z "${NAME// }" ]] && continue
104141
[[ "$NAME" =~ ^# ]] && continue
105-
#echo "DEBUG To check $NAME $VERSION"
142+
106143
case "$manifest_type" in
107144
"pnpm-lock.yaml")
108145
checkPnpmLockfile
109146
;;
110-
"yarn.lock")
111-
checkYarnLockfile "$NAME" "$VERSION"
112-
;;
113-
"package-lock.json")
114-
checkNpmLockfile "$NAME" "$VERSION"
115-
;;
116147
"*")
117-
echo "KO manifest not managed"
118-
exit 1
148+
message_type "Dependency manager not managed. Found in $LOCKFILE" >> "$ERROR_LOG"
119149
esac
150+
120151
COUNT=$((COUNT+1))
121152
done < "$DEP_LIST"
122-
echo "Scanned $COUNT IOC"
153+
echo "[INFO] - Scanned $COUNT IOC"
123154
}
124155
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"))
126162
for LOCKFILE in "${LOCKFILES[@]}"; do
127163
checkManifest "$LOCKFILE"
128164
done
165+
166+
# Quality gate
129167
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
133172
else
134-
echo "OK nothing found"
173+
echo "[OK] - Great, nothing found !"
135174
fi
136-
175+
echo "fail_the_build=$FAIL_THE_BUILD" >> "$GITHUB_ENV"
176+
cat $GITHUB_ENV
137177
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

blacklist/compromised-packages.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1704,3 +1704,5 @@ zapier-scripts:7.8.4
17041704
zuper-cli:1.0.1
17051705
zuper-sdk:1.0.57
17061706
zuper-stream:2.0.9
1707+
safe-chain-test:0.0.1-security
1708+
eslint-js:0.0.1-security

0 commit comments

Comments
 (0)