misp 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: db847f1e32b2e0a1064180b310b087ade5c9d608282bcbeebc5403f02b0cb71d
4
+ data.tar.gz: e1090389330d2de0bfd8b0c58cd0768f1a9bc58bb220d80c9d74aea6cb9ec738
5
+ SHA512:
6
+ metadata.gz: a4b3e71c139193309e919b6dc344f4fb200521b07a90fd3e45384d272549632af96eec9852416fdc8ddbfda31c9675f5ab107ae4cb1b2ff61b98dbeba0979457
7
+ data.tar.gz: 16b98a9dae45d273b70600565f8130d43d592e4097b517f49a69177c367a8c88a5841e4eb238a9629f9a45d1ca12ce7addfb5118c27f52d45a9c4a0ae3e56f61
@@ -0,0 +1,52 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/examples.txt
9
+ /test/tmp/
10
+ /test/version_tmp/
11
+ /tmp/
12
+
13
+ # Used by dotenv library to load environment variables.
14
+ .env
15
+
16
+ ## Specific to RubyMotion:
17
+ .dat*
18
+ .repl_history
19
+ build/
20
+ *.bridgesupport
21
+ build-iPhoneOS/
22
+ build-iPhoneSimulator/
23
+
24
+ ## Specific to RubyMotion (use of CocoaPods):
25
+ #
26
+ # We recommend against adding the Pods directory to your .gitignore. However
27
+ # you should judge for yourself, the pros and cons are mentioned at:
28
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
29
+ #
30
+ # vendor/Pods/
31
+
32
+ ## Documentation cache and generated files:
33
+ /.yardoc/
34
+ /_yardoc/
35
+ /doc/
36
+ /rdoc/
37
+
38
+ ## Environment normalization:
39
+ /.bundle/
40
+ /vendor/bundle
41
+ /lib/bundler/man/
42
+
43
+ # for a library or gem, you might want to ignore these files since the code is
44
+ # intended to run in multiple environments; otherwise, check them in:
45
+ Gemfile.lock
46
+ .ruby-version
47
+ .ruby-gemset
48
+
49
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
50
+ .rvmrc
51
+
52
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,7 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.6
7
+ before_install: gem install bundler -v 2.0.2
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in misp-rb.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2019 Manabu Niseki
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,118 @@
1
+ # misp-rb
2
+
3
+ [![Build Status](https://travis-ci.com/ninoseki/misp-rb.svg?branch=master)](https://travis-ci.com/ninoseki/misp-rb)
4
+ [![Coverage Status](https://coveralls.io/repos/github/ninoseki/misp-rb/badge.svg?branch=master)](https://coveralls.io/github/ninoseki/misp-rb?branch=master)
5
+ [![CodeFactor](https://www.codefactor.io/repository/github/ninoseki/misp-rb/badge)](https://www.codefactor.io/repository/github/ninoseki/misp-rb)
6
+
7
+ A dead simple MISP API wrapper for Ruby.
8
+
9
+ If you aren't a Rubyist, I highly recommend to use the official [PyMISP](https://github.com/MISP/PyMISP).
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ gem install misp
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ### Configuration
20
+
21
+ By default, it tries to load configurations from environmental variables:
22
+
23
+ - MISP API endpoint from ENV["MISP_API_ENDPOINT"]
24
+ - MISP API key from ENV["MISP_API_KEY"]
25
+
26
+ Also, you can configure them manually.
27
+
28
+ ```ruby
29
+ require "misp"
30
+
31
+ MISP.configure do |config|
32
+ config.api_endpoint = "https://misppriv.circl.lu"
33
+ config.api_key = "MISP_API_KEY"
34
+ end
35
+ ```
36
+
37
+ ### Create an event
38
+
39
+ ```ruby
40
+ event = MISP::Event.create(info: "my event")
41
+ ```
42
+
43
+ ### Retrive an event
44
+
45
+ ```ruby
46
+ event = MISP::Event.get(15)
47
+ ```
48
+
49
+ ### Update an event
50
+
51
+ ```ruby
52
+ event = MISP::Event.get(17)
53
+ event.info = "my new info field"
54
+ event.update
55
+ ```
56
+
57
+ ### Add an attribute
58
+
59
+ ```ruby
60
+ event = MISP::Event.get(17)
61
+ event.add_attribute(value: "8.8.8.8", type: "ip-dst")
62
+ # or
63
+ attribute = MISP::Attribute(value: "1.1.1.1", type: "ip-dst")
64
+ event.add_attribute attribute
65
+ event.update
66
+ ```
67
+
68
+ ### Tag an event
69
+
70
+ ```ruby
71
+ event = MISP::Event.get(17)
72
+ event.add_tag name: "my tag"
73
+ event.update
74
+ ```
75
+
76
+ ### Tag an attribute
77
+
78
+ ```ruby
79
+ attribute = MISP::Attribute.search(value: "8.8.8.8").first
80
+ attribute.add_tag(name: "my tag")
81
+ ```
82
+
83
+ ### Create an event with attributes and tags already applied
84
+
85
+ ```ruby
86
+ event = MISP::Event.new(
87
+ info: "my event",
88
+ Attribute: [
89
+ value: "8.8.8.8",
90
+ type: "ip-dst",
91
+ Tag: [
92
+ { name: "my attribute-level tag" }
93
+ ]
94
+ ],
95
+ Tag: [
96
+ { name: "my event-level tag" }
97
+ ]
98
+ )
99
+ event.create
100
+ # or
101
+ event = MISP::Event.new(info: "my event")
102
+
103
+ attribute = MISP::Attribute.new(value: "8.8.8.8", type: "ip-dst")
104
+ attribute.tags << MISP::Tag.new(name: "my attribute-level tag")
105
+
106
+ event.attributes << attribute
107
+ event.tags << MISP::Tag.new(name: "my event-level tag")
108
+
109
+ event.create
110
+ ```
111
+
112
+ ## Acknowledgement
113
+
114
+ The implementation design of this gem is highly influenced by [FloatingGhost/mispex](https://github.com/FloatingGhost/mispex).
115
+
116
+ ## License
117
+
118
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "misp/version"
4
+
5
+ require "misp/configuration"
6
+
7
+ require "misp/base"
8
+
9
+ require "misp/org"
10
+ require "misp/orgc"
11
+
12
+ require "misp/feed"
13
+ require "misp/server"
14
+
15
+ require "misp/sharing_group_org"
16
+ require "misp/sharing_group_server"
17
+ require "misp/sharing_group"
18
+
19
+ require "misp/galaxy_cluster"
20
+ require "misp/galaxy"
21
+
22
+ require "misp/tag"
23
+
24
+ require "misp/attribute"
25
+
26
+ require "misp/event"
27
+
28
+ module MISP
29
+ class Error < StandardError; end
30
+ end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MISP
4
+ class Attribute < Base
5
+ attr_reader :id
6
+ attr_accessor :type
7
+ attr_accessor :category
8
+ attr_accessor :to_ids
9
+ attr_reader :uuid
10
+ attr_reader :event_id
11
+ attr_accessor :distribution
12
+ attr_accessor :timestamp
13
+ attr_accessor :comment
14
+ attr_accessor :sharing_group_id
15
+ attr_accessor :deleted
16
+ attr_accessor :disable_correlation
17
+ attr_accessor :value
18
+ attr_accessor :data
19
+
20
+ attr_accessor :sharing_groups
21
+ attr_accessor :shadow_attributes
22
+ attr_accessor :tags
23
+
24
+ def initialize(**attributes)
25
+ attributes = normalize_attributes(attributes)
26
+
27
+ @id = attributes.dig(:id)
28
+ @type = attributes.dig(:type)
29
+ @category = attributes.dig(:category)
30
+ @to_ids = attributes.dig(:to_ids)
31
+ @uuid = attributes.dig(:uuid)
32
+ @event_id = attributes.dig(:event_id)
33
+ @distribution = attributes.dig(:distribution)
34
+ @timestamp = attributes.dig(:timestamp)
35
+ @comment = attributes.dig(:comment)
36
+ @sharing_group_id = attributes.dig(:sharing_group_id)
37
+ @deleted = attributes.dig(:deleted)
38
+ @disable_correlation = attributes.dig(:disable_correlation)
39
+ @value = attributes.dig(:value)
40
+ @data = attributes.dig(:data)
41
+
42
+ @sharing_groups = build_plural_attribute(items: attributes.dig(:SharingGroup), klass: SharingGroup)
43
+ @shadow_attributes = build_plural_attribute(items: attributes.dig(:ShadowAttribute), klass: Attribute )
44
+ @tags = build_plural_attribute(items: attributes.dig(:Tag), klass: Tag)
45
+ end
46
+
47
+ def to_h
48
+ {
49
+ id: id,
50
+ type: type,
51
+ category: category,
52
+ to_ids: to_ids,
53
+ uuid: uuid,
54
+ event_id: event_id,
55
+ distribution: distribution,
56
+ timestamp: timestamp,
57
+ comment: comment,
58
+ sharing_group_id: sharing_group_id,
59
+ deleted: deleted,
60
+ disable_correlation: disable_correlation,
61
+ value: value,
62
+ data: data,
63
+ SharingGroup: sharing_groups.map(&:to_h),
64
+ ShadowAttribute: shadow_attributes.map(&:to_h),
65
+ Tag: tags.map(&:to_h)
66
+ }.compact
67
+ end
68
+
69
+ def get
70
+ _get("/attributes/#{id}") { |attribute| Attribute.new symbolize_keys(attribute) }
71
+ end
72
+
73
+ def self.get(id)
74
+ new(id: id).get
75
+ end
76
+
77
+ def delete
78
+ _post("/attributes/delete/#{id}") { |json| json }
79
+ end
80
+
81
+ def self.delete(id)
82
+ new(id: id).delete
83
+ end
84
+
85
+ def create(event_id)
86
+ _post("/attributes/add/#{event_id}", wrap(to_h)) { |attribute| Attribute.new symbolize_keys(attribute) }
87
+ end
88
+
89
+ def self.create(event_id, **attributes)
90
+ new(attributes).create(event_id)
91
+ end
92
+
93
+ def update(**attrs)
94
+ payload = to_h.merge(attrs)
95
+ payload[:timestamp] = nil
96
+ _post("/attributes/edit/#{id}", wrap(payload)) { |json| Attribute.new symbolize_keys(json.dig("response", "Attribute")) }
97
+ end
98
+
99
+ def search(**params)
100
+ base = {
101
+ returnFormat: "json",
102
+ limit: "100",
103
+ page: "0"
104
+ }
105
+
106
+ _post("/attributes/restSearch", base.merge(params)) do |json|
107
+ attributes = json.dig("response", "Attribute") || []
108
+ attributes.map { |attribute| Attribute.new symbolize_keys(attribute) }
109
+ end
110
+ end
111
+
112
+ def self.search(**params)
113
+ new.search params
114
+ end
115
+
116
+ def add_tag(tag)
117
+ tag = Tag.new(symbolize_keys(tag)) unless tag.is_a?(MISP::Tag)
118
+ payload = { uuid: uuid, tag: tag.name }
119
+ _post("/tags/attachTagToObject", payload) { |json| Tag.new symbolize_keys(json) }
120
+ end
121
+
122
+ def remove_tag(tag)
123
+ tag = Tag.new(symbolize_keys(tag)) unless tag.is_a?(MISP::Tag)
124
+ payload = { uuid: uuid, tag: tag.name }
125
+ _post("/tags/removeTagFromObject", payload) { |json| json }
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "net/https"
5
+ require "uri"
6
+
7
+ module MISP
8
+ class Base
9
+ def api_endpoint
10
+ @api_endpoint ||= URI(MISP.configuration.api_endpoint)
11
+ end
12
+
13
+ def api_key
14
+ @api_key ||= MISP.configuration.api_key
15
+ end
16
+
17
+ private
18
+
19
+ def build_attribute(item:, klass:)
20
+ return nil unless item
21
+
22
+ klass.new symbolize_keys(item)
23
+ end
24
+
25
+ def build_plural_attribute(items:, klass:)
26
+ (items || []).map do |item|
27
+ klass.new symbolize_keys(item)
28
+ end
29
+ end
30
+
31
+ def symbolize_keys(hash)
32
+ hash.map { |k, v| [k.to_sym, v] }.to_h
33
+ end
34
+
35
+ def class_name
36
+ self.class.to_s.split("::").last.to_s
37
+ end
38
+
39
+ def normalize_attributes(attributes)
40
+ klass = class_name.to_sym
41
+
42
+ attributes.key?(klass) ? symbolize_keys(attributes.dig(klass)) : attributes
43
+ end
44
+
45
+ def wrap(params)
46
+ klass = class_name.to_sym
47
+ return params if params.key?(klass)
48
+
49
+ [[klass, params]].to_h
50
+ end
51
+
52
+ def hostname
53
+ @hostname ||= api_endpoint.hostname
54
+ end
55
+
56
+ def port
57
+ @port ||= api_endpoint.port
58
+ end
59
+
60
+ def scheme
61
+ @scheme ||= api_endpoint.scheme
62
+ end
63
+
64
+ def base_url
65
+ "#{scheme}://#{hostname}:#{port}"
66
+ end
67
+
68
+ def url_for(path)
69
+ URI(base_url + path)
70
+ end
71
+
72
+ def https_options
73
+ return nil if scheme != "https"
74
+
75
+ if proxy = ENV["HTTPS_PROXY"] || ENV["https_proxy"]
76
+ uri = URI(proxy)
77
+ {
78
+ proxy_address: uri.hostname,
79
+ proxy_port: uri.port,
80
+ proxy_from_env: false,
81
+ use_ssl: true,
82
+ }
83
+ else
84
+ { use_ssl: true }
85
+ end
86
+ end
87
+
88
+ def http_options
89
+ if proxy = ENV["HTTP_PROXY"] || ENV["http_proxy"]
90
+ uri = URI(proxy)
91
+ {
92
+ proxy_address: uri.hostname,
93
+ proxy_port: uri.port,
94
+ proxy_from_env: false,
95
+ }
96
+ else
97
+ {}
98
+ end
99
+ end
100
+
101
+ def parse_body(body)
102
+ JSON.parse body.to_s
103
+ rescue JSON::ParserError => _e
104
+ body.to_s
105
+ end
106
+
107
+ def request(req)
108
+ Net::HTTP.start(hostname, port, https_options || http_options) do |http|
109
+ req.initialize_http_header default_headers
110
+
111
+ response = http.request(req)
112
+ json = parse_body(response.body)
113
+
114
+ code = response.code
115
+ unless code.start_with? "20"
116
+ raise Error, "Unsupported response code returned: #{code} (#{json})"
117
+ end
118
+
119
+ yield json
120
+ end
121
+ end
122
+
123
+ def default_headers
124
+ {
125
+ "Content-Type": "application/json",
126
+ "Accept": "application/json",
127
+ "Authorization": api_key
128
+ }
129
+ end
130
+
131
+ def _get(path, params = {}, &block)
132
+ url = url_for(path)
133
+ url.query = URI.encode_www_form(params) unless params.empty?
134
+
135
+ get = Net::HTTP::Get.new(url)
136
+ request(get, &block)
137
+ end
138
+
139
+ def _post(path, params = {}, &block)
140
+ url = url_for(path)
141
+
142
+ post = Net::HTTP::Post.new(url)
143
+ post.body = params.is_a?(Hash) ? params.to_json : params.to_s
144
+
145
+ request(post, &block)
146
+ end
147
+
148
+ def _delete(path, params = {}, &block)
149
+ url = url_for(path)
150
+ url.query = URI.encode_www_form(params) unless params.empty?
151
+
152
+ delete = Net::HTTP::Delete.new(url)
153
+ request(delete, &block)
154
+ end
155
+ end
156
+ end