Skip to content

Commit f65ef2e

Browse files
authored
feat(scan): add vtk scan credentials for security incident response (#68)
* feat(scan): add vtk scan credentials for security incident response Adds a credential audit command that checks for credentials present on the machine and provides rotation instructions. Useful after any security incident. Checks: - NPM: ~/.npmrc, $NPM_TOKEN, $NPM_CONFIG_TOKEN - AWS: ~/.aws/credentials, ~/.aws/config, env vars - GCP: ADC, $GOOGLE_APPLICATION_CREDENTIALS - Azure: ~/.azure/, $AZURE_CLIENT_SECRET - GitHub: ~/.config/gh/hosts.yml, $GITHUB_TOKEN, $GH_TOKEN - SSH: Private keys in ~/.ssh/ - Docker: ~/.docker/config.json - Kubernetes: ~/.kube/config - Environment: Sensitive env vars Exit codes: 0 = no credentials, 1 = credentials found Closes: va.ghe.com/software/eert/issues/95 * fix: move constant above private to fix RuboCop warning
1 parent 9e337c3 commit f65ef2e

6 files changed

Lines changed: 703 additions & 3 deletions

File tree

README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,41 @@ $ vtk scan machine --quiet && echo "Clean" || echo "Check machine!"
100100

101101
---
102102

103+
```
104+
$ vtk scan credentials
105+
```
106+
107+
The **credentials subcommand** audits which credentials are present on your machine and provides rotation instructions for each. Run this after a suspected or confirmed security incident.
108+
109+
**What it checks:**
110+
- **NPM**: `~/.npmrc`, `$NPM_TOKEN`, `$NPM_CONFIG_TOKEN`
111+
- **AWS**: `~/.aws/credentials`, `~/.aws/config`, `$AWS_ACCESS_KEY_ID`, `$AWS_SECRET_ACCESS_KEY`
112+
- **GCP**: `~/.config/gcloud/application_default_credentials.json`, `$GOOGLE_APPLICATION_CREDENTIALS`
113+
- **Azure**: `~/.azure/` directory, `$AZURE_CLIENT_SECRET`
114+
- **GitHub**: `~/.config/gh/hosts.yml`, `$GITHUB_TOKEN`, `$GH_TOKEN`, `~/.git-credentials`
115+
- **SSH**: Private keys in `~/.ssh/`
116+
- **Docker**: `~/.docker/config.json`
117+
- **Kubernetes**: `~/.kube/config`
118+
- **Environment**: Sensitive env vars (token, secret, password, etc.)
119+
120+
**Exit codes:**
121+
- `0` - No credentials found
122+
- `1` - Credentials found (rotation recommended)
123+
124+
**Options:**
125+
- `--verbose` / `-v` - Show all checks including clean ones
126+
- `--json` / `-j` - JSON output format
127+
128+
Example:
129+
```
130+
$ vtk scan credentials --json | jq -r '.credentials[].service' | sort -u
131+
AWS
132+
GitHub
133+
SSH
134+
```
135+
136+
---
137+
103138
### Help
104139

105140
For helpful information about commands and subcommands run the following:

lib/vtk/commands/scan.rb

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,22 @@ def repo(path = nil)
5454
end
5555
end
5656

57-
# Future subcommands:
58-
# desc 'repos', 'Scan all Node.js projects in common directories'
59-
# desc 'credentials', 'Inventory credentials that may need rotation'
57+
desc 'credentials', 'Audit credentials that may need rotation after a security incident'
58+
method_option :help, aliases: '-h', type: :boolean,
59+
desc: 'Display usage information'
60+
method_option :verbose, aliases: '-v', type: :boolean,
61+
desc: 'Show all checks including clean ones'
62+
method_option :json, aliases: '-j', type: :boolean,
63+
desc: 'Output results as JSON'
64+
def credentials
65+
if options[:help]
66+
invoke :help, ['credentials']
67+
else
68+
require_relative 'scan/credentials'
69+
exit_status = Vtk::Commands::Scan::Credentials.new(options).execute
70+
exit exit_status
71+
end
72+
end
6073
end
6174
end
6275
end
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# frozen_string_literal: true
2+
3+
require 'English'
4+
require_relative '../../command'
5+
6+
module Vtk
7+
module Commands
8+
class Scan
9+
# Audit credentials that may have been accessed by Shai-Hulud malware
10+
class Credentials < Vtk::Command
11+
attr_reader :options
12+
13+
def initialize(options)
14+
@options = options
15+
super()
16+
end
17+
18+
OPTION_FLAGS = {
19+
verbose: '--verbose',
20+
json: '--json'
21+
}.freeze
22+
23+
def execute(output: $stdout)
24+
@output = output
25+
26+
script_path, gem_root = find_script
27+
return script_not_found(output, gem_root) unless script_path
28+
29+
run_script(script_path)
30+
end
31+
32+
private
33+
34+
def script_not_found(output, gem_root)
35+
output.puts 'ERROR: Could not find credential-audit.sh script'
36+
output.puts "Expected at: #{gem_root}/scripts/credential-audit.sh"
37+
1
38+
end
39+
40+
def run_script(script_path)
41+
cmd = ['bash', script_path]
42+
cmd += OPTION_FLAGS.filter_map { |key, flag| flag if options[key] }
43+
44+
system(*cmd)
45+
$CHILD_STATUS.exitstatus
46+
end
47+
48+
def find_script
49+
gem_root = File.expand_path('../../../..', __dir__)
50+
script_path = File.join(gem_root, 'scripts', 'credential-audit.sh')
51+
52+
return [script_path, gem_root] if File.exist?(script_path)
53+
54+
[nil, gem_root]
55+
end
56+
end
57+
end
58+
end
59+
end

0 commit comments

Comments
 (0)