-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathrun-scans.sh
More file actions
executable file
·235 lines (194 loc) · 8.74 KB
/
Copy pathrun-scans.sh
File metadata and controls
executable file
·235 lines (194 loc) · 8.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
#!/bin/bash
set -u
# This does not work with a symlink to this script
# SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
# See https://stackoverflow.com/a/246128/24637657
SOURCE=${BASH_SOURCE[0]}
while [ -L "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
SCRIPT_DIR=$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )
SOURCE=$(readlink "$SOURCE")
# if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
[[ $SOURCE != /* ]] && SOURCE=$SCRIPT_DIR/$SOURCE
done
SCRIPT_DIR=$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )
REPO_ROOT=${REPO_ROOT:-$SCRIPT_DIR/../..}
CVE_DIR=${CVE_DIR:-$REPO_ROOT/docs/agent/security}
TEMPLATE_DIR=${TEMPLATE_DIR:-$SCRIPT_DIR/templates}
SCAN_FILE=${SCAN_FILE:-$SCRIPT_DIR/scan-config.json}
# Check if all tools are installed
if ! command -v syft &> /dev/null || ! command -v grype &> /dev/null || ! command -v jq &> /dev/null || ! command -v gh &> /dev/null; then
echo "ERROR: gh, jq, syft and grype must be installed to run this script."
exit 1
fi
# Check if the scan file exists
if [[ ! -f "$SCAN_FILE" ]]; then
echo "ERROR: Scan file $SCAN_FILE does not exist."
exit 1
fi
# Read the scan file and extract the versions
readarray -t OSS_VERSIONS < <(jq -r '.oss_versions[]' "$SCAN_FILE")
readarray -t AGENT_VERSIONS < <(jq -r '.agent_versions[]' "$SCAN_FILE")
# Check if the versions are not empty
if [[ ${#OSS_VERSIONS[@]} -eq 0 || ${#AGENT_VERSIONS[@]} -eq 0 ]]; then
echo "ERROR: No versions found in $SCAN_FILE."
exit 1
fi
# Print the versions
echo "OSS Versions: ${OSS_VERSIONS[*]}"
echo "Agent Versions: ${AGENT_VERSIONS[*]}"
# Check if the versions are valid semver
for version in "${OSS_VERSIONS[@]}" "${AGENT_VERSIONS[@]}"; do
if [[ ! "$version" =~ ^v?([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then
echo "ERROR: Invalid version format: $version"
exit 1
fi
done
# If all checks pass, print success message
echo "All versions are valid semver format."
# Create directories for CVE scans
mkdir -p "$CVE_DIR"/oss "$CVE_DIR"/agent
if ! gh auth status &> /dev/null; then
echo "ERROR: gh CLI is not authenticated. Please run 'gh auth login' to authenticate."
exit 1
fi
# Start constructing a top-level index of all the OSS and Agent scans
cat <<EOF > "$CVE_DIR/cves.md"
# CVE reporting
This page hosts all known information about any security issues, mitigations and triaged CVEs.
Please reach out to us at <info@telemetryforge.io> directly for any specific concerns or queries.
--8<-- "docs/agent/security/triaged.md"
--8<-- "docs/agent/security/grype-latest.md"
## All agent and OSS versions
Full unfiltered reports are shown below, covering all severities and without any filtering for triaged issues.
EOF
function generateReports() {
local version="${1:?Version is required}"
local type="${2:?Type is required}"
local repository=${3:?Repository is required}
# Set the first character to uppercase in the type for display purposes
local type_capitalised="${type^}"
local dir="$CVE_DIR/$type"
[[ ! -d "$dir" ]] && mkdir -p "$dir"
# Check if SBOMs are already provided, if so skip generation to prevent any issues with non-reproducible builds
# as some fields may change each time we run in each format.
# We check for the JSON format as that is the most likely to be used for scans.
# Syft seems to not want to support this: https://github.com/anchore/syft/issues/1100
if [[ -f "$dir/syft-$version.json" ]]; then
echo "SBOMs already exist for $type_capitalised version: $version, skipping syft generation."
else
echo "Running syft for $type_capitalised version: $version"
if ! syft "$repository:$version" --output json="$dir/syft-$version.json" --output cyclonedx-json="$dir/cyclonedx-$version.cdx.json" --output spdx-json="$dir/spdx-$version.spdx.json"; then
echo "ERROR: Failed to run syft for $type version: $version, skipping grype scan."
rm -f "$dir/*-$version.json"
exit 1
fi
fi
# We always run CVE scans though as new ones may appear.
# Grype supports reproducible builds as you can disable timestamps in the output which are the only thing that would change.
# Disable timestamp usage otherwise we get deltas for every run: https://github.com/anchore/grype/pull/2724
export GRYPE_TIMESTAMP=false
# Pretty-print JSON output by default
export GRYPE_PRETTY=true
echo "Running grype for $type_capitalised version: $version"
# Use our VEX file to exclude any triaged CVEs from the report
grype "sbom:$dir/syft-$version.json" --output json --file "$dir/grype-$version.json" --quiet
# Remove any local filesystem information in the output files
sed -i "s|$REPO_ROOT/docs/||g" "$dir/grype-$version.json"
sed -i "s|$REPO_ROOT/||g" "$dir/grype-$version.json"
sed -i "s|$HOME/||g" "$dir/grype-$version.json"
sed -i "s|$REPO_ROOT/docs/||g" "$dir/syft-$version.json"
sed -i "s|$REPO_ROOT/||g" "$dir/syft-$version.json"
sed -i "s|$HOME/||g" "$dir/syft-$version.json"
grype "sbom:$dir/syft-$version.json" \
--output template \
--template "$TEMPLATE_DIR/grype-markdown.tmpl" \
--file "$dir/grype-$version.md" \
--sort-by severity
echo "Grype scan completed for $type_capitalised version: $version"
# Add to the index file
{
echo ""
echo "### $type_capitalised Version: $version"
echo ""
echo "- [Grype Markdown Report]($type/grype-$version.md)"
echo "- [Grype JSON Report]($type/grype-$version.json)"
echo ""
echo "- [Syft JSON SBOM]($type/syft-$version.json)"
echo "- [CycloneDX JSON SBOM]($type/cyclonedx-$version.cdx.json)"
echo "- [SPDX JSON SBOM]($type/spdx-$version.spdx.json)"
} >> "$CVE_DIR/cves.md"
}
# Run grype for each Agent version
for agent_version in "${AGENT_VERSIONS[@]}"; do
generateReports "$agent_version" "agent" "ghcr.io/telemetryforge/agent"
done
# Get latest release version of the agent from GitHub API using gh client
# We require GITHUB_TOKEN to be set in the environment for this to work reliably
# See https://docs.github.com/en/rest/releases/releases?apiVersion=2022-1128#get-the-latest-release
LATEST_AGENT_VERSION=$(gh api /repos/telemetryforge/agent/releases/latest | jq -r .tag_name)
# Remove any leading 'v' from the version
LATEST_AGENT_VERSION=${LATEST_AGENT_VERSION#v}
# Trim any whitespace
LATEST_AGENT_VERSION=$(echo -n "$LATEST_AGENT_VERSION" | xargs)
if [[ -z "$LATEST_AGENT_VERSION" ]]; then
echo "ERROR: Could not determine latest agent version from GitHub API, ensure gh CLI is authenticated correctly."
exit 1
else
# We customise the latest agent version scan generation to use the VEX file to exclude any triaged CVEs.
# We remove any CVEs that are medium or lower severity as well to reduce noise.
echo "Latest agent version from GitHub API is: $LATEST_AGENT_VERSION"
echo "Generating latest agent version scan with VEX filtering for version: $LATEST_AGENT_VERSION"
grype "ghcr.io/telemetryforge/agent:$LATEST_AGENT_VERSION" \
--output template \
--template "$TEMPLATE_DIR/grype-markdown-table-above-high.tmpl" \
--file "$CVE_DIR/agent/grype-latest.md" \
--sort-by severity \
--vex "$CVE_DIR/vex.json"
fi
# Run grype for each OSS version
for oss_version in "${OSS_VERSIONS[@]}"; do
generateReports "$oss_version" "oss" "ghcr.io/fluent/fluent-bit"
done
# Now iterate over each JSON file and ensure we pretty-print it rather than all on one line.
for json_file in "$CVE_DIR"/oss/*.json "$CVE_DIR"/agent/*.json; do
if [[ -f "$json_file" ]]; then
echo "Pretty-printing JSON file: $json_file"
jq . "$json_file" > "$json_file.tmp" && mv "$json_file.tmp" "$json_file"
fi
done
# Validate that all expected SBOM and scan files are non-empty and contain valid content
echo "Validating generated files..."
validation_failed=false
function validateFiles() {
local version="${1:?Version is required}"
local dir="${2:?Directory is required}"
local expected_files=(
"syft-$version.json"
"cyclonedx-$version.cdx.json"
"spdx-$version.spdx.json"
"grype-$version.json"
"grype-$version.md"
)
for file in "${expected_files[@]}"; do
local file_path="$dir/$file"
if [[ ! -f "$file_path" ]] || [[ ! -s "$file_path" ]]; then
echo "ERROR: File $file_path is missing or empty"
validation_failed=true
elif [[ "$file" == *.json ]] && ! jq empty "$file_path" 2>/dev/null; then
echo "ERROR: File $file_path contains invalid JSON"
validation_failed=true
fi
done
}
for agent_version in "${AGENT_VERSIONS[@]}"; do
validateFiles "$agent_version" "$CVE_DIR/agent"
done
for oss_version in "${OSS_VERSIONS[@]}"; do
validateFiles "$oss_version" "$CVE_DIR/oss"
done
if [[ "$validation_failed" == "true" ]]; then
echo "ERROR: Validation failed - some files are missing, empty, or contain invalid JSON"
exit 1
fi
echo "All generated files are valid."
echo "CVE scans completed. Reports are available in $CVE_DIR"