vtk 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +1 -1
- data/.tool-versions +1 -1
- data/CHANGELOG.md +39 -0
- data/README.md +109 -0
- data/lib/vtk/cli.rb +3 -0
- data/lib/vtk/commands/scan/README.md +102 -0
- data/lib/vtk/commands/scan/credentials.rb +59 -0
- data/lib/vtk/commands/scan/machine.rb +60 -0
- data/lib/vtk/commands/scan/repo.rb +77 -0
- data/lib/vtk/commands/scan.rb +75 -0
- data/lib/vtk/commands/socks/setup.rb +4 -4
- data/lib/vtk/version.rb +1 -1
- data/scripts/credential-audit.ps1 +620 -0
- data/scripts/credential-audit.sh +535 -0
- data/scripts/shai-hulud-machine-check.ps1 +625 -0
- data/scripts/shai-hulud-machine-check.sh +531 -0
- data/scripts/shai-hulud-repo-check.ps1 +615 -0
- data/scripts/shai-hulud-repo-check.sh +849 -0
- metadata +14 -6
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# Credential Audit Script
|
|
4
|
+
# =======================
|
|
5
|
+
#
|
|
6
|
+
# Audits which credentials are present on this machine and provides rotation
|
|
7
|
+
# instructions for each. Run this after a suspected or confirmed security incident.
|
|
8
|
+
#
|
|
9
|
+
# WHAT THIS CHECKS:
|
|
10
|
+
#
|
|
11
|
+
# NPM:
|
|
12
|
+
# - ~/.npmrc (auth tokens)
|
|
13
|
+
# - $NPM_TOKEN, $NPM_CONFIG_TOKEN environment variables
|
|
14
|
+
#
|
|
15
|
+
# AWS:
|
|
16
|
+
# - ~/.aws/credentials, ~/.aws/config
|
|
17
|
+
# - $AWS_ACCESS_KEY_ID, $AWS_SECRET_ACCESS_KEY
|
|
18
|
+
#
|
|
19
|
+
# GCP:
|
|
20
|
+
# - ~/.config/gcloud/application_default_credentials.json
|
|
21
|
+
# - $GOOGLE_APPLICATION_CREDENTIALS
|
|
22
|
+
#
|
|
23
|
+
# Azure:
|
|
24
|
+
# - ~/.azure/ directory
|
|
25
|
+
# - $AZURE_CLIENT_SECRET, $AZURE_TENANT_ID
|
|
26
|
+
#
|
|
27
|
+
# GitHub:
|
|
28
|
+
# - ~/.config/gh/hosts.yml (GitHub CLI)
|
|
29
|
+
# - .git/config (stored credentials)
|
|
30
|
+
# - $GITHUB_TOKEN, $GH_TOKEN
|
|
31
|
+
#
|
|
32
|
+
# Other:
|
|
33
|
+
# - SSH keys (~/.ssh/)
|
|
34
|
+
# - Git credentials (~/.git-credentials)
|
|
35
|
+
# - Docker config (~/.docker/config.json)
|
|
36
|
+
# - Kubernetes config (~/.kube/config)
|
|
37
|
+
# - Sensitive environment variables
|
|
38
|
+
#
|
|
39
|
+
# EXIT CODES:
|
|
40
|
+
# 0 - No credentials found
|
|
41
|
+
# 1 - Credentials found (rotation recommended)
|
|
42
|
+
#
|
|
43
|
+
# USAGE:
|
|
44
|
+
# ./credential-audit.sh # Standard output
|
|
45
|
+
# ./credential-audit.sh --verbose # Show all checks (even clean)
|
|
46
|
+
# ./credential-audit.sh --json # JSON output
|
|
47
|
+
#
|
|
48
|
+
# References:
|
|
49
|
+
# - EERT Playbooks: https://department-of-veterans-affairs.github.io/eert/
|
|
50
|
+
#
|
|
51
|
+
# Author: Eric Boehs / EERT (with Claude Code)
|
|
52
|
+
# Version: 1.0.0
|
|
53
|
+
# Date: December 2025
|
|
54
|
+
#
|
|
55
|
+
|
|
56
|
+
set -e
|
|
57
|
+
|
|
58
|
+
# Parse arguments
|
|
59
|
+
JSON=false
|
|
60
|
+
VERBOSE=false
|
|
61
|
+
for arg in "$@"; do
|
|
62
|
+
case $arg in
|
|
63
|
+
--json|-j) JSON=true ;;
|
|
64
|
+
--verbose|-v) VERBOSE=true ;;
|
|
65
|
+
--help|-h)
|
|
66
|
+
echo "Usage: $0 [--verbose|-v] [--json|-j]"
|
|
67
|
+
echo " --verbose Show all checks including clean ones"
|
|
68
|
+
echo " --json JSON output format"
|
|
69
|
+
exit 0
|
|
70
|
+
;;
|
|
71
|
+
esac
|
|
72
|
+
done
|
|
73
|
+
|
|
74
|
+
# Results tracking
|
|
75
|
+
ROTATION_INSTRUCTIONS=()
|
|
76
|
+
TOTAL_FOUND=0
|
|
77
|
+
|
|
78
|
+
# Colors (disabled in json mode)
|
|
79
|
+
if [ "$JSON" = false ] && [ -t 1 ]; then
|
|
80
|
+
RED='\033[0;31m'
|
|
81
|
+
YELLOW='\033[0;33m'
|
|
82
|
+
GREEN='\033[0;32m'
|
|
83
|
+
CYAN='\033[0;36m'
|
|
84
|
+
BOLD='\033[1m'
|
|
85
|
+
DIM='\033[2m'
|
|
86
|
+
NC='\033[0m'
|
|
87
|
+
else
|
|
88
|
+
RED='' YELLOW='' GREEN='' CYAN='' BOLD='' DIM='' NC=''
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
# Logging functions
|
|
92
|
+
log() {
|
|
93
|
+
if [ "$JSON" = false ]; then
|
|
94
|
+
echo -e "$@"
|
|
95
|
+
fi
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
log_verbose() {
|
|
99
|
+
if [ "$VERBOSE" = true ] && [ "$JSON" = false ]; then
|
|
100
|
+
echo -e "$@"
|
|
101
|
+
fi
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
log_found() {
|
|
105
|
+
local service="$1"
|
|
106
|
+
local location="$2"
|
|
107
|
+
local instruction="$3"
|
|
108
|
+
ROTATION_INSTRUCTIONS+=("$service|$location|$instruction")
|
|
109
|
+
((TOTAL_FOUND++)) || true
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
# Header
|
|
113
|
+
log ""
|
|
114
|
+
log "${BOLD}Credential Audit${NC}"
|
|
115
|
+
log "${DIM}Checking for credentials that may need rotation...${NC}"
|
|
116
|
+
log ""
|
|
117
|
+
|
|
118
|
+
###########################################
|
|
119
|
+
# NPM CREDENTIALS
|
|
120
|
+
###########################################
|
|
121
|
+
|
|
122
|
+
log "${BOLD}NPM${NC}"
|
|
123
|
+
NPM_FOUND=0
|
|
124
|
+
|
|
125
|
+
# Check ~/.npmrc
|
|
126
|
+
if [ -f "$HOME/.npmrc" ]; then
|
|
127
|
+
if grep -qiE "//.*:_authToken=|_auth=|authToken" "$HOME/.npmrc" 2>/dev/null; then
|
|
128
|
+
log " ${RED}[FOUND]${NC} ~/.npmrc contains auth tokens"
|
|
129
|
+
log_found "NPM" "~/.npmrc" "npm token revoke <token> && npm login"
|
|
130
|
+
NPM_FOUND=1
|
|
131
|
+
else
|
|
132
|
+
log_verbose " ${GREEN}[CLEAN]${NC} ~/.npmrc exists but no tokens found"
|
|
133
|
+
fi
|
|
134
|
+
else
|
|
135
|
+
log_verbose " ${DIM}[SKIP]${NC} ~/.npmrc not found"
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
# Check NPM environment variables
|
|
139
|
+
if [ -n "$NPM_TOKEN" ]; then
|
|
140
|
+
log " ${RED}[FOUND]${NC} \$NPM_TOKEN is set"
|
|
141
|
+
log_found "NPM" "\$NPM_TOKEN" "Revoke token in npm account settings, regenerate and update env"
|
|
142
|
+
NPM_FOUND=1
|
|
143
|
+
fi
|
|
144
|
+
|
|
145
|
+
if [ -n "$NPM_CONFIG_TOKEN" ]; then
|
|
146
|
+
log " ${RED}[FOUND]${NC} \$NPM_CONFIG_TOKEN is set"
|
|
147
|
+
log_found "NPM" "\$NPM_CONFIG_TOKEN" "Revoke token in npm account settings, regenerate and update env"
|
|
148
|
+
NPM_FOUND=1
|
|
149
|
+
fi
|
|
150
|
+
|
|
151
|
+
if [ "$NPM_FOUND" -eq 0 ]; then
|
|
152
|
+
log " ${DIM}None found${NC}"
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
log ""
|
|
156
|
+
|
|
157
|
+
###########################################
|
|
158
|
+
# AWS CREDENTIALS
|
|
159
|
+
###########################################
|
|
160
|
+
|
|
161
|
+
log "${BOLD}AWS${NC}"
|
|
162
|
+
AWS_FOUND=0
|
|
163
|
+
|
|
164
|
+
# Check ~/.aws/credentials
|
|
165
|
+
if [ -f "$HOME/.aws/credentials" ]; then
|
|
166
|
+
log " ${RED}[FOUND]${NC} ~/.aws/credentials"
|
|
167
|
+
log_found "AWS" "~/.aws/credentials" "aws iam delete-access-key && aws iam create-access-key"
|
|
168
|
+
AWS_FOUND=1
|
|
169
|
+
else
|
|
170
|
+
log_verbose " ${DIM}[SKIP]${NC} ~/.aws/credentials not found"
|
|
171
|
+
fi
|
|
172
|
+
|
|
173
|
+
# Check ~/.aws/config (may contain SSO or role info)
|
|
174
|
+
if [ -f "$HOME/.aws/config" ]; then
|
|
175
|
+
if grep -qE "aws_access_key_id|aws_secret_access_key" "$HOME/.aws/config" 2>/dev/null; then
|
|
176
|
+
log " ${RED}[FOUND]${NC} ~/.aws/config contains access keys"
|
|
177
|
+
log_found "AWS" "~/.aws/config" "Remove keys from config, use aws configure"
|
|
178
|
+
AWS_FOUND=1
|
|
179
|
+
else
|
|
180
|
+
log_verbose " ${GREEN}[CLEAN]${NC} ~/.aws/config exists (no embedded keys)"
|
|
181
|
+
fi
|
|
182
|
+
else
|
|
183
|
+
log_verbose " ${DIM}[SKIP]${NC} ~/.aws/config not found"
|
|
184
|
+
fi
|
|
185
|
+
|
|
186
|
+
# Check AWS environment variables
|
|
187
|
+
if [ -n "$AWS_ACCESS_KEY_ID" ]; then
|
|
188
|
+
log " ${RED}[FOUND]${NC} \$AWS_ACCESS_KEY_ID is set"
|
|
189
|
+
log_found "AWS" "\$AWS_ACCESS_KEY_ID" "Rotate key in IAM console, update env"
|
|
190
|
+
AWS_FOUND=1
|
|
191
|
+
fi
|
|
192
|
+
|
|
193
|
+
if [ -n "$AWS_SECRET_ACCESS_KEY" ]; then
|
|
194
|
+
log " ${RED}[FOUND]${NC} \$AWS_SECRET_ACCESS_KEY is set"
|
|
195
|
+
log_found "AWS" "\$AWS_SECRET_ACCESS_KEY" "Rotate key in IAM console, update env"
|
|
196
|
+
AWS_FOUND=1
|
|
197
|
+
fi
|
|
198
|
+
|
|
199
|
+
if [ -n "$AWS_SESSION_TOKEN" ]; then
|
|
200
|
+
log " ${YELLOW}[FOUND]${NC} \$AWS_SESSION_TOKEN is set (temporary)"
|
|
201
|
+
log_found "AWS" "\$AWS_SESSION_TOKEN" "Wait for expiration or re-authenticate with aws sso login"
|
|
202
|
+
AWS_FOUND=1
|
|
203
|
+
fi
|
|
204
|
+
|
|
205
|
+
if [ "$AWS_FOUND" -eq 0 ]; then
|
|
206
|
+
log " ${DIM}None found${NC}"
|
|
207
|
+
fi
|
|
208
|
+
|
|
209
|
+
log ""
|
|
210
|
+
|
|
211
|
+
###########################################
|
|
212
|
+
# GCP CREDENTIALS
|
|
213
|
+
###########################################
|
|
214
|
+
|
|
215
|
+
log "${BOLD}GCP${NC}"
|
|
216
|
+
GCP_FOUND=0
|
|
217
|
+
|
|
218
|
+
# Check Application Default Credentials
|
|
219
|
+
ADC_PATH="$HOME/.config/gcloud/application_default_credentials.json"
|
|
220
|
+
if [ -f "$ADC_PATH" ]; then
|
|
221
|
+
log " ${RED}[FOUND]${NC} Application Default Credentials"
|
|
222
|
+
log_found "GCP" "$ADC_PATH" "gcloud auth application-default revoke && gcloud auth application-default login"
|
|
223
|
+
GCP_FOUND=1
|
|
224
|
+
else
|
|
225
|
+
log_verbose " ${DIM}[SKIP]${NC} ADC not found"
|
|
226
|
+
fi
|
|
227
|
+
|
|
228
|
+
# Check gcloud auth
|
|
229
|
+
if [ -d "$HOME/.config/gcloud" ] && [ -f "$HOME/.config/gcloud/credentials.db" ]; then
|
|
230
|
+
log " ${RED}[FOUND]${NC} gcloud credentials.db"
|
|
231
|
+
log_found "GCP" "~/.config/gcloud/credentials.db" "gcloud auth revoke --all && gcloud auth login"
|
|
232
|
+
GCP_FOUND=1
|
|
233
|
+
fi
|
|
234
|
+
|
|
235
|
+
# Check GOOGLE_APPLICATION_CREDENTIALS
|
|
236
|
+
if [ -n "$GOOGLE_APPLICATION_CREDENTIALS" ]; then
|
|
237
|
+
if [ -f "$GOOGLE_APPLICATION_CREDENTIALS" ]; then
|
|
238
|
+
log " ${RED}[FOUND]${NC} \$GOOGLE_APPLICATION_CREDENTIALS points to: $GOOGLE_APPLICATION_CREDENTIALS"
|
|
239
|
+
log_found "GCP" "\$GOOGLE_APPLICATION_CREDENTIALS" "Rotate service account key in GCP Console"
|
|
240
|
+
GCP_FOUND=1
|
|
241
|
+
else
|
|
242
|
+
log_verbose " ${DIM}[SKIP]${NC} \$GOOGLE_APPLICATION_CREDENTIALS set but file doesn't exist"
|
|
243
|
+
fi
|
|
244
|
+
fi
|
|
245
|
+
|
|
246
|
+
if [ "$GCP_FOUND" -eq 0 ]; then
|
|
247
|
+
log " ${DIM}None found${NC}"
|
|
248
|
+
fi
|
|
249
|
+
|
|
250
|
+
log ""
|
|
251
|
+
|
|
252
|
+
###########################################
|
|
253
|
+
# AZURE CREDENTIALS
|
|
254
|
+
###########################################
|
|
255
|
+
|
|
256
|
+
log "${BOLD}Azure${NC}"
|
|
257
|
+
AZURE_FOUND=0
|
|
258
|
+
|
|
259
|
+
# Check ~/.azure directory
|
|
260
|
+
if [ -d "$HOME/.azure" ]; then
|
|
261
|
+
if [ -f "$HOME/.azure/accessTokens.json" ] || [ -f "$HOME/.azure/azureProfile.json" ]; then
|
|
262
|
+
log " ${RED}[FOUND]${NC} ~/.azure/ contains auth tokens"
|
|
263
|
+
log_found "Azure" "~/.azure/" "az logout && az login"
|
|
264
|
+
AZURE_FOUND=1
|
|
265
|
+
else
|
|
266
|
+
log_verbose " ${GREEN}[CLEAN]${NC} ~/.azure/ exists but no tokens found"
|
|
267
|
+
fi
|
|
268
|
+
else
|
|
269
|
+
log_verbose " ${DIM}[SKIP]${NC} ~/.azure/ not found"
|
|
270
|
+
fi
|
|
271
|
+
|
|
272
|
+
# Check Azure environment variables
|
|
273
|
+
if [ -n "$AZURE_CLIENT_SECRET" ]; then
|
|
274
|
+
log " ${RED}[FOUND]${NC} \$AZURE_CLIENT_SECRET is set"
|
|
275
|
+
log_found "Azure" "\$AZURE_CLIENT_SECRET" "Rotate client secret in Azure AD app registration"
|
|
276
|
+
AZURE_FOUND=1
|
|
277
|
+
fi
|
|
278
|
+
|
|
279
|
+
if [ -n "$AZURE_CLIENT_ID" ] && [ -n "$AZURE_TENANT_ID" ]; then
|
|
280
|
+
log " ${YELLOW}[INFO]${NC} Azure service principal env vars configured"
|
|
281
|
+
fi
|
|
282
|
+
|
|
283
|
+
if [ "$AZURE_FOUND" -eq 0 ]; then
|
|
284
|
+
log " ${DIM}None found${NC}"
|
|
285
|
+
fi
|
|
286
|
+
|
|
287
|
+
log ""
|
|
288
|
+
|
|
289
|
+
###########################################
|
|
290
|
+
# GITHUB CREDENTIALS
|
|
291
|
+
###########################################
|
|
292
|
+
|
|
293
|
+
log "${BOLD}GitHub${NC}"
|
|
294
|
+
GITHUB_FOUND=0
|
|
295
|
+
|
|
296
|
+
# Check GitHub CLI
|
|
297
|
+
if [ -f "$HOME/.config/gh/hosts.yml" ]; then
|
|
298
|
+
log " ${RED}[FOUND]${NC} GitHub CLI authenticated (~/.config/gh/hosts.yml)"
|
|
299
|
+
log_found "GitHub" "~/.config/gh/hosts.yml" "gh auth logout && gh auth login"
|
|
300
|
+
GITHUB_FOUND=1
|
|
301
|
+
else
|
|
302
|
+
log_verbose " ${DIM}[SKIP]${NC} GitHub CLI not authenticated"
|
|
303
|
+
fi
|
|
304
|
+
|
|
305
|
+
# Check for GITHUB_TOKEN / GH_TOKEN
|
|
306
|
+
if [ -n "$GITHUB_TOKEN" ]; then
|
|
307
|
+
log " ${RED}[FOUND]${NC} \$GITHUB_TOKEN is set"
|
|
308
|
+
log_found "GitHub" "\$GITHUB_TOKEN" "Revoke token at github.com/settings/tokens, regenerate"
|
|
309
|
+
GITHUB_FOUND=1
|
|
310
|
+
fi
|
|
311
|
+
|
|
312
|
+
if [ -n "$GH_TOKEN" ]; then
|
|
313
|
+
log " ${RED}[FOUND]${NC} \$GH_TOKEN is set"
|
|
314
|
+
log_found "GitHub" "\$GH_TOKEN" "Revoke token at github.com/settings/tokens, regenerate"
|
|
315
|
+
GITHUB_FOUND=1
|
|
316
|
+
fi
|
|
317
|
+
|
|
318
|
+
# Check .git/config for stored credentials (in home dir)
|
|
319
|
+
if [ -f "$HOME/.gitconfig" ]; then
|
|
320
|
+
if grep -qE "helper.*store|credential.*=.*https" "$HOME/.gitconfig" 2>/dev/null; then
|
|
321
|
+
log " ${YELLOW}[WARN]${NC} ~/.gitconfig uses credential store"
|
|
322
|
+
log_found "GitHub" "~/.gitconfig" "git config --global --unset credential.helper (if using store)"
|
|
323
|
+
GITHUB_FOUND=1
|
|
324
|
+
fi
|
|
325
|
+
fi
|
|
326
|
+
|
|
327
|
+
# Check .git-credentials
|
|
328
|
+
if [ -f "$HOME/.git-credentials" ]; then
|
|
329
|
+
log " ${RED}[FOUND]${NC} ~/.git-credentials (plaintext credentials)"
|
|
330
|
+
log_found "GitHub" "~/.git-credentials" "rm ~/.git-credentials && regenerate PATs"
|
|
331
|
+
GITHUB_FOUND=1
|
|
332
|
+
fi
|
|
333
|
+
|
|
334
|
+
if [ "$GITHUB_FOUND" -eq 0 ]; then
|
|
335
|
+
log " ${DIM}None found${NC}"
|
|
336
|
+
fi
|
|
337
|
+
|
|
338
|
+
log ""
|
|
339
|
+
|
|
340
|
+
###########################################
|
|
341
|
+
# SSH KEYS
|
|
342
|
+
###########################################
|
|
343
|
+
|
|
344
|
+
log "${BOLD}SSH${NC}"
|
|
345
|
+
SSH_FOUND=0
|
|
346
|
+
|
|
347
|
+
if [ -d "$HOME/.ssh" ]; then
|
|
348
|
+
# Count private keys (files without .pub extension that aren't config/known_hosts)
|
|
349
|
+
PRIVATE_KEYS=$(find "$HOME/.ssh" -type f ! -name "*.pub" ! -name "known_hosts*" ! -name "config" ! -name "authorized_keys" 2>/dev/null | wc -l | tr -d ' ')
|
|
350
|
+
if [ "$PRIVATE_KEYS" -gt 0 ]; then
|
|
351
|
+
log " ${RED}[FOUND]${NC} $PRIVATE_KEYS SSH private key(s) in ~/.ssh/"
|
|
352
|
+
log_found "SSH" "~/.ssh/" "ssh-keygen -t ed25519, update public keys on all services"
|
|
353
|
+
SSH_FOUND=1
|
|
354
|
+
|
|
355
|
+
# List the keys
|
|
356
|
+
if [ "$VERBOSE" = true ]; then
|
|
357
|
+
find "$HOME/.ssh" -type f ! -name "*.pub" ! -name "known_hosts*" ! -name "config" ! -name "authorized_keys" 2>/dev/null | while read -r key; do
|
|
358
|
+
log " - $(basename "$key")"
|
|
359
|
+
done
|
|
360
|
+
fi
|
|
361
|
+
else
|
|
362
|
+
log_verbose " ${DIM}[SKIP]${NC} No SSH private keys found"
|
|
363
|
+
fi
|
|
364
|
+
else
|
|
365
|
+
log_verbose " ${DIM}[SKIP]${NC} ~/.ssh/ not found"
|
|
366
|
+
fi
|
|
367
|
+
|
|
368
|
+
if [ "$SSH_FOUND" -eq 0 ]; then
|
|
369
|
+
log " ${DIM}None found${NC}"
|
|
370
|
+
fi
|
|
371
|
+
|
|
372
|
+
log ""
|
|
373
|
+
|
|
374
|
+
###########################################
|
|
375
|
+
# DOCKER CREDENTIALS
|
|
376
|
+
###########################################
|
|
377
|
+
|
|
378
|
+
log "${BOLD}Docker${NC}"
|
|
379
|
+
DOCKER_FOUND=0
|
|
380
|
+
|
|
381
|
+
if [ -f "$HOME/.docker/config.json" ]; then
|
|
382
|
+
if grep -q "auth" "$HOME/.docker/config.json" 2>/dev/null; then
|
|
383
|
+
log " ${RED}[FOUND]${NC} ~/.docker/config.json contains auth"
|
|
384
|
+
log_found "Docker" "~/.docker/config.json" "docker logout && docker login"
|
|
385
|
+
DOCKER_FOUND=1
|
|
386
|
+
else
|
|
387
|
+
log_verbose " ${GREEN}[CLEAN]${NC} ~/.docker/config.json exists (no auth)"
|
|
388
|
+
fi
|
|
389
|
+
else
|
|
390
|
+
log_verbose " ${DIM}[SKIP]${NC} ~/.docker/config.json not found"
|
|
391
|
+
fi
|
|
392
|
+
|
|
393
|
+
if [ "$DOCKER_FOUND" -eq 0 ]; then
|
|
394
|
+
log " ${DIM}None found${NC}"
|
|
395
|
+
fi
|
|
396
|
+
|
|
397
|
+
log ""
|
|
398
|
+
|
|
399
|
+
###########################################
|
|
400
|
+
# KUBERNETES CREDENTIALS
|
|
401
|
+
###########################################
|
|
402
|
+
|
|
403
|
+
log "${BOLD}Kubernetes${NC}"
|
|
404
|
+
K8S_FOUND=0
|
|
405
|
+
|
|
406
|
+
if [ -f "$HOME/.kube/config" ]; then
|
|
407
|
+
log " ${RED}[FOUND]${NC} ~/.kube/config"
|
|
408
|
+
log_found "Kubernetes" "~/.kube/config" "Rotate cluster credentials, re-run az aks get-credentials or equivalent"
|
|
409
|
+
K8S_FOUND=1
|
|
410
|
+
else
|
|
411
|
+
log_verbose " ${DIM}[SKIP]${NC} ~/.kube/config not found"
|
|
412
|
+
fi
|
|
413
|
+
|
|
414
|
+
if [ "$K8S_FOUND" -eq 0 ]; then
|
|
415
|
+
log " ${DIM}None found${NC}"
|
|
416
|
+
fi
|
|
417
|
+
|
|
418
|
+
log ""
|
|
419
|
+
|
|
420
|
+
###########################################
|
|
421
|
+
# SENSITIVE ENVIRONMENT VARIABLES
|
|
422
|
+
###########################################
|
|
423
|
+
|
|
424
|
+
log "${BOLD}Environment Variables${NC}"
|
|
425
|
+
|
|
426
|
+
# Count sensitive env vars (excluding ones we already checked)
|
|
427
|
+
SENSITIVE_PATTERNS="token|secret|password|credential|api.?key|auth"
|
|
428
|
+
EXCLUDED_VARS="NPM_TOKEN|NPM_CONFIG_TOKEN|AWS_ACCESS_KEY_ID|AWS_SECRET_ACCESS_KEY|AWS_SESSION_TOKEN|GITHUB_TOKEN|GH_TOKEN|GOOGLE_APPLICATION_CREDENTIALS|AZURE_CLIENT_SECRET"
|
|
429
|
+
SENSITIVE_VARS=$(env | grep -iE "$SENSITIVE_PATTERNS" | grep -vE "^($EXCLUDED_VARS)=" 2>/dev/null || true)
|
|
430
|
+
SENSITIVE_COUNT=$(echo "$SENSITIVE_VARS" | grep -c . 2>/dev/null || echo 0)
|
|
431
|
+
|
|
432
|
+
if [ "$SENSITIVE_COUNT" -gt 0 ]; then
|
|
433
|
+
log " ${YELLOW}[FOUND]${NC} $SENSITIVE_COUNT additional sensitive env var(s)"
|
|
434
|
+
log_found "Environment" "shell environment" "Check ~/.zshrc, ~/.bashrc, or shell profile for secrets"
|
|
435
|
+
|
|
436
|
+
if [ "$VERBOSE" = true ]; then
|
|
437
|
+
echo "$SENSITIVE_VARS" | while read -r line; do
|
|
438
|
+
VAR_NAME=$(echo "$line" | cut -d= -f1)
|
|
439
|
+
log " - \$$VAR_NAME"
|
|
440
|
+
done
|
|
441
|
+
fi
|
|
442
|
+
else
|
|
443
|
+
log " ${DIM}None found${NC}"
|
|
444
|
+
fi
|
|
445
|
+
|
|
446
|
+
log ""
|
|
447
|
+
|
|
448
|
+
###########################################
|
|
449
|
+
# SUMMARY
|
|
450
|
+
###########################################
|
|
451
|
+
|
|
452
|
+
log "${BOLD}========================================${NC}"
|
|
453
|
+
|
|
454
|
+
if [ "$TOTAL_FOUND" -gt 0 ]; then
|
|
455
|
+
log "${RED}${BOLD} CREDENTIALS FOUND: $TOTAL_FOUND${NC}"
|
|
456
|
+
log "${BOLD}========================================${NC}"
|
|
457
|
+
log ""
|
|
458
|
+
log "${BOLD}Rotation Instructions:${NC}"
|
|
459
|
+
log ""
|
|
460
|
+
|
|
461
|
+
# Group by service
|
|
462
|
+
CURRENT_SERVICE=""
|
|
463
|
+
for entry in "${ROTATION_INSTRUCTIONS[@]}"; do
|
|
464
|
+
SERVICE=$(echo "$entry" | cut -d'|' -f1)
|
|
465
|
+
LOCATION=$(echo "$entry" | cut -d'|' -f2)
|
|
466
|
+
INSTRUCTION=$(echo "$entry" | cut -d'|' -f3)
|
|
467
|
+
|
|
468
|
+
if [ "$SERVICE" != "$CURRENT_SERVICE" ]; then
|
|
469
|
+
log "${CYAN}$SERVICE:${NC}"
|
|
470
|
+
CURRENT_SERVICE="$SERVICE"
|
|
471
|
+
fi
|
|
472
|
+
log " ${DIM}$LOCATION${NC}"
|
|
473
|
+
log " ${BOLD}$INSTRUCTION${NC}"
|
|
474
|
+
log ""
|
|
475
|
+
done
|
|
476
|
+
|
|
477
|
+
log "${YELLOW}${BOLD}IMPORTANT:${NC} If your machine was compromised, assume ALL of these"
|
|
478
|
+
log "credentials were exfiltrated. Rotate them immediately."
|
|
479
|
+
log ""
|
|
480
|
+
log "${DIM}Note: This list is not exhaustive. You may need to rotate other"
|
|
481
|
+
log "credentials not detected by this scan (e.g., database passwords,"
|
|
482
|
+
log "API keys in config files, or service-specific tokens).${NC}"
|
|
483
|
+
log ""
|
|
484
|
+
log "See: ${CYAN}https://department-of-veterans-affairs.github.io/eert/${NC}"
|
|
485
|
+
|
|
486
|
+
EXIT_CODE=1
|
|
487
|
+
else
|
|
488
|
+
log "${GREEN}${BOLD} NO CREDENTIALS FOUND${NC}"
|
|
489
|
+
log "${BOLD}========================================${NC}"
|
|
490
|
+
log ""
|
|
491
|
+
log "No credential files or environment variables were detected."
|
|
492
|
+
log "This machine has minimal credential exposure risk."
|
|
493
|
+
|
|
494
|
+
EXIT_CODE=0
|
|
495
|
+
fi
|
|
496
|
+
|
|
497
|
+
log ""
|
|
498
|
+
|
|
499
|
+
###########################################
|
|
500
|
+
# JSON OUTPUT
|
|
501
|
+
###########################################
|
|
502
|
+
|
|
503
|
+
if [ "$JSON" = true ]; then
|
|
504
|
+
echo "{"
|
|
505
|
+
echo " \"status\": \"$([ "$TOTAL_FOUND" -gt 0 ] && echo 'CREDENTIALS_FOUND' || echo 'CLEAN')\","
|
|
506
|
+
echo " \"credentials_found\": $TOTAL_FOUND,"
|
|
507
|
+
echo " \"credentials\": ["
|
|
508
|
+
|
|
509
|
+
FIRST=true
|
|
510
|
+
for entry in "${ROTATION_INSTRUCTIONS[@]}"; do
|
|
511
|
+
SERVICE=$(echo "$entry" | cut -d'|' -f1)
|
|
512
|
+
LOCATION=$(echo "$entry" | cut -d'|' -f2)
|
|
513
|
+
INSTRUCTION=$(echo "$entry" | cut -d'|' -f3)
|
|
514
|
+
|
|
515
|
+
# Escape for JSON
|
|
516
|
+
LOCATION="${LOCATION//\\/\\\\}"
|
|
517
|
+
LOCATION="${LOCATION//\"/\\\"}"
|
|
518
|
+
INSTRUCTION="${INSTRUCTION//\\/\\\\}"
|
|
519
|
+
INSTRUCTION="${INSTRUCTION//\"/\\\"}"
|
|
520
|
+
|
|
521
|
+
if [ "$FIRST" = true ]; then
|
|
522
|
+
FIRST=false
|
|
523
|
+
else
|
|
524
|
+
echo ","
|
|
525
|
+
fi
|
|
526
|
+
printf " {\"service\": \"%s\", \"location\": \"%s\", \"rotation\": \"%s\"}" "$SERVICE" "$LOCATION" "$INSTRUCTION"
|
|
527
|
+
done
|
|
528
|
+
|
|
529
|
+
echo ""
|
|
530
|
+
echo " ],"
|
|
531
|
+
echo " \"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\""
|
|
532
|
+
echo "}"
|
|
533
|
+
fi
|
|
534
|
+
|
|
535
|
+
exit $EXIT_CODE
|