appcanary 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/README.md +155 -0
- data/Rakefile +13 -0
- data/appcanary.gemspec +39 -0
- data/bin/appcanary +104 -0
- data/bin/console +7 -0
- data/bin/setup +8 -0
- data/lib/appcanary.rb +5 -0
- data/lib/appcanary/assert.rb +59 -0
- data/lib/appcanary/configuration.rb +154 -0
- data/lib/appcanary/http.rb +96 -0
- data/lib/appcanary/railtie.rb +12 -0
- data/lib/appcanary/tasks/appcanary/check.rake +28 -0
- data/lib/appcanary/tasks/appcanary/monitor.rake +21 -0
- data/lib/appcanary/version.rb +3 -0
- metadata +146 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d4e52c8e7c0d2fea19bcfa7967090d91db713bf8
|
4
|
+
data.tar.gz: 79207694fe161235240740d2ffa695a5f6955409
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e884ca5e62d5b68bea65ee3a9b8035828fef0b2e6258aa456023f059cdb5077e75b1842722711e6e63cc8d769aabc5c804d972c3c98f1a7f09e995bf5f9a2e8d
|
7
|
+
data.tar.gz: 8cca868bda7871c9c7253e030572addf4d502a45c9183d031c17de79b5a6a020ee957bd98e344ef3e32820da8d0ad23859a5d4bf505f9318dbd09ef13905f243
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
# Appcanary
|
2
|
+
|
3
|
+
[](https://circleci.com/gh/appcanary/appcanary.rb)
|
4
|
+
|
5
|
+
[Appcanary](https://appcanary.co) is a service which keeps track of which
|
6
|
+
versions of what packages are vulnerable to which security vulnerabilities, so
|
7
|
+
you don't have to.
|
8
|
+
|
9
|
+
The Appcanary ruby gem offers a way to automate your vulnerability checks either
|
10
|
+
as part of your Continuous Integration builds, or just programmatically
|
11
|
+
elsewhere. It also provides rake tasks for convenience.
|
12
|
+
|
13
|
+
## Quickstart
|
14
|
+
|
15
|
+
These instructions will get you going on CircleCI with a rails project.
|
16
|
+
|
17
|
+
First, add the appcanary gem to your Gemfile:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
gem "appcanary", :git => "https://github.com/appcanary/appcanary.rb"
|
21
|
+
```
|
22
|
+
|
23
|
+
`bundle install` it to update your `Gemfile.lock`.
|
24
|
+
|
25
|
+
Add some configuration to your `config/initializers/appcanary.rb` file:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
Appcanary.api_key = ENV["APPCANARY_API_KEY"] || "api key not set"
|
29
|
+
```
|
30
|
+
|
31
|
+
Now, add the following lines to your `circle.yml` file:
|
32
|
+
|
33
|
+
```yaml
|
34
|
+
dependencies:
|
35
|
+
# [ ... other dependency bits elided ... ]
|
36
|
+
post:
|
37
|
+
# outputs CVEs and references
|
38
|
+
- bundle exec rake appcanary:check
|
39
|
+
# update the appcanary monitor for this app
|
40
|
+
- bundle exec rake appcanary:update_monitor
|
41
|
+
```
|
42
|
+
|
43
|
+
Don't forget to add the `APPCANARY_API_KEY` environment variable in your
|
44
|
+
project settings in the CircleCI web app. You can find your API key in
|
45
|
+
your [Appcanary settings](https://appcanary.com/settings).
|
46
|
+
|
47
|
+
Commit and push your changes, and CircleCI should do the right thing.
|
48
|
+
|
49
|
+
## Alternative setups
|
50
|
+
|
51
|
+
There are several ways to use the Appcanary gem. The simplest of all is to write
|
52
|
+
a small program, in the context of a Bundler managed project, like this:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
require "appcanary"
|
56
|
+
|
57
|
+
config = {
|
58
|
+
base_uri: "https://appcanary.com/api/v3",
|
59
|
+
api_key: "XXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
60
|
+
monitor_name: "my_monitor"
|
61
|
+
}
|
62
|
+
|
63
|
+
canary = Appcanary::Client.new(config)
|
64
|
+
|
65
|
+
if canary.is_this_app_vulnerable?
|
66
|
+
puts "you appear to have your ass in the air"
|
67
|
+
end
|
68
|
+
```
|
69
|
+
|
70
|
+
Instead, you can use a global configuration block, in the traditional rails
|
71
|
+
idiom:
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
Appcanary.configure do |canary|
|
75
|
+
canary.api_key = ENV["APPCANARY_API_KEY"] || "api key not set"
|
76
|
+
canary.base_uri = "https://appcanary.com/api/v3"
|
77
|
+
canary.monitor_name = "my_monitor"
|
78
|
+
end
|
79
|
+
```
|
80
|
+
|
81
|
+
This config style is perhaps best suited to use an initializer file in rails
|
82
|
+
projects.
|
83
|
+
|
84
|
+
Here's a static configuration which is a bit less railsish:
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
Appcanary.api_key = ENV["APPCANARY_API_KEY"] || "api key not set"
|
88
|
+
Appcanary.gemfile_lock_path = "/path/to/gemfile"
|
89
|
+
```
|
90
|
+
|
91
|
+
The gem may then be used without instantiating a client, like this:
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
if Appcanary.is_this_app_vulnerable? :critical
|
95
|
+
puts "I see your shiny attack surface! It BIG!"
|
96
|
+
end
|
97
|
+
```
|
98
|
+
|
99
|
+
Finally, we provide two rake tasks, which are installed automatically in rails
|
100
|
+
projects. They are as follows:
|
101
|
+
|
102
|
+
```
|
103
|
+
$ rake -T
|
104
|
+
...
|
105
|
+
rake appcanary:check # Check vulnerability status
|
106
|
+
rake appcanary:update_monitor # Update the appcanary monitor for this project
|
107
|
+
...
|
108
|
+
|
109
|
+
$ rake appcanary:check
|
110
|
+
CVE-2016-6316
|
111
|
+
CVE-2016-6317
|
112
|
+
$ rake appcanary:update_monitor
|
113
|
+
$
|
114
|
+
```
|
115
|
+
|
116
|
+
If you're using the rake tasks in a non-Rails environment, you'll need to
|
117
|
+
configure the appcanary gem using the third and final method; a YAML file called
|
118
|
+
`appcanary.yml`, in your project root. The contents, unsurprisingly, look like
|
119
|
+
this:
|
120
|
+
|
121
|
+
```yaml
|
122
|
+
api_token: "xxxxxxxxxxxxxxxxxxxxxxxxxx"
|
123
|
+
base_uri: "https://appcanary.com/api/v3"
|
124
|
+
monitor_name: "my_monitor"
|
125
|
+
```
|
126
|
+
|
127
|
+
## Configuration
|
128
|
+
|
129
|
+
As we've seen, you can configure the appcanary gem several different ways. All
|
130
|
+
configurations include the following items however.
|
131
|
+
|
132
|
+
| Key | Required? | Description | Notes |
|
133
|
+
| ------------------- | --------- | ----------- | ----- |
|
134
|
+
| `api_key` | Y | Your Appcanary API key, found in your [Appcanary settings](https://appcanary.com/settings). | |
|
135
|
+
| `gemfile_lock_path` | N | Path to your `Gemfile.lock`, which gets shipped to Appcanary for analysis. | Most of the time you can leave this undefined. |
|
136
|
+
| `monitor_name` | Y* | The base name for the monitor to be updated. *This is required if and only if you plan to use the `update_monitor` functionality. | If you're running in CI, the gem will attempt to acquire the name of the current branch and append that to your monitor name before sending the update. If a monitor does not already exist, it will be created. If this attribute is unset and the gem is loaded in the context of a Rails application, it will use the rails application name as the monitor name. |
|
137
|
+
| `base_uri` | N | The url for the Appcanary service endpoint. | You should leave this unset unless you have a very good reason not to. |
|
138
|
+
|
139
|
+
|
140
|
+
## Development
|
141
|
+
|
142
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
143
|
+
`rake test` to run the tests. You can also run `bin/console` for an interactive
|
144
|
+
prompt that will allow you to experiment.
|
145
|
+
|
146
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To
|
147
|
+
release a new version, update the version number in `version.rb`, and then run
|
148
|
+
`bundle exec rake release`, which will create a git tag for the version, push
|
149
|
+
git commits and tags, and push the `.gem` file
|
150
|
+
to [rubygems.org](https://rubygems.org).
|
151
|
+
|
152
|
+
## Contributing
|
153
|
+
|
154
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/appcanary/appcanary.rb.
|
155
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "rake/testtask"
|
3
|
+
|
4
|
+
Rake::TestTask.new(:test) do |t|
|
5
|
+
t.libs << "test"
|
6
|
+
t.libs << "lib"
|
7
|
+
t.test_files = FileList['test/**/*_test.rb']
|
8
|
+
end
|
9
|
+
|
10
|
+
load "lib/appcanary/tasks/appcanary/check.rake"
|
11
|
+
load "lib/appcanary/tasks/appcanary/monitor.rake"
|
12
|
+
|
13
|
+
task :default => :test
|
data/appcanary.gemspec
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'appcanary/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "appcanary"
|
8
|
+
spec.version = Appcanary::VERSION
|
9
|
+
spec.authors = ["J Irving", "Phill MV"]
|
10
|
+
spec.email = ["hello@appcanary.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Check your dependencies against Appcanary's database.}
|
13
|
+
spec.description = %q{}
|
14
|
+
spec.homepage = "https://appcanary.co"
|
15
|
+
|
16
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
17
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
18
|
+
if spec.respond_to?(:metadata)
|
19
|
+
spec.metadata['allowed_push_host'] = "https://rubygems.org"
|
20
|
+
else
|
21
|
+
raise "RubyGems 2.0 or newer is required to protect against " \
|
22
|
+
"public gem pushes."
|
23
|
+
end
|
24
|
+
|
25
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
26
|
+
f.match(%r{^(test|spec|features)/})
|
27
|
+
end
|
28
|
+
spec.bindir = "exe"
|
29
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
30
|
+
spec.require_paths = ["lib"]
|
31
|
+
|
32
|
+
spec.add_runtime_dependency "multipart-post", "~> 2.0"
|
33
|
+
spec.add_runtime_dependency "json", ">= 1.8.3"
|
34
|
+
|
35
|
+
spec.add_development_dependency "bundler", "~> 1.13"
|
36
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
37
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
38
|
+
spec.add_development_dependency "pry", "~> 0.10"
|
39
|
+
end
|
data/bin/appcanary
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "appcanary"
|
5
|
+
require "optparse"
|
6
|
+
|
7
|
+
class AppcanaryCLI
|
8
|
+
class << self
|
9
|
+
def api_key_opt(opts)
|
10
|
+
opts.on("-a", "--api-key API_KEY", :REQUIRED,
|
11
|
+
"Your Appcanary API key. Find it at https://appcanary.com/settings") do |ak|
|
12
|
+
Appcanary.api_key = ak
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def gemfile_lock_opt(opts)
|
17
|
+
opts.on("-g", "--gemfile-lock GEMFILE_LOCK", :OPTIONAL,
|
18
|
+
"Path to the Gemfile.lock to ship to Appcanary") do |gl|
|
19
|
+
Appcanary.gemfile_lock_path = gl
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def base_uri_opt(opts)
|
24
|
+
opts.on("-b", "--base-uri BASE_URI", :OPTIONAL,
|
25
|
+
"The URL for the Appcanary endpoint to use.") do |bu|
|
26
|
+
Appcanary.base_uri = bu
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def monitor_name_opt(opts)
|
31
|
+
opts.on("-m", "--monitor-name MONITOR_NAME", :REQUIRED,
|
32
|
+
"The name of the Appcanary monitor to update.") do |mn|
|
33
|
+
Appcanary.monitor_name = mn
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def parse
|
38
|
+
top_level_help = <<-HELP
|
39
|
+
Subcommands are:
|
40
|
+
check - Check your gem bundle for vulnerabilities
|
41
|
+
update - Update an Appcanary monitor
|
42
|
+
|
43
|
+
See "appcanary COMMAND --help" for more information about a specific command.
|
44
|
+
HELP
|
45
|
+
|
46
|
+
common = OptionParser.new do |opts|
|
47
|
+
opts.banner = "Usage: appcanary check|update [options]"
|
48
|
+
opts.separator ""
|
49
|
+
opts.separator top_level_help
|
50
|
+
end
|
51
|
+
|
52
|
+
subcommands = {
|
53
|
+
"update" => OptionParser.new do |opts|
|
54
|
+
opts.banner = "Usage: update [options]"
|
55
|
+
api_key_opt(opts)
|
56
|
+
base_uri_opt(opts)
|
57
|
+
gemfile_lock_opt(opts)
|
58
|
+
monitor_name_opt(opts)
|
59
|
+
end,
|
60
|
+
"check" => OptionParser.new do |opts|
|
61
|
+
opts.banner = "Usage: check [options]"
|
62
|
+
api_key_opt(opts)
|
63
|
+
base_uri_opt(opts)
|
64
|
+
gemfile_lock_opt(opts)
|
65
|
+
end
|
66
|
+
}
|
67
|
+
|
68
|
+
common.order!
|
69
|
+
|
70
|
+
command = ARGV.shift
|
71
|
+
if command.nil?
|
72
|
+
puts "No subcommand found -- try appcanary --help"
|
73
|
+
else
|
74
|
+
subcommands[command].order!
|
75
|
+
end
|
76
|
+
|
77
|
+
command
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def run_check
|
83
|
+
response = Appcanary.check
|
84
|
+
if response["meta"]["vulnerable"]
|
85
|
+
response["included"].map do |vuln|
|
86
|
+
vuln["attributes"]["reference-ids"]
|
87
|
+
end.flatten.uniq.each do |ref|
|
88
|
+
puts ref
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def run_appcanary_command
|
94
|
+
case AppcanaryCLI.parse
|
95
|
+
when "update"
|
96
|
+
Appcanary.update_monitor!
|
97
|
+
when "check"
|
98
|
+
run_check
|
99
|
+
end
|
100
|
+
rescue => e
|
101
|
+
puts e
|
102
|
+
end
|
103
|
+
|
104
|
+
run_appcanary_command
|
data/bin/console
ADDED
data/bin/setup
ADDED
data/lib/appcanary.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
require "json"
|
2
|
+
|
3
|
+
module Appcanary
|
4
|
+
class Client
|
5
|
+
include HTTP
|
6
|
+
|
7
|
+
attr_reader :config
|
8
|
+
|
9
|
+
def initialize(config)
|
10
|
+
@config = config
|
11
|
+
end
|
12
|
+
|
13
|
+
def is_this_app_vulnerable?(criticality = nil)
|
14
|
+
check do |response|
|
15
|
+
vulnerable = response["meta"]["vulnerable"]
|
16
|
+
if vulnerable == true || vulnerable == "true"
|
17
|
+
return true if criticality.nil?
|
18
|
+
|
19
|
+
cnt = count_criticalities(response)[criticality.to_s]
|
20
|
+
cnt && cnt > 0
|
21
|
+
else
|
22
|
+
false
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def check(&block)
|
28
|
+
response = ship_gemfile(:check, config)
|
29
|
+
|
30
|
+
if block
|
31
|
+
block.call(response)
|
32
|
+
else
|
33
|
+
response
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def update_monitor!
|
38
|
+
if config.sufficient_for_monitor?
|
39
|
+
ship_gemfile(:monitors, config)
|
40
|
+
else
|
41
|
+
raise Appcanary::ConfigurationError.new("Appcanary.monitor_name = ???")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
def count_frequencies(arr)
|
47
|
+
arr.inject({}) do |freqs, i|
|
48
|
+
freqs[i] ||= 0
|
49
|
+
freqs[i] += 1
|
50
|
+
freqs
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def count_criticalities(response)
|
55
|
+
count_frequencies(
|
56
|
+
response["included"].map { |vuln| vuln["attributes"]["criticality"] })
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
require "yaml"
|
2
|
+
|
3
|
+
module Appcanary
|
4
|
+
APPCANARY_DEFAULT_BASE_URI = "https://appcanary.com/api/v3"
|
5
|
+
|
6
|
+
APPCANARY_YAML = "appcanary.yml"
|
7
|
+
GEMFILE_LOCK = "Gemfile.lock"
|
8
|
+
|
9
|
+
class Configuration
|
10
|
+
attr_accessor :base_uri, :api_key, :monitor_name, :gemfile_lock_path
|
11
|
+
|
12
|
+
def [](k)
|
13
|
+
self.send(k.to_s)
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
self.base_uri = APPCANARY_DEFAULT_BASE_URI
|
18
|
+
self.monitor_name = maybe_guess_monitor_name
|
19
|
+
self.gemfile_lock_path = locate_gemfile_lockpath
|
20
|
+
end
|
21
|
+
|
22
|
+
def maybe_guess_monitor_name
|
23
|
+
Rails.application.class.parent_name if defined?(Rails)
|
24
|
+
end
|
25
|
+
|
26
|
+
def locate_gemfile_lockpath
|
27
|
+
# make an educated guess
|
28
|
+
gemfile_lock_path = "#{Dir.pwd}/#{GEMFILE_LOCK}"
|
29
|
+
return gemfile_lock_path if File.exist?(gemfile_lock_path)
|
30
|
+
|
31
|
+
# otherwise try out bundler
|
32
|
+
begin
|
33
|
+
require "bundler"
|
34
|
+
if defined?(Bundler)
|
35
|
+
Bundler.default_lockfile.to_s
|
36
|
+
end
|
37
|
+
rescue LoadError
|
38
|
+
# ignore, handle at resolution time
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def sufficient_for_check?
|
43
|
+
! (base_uri.nil? || api_key.nil? || gemfile_lock_path.nil?)
|
44
|
+
end
|
45
|
+
|
46
|
+
def sufficient_for_monitor?
|
47
|
+
sufficient_for_check? && !monitor_name.nil?
|
48
|
+
end
|
49
|
+
|
50
|
+
def resolve!
|
51
|
+
# 1. static configuration takes precedence over yaml, and only one may be
|
52
|
+
# used. If the configuration block is present and valid, use it
|
53
|
+
# exclusively, otherwise looks for yaml.
|
54
|
+
#
|
55
|
+
# 2. within that context, use the following rules
|
56
|
+
# - api_key: required, no attempt to guess/derive
|
57
|
+
#
|
58
|
+
# - gemfile_lock_path: path to Gemfile.lock; if missing, attempt to
|
59
|
+
# guess based on Bundler (if defined -- if not, attempt to require
|
60
|
+
# it, and fail angrily if that doesn't work).
|
61
|
+
#
|
62
|
+
# - monitor_name: name to use as the base for the monitor update. If
|
63
|
+
# this is missing, attempt to derive it by finding the rails app name
|
64
|
+
# (if Rails is defined). Otherwise fail, but only when updating
|
65
|
+
# monitors, not when running checks.
|
66
|
+
#
|
67
|
+
# - base_uri: if this is missing (as it probably should be in all cases
|
68
|
+
# except working on this gem), default to prod appcanary.com.
|
69
|
+
unless self.sufficient_for_check?
|
70
|
+
yaml_file = "#{Dir.pwd}/#{APPCANARY_YAML}"
|
71
|
+
begin
|
72
|
+
load_yaml_config!(yaml_file)
|
73
|
+
rescue
|
74
|
+
load_yaml_config!("#{Bundler.root}/#{APPCANARY_YAML}") if defined?(Bundler)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# UX for validation
|
79
|
+
errors = []
|
80
|
+
errors << "\tAppcanary.api_key = ???" if api_key.nil?
|
81
|
+
errors << "\tAppcanary.gemfile_lock_path = ???" if gemfile_lock_path.nil?
|
82
|
+
unless errors.empty?
|
83
|
+
raise ConfigurationError.new("Missing configuration:\n#{errors.join("\n")}")
|
84
|
+
end
|
85
|
+
|
86
|
+
self
|
87
|
+
end
|
88
|
+
|
89
|
+
def load_yaml_config!(path)
|
90
|
+
begin
|
91
|
+
yaml_config = YAML.load_file(path)
|
92
|
+
self.api_key = yaml_config["api_key"]
|
93
|
+
self.gemfile_lock_path = yaml_config["gemfile_lock_path"]
|
94
|
+
self.monitor_name = yaml_config["monitor_name"]
|
95
|
+
self.base_uri = yaml_config["base_uri"] || APPCANARY_DEFAULT_BASE_URI
|
96
|
+
rescue Errno::ENOENT
|
97
|
+
# ignore, fall through
|
98
|
+
rescue => e
|
99
|
+
# there was a file, but something was wrong with it
|
100
|
+
raise ConfigurationError.new(e)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
class ConfigurationError < RuntimeError
|
106
|
+
SUFFIX = <<-EOS
|
107
|
+
Consult the following docs for more information:
|
108
|
+
- https://github.com/appcanary/appcanary.rb
|
109
|
+
- https://appcanary.com/settings
|
110
|
+
EOS
|
111
|
+
|
112
|
+
def initialize(msg)
|
113
|
+
super("#{msg}\n\n#{SUFFIX}")
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
class << self
|
118
|
+
def configuration
|
119
|
+
@@configuration ||= Configuration.new
|
120
|
+
end
|
121
|
+
|
122
|
+
def reset
|
123
|
+
@@configuration = Configuration.new
|
124
|
+
end
|
125
|
+
|
126
|
+
def configure(&block)
|
127
|
+
block.call(configuration) if block
|
128
|
+
end
|
129
|
+
|
130
|
+
# another way to do static configuration
|
131
|
+
def api_key=(val); configuration.api_key = val; end
|
132
|
+
def gemfile_lock_path=(val); configuration.gemfile_lock_path = val; end
|
133
|
+
def monitor_name=(val); configuration.monitor_name = val; end
|
134
|
+
def base_uri=(val); configuration.base_uri = val; end
|
135
|
+
|
136
|
+
# static API
|
137
|
+
def is_this_app_vulnerable?(criticality = nil)
|
138
|
+
canary.is_this_app_vulnerable?(criticality)
|
139
|
+
end
|
140
|
+
|
141
|
+
def update_monitor!
|
142
|
+
canary.update_monitor!
|
143
|
+
end
|
144
|
+
|
145
|
+
def check
|
146
|
+
canary.check
|
147
|
+
end
|
148
|
+
|
149
|
+
private
|
150
|
+
def canary
|
151
|
+
@@canary ||= Appcanary::Client.new(Appcanary.configuration.resolve!)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require "net/http"
|
2
|
+
require "net/http/post/multipart"
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module Appcanary
|
6
|
+
class ServiceError < RuntimeError
|
7
|
+
end
|
8
|
+
|
9
|
+
# In this module, `config` should always be a hash, or an object that responds
|
10
|
+
# to `[](k)`, typically obtained by calling `Appcanary::Configuration#resolve!`.
|
11
|
+
module HTTP
|
12
|
+
def ship_gemfile(endpoint, config, &block)
|
13
|
+
payload = {
|
14
|
+
file: config[:gemfile_lock_path],
|
15
|
+
platform: "ruby"
|
16
|
+
}
|
17
|
+
|
18
|
+
parsed_response = ship_file(endpoint, payload, config)
|
19
|
+
|
20
|
+
if block
|
21
|
+
block.call(parsed_response)
|
22
|
+
else
|
23
|
+
parsed_response
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def ship_file(endpoint, payload, config)
|
28
|
+
resp = try_request_with(:put, endpoint, payload, config)
|
29
|
+
|
30
|
+
unless resp.code.to_s == "200"
|
31
|
+
resp = try_request_with(:post, endpoint, payload, config)
|
32
|
+
end
|
33
|
+
|
34
|
+
unless %w[200 201].include? resp.code.to_s
|
35
|
+
raise ServiceError.new("Failed to ship file to Appcanary: #{resp}")
|
36
|
+
end
|
37
|
+
|
38
|
+
JSON.parse(resp.body)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
def url_for(endpoint, config)
|
43
|
+
case endpoint
|
44
|
+
when :monitors
|
45
|
+
monitor = "#{config[:monitor_name]}"
|
46
|
+
|
47
|
+
if ENV["CIRCLECI"] && ENV["CIRCLECI"] == "true"
|
48
|
+
monitor = "#{monitor}_#{ENV['CIRCLE_BRANCH']}"
|
49
|
+
end
|
50
|
+
|
51
|
+
# these are rails routing delimiters
|
52
|
+
monitor.gsub!("/", "_")
|
53
|
+
monitor.gsub!(".", "_")
|
54
|
+
|
55
|
+
URI.parse("#{config[:base_uri]}/monitors/#{monitor}")
|
56
|
+
when :check
|
57
|
+
URI.parse("#{config[:base_uri]}/check")
|
58
|
+
else
|
59
|
+
# internal brokenness
|
60
|
+
raise RuntimeError.new("Unknown Appcanary endpoint: #{endpoint.to_s}!")
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
REQUEST_TYPES = {
|
65
|
+
post: Net::HTTP::Post::Multipart,
|
66
|
+
put: Net::HTTP::Put::Multipart
|
67
|
+
}
|
68
|
+
|
69
|
+
def try_request_with(method, endpoint, payload, config)
|
70
|
+
request_type = REQUEST_TYPES[method]
|
71
|
+
url = url_for(endpoint, config)
|
72
|
+
filename = File.basename(payload[:file])
|
73
|
+
url.query = URI.encode_www_form("platform" => payload[:platform])
|
74
|
+
|
75
|
+
File.open(payload[:file]) do |file|
|
76
|
+
params = {}.tap do |p|
|
77
|
+
p["file"] = UploadIO.new(file, "text/plain", filename)
|
78
|
+
p["platform"] = payload[:platform]
|
79
|
+
|
80
|
+
if payload[:version]
|
81
|
+
p["version"] = payload[:version]
|
82
|
+
url.query = url.query.merge("version", payload[:version])
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
headers = {"Authorization" => "Token #{config[:api_key]}"}
|
87
|
+
req = request_type.new(url.path, params, headers, SecureRandom.base64)
|
88
|
+
options = { use_ssl: url.scheme == "https" }
|
89
|
+
|
90
|
+
Net::HTTP.start(url.host, url.port, options) do |http|
|
91
|
+
http.request(req)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require "rails/railtie"
|
2
|
+
|
3
|
+
module Appcanary
|
4
|
+
class Railtie < Rails::Railtie
|
5
|
+
rake_tasks do
|
6
|
+
spec = Gem::Specification.find_by_name("appcanary")
|
7
|
+
gem_root = spec.gem_dir
|
8
|
+
load "#{gem_root}/lib/appcanary/tasks/appcanary/check.rake"
|
9
|
+
load "#{gem_root}/lib/appcanary/tasks/appcanary/monitor.rake"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require "appcanary"
|
2
|
+
require "rake"
|
3
|
+
|
4
|
+
def run_check
|
5
|
+
response = Appcanary.check
|
6
|
+
if response["meta"]["vulnerable"]
|
7
|
+
response["included"].map do |vuln|
|
8
|
+
vuln["attributes"]["reference-ids"]
|
9
|
+
end.flatten.uniq.each do |ref|
|
10
|
+
puts ref
|
11
|
+
end
|
12
|
+
end
|
13
|
+
rescue => e
|
14
|
+
puts e
|
15
|
+
end
|
16
|
+
|
17
|
+
namespace :appcanary do
|
18
|
+
desc "Check vulnerability status"
|
19
|
+
if defined?(Rails)
|
20
|
+
task :check => :environment do
|
21
|
+
run_check
|
22
|
+
end
|
23
|
+
else
|
24
|
+
task :check do
|
25
|
+
run_check
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "appcanary"
|
2
|
+
require "rake"
|
3
|
+
|
4
|
+
def run_update_monitor
|
5
|
+
Appcanary.update_monitor!
|
6
|
+
rescue => e
|
7
|
+
puts e
|
8
|
+
end
|
9
|
+
|
10
|
+
namespace :appcanary do
|
11
|
+
desc "Update the appcanary monitor for this project"
|
12
|
+
if defined?(Rails)
|
13
|
+
task :update_monitor => :environment do
|
14
|
+
run_update_monitor
|
15
|
+
end
|
16
|
+
else
|
17
|
+
task :update_monitor do
|
18
|
+
run_update_monitor
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: appcanary
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- J Irving
|
8
|
+
- Phill MV
|
9
|
+
autorequire:
|
10
|
+
bindir: exe
|
11
|
+
cert_chain: []
|
12
|
+
date: 2017-01-10 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: multipart-post
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '2.0'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '2.0'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: json
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: 1.8.3
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: 1.8.3
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: bundler
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - "~>"
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '1.13'
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '1.13'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: rake
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - "~>"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '10.0'
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '10.0'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: minitest
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - "~>"
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '5.0'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - "~>"
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '5.0'
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: pry
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - "~>"
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0.10'
|
91
|
+
type: :development
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - "~>"
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0.10'
|
98
|
+
description: ''
|
99
|
+
email:
|
100
|
+
- hello@appcanary.com
|
101
|
+
executables: []
|
102
|
+
extensions: []
|
103
|
+
extra_rdoc_files: []
|
104
|
+
files:
|
105
|
+
- ".gitignore"
|
106
|
+
- ".travis.yml"
|
107
|
+
- Gemfile
|
108
|
+
- README.md
|
109
|
+
- Rakefile
|
110
|
+
- appcanary.gemspec
|
111
|
+
- bin/appcanary
|
112
|
+
- bin/console
|
113
|
+
- bin/setup
|
114
|
+
- lib/appcanary.rb
|
115
|
+
- lib/appcanary/assert.rb
|
116
|
+
- lib/appcanary/configuration.rb
|
117
|
+
- lib/appcanary/http.rb
|
118
|
+
- lib/appcanary/railtie.rb
|
119
|
+
- lib/appcanary/tasks/appcanary/check.rake
|
120
|
+
- lib/appcanary/tasks/appcanary/monitor.rake
|
121
|
+
- lib/appcanary/version.rb
|
122
|
+
homepage: https://appcanary.co
|
123
|
+
licenses: []
|
124
|
+
metadata:
|
125
|
+
allowed_push_host: https://rubygems.org
|
126
|
+
post_install_message:
|
127
|
+
rdoc_options: []
|
128
|
+
require_paths:
|
129
|
+
- lib
|
130
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
131
|
+
requirements:
|
132
|
+
- - ">="
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: '0'
|
135
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
136
|
+
requirements:
|
137
|
+
- - ">="
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: '0'
|
140
|
+
requirements: []
|
141
|
+
rubyforge_project:
|
142
|
+
rubygems_version: 2.5.2
|
143
|
+
signing_key:
|
144
|
+
specification_version: 4
|
145
|
+
summary: Check your dependencies against Appcanary's database.
|
146
|
+
test_files: []
|