Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add list-transform feature #987

Merged
merged 1 commit into from
Jan 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 38 additions & 1 deletion docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,44 @@ Searchable fields are
**Examples**

```bash
mihari rule list "description:foo OR title:bar"
mihari alert list "rule.id:foo"
mihari artifact list "rule.id: foo AND data_type:ip"
mihari rule list "description:foo OR title:bar"
```

### Search With Transformation

Additionally you can search rules, alerts and artifacts with transformation by using [Jbuilder](https://github.com/rails/jbuilder).

```bash
mihari rule list-transform -t /path/to/json.jbuilder
mihari alert list-transform -t /path/to/json.jbuilder
mihari artifact list-transform -t /path/to/json.jbuilder
```

For example, you can combine IP addresses and ports by using the following template.

**ip_port.json.jbuilder**

```ruby
artifacts = results.map(&:artifacts).flatten

ip_ports = artifacts.map do |artifact|
artifact.ports.map do |port|
"#{artifact.data}:#{port.port}"
end
end.flatten

json.array! ip_ports
```

```bash
mihari artifact list-transform -t test.json.jbuilder
```

A template can use the following attributes.

- `results`: a list of search results (= `Array[Mihari::Models::Rule]`, `Array[Mihari::Models::Alert]`, `Array[Mihari::Models::Artifact]` or `Array[Mihari::Models::Tag]`)
- `total`: a total number of search results
- `page_size` a page size
- `current_page` a current page number
1 change: 1 addition & 0 deletions lib/mihari.rb
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ def initialize_sentry
require "mihari/services/getters"
require "mihari/services/initializers"
require "mihari/services/proxies"
require "mihari/services/renderer"
require "mihari/services/searchers"

# Entities
Expand Down
3 changes: 3 additions & 0 deletions lib/mihari/cli/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ def safe_execute
rescue StandardError => e
error = unwrap_error(e)

# Raise error if it's a Thor::Error to follow Thor's manner
raise error if error.is_a?(Thor::Error)
# Raise error if debug is set as true
raise error if options["debug"]

data = Entities::ErrorMessage.represent(
Expand Down
39 changes: 37 additions & 2 deletions lib/mihari/commands/alert.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,20 @@ def included(thor)
thor.class_eval do
include Concerns::DatabaseConnectable

no_commands do
#
# @param [String] q
# @param [Integer] page
# @param [Integer] limit
#
# @return [Mihari::Services::ResultValue]
#
def _search(q, page: 1, limit: 10)
filter = Structs::Filters::Search.new(q: q, page: page, limit: limit)
Services::AlertSearcher.result(filter).value!
end
end

desc "create [PATH]", "Create an alert"
around :with_db_connection
#
Expand Down Expand Up @@ -40,8 +54,7 @@ def create(path)
# @param [String] q
#
def list(q = "")
filter = Structs::Filters::Search.new(q: q, page: options["page"], limit: options["limit"])
value = Services::AlertSearcher.result(filter).value!
value = _search(q, page: options["page"], limit: options["limit"])
data = Entities::AlertsWithPagination.represent(
results: value.results,
total: value.total,
Expand All @@ -51,6 +64,28 @@ def list(q = "")
puts JSON.pretty_generate(data.as_json)
end

desc "list-transform QUERY", "List/search alerts with transformation"
around :with_db_connection
method_option :template, type: :string, required: true, aliases: "-t",
description: "Jbuilder template itself or a path to a template file"
method_option :page, type: :numeric, default: 1
method_option :limit, type: :numeric, default: 10
#
# @param [String] q
#
def list_transform(q = "")
value = _search(q, page: options["page"], limit: options["limit"])
puts Services::JbuilderRenderer.call(
options["template"],
{
results: value.results,
total: value.total,
current_page: value.filter[:page].to_i,
page_size: value.filter[:limit].to_i
}
)
end

desc "get [ID]", "Get an alert"
around :with_db_connection
#
Expand Down
39 changes: 37 additions & 2 deletions lib/mihari/commands/artifact.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,20 @@ def included(thor)
thor.class_eval do
include Concerns::DatabaseConnectable

no_commands do
#
# @param [String] q
# @param [Integer] page
# @param [Integer] limit
#
# @return [Mihari::Services::ResultValue]
#
def _search(q, page: 1, limit: 10)
filter = Structs::Filters::Search.new(q: q, page: page, limit: limit)
Services::ArtifactSearcher.result(filter).value!
end
end

desc "list [QUERY]", "List/search artifacts"
around :with_db_connection
method_option :page, type: :numeric, default: 1
Expand All @@ -19,8 +33,7 @@ def included(thor)
# @param [String] q
#
def list(q = "")
filter = Structs::Filters::Search.new(q: q, page: options["page"], limit: options["limit"])
value = Services::ArtifactSearcher.result(filter).value!
value = _search(q, page: options["page"], limit: options["limit"])
data = Entities::ArtifactsWithPagination.represent(
results: value.results,
total: value.total,
Expand All @@ -30,6 +43,28 @@ def list(q = "")
puts JSON.pretty_generate(data.as_json)
end

desc "list-transform QUERY", "List/search artifacts with transformation"
around :with_db_connection
method_option :template, type: :string, required: true, aliases: "-t",
description: "Jbuilder template itself or a path to a template file"
method_option :page, type: :numeric, default: 1
method_option :limit, type: :numeric, default: 10
#
# @param [String] q
#
def list_transform(q = "")
value = _search(q, page: options["page"], limit: options["limit"])
puts Services::JbuilderRenderer.call(
options["template"],
{
results: value.results,
total: value.total,
current_page: value.filter[:page].to_i,
page_size: value.filter[:limit].to_i
}
)
end

desc "get [ID]", "Get an artifact"
around :with_db_connection
#
Expand Down
2 changes: 1 addition & 1 deletion lib/mihari/commands/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ module Config
class << self
def included(thor)
thor.class_eval do
desc "list", "List config"
desc "list", "List configs"
def list
configs = Services::ConfigSearcher.call
data = configs.map { |config| Entities::Config.represent(config) }
Expand Down
39 changes: 37 additions & 2 deletions lib/mihari/commands/rule.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,20 @@ def included(thor)
thor.class_eval do
include Concerns::DatabaseConnectable

no_commands do
#
# @param [String] q
# @param [Integer] page
# @param [Integer] limit
#
# @return [Mihari::Services::ResultValue]
#
def _search(q, page: 1, limit: 10)
filter = Structs::Filters::Search.new(q: q, page: page, limit: limit)
Services::RuleSearcher.result(filter).value!
end
end

desc "validate [PATH]", "Validate a rule file"
#
# Validate format of a rule
Expand Down Expand Up @@ -44,8 +58,7 @@ def init(path = "./rule.yml")
# @param [String] q
#
def list(q = "")
filter = Structs::Filters::Search.new(q: q, page: options["page"], limit: options["limit"])
value = Services::RuleSearcher.result(filter).value!
value = _search(q, page: options["page"], limit: options["limit"])
data = Entities::RulesWithPagination.represent(
results: value.results,
total: value.total,
Expand All @@ -55,6 +68,28 @@ def list(q = "")
puts JSON.pretty_generate(data.as_json)
end

desc "list-transform QUERY", "List/search rules with transformation"
around :with_db_connection
method_option :template, type: :string, required: true, aliases: "-t",
description: "Jbuilder template itself or a path to a template file"
method_option :page, type: :numeric, default: 1
method_option :limit, type: :numeric, default: 10
#
# @param [String] q
#
def list_transform(q = "")
value = _search(q, page: options["page"], limit: options["limit"])
puts Services::JbuilderRenderer.call(
options["template"],
{
results: value.results,
total: value.total,
current_page: value.filter[:page].to_i,
page_size: value.filter[:limit].to_i
}
)
end

desc "get [ID]", "Get a rule"
around :with_db_connection
def get(id)
Expand Down
39 changes: 37 additions & 2 deletions lib/mihari/commands/tag.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,20 @@ def included(thor)
thor.class_eval do
include Concerns::DatabaseConnectable

no_commands do
#
# @param [String] q
# @param [Integer] page
# @param [Integer] limit
#
# @return [Mihari::Services::ResultValue]
#
def _search(q, page: 1, limit: 10)
filter = Structs::Filters::Search.new(q: q, page: page, limit: limit)
Services::TagSearcher.result(filter).value!
end
end

desc "list", "List/search tags"
around :with_db_connection
method_option :page, type: :numeric, default: 1
Expand All @@ -19,8 +33,7 @@ def included(thor)
# @param [String] q
#
def list(q = "")
filter = Structs::Filters::Search.new(q: q, page: options["page"], limit: options["limit"])
value = Services::TagSearcher.result(filter).value!
value = _search(q, page: options["page"], limit: options["limit"])
data = Entities::TagsWithPagination.represent(
results: value.results,
total: value.total,
Expand All @@ -30,6 +43,28 @@ def list(q = "")
puts JSON.pretty_generate(data.as_json)
end

desc "list-transform QUERY", "List/search tags with transformation"
around :with_db_connection
method_option :template, type: :string, required: true, aliases: "-t",
description: "Jbuilder template itself or a path to a template file"
method_option :page, type: :numeric, default: 1
method_option :limit, type: :numeric, default: 10
#
# @param [String] q
#
def list_transform(q = "")
value = _search(q, page: options["page"], limit: options["limit"])
puts Services::JbuilderRenderer.call(
options["template"],
{
results: value.results,
total: value.total,
current_page: value.filter[:page].to_i,
page_size: value.filter[:limit].to_i
}
)
end

desc "delete [ID]", "Delete a tag"
around :with_db_connection
#
Expand Down
23 changes: 4 additions & 19 deletions lib/mihari/emitters/webhook.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# frozen_string_literal: true

require "tilt/jbuilder"

module Mihari
module Emitters
class Webhook < Base
Expand All @@ -14,6 +12,9 @@ class Webhook < Base
# @return [String]
attr_reader :method

# @return [String]
attr_reader :template

# @return [Array<Mihari::Models::Artifact>]
attr_accessor :artifacts

Expand Down Expand Up @@ -77,29 +78,13 @@ def http
HTTP::Factory.build headers: headers, timeout: timeout
end

#
# @return [String]
#
def template_string
return File.read(@template) if Pathname(@template).exist?

@template
end

#
# @return [Tilt::JbuilderTemplate]
#
def template
Tilt::JbuilderTemplate.new { template_string }
end

#
# Render template
#
# @return [String]
#
def render
template.render(nil, rule: rule, artifacts: artifacts)
Services::JbuilderRenderer.call(template, { rule: rule, artifacts: artifacts })
end

#
Expand Down
31 changes: 31 additions & 0 deletions lib/mihari/services/renderer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
require "tilt/jbuilder"

module Mihari
module Services
#
# Jbuilder based JSON renderer
#
class JbuilderRenderer < Service
attr_reader :template

#
# @param [String] template
# @param [Hash] params
#
# @return [String]
#
def call(template, params = {})
@template = template

jbuilder_template = Tilt::JbuilderTemplate.new { template_string }
jbuilder_template.render(nil, params)
end

def template_string
return File.read(template) if Pathname(template).exist?

template
end
end
end
end
Loading