From 3d9ac0135d8b61983ab9b3f9dbe0484658aeb907 Mon Sep 17 00:00:00 2001 From: Manabu Niseki Date: Sun, 14 Jan 2024 16:29:39 +0900 Subject: [PATCH] feat: ensure emitters have "database" at least --- lib/mihari/schemas/rule.rb | 7 ++ spec/factories/rules.rb | 2 +- spec/schemas/rule_spec.rb | 191 ++++++++++++++++++------------------- 3 files changed, 99 insertions(+), 101 deletions(-) diff --git a/lib/mihari/schemas/rule.rb b/lib/mihari/schemas/rule.rb index a683b5fcd..1fc912a97 100644 --- a/lib/mihari/schemas/rule.rb +++ b/lib/mihari/schemas/rule.rb @@ -45,6 +45,13 @@ class RuleContract < Dry::Validation::Contract key.failure("#{v} is not a valid format.") unless valid_falsepositive?(v) end end + + rule(:emitters) do + emitters = value.filter_map { |v| v[:emitter] } + unless emitters.include?(Mihari::Emitters::Database.key) + key.failure("Emitter:#{Mihari::Emitters::Database.key} should be included in emitters") + end + end end end end diff --git a/spec/factories/rules.rb b/spec/factories/rules.rb index 8f46fc090..530fc6638 100644 --- a/spec/factories/rules.rb +++ b/spec/factories/rules.rb @@ -13,7 +13,7 @@ description: description, queries: [], tags: [], - emitters: [] + emitters: [{ emitter: "database" }] } end diff --git a/spec/schemas/rule_spec.rb b/spec/schemas/rule_spec.rb index f7820262a..7ee0e9b8c 100644 --- a/spec/schemas/rule_spec.rb +++ b/spec/schemas/rule_spec.rb @@ -6,15 +6,29 @@ let!(:description) { "test" } let!(:title) { "test" } + let(:queries) { [] } + let(:emitters) { nil } + let(:enrichers) { nil } + let(:falsepositives) { nil } + let(:data_types) { nil } + let(:artifact_ttl) { nil } + let(:data) do + { + id: id, + description: description, + title: title, + queries: queries, + emitters: emitters, + enrichers: enrichers, + falsepositives: falsepositives, + data_types: data_types, + artifact_ttl: artifact_ttl + }.compact + end + context "with valid rule" do it "has default values" do - result = contract.call( - id: id, - description: description, - title: title, - queries: [{ analyzer: "shodan", query: "foo" }] - ) - + result = contract.call(**data) expect(result[:enrichers].length).to eq Mihari::DEFAULT_ENRICHERS.length expect(result[:emitters].length).to eq Mihari::DEFAULT_EMITTERS.length expect(result[:data_types].length).to eq Mihari::DEFAULT_DATA_TYPES.length @@ -22,151 +36,128 @@ end context "with analyzers don't need additional options" do - it do + let(:queries) do analyzers = Mihari.analyzer_to_class.keys - %w[zoomeye crtsh feed hunterhow] - - analyzers.each do |analyzer| - result = contract.call( - id: id, - description: description, - title: title, - queries: [{ analyzer: analyzer, query: "foo" }] - ) - expect(result.errors.empty?).to eq true + analyzers.map do |analyzer| + { analyzer: analyzer, query: "foo" } end end + + it do + result = contract.call(**data) + expect(result.errors.empty?).to eq true + end end context "with analyzers need additional options" do + let(:queries) do + [ + { analyzer: "crtsh", query: "foo", exclude_expired: true }, + { analyzer: "zoomeye", query: "foo", type: "host" }, + { analyzer: "zoomeye", query: "foo", type: "host", options: { interval: 10 } } + ] + end + it do - result = contract.call( - id: id, - description: description, - title: title, - queries: [ - { analyzer: "crtsh", query: "foo", exclude_expired: true }, - { analyzer: "zoomeye", query: "foo", type: "host" }, - { analyzer: "zoomeye", query: "foo", type: "host", options: { interval: 10 } } - ] - ) + result = contract.call(**data) expect(result.errors.empty?).to be true end end context "with allowed_data_types" do + let(:data_types) { ["ip"] } + it do - result = contract.call( - id: id, - description: description, - title: title, - queries: [ - { analyzer: "shodan", query: "foo" } - ], - data_types: ["ip"] - ) + result = contract.call(**data) expect(result.errors.empty?).to be true end end end context "with invalid analyzer name" do + let(:queries) { [{ analyzer: "foo", query: "foo" }] } + it do - result = contract.call( - id: id, - description: description, - title: title, - queries: [{ analyzer: "foo", query: "foo" }] - ) + result = contract.call(**data) expect(result.errors.empty?).to be false end + end + + context "with invalid options" do + let(:queries) do + [ + { analyzer: "shodan", query: "foo" }, + { analyzer: "crtsh", query: "foo", exclude_expired: 1 }, # should be bool + { analyzer: "zoomeye", query: "foo", type: "bar" } # should be any of host or web + ] + end it do - result = contract.call( - id: id, - description: description, - title: title, - queries: [ - { analyzer: "shodan", query: "foo" }, - { analyzer: "crtsh", query: "foo", exclude_expired: 1 }, # should be bool - { analyzer: "zoomeye", query: "foo", type: "bar" } # should be any of host or web - ] - ) + result = contract.call(**data) expect(result.errors.empty?).to be false end end context "with invalid data types" do + let(:data_types) do + # should be any of ip, domain, mail, url or hash + ["foo"] + end + it do - result = contract.call( - id: id, - description: description, - title: title, - queries: [ - { analyzer: "shodan", query: "foo" } - ], - data_types: ["foo"] # should be any of ip, domain, mail, url or hash - ) + result = contract.call(**data) expect(result.errors.empty?).to be false end end - context "with invalid disallowed data values" do + context "with non-string falsepositives values" do + let(:falsepositives) do + # should be string + [1] + end + it do - result = contract.call( - id: id, - description: description, - title: title, - queries: [ - { analyzer: "shodan", query: "foo" } - ], - falsepositives: [1] # should be string - ) + result = contract.call(**data) expect(result.errors.empty?).to be false end + end + + context "with non-string falsepositives values" do + let(:falsepositives) do + # /*/ is not compilable as a regexp + ["/*/"] + end it do - result = contract.call( - id: id, - description: description, - title: title, - queries: [ - { analyzer: "shodan", query: "foo" } - ], - falsepositives: ["/*/"] - ) + result = contract.call(**data) expect(result.errors.empty?).to be false end end context "with invalid artifact_ttl" do + let(:artifact_ttl) { "foo " } + it do - result = contract.call( - id: id, - description: description, - title: title, - queries: [ - { analyzer: "shodan", query: "foo" } - ], - artifact_ttl: "foo" # should be an integer - ) + result = contract.call(**data) expect(result.errors.empty?).to be false end end context "with invalid emitter name" do + let(:emitters) { [{ emitter: "foo" }] } + it do - result = contract.call( - id: id, - description: description, - title: title, - queries: [ - { analyzer: "shodan", query: "foo" } - ], - emitters: [ - { emitter: "foo" } - ] - ) + result = contract.call(**data) expect(result.errors.empty?).to be false end end + + context "without having database emitter" do + let(:emitters) { [{ emitter: "misp" }] } + + it do + result = contract.call(**data) + expect(result.errors.to_h).to eq({ emitters: ["Emitter:database should be included in emitters"] }) + end + end end