Add ability to define custom signatures

Gitrob will now look for a file at `~/.gitrobsignatures` which it
expects to be a JSON document that follows the same structure as the
main signatures.json file. If the signatures are valid, it will be
loaded into the BlobObserver.
This commit is contained in:
Michael Henriksen
2016-04-10 10:57:29 +02:00
parent 2d81520af9
commit 94cdc61fa4
6 changed files with 207 additions and 18 deletions

View File

@@ -3,6 +3,8 @@ All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased]
### Added
- Ability to define custom signatures in `~/.gitrobsignatures`
## [1.0.1]
### Fixed

View File

@@ -86,6 +86,41 @@ By default, the server will listen on [localhost:9393](http://localhost:9393). T
See `gitrob help server` for more options.
### Adding custom signatures
If you want to look for files that are specific to your organisation or projects, it is easy to add custom signatures.
When Gitrob starts it looks for a file at `~/.gitrobsignatures` which it expects to be a JSON document with signatures that follow the same structure as the main [signatures.json](signatures.json) file. Here is an example:
[
{
"part": "filename",
"type": "match",
"pattern": "otr.private_key",
"caption": "Pidgin OTR private key",
"description": null
}
]
This signature instructs Gitrob to flag files where the filename exactly matches `otr.private_key`. The caption and description are used in the web interface when displaying the findings.
#### Signature keys
* `part`: Can be one of:
* `path`: The complete file path
* `filename`: Only the filename
* `extension`: Only the file extension
* `type`: Can be one of:
* `match`: Simple match of part and pattern
* `regex`: Regular expression matching of part and pattern
* `pattern`: The value or regular expression to match with
* `caption`: A short description of the finding
* `description`: More detailed description if needed (set to `null` if not).
Have a look at the main [signatures.json](signatures.json) file for more examples of signatures.
**If you think other people can benefit from your custom signatures, please consider contributing them back to the Gitrob project by opening a Pull Request or an Issue. Thanks!**
## Development
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. Run `bundle exec gitrob` to use the gem in this directory, ignoring other installed copies of this gem.

View File

@@ -2,6 +2,8 @@ module Gitrob
class BlobObserver
SIGNATURES_FILE_PATH = File.expand_path(
"../../../signatures.json", __FILE__)
CUSTOM_SIGNATURES_FILE_PATH = File.join(
Dir.home, ".gitrobsignatures")
REQUIRED_SIGNATURE_KEYS = %w(part type pattern caption description)
ALLOWED_TYPES = %w(regex match)
@@ -30,23 +32,49 @@ module Gitrob
def self.load_signatures!
@signatures = []
JSON.load(File.read(SIGNATURES_FILE_PATH)).each do |signature|
signatures = JSON.load(File.read(SIGNATURES_FILE_PATH))
validate_signatures!(signatures)
signatures.each_with_index do |signature|
@signatures << Signature.new(signature)
end
validate_signatures!
rescue CorruptSignaturesError => e
raise e
rescue
raise CorruptSignaturesError, "Could not parse signature file"
end
def self.validate_signatures!
def self.unload_signatures
@signatures = []
end
def self.custom_signatures?
File.exist?(CUSTOM_SIGNATURES_FILE_PATH)
end
def self.load_custom_signatures!
signatures = JSON.load(File.read(CUSTOM_SIGNATURES_FILE_PATH))
validate_signatures!(signatures)
signatures.each do |signature|
@signatures << Signature.new(signature)
end
rescue CorruptSignaturesError => e
raise e
rescue
raise CorruptSignaturesError, "Could not parse signature file"
end
def self.validate_signatures!(signatures)
if !signatures.is_a?(Array) || signatures.empty?
fail CorruptSignaturesError,
"Signature file contains no signatures"
end
signatures.each do |signature|
validate_signature!(signature)
signatures.each_with_index do |signature, index|
begin
validate_signature!(signature)
rescue CorruptSignaturesError => e
raise CorruptSignaturesError,
"Validation failed for Signature ##{index + 1}: #{e.message}"
end
end
end
@@ -58,7 +86,7 @@ module Gitrob
def self.validate_signature_keys!(signature)
REQUIRED_SIGNATURE_KEYS.each do |key|
unless signature.respond_to?(key)
unless signature.key?(key)
fail CorruptSignaturesError,
"Missing required signature key: #{key}"
end
@@ -66,16 +94,16 @@ module Gitrob
end
def self.validate_signature_type!(signature)
unless ALLOWED_TYPES.include?(signature.type)
unless ALLOWED_TYPES.include?(signature["type"])
fail CorruptSignaturesError,
"Invalid signature type: #{signature.type}"
"Invalid signature type: #{signature['type']}"
end
end
def self.validate_signature_part!(signature)
unless ALLOWED_PARTS.include?(signature.part)
unless ALLOWED_PARTS.include?(signature["part"])
fail CorruptSignaturesError,
"Invalid signature part: #{signature.part}"
"Invalid signature part: #{signature['part']}"
end
end

View File

@@ -40,6 +40,15 @@ module Gitrob
task("Loading signatures...", true) do
Gitrob::BlobObserver.load_signatures!
end
if Gitrob::BlobObserver.custom_signatures?
task("Loading custom signatures...", true) do
Gitrob::BlobObserver.load_custom_signatures!
end
info("Please consider contributing your custom signatures to the " \
"Gitrob project.")
end
info("Loaded #{Gitrob::BlobObserver.signatures.count} signatures")
end
def start_web_server

View File

@@ -1248,6 +1248,70 @@ describe Gitrob::BlobObserver do
File.expand_path("../../../../signatures.json", __FILE__)
end
let(:custom_signatures_file_path) do
File.join(Dir.home, ".gitrobsignatures")
end
context "when custom signatures file is present" do
it "loads custom signatures" do
described_class.unload_signatures
allow(described_class).to receive(:custom_signatures?)
.and_return(true)
expect(File).to receive(:read)
.with(custom_signatures_file_path)
.and_return('
[
{
"part": "filename",
"type": "match",
"pattern": "test",
"caption": "Test signature",
"description": "This is a test signature"
}
]
')
described_class.load_custom_signatures!
expect(described_class.signatures.count).to eq(1)
signature = described_class.signatures.first
expect(signature).to be_a(Gitrob::BlobObserver::Signature)
expect(signature.part).to eq("filename")
expect(signature.type).to eq("match")
expect(signature.pattern).to eq("test")
expect(signature.caption).to eq("Test signature")
expect(signature.description).to eq("This is a test signature")
end
it "validates custom signatures" do
described_class.unload_signatures
allow(described_class).to receive(:custom_signatures?)
.and_return(true)
allow(File).to receive(:read)
.with(custom_signatures_file_path)
.and_return('
[
{
"part": "filename",
"type": "match",
"pattern": "test",
"caption": "Test signature",
"description": "This is a test signature"
}
]
')
expect(described_class).to receive(:validate_signatures!)
.with([
{
"part" => "filename",
"type" => "match",
"pattern" => "test",
"caption" => "Test signature",
"description" => "This is a test signature"
}
])
described_class.load_custom_signatures!
end
end
context "when Signature file is empty" do
it "raises CorruptSignaturesError" do
expect(File).to receive(:read)
@@ -1258,7 +1322,7 @@ describe Gitrob::BlobObserver do
end
.to raise_error(
Gitrob::BlobObserver::CorruptSignaturesError,
"Could not parse signature file"
"Signature file contains no signatures"
)
end
end
@@ -1312,7 +1376,7 @@ describe Gitrob::BlobObserver do
end
.to raise_error(
Gitrob::BlobObserver::CorruptSignaturesError,
"Missing required signature key: part"
"Validation failed for Signature #1: Missing required signature key: part"
)
end
end
@@ -1336,7 +1400,7 @@ describe Gitrob::BlobObserver do
end
.to raise_error(
Gitrob::BlobObserver::CorruptSignaturesError,
"Missing required signature key: type"
"Validation failed for Signature #1: Missing required signature key: type"
)
end
end
@@ -1360,7 +1424,7 @@ describe Gitrob::BlobObserver do
end
.to raise_error(
Gitrob::BlobObserver::CorruptSignaturesError,
"Missing required signature key: pattern"
"Validation failed for Signature #1: Missing required signature key: pattern"
)
end
end
@@ -1384,7 +1448,7 @@ describe Gitrob::BlobObserver do
end
.to raise_error(
Gitrob::BlobObserver::CorruptSignaturesError,
"Missing required signature key: caption"
"Validation failed for Signature #1: Missing required signature key: caption"
)
end
end
@@ -1408,7 +1472,7 @@ describe Gitrob::BlobObserver do
end
.to raise_error(
Gitrob::BlobObserver::CorruptSignaturesError,
"Missing required signature key: description"
"Validation failed for Signature #1: Missing required signature key: description"
)
end
end
@@ -1433,7 +1497,7 @@ describe Gitrob::BlobObserver do
end
.to raise_error(
Gitrob::BlobObserver::CorruptSignaturesError,
"Invalid signature part: what"
"Validation failed for Signature #1: Invalid signature part: what"
)
end
end
@@ -1458,7 +1522,7 @@ describe Gitrob::BlobObserver do
end
.to raise_error(
Gitrob::BlobObserver::CorruptSignaturesError,
"Invalid signature type: what"
"Validation failed for Signature #1: Invalid signature type: what"
)
end
end

View File

@@ -32,6 +32,10 @@ describe Gitrob::CLI::Commands::Analyze do
deadbabedeadbabedeadbabedeadbabedeadbabe
)
)
allow(Gitrob::BlobObserver).to receive(:custom_signatures?)
.and_return(false)
allow(Gitrob::BlobObserver).to receive(:signatures)
.and_return([])
expect_any_instance_of(described_class)
.to receive(:task)
@@ -41,6 +45,53 @@ describe Gitrob::CLI::Commands::Analyze do
described_class.new(target, options)
end
context "When custom signatures are present" do
it "loads custom signtures" do
stub_db_assessment = spy
allow(stub_db_assessment)
.to receive(:save)
allow_any_instance_of(described_class)
.to receive(:gather_owners)
allow_any_instance_of(described_class)
.to receive(:gather_repositories)
allow(Gitrob::Models::Assessment)
.to receive(:create)
.and_return(stub_db_assessment)
allow_any_instance_of(described_class)
.to receive(:analyze_repositories)
allow(Gitrob::CLI)
.to receive(:configuration)
.and_return(
"github_access_tokens" => %w(
deadbeefdeadbeefdeadbeefdeadbeefdeadbeef
deadbabedeadbabedeadbabedeadbabedeadbabe
)
)
allow(Gitrob::BlobObserver)
.to receive(:custom_signatures?)
.and_return(true)
allow(Gitrob::BlobObserver).to receive(:signatures)
.and_return([])
allow_any_instance_of(described_class)
.to receive(:task)
.with("Loading signatures...", true)
.and_yield
expect_any_instance_of(described_class)
.to receive(:task)
.with("Loading custom signatures...", true)
.and_yield
expect(Gitrob::BlobObserver).to receive(:load_custom_signatures!)
expect_any_instance_of(described_class)
.to receive(:info)
.with("Please consider contributing your custom signatures to the Gitrob project.")
expect_any_instance_of(described_class)
.to receive(:info)
.with("Loaded 0 signatures")
described_class.new(target, options)
end
end
it "gathers owners" do
stub_db_assessment = spy
allow(stub_db_assessment)