├── .gitignore ├── .rspec ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── bin ├── console └── setup ├── dockscan.gemspec ├── docs └── dockscan.png ├── exe └── dockscan ├── lib ├── dockscan.rb └── dockscan │ ├── modules │ ├── audit.rb │ ├── audit │ │ ├── container-filesystem-diff.rb │ │ ├── container-filesystem-shadow.rb │ │ ├── container-number-process.rb │ │ ├── container-sshd-process.rb │ │ ├── docker-experimental-build.rb │ │ ├── docker-insecure-registries.rb │ │ ├── docker-limits.rb │ │ ├── docker-networking-forwarding.rb │ │ ├── docker-registry-mirror.rb │ │ └── docker-storage-driver-aufs.rb │ ├── discover.rb │ ├── discover │ │ ├── get-containers.rb │ │ ├── get-docker-info.rb │ │ ├── get-docker-version.rb │ │ ├── get-images.rb │ │ └── get-run-containers.rb │ ├── genmodule.rb │ ├── report.rb │ └── report │ │ ├── html.rb │ │ ├── stdout.rb │ │ └── txt.rb │ ├── scan │ ├── issue.rb │ ├── manage.rb │ └── plugin.rb │ └── version.rb └── spec ├── dockscan_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: ruby 3 | rvm: 4 | - 2.2.5 5 | - 2.3.0 6 | - 2.3.1 7 | before_install: gem install bundler 8 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, and in the interest of 4 | fostering an open and welcoming community, we pledge to respect all people who 5 | contribute through reporting issues, posting feature requests, updating 6 | documentation, submitting pull requests or patches, and other activities. 7 | 8 | We are committed to making participation in this project a harassment-free 9 | experience for everyone, regardless of level of experience, gender, gender 10 | identity and expression, sexual orientation, disability, personal appearance, 11 | body size, race, ethnicity, age, religion, or nationality. 12 | 13 | Examples of unacceptable behavior by participants include: 14 | 15 | * The use of sexualized language or imagery 16 | * Personal attacks 17 | * Trolling or insulting/derogatory comments 18 | * Public or private harassment 19 | * Publishing other's private information, such as physical or electronic 20 | addresses, without explicit permission 21 | * Other unethical or unprofessional conduct 22 | 23 | Project maintainers have the right and responsibility to remove, edit, or 24 | reject comments, commits, code, wiki edits, issues, and other contributions 25 | that are not aligned to this Code of Conduct, or to ban temporarily or 26 | permanently any contributor for other behaviors that they deem inappropriate, 27 | threatening, offensive, or harmful. 28 | 29 | By adopting this Code of Conduct, project maintainers commit themselves to 30 | fairly and consistently applying these principles to every aspect of managing 31 | this project. Project maintainers who do not follow or enforce the Code of 32 | Conduct may be permanently removed from the project team. 33 | 34 | This code of conduct applies both within project spaces and in public spaces 35 | when an individual is representing the project or its community. 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 38 | reported by contacting a project maintainer at kost@linux.hr. All 39 | complaints will be reviewed and investigated and will result in a response that 40 | is deemed necessary and appropriate to the circumstances. Maintainers are 41 | obligated to maintain confidentiality with regard to the reporter of an 42 | incident. 43 | 44 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 45 | version 1.3.0, available at 46 | [http://contributor-covenant.org/version/1/3/0/][version] 47 | 48 | [homepage]: http://contributor-covenant.org 49 | [version]: http://contributor-covenant.org/version/1/3/0/ -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in nessus_console.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | 341 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/kost/dockscan.png)](https://travis-ci.org/kost/dockscan) 2 | [![Coverage Status](https://coveralls.io/repos/kost/dockscan/badge.png?branch=master)](https://coveralls.io/r/kost/dockscan?branch=master) 3 | 4 | dockscan 5 | =========== 6 | 7 | ![logo](https://raw.githubusercontent.com/kost/dockscan/master/docs/dockscan.png) 8 | 9 | Scan Docker installations for security issues and vulnerabilities. 10 | 11 | 12 | ## Features 13 | 14 | - plugin based system for discovery, audit and reporting 15 | - able to scan local and remote docker installations 16 | - plugins are easy to write 17 | 18 | 19 | ## Requirements 20 | 21 | - Ruby 2.0 or above (1.9.x does not work!) 22 | - Ruby gem: docker-api (docker) 23 | 24 | 25 | ## Installation 26 | 27 | You can install dockscan by installing dockscan gem: 28 | 29 | `gem install dockscan` 30 | 31 | ## Usage 32 | 33 | Typical usage for scanning docker installation. 34 | 35 | If you wish to scan local Docker installation: 36 | 37 | `dockscan unix:///var/run/docker.sock` 38 | 39 | If you wish to scan remote Docker installation and produce HTML report: 40 | 41 | `dockscan -r html -o myreport -v tcp://example.com:5422` 42 | 43 | If you wish to scan remote Docker installation and produce text report: 44 | 45 | `dockscan -r txt -o myreport -v tcp://example.com:5422` 46 | 47 | 48 | ## Environment variables 49 | 50 | DOCKER_CERT_PATH will configure dockscan to use SSL 51 | 52 | DOCKER_SSL_VERIFY if set to false will not verify certificates. 53 | 54 | 55 | ### ToDo 56 | - [ ] Implement web frontend for scanner 57 | - [ ] Progress bars 58 | 59 | ### Done 60 | - [x] Different reporting (HTML, txt, ...) 61 | 62 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rspec/core/rake_task" 3 | 4 | RSpec::Core::RakeTask.new(:spec) 5 | 6 | task :default => :spec 7 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "dockscan" 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require "pry" 11 | # Pry.start 12 | 13 | require "irb" 14 | IRB.start 15 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /dockscan.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'dockscan/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "dockscan" 8 | spec.version = Dockscan::VERSION 9 | spec.authors = ["Vlatko Kosturjak"] 10 | spec.email = ["kost@linux.hr"] 11 | 12 | spec.summary = %q{Security vulnerability and audit scanner for Docker installations.} 13 | spec.description = %q{security vulnerability and audit scanner for Docker installations.} 14 | spec.homepage = "https://github.com/kost/dockscan" 15 | spec.license = "MIT" 16 | 17 | spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 18 | spec.bindir = "exe" 19 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 20 | spec.require_paths = ["lib"] 21 | 22 | spec.add_development_dependency "bundler", ">= 1.12" 23 | spec.add_development_dependency "rake", "~> 10.0" 24 | spec.add_development_dependency "rspec", "~> 3.0" 25 | 26 | # spec.add_runtime_dependency 'docker-api', '~> 1.31' 27 | spec.add_runtime_dependency 'docker-api', '>= 0' 28 | end 29 | -------------------------------------------------------------------------------- /docs/dockscan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kost/dockscan/0a21d64bc8ebe2c9c6e0324d7884afe20a6cb13d/docs/dockscan.png -------------------------------------------------------------------------------- /exe/dockscan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'logger' 3 | require 'optparse' 4 | 5 | # add script dir to load path 6 | $:.unshift File.dirname(__FILE__) 7 | 8 | # main thing 9 | # require 'scan/manage-scan' 10 | require 'dockscan' 11 | 12 | $PRGNAME='dockscan' 13 | 14 | # helpful class for logger 15 | class MultiDelegator 16 | def initialize(*targets) 17 | @targets = targets 18 | end 19 | 20 | def self.delegate(*methods) 21 | methods.each do |m| 22 | define_method(m) do |*args| 23 | @targets.map { |t| t.send(m, *args) } 24 | end 25 | end 26 | self 27 | end 28 | 29 | class < 3.2 } 17 | sp.vuln=si 18 | sp.output="" 19 | if scandata.key?("GetContainers") and not scandata["GetContainers"].obj.empty? 20 | sp.state="run" 21 | scandata["GetContainers"].obj.each do |container| 22 | begin 23 | ps=container.changes 24 | if ps.count > limit then 25 | sp.state="vulnerable" 26 | allch = '' 27 | ps.each do |change| 28 | allch << change["Path"] << "\n" 29 | end 30 | sp.output << idcontainer(container) << " has more than #{limit} file changes: #{ps.count}\n" 31 | sp.output << allch 32 | sp.output << "\n" 33 | end 34 | rescue 35 | end 36 | end 37 | end 38 | return sp 39 | end 40 | end 41 | 42 | -------------------------------------------------------------------------------- /lib/dockscan/modules/audit/container-filesystem-shadow.rb: -------------------------------------------------------------------------------- 1 | class ContainerFileSystemShadow < Dockscan::Modules::AuditModule 2 | 3 | def info 4 | return 'This plugin checks /etc/shadow for problems' 5 | end 6 | 7 | def check(dockercheck) 8 | sp=Dockscan::Scan::Plugin.new 9 | si=Dockscan::Scan::Issue.new 10 | si.title="Container have passwordless users in shadow" 11 | si.description="Container have vulnerable entries in /etc/shadow.\nIt allows attacker to login or switch to user without password." 12 | si.solution="It is recommended to set password for user or to lock user account." 13 | si.severity=6 # High 14 | si.risk = { "cvss" => 7.5 } 15 | sp.vuln=si 16 | sp.output="" 17 | if scandata.key?("GetContainers") and not scandata["GetContainers"].obj.empty? 18 | sp.state="run" 19 | scandata["GetContainers"].obj.each do |container| 20 | content='' 21 | container.copy('/etc/shadow') { |chunk| content=content+chunk } 22 | shcontent='' 23 | Gem::Package::TarReader.new(StringIO.new(content)) { |t| shcontent=t.first.read } 24 | # shcontent.split("\n").each do |line| 25 | shcontent.lines.map(&:chomp).each do |line| 26 | shfield=line.split(":") 27 | if shfield[1].to_s=='' then 28 | sp.state="vulnerable" 29 | sp.output << idcontainer(container) << " does not have password set for user: #{shfield[0]}\n" 30 | end 31 | end 32 | end 33 | end 34 | return sp 35 | end 36 | end 37 | 38 | -------------------------------------------------------------------------------- /lib/dockscan/modules/audit/container-number-process.rb: -------------------------------------------------------------------------------- 1 | class ContainerNumberProcess < Dockscan::Modules::AuditModule 2 | 3 | def info 4 | return 'This plugin checks number of container processes' 5 | end 6 | 7 | def check(dockercheck) 8 | 9 | limit=1 10 | sp=Dockscan::Scan::Plugin.new 11 | si=Dockscan::Scan::Issue.new 12 | si.title="Container have higher number of processess" 13 | si.description="Container have more than allowable number of processes.\nThis is not recommended for production as it does not provide intended isolation." 14 | si.solution="It is recommended to have single process inside container. If you have more than one process, it is recommended to split them in separate containers." 15 | si.severity=4 # Low 16 | si.risk = { "cvss" => 3.2 } 17 | sp.vuln=si 18 | sp.output="" 19 | if scandata.key?("GetContainersRunning") and not scandata["GetContainersRunning"].obj.empty? 20 | sp.state="run" 21 | scandata["GetContainersRunning"].obj.each do |container| 22 | ps=container.top 23 | if ps.count > limit then 24 | sp.state="vulnerable" 25 | sp.output << idcontainer(container) << " has more than #{limit} process(es): #{ps.count}\n" 26 | ps.each do |process| 27 | sp.output << process["CMD"] << "\n" 28 | end 29 | sp.output << "\n" 30 | end 31 | end 32 | end 33 | return sp 34 | end 35 | end 36 | 37 | -------------------------------------------------------------------------------- /lib/dockscan/modules/audit/container-sshd-process.rb: -------------------------------------------------------------------------------- 1 | class ContainerSSHProcess < Dockscan::Modules::AuditModule 2 | 3 | def info 4 | return 'This plugin checks if SSH is running inside container' 5 | end 6 | 7 | def check(dockercheck) 8 | sp=Dockscan::Scan::Plugin.new 9 | si=Dockscan::Scan::Issue.new 10 | si.title="Container have SSH server process" 11 | si.description="Docker daemon reports it is running SSH daemon inside container.\nThis is not recommended practice as it provides yet another attack surface for attackers and wastes computer resources." 12 | si.solution="It is recommended to remove SSH daemon/client from container. It is recommended to use docker exec command to execute commands inside container." 13 | si.severity=4 # Low 14 | si.risk = { "cvss" => 3.2 } 15 | sp.vuln=si 16 | sp.output="" 17 | if scandata.key?("GetContainersRunning") and not scandata["GetContainersRunning"].obj.empty? 18 | sp.state="run" 19 | scandata["GetContainersRunning"].obj.each do |container| 20 | ps=container.top 21 | ps.each do |process| 22 | if process["CMD"].include?("ssh") then 23 | sp.output << idcontainer(container) << " has SSH process running: " << process["CMD"] << "\n" 24 | sp.state="vulnerable" 25 | break 26 | end 27 | end 28 | end 29 | end 30 | return sp 31 | end 32 | end 33 | 34 | -------------------------------------------------------------------------------- /lib/dockscan/modules/audit/docker-experimental-build.rb: -------------------------------------------------------------------------------- 1 | class DockerExperimentalBuild < Dockscan::Modules::AuditModule 2 | 3 | def info 4 | return 'This plugin checks if docker is running Experimental Build' 5 | end 6 | 7 | def check(dockercheck) 8 | sp=Dockscan::Scan::Plugin.new 9 | si=Dockscan::Scan::Issue.new 10 | si.title="Running Experimental version of Docker." 11 | si.description="Docker daemon reports it is running ExperimentalBuild.\nThis is not recommended for production as it might have problems and security issues." 12 | si.solution="It is recommended to replace Docker version with stable and production ready one." 13 | si.severity=6 # High 14 | si.risk = { "cvss" => 7.0 } 15 | si.reflinks = {"Docker's Experimental Binary" => "https://blog.docker.com/2015/06/experimental-binary/"} 16 | sp.vuln=si 17 | if scandata.key?("GetDockerInfo") and scandata["GetDockerInfo"].obj.key?("ExperimentalBuild") 18 | sp.state="run" 19 | if scandata["GetDockerInfo"].obj["ExperimentalBuild"] == true then 20 | sp.output = "Docker daemon reports it is running ExperimentalBuild." 21 | sp.state="vulnerable" 22 | end 23 | end 24 | return sp 25 | end 26 | end 27 | 28 | -------------------------------------------------------------------------------- /lib/dockscan/modules/audit/docker-insecure-registries.rb: -------------------------------------------------------------------------------- 1 | class DockerInsecureRegistries < Dockscan::Modules::AuditModule 2 | 3 | def info 4 | return 'This plugin checks if insecure registries in use' 5 | end 6 | 7 | def check(dockercheck) 8 | sp=Dockscan::Scan::Plugin.new 9 | si=Dockscan::Scan::Issue.new 10 | si.title="Insecure registries in use" 11 | si.description="Docker daemon reports it is running configuration with insecure registries.\nThis is not recommended as attacker is able to deploy malicious images to registries." 12 | si.solution="It is recommended to use secure registries and configuration without insecure registries." 13 | si.severity=4 # Low 14 | si.risk = { "cvss" => 3.2 } 15 | si.references = {"CIS" => "2.5 Do not use insecure registries" } 16 | sp.vuln=si 17 | if scandata.key?("GetDockerInfo") and scandata["GetDockerInfo"].obj.key?("RegistryConfig") 18 | sp.state="run" 19 | vulnerable = false 20 | outputregs = "" 21 | outputindexs = "" 22 | # ["RegistryConfig"]["InsecureRegistryCIDRs"].each do |item| puts item end 23 | scandata["GetDockerInfo"].obj["RegistryConfig"]["InsecureRegistryCIDRs"].each do |item| 24 | if item != "127.0.0.0/8" then 25 | vulnerable=true 26 | outputregs = item << "\n" 27 | end 28 | end 29 | # Docker.info["RegistryConfig"]["IndexConfigs"].each do |item,value| puts item,value,value["Secure"] end 30 | scandata["GetDockerInfo"].obj["RegistryConfig"]["IndexConfigs"].each do |item, value| 31 | if value["Secure"] != true 32 | vulnerable=true 33 | outputindexs = item value["Name"] << "\n" 34 | end 35 | end 36 | 37 | if vulnerable then 38 | sp.state="vulnerable" 39 | sp.output = "Docker daemon reports it is using insecure registries. Offending issues below.\n " 40 | if outputregs != "" then 41 | sp.output << "Insecure CIDRs offending configuration:\n" 42 | sp.output << outputregs << "\n" 43 | end 44 | if outputindexs != "" then 45 | sp.output << "Offending registry indexes:\n" 46 | sp.output << outputindexs << "\n" 47 | end 48 | end 49 | end 50 | return sp 51 | end 52 | end 53 | 54 | -------------------------------------------------------------------------------- /lib/dockscan/modules/audit/docker-limits.rb: -------------------------------------------------------------------------------- 1 | class DockerLimits < Dockscan::Modules::AuditModule 2 | 3 | def info 4 | return 'This plugin checks if docker is running with defined limits' 5 | end 6 | 7 | def check(dockercheck) 8 | sp=Dockscan::Scan::Plugin.new 9 | si=Dockscan::Scan::Issue.new 10 | si.title="Docker running without defined limits" 11 | si.description="Docker daemon reports it is running daemon without defined limits.\nThis is not recommended as offending containers could use up all resources." 12 | si.solution="It is recommended to define docker limits." 13 | si.severity=5 # Medium 14 | si.risk = { "cvss" => 4.4 } 15 | si.references = {"CIS" => "2.10 Set default ulimit as appropriate" } 16 | sp.output="" 17 | sp.vuln=si 18 | if scandata.key?("GetDockerInfo") and scandata["GetDockerInfo"].obj.key?("MemoryLimit") 19 | sp.state="run" 20 | if scandata["GetDockerInfo"].obj["MemoryLimit"] == false then 21 | sp.output << "Docker daemon reports it is running without memory limit.\n" 22 | sp.state="vulnerable" 23 | end 24 | end 25 | if scandata.key?("GetDockerInfo") and scandata["GetDockerInfo"].obj.key?("SwapLimit") 26 | if scandata["GetDockerInfo"].obj["SwapLimit"] == false then 27 | sp.output << "Docker daemon reports it is running without swap limit.\n" 28 | sp.state="vulnerable" 29 | end 30 | end 31 | return sp 32 | end 33 | end 34 | 35 | -------------------------------------------------------------------------------- /lib/dockscan/modules/audit/docker-networking-forwarding.rb: -------------------------------------------------------------------------------- 1 | class DockerIPV4Forwarding < Dockscan::Modules::AuditModule 2 | 3 | def info 4 | return 'This plugin checks if docker is running with ipv4 forwarding enabled' 5 | end 6 | 7 | def check(dockercheck) 8 | sp=Dockscan::Scan::Plugin.new 9 | si=Dockscan::Scan::Issue.new 10 | si.title="Docker running with IPv4 forwarding enabled" 11 | si.description="Docker daemon reports it is running daemon with IPv4 forwarding enabled.\nThis is not recommended for production as it forwards network packets without rules." 12 | si.solution="It is recommended to disable IPv4 forwarding by default." 13 | si.severity=5 # Medium 14 | si.risk = { "cvss" => 5.0 } 15 | si.reflinks = {"ip_forward to expose containers to the public internet" => "https://github.com/docker/docker/issues/11508"} 16 | sp.vuln=si 17 | if scandata.key?("GetDockerInfo") and scandata["GetDockerInfo"].obj.key?("IPv4Forwarding") 18 | sp.state="run" 19 | if scandata["GetDockerInfo"].obj["IPv4Forwarding"] == true then 20 | sp.output = "Docker daemon reports it is running with automatic IPv4 forwarding." 21 | sp.state="vulnerable" 22 | end 23 | end 24 | return sp 25 | end 26 | end 27 | 28 | -------------------------------------------------------------------------------- /lib/dockscan/modules/audit/docker-registry-mirror.rb: -------------------------------------------------------------------------------- 1 | class DockerRegistryMirror < Dockscan::Modules::AuditModule 2 | 3 | def info 4 | return 'This plugin checks if mirror registries are in use' 5 | end 6 | 7 | def check(dockercheck) 8 | sp=Dockscan::Scan::Plugin.new 9 | si=Dockscan::Scan::Issue.new 10 | si.title="Docker registries are not mirrored" 11 | si.description="Docker daemon reports it is running configuration without registry mirrors.\nIf you set up local mirror, your docker host does not have to go directly to internet if not needed." 12 | si.solution="It is recommended to setup mirror registry." 13 | si.severity=4 # Low 14 | si.risk = { "cvss" => 3.0 } 15 | si.references = {"CIS" => "2.6 Setup a local registry mirror" } 16 | sp.vuln=si 17 | if scandata.key?("GetDockerInfo") and scandata["GetDockerInfo"].obj.key?("RegistryConfig") 18 | sp.state="run" 19 | vulnerable=true 20 | outputindexs = "" 21 | scandata["GetDockerInfo"].obj["RegistryConfig"]["IndexConfigs"].each do |item, value| 22 | if value["Mirrors"] != nil 23 | vulnerable = false 24 | else 25 | outputindexs << value["Name"] << "\n" 26 | end 27 | end 28 | 29 | if vulnerable then 30 | sp.state="vulnerable" 31 | sp.output = "Docker daemon reports it does not have mirror registries.\n" 32 | if outputindexs != "" then 33 | sp.output << "Offending registry indexes:\n" 34 | sp.output << outputindexs << "\n" 35 | end 36 | end 37 | end 38 | return sp 39 | end 40 | end 41 | 42 | -------------------------------------------------------------------------------- /lib/dockscan/modules/audit/docker-storage-driver-aufs.rb: -------------------------------------------------------------------------------- 1 | class DockerStorageDriverAufs < Dockscan::Modules::AuditModule 2 | 3 | def info 4 | return 'This plugin checks if storage driver is aufs' 5 | end 6 | 7 | def check(dockercheck) 8 | sp=Dockscan::Scan::Plugin.new 9 | si=Dockscan::Scan::Issue.new 10 | si.title="Running aufs as storage driver" 11 | si.description="Docker daemon reports it is running aufs as storage driver.\nThis is not recommended for production as it might have problems and security issues." 12 | si.solution="It is recommended to use devicemapper instead of aufs storage driver. Actually, you should use the storage driver that is best supported by your vendor." 13 | si.severity=4 # Low 14 | si.risk = { "cvss" => 3.2 } 15 | si.reflinks = {"Switching Docker from aufs to devicemapper" => "http://muehe.org/posts/switching-docker-from-aufs-to-devicemapper/"} 16 | si.references = {"CIS" => "2.7 Do not use the aufs storage driver" } 17 | sp.vuln=si 18 | if scandata.key?("GetDockerInfo") and scandata["GetDockerInfo"].obj.key?("Driver") 19 | sp.state="run" 20 | if scandata["GetDockerInfo"].obj["Driver"] == true then 21 | sp.output = "Docker daemon reports it is running aufs as storage driver." 22 | sp.state="vulnerable" 23 | end 24 | end 25 | return sp 26 | end 27 | end 28 | 29 | -------------------------------------------------------------------------------- /lib/dockscan/modules/discover.rb: -------------------------------------------------------------------------------- 1 | module Dockscan 2 | module Modules 3 | 4 | class DiscoverModule < GenericModule 5 | attr_accessor :scandata 6 | def info 7 | raise "#{self.class.name} doesn't implement `handle_command`!" 8 | end 9 | 10 | def run 11 | raise "#{self.class.name} doesn't implement `handle_command`!" 12 | end 13 | end # class 14 | 15 | end # Module 16 | end # Dockscan 17 | -------------------------------------------------------------------------------- /lib/dockscan/modules/discover/get-containers.rb: -------------------------------------------------------------------------------- 1 | class GetContainers < Dockscan::Modules::DiscoverModule 2 | 3 | def info 4 | return 'Container discovery module' 5 | end 6 | 7 | def run 8 | sp=Dockscan::Scan::Plugin.new 9 | sp.obj = Docker::Container.all(:all => true) 10 | sp.output = sp.obj.map{|k,v| "#{k}=#{v}"}.join("\n") 11 | return sp 12 | end 13 | end 14 | 15 | -------------------------------------------------------------------------------- /lib/dockscan/modules/discover/get-docker-info.rb: -------------------------------------------------------------------------------- 1 | require 'docker' 2 | 3 | class GetDockerInfo < Dockscan::Modules::DiscoverModule 4 | 5 | def info 6 | return 'Info discovery module' 7 | end 8 | 9 | def run 10 | sp=Dockscan::Scan::Plugin.new 11 | sp.obj = Docker.info 12 | sp.output = sp.obj.map{|k,v| "#{k}=#{v}"}.join("\n") 13 | sp.state = "run" 14 | return sp 15 | end 16 | end 17 | 18 | -------------------------------------------------------------------------------- /lib/dockscan/modules/discover/get-docker-version.rb: -------------------------------------------------------------------------------- 1 | class GetDockerVersion < Dockscan::Modules::DiscoverModule 2 | 3 | def info 4 | return 'Info discovery module' 5 | end 6 | 7 | def run 8 | sp=Dockscan::Scan::Plugin.new 9 | sp.obj = Docker.version 10 | sp.output = sp.obj.map{|k,v| "#{k}=#{v}"}.join("\n") 11 | return sp 12 | end 13 | end 14 | 15 | -------------------------------------------------------------------------------- /lib/dockscan/modules/discover/get-images.rb: -------------------------------------------------------------------------------- 1 | require 'docker' 2 | 3 | class GetImages < Dockscan::Modules::DiscoverModule 4 | 5 | def info 6 | return 'Image discovery module' 7 | end 8 | 9 | def run 10 | sp=Dockscan::Scan::Plugin.new 11 | sp.obj = Docker::Image.all 12 | sp.output = sp.obj.map{|k,v| "#{k}=#{v}"}.join("\n") 13 | return sp 14 | end 15 | end 16 | 17 | -------------------------------------------------------------------------------- /lib/dockscan/modules/discover/get-run-containers.rb: -------------------------------------------------------------------------------- 1 | class GetContainersRunning < Dockscan::Modules::DiscoverModule 2 | 3 | def info 4 | return 'Running Container discovery module' 5 | end 6 | 7 | def run 8 | sp=Dockscan::Scan::Plugin.new 9 | sp.obj = Docker::Container.all 10 | sp.output = sp.obj.map{|k,v| "#{k}=#{v}"}.join("\n") 11 | return sp 12 | end 13 | end 14 | 15 | -------------------------------------------------------------------------------- /lib/dockscan/modules/genmodule.rb: -------------------------------------------------------------------------------- 1 | module Dockscan 2 | module Modules 3 | 4 | class GenericModule 5 | def self.modules 6 | @modules ||= [] 7 | end 8 | 9 | def self.inherited(klass) 10 | @modules ||= [] 11 | 12 | @modules << klass 13 | end 14 | end # Class 15 | 16 | end # Module 17 | end # Dockscan 18 | -------------------------------------------------------------------------------- /lib/dockscan/modules/report.rb: -------------------------------------------------------------------------------- 1 | module Dockscan 2 | module Modules 3 | 4 | class ReportModule < GenericModule 5 | attr_accessor :scandata 6 | 7 | def info 8 | raise "#{self.class.name} doesn't implement `handle_command`!" 9 | end 10 | 11 | def desc(item) 12 | if item.vuln.description 13 | return item.vuln.description 14 | else 15 | return false 16 | end 17 | end 18 | 19 | def sortvulns 20 | severity_sorted=Hash.new 21 | scandata.each do |classname,scanissue| 22 | if scanissue.state == "vulnerable" or scanissue.state=="info" then 23 | severity_sorted[scanissue.vuln.severity] ||= [] 24 | severity_sorted[scanissue.vuln.severity] << scanissue 25 | end 26 | end 27 | return severity_sorted 28 | end 29 | 30 | def format 31 | return "unknown" 32 | end 33 | 34 | def file_extension 35 | return ".unknown" 36 | end 37 | 38 | def getkey(hsh,hkey) 39 | if hsh.has_key?(hkey) then 40 | return hsh[hkey] 41 | else 42 | return '' 43 | end 44 | end 45 | 46 | def sev2word(sev) 47 | case sev 48 | when 7 49 | return "Critical" 50 | when 6 51 | return "High" 52 | when 5 53 | return "Medium" 54 | when 4 55 | return "Low" 56 | when 3 57 | return "Info" 58 | when 2 59 | return "Verbose" 60 | when 1 61 | return "Inspect" 62 | when 0 63 | return "Debug" 64 | else 65 | return "DebugMiss" 66 | end 67 | end 68 | 69 | def report(opts) 70 | raise "#{self.class.name} doesn't implement `handle_command`!" 71 | end 72 | end # Class 73 | 74 | end # Module 75 | end # Dockscan 76 | -------------------------------------------------------------------------------- /lib/dockscan/modules/report/html.rb: -------------------------------------------------------------------------------- 1 | class ReportHTML < Dockscan::Modules::ReportModule 2 | 3 | def info 4 | return 'This plugin produces HTML reports' 5 | end 6 | 7 | def file_extension 8 | return ".html" 9 | end 10 | 11 | def format 12 | return "html" 13 | end 14 | 15 | def htmlhead 16 | htmlout = "" 17 | htmlout << "dockscan Report\n" 18 | htmlout << '' 130 | htmlout << "\n" 131 | htmlout << "\n" 132 | htmlout << '' 133 | return htmlout 134 | end 135 | 136 | def htmlfoot 137 | htmlout = "" 138 | htmlout << "\n" 139 | htmlout << "\n" 140 | return htmlout 141 | end 142 | 143 | def report(opts) 144 | output="" 145 | output << htmlhead 146 | output << "

dockscan Report

" 147 | issues = sortvulns 148 | 7.downto(3) do |sev| 149 | if issues.key?(sev) 150 | output << "

" << sev2word(sev) << " vulnerabilities

\n" 151 | issues[sev].each do |v| 152 | if sev >= 7 then 153 | output << '' 154 | elsif sev == 6 then 155 | output << '
' 156 | elsif sev == 5 157 | output << '
' 158 | elsif sev == 4 159 | output << '
' 160 | elsif sev == 3 161 | output << '
' 162 | else 163 | output << '
' 164 | end 165 | output << "\n" 166 | output << "\n" 167 | output << "\n" 168 | output << "\n" 169 | output << "\n" 170 | output << "\n" 171 | output << "\n" 172 | output << "\n" 173 | output << "
" << v.vuln.title << "" << sev2word(v.vuln.severity) << "
" << v.vuln.description << "
" << v.output << "
" << v.vuln.solution << "
CVSS: " << getkey(v.vuln.risk,"cvss").to_s << "State: " << v.state << "
\n\n" 174 | end 175 | output << "

\n" 176 | end 177 | end 178 | return output 179 | 180 | end 181 | end 182 | 183 | -------------------------------------------------------------------------------- /lib/dockscan/modules/report/stdout.rb: -------------------------------------------------------------------------------- 1 | class ReportStdout < Dockscan::Modules::ReportModule 2 | 3 | def info 4 | return 'This plugin produces brief stdout reports' 5 | end 6 | 7 | def format 8 | return "stdout" 9 | end 10 | 11 | def file_extension 12 | return "-stdout.txt" 13 | end 14 | 15 | def report(opts) 16 | output="" 17 | output << "Dockscan Report\n\n" 18 | 19 | issues = sortvulns 20 | 7.downto(3) do |sev| 21 | if issues.key?(sev) 22 | output << sev2word(sev) << "\n" 23 | issues[sev].each do |v| 24 | # output << k << ": " 25 | output << v.vuln.title << ": " 26 | output << v.vuln.solution 27 | # output << v.output 28 | output << "\n" 29 | end 30 | output << "\n" 31 | end 32 | end 33 | return output 34 | end 35 | end 36 | 37 | -------------------------------------------------------------------------------- /lib/dockscan/modules/report/txt.rb: -------------------------------------------------------------------------------- 1 | class ReportText < Dockscan::Modules::ReportModule 2 | 3 | def info 4 | return 'This plugin produces text reports' 5 | end 6 | 7 | def format 8 | return "txt" 9 | end 10 | 11 | def file_extension 12 | return ".txt" 13 | end 14 | 15 | def report(opts) 16 | output="" 17 | output << "Dockscan Report\n\n" 18 | 19 | issues = sortvulns 20 | 7.downto(3) do |sev| 21 | if issues.key?(sev) 22 | output << "-[ " << sev2word(sev) << " ]-\n" 23 | issues[sev].each do |v| 24 | output << "=" << v.vuln.title << "=\n" 25 | output << "Description:\n" << v.vuln.description << "\n" 26 | output << "Output:\n" << v.output << "\n" 27 | output << "Solution:\n" << v.vuln.solution << "\n" 28 | output << "\n" 29 | end 30 | output << "\n" 31 | end 32 | end 33 | return output 34 | end 35 | end 36 | 37 | -------------------------------------------------------------------------------- /lib/dockscan/scan/issue.rb: -------------------------------------------------------------------------------- 1 | module Dockscan 2 | module Scan 3 | class Issue 4 | attr_accessor :severity, :description, :solution, :title, :tags, :references, :risk, :reflinks 5 | 6 | def initialize 7 | severity=0 8 | description="Forgot to add description" 9 | solution="Information only." 10 | title="Forgot to add title" 11 | tags=Hash.new 12 | references=Hash.new 13 | risk=Hash.new 14 | reflinks=Hash.new 15 | end 16 | end # Issue 17 | end # Scan 18 | end # Dockscan 19 | -------------------------------------------------------------------------------- /lib/dockscan/scan/manage.rb: -------------------------------------------------------------------------------- 1 | require 'docker' 2 | 3 | require 'dockscan/modules/genmodule' 4 | require 'dockscan/modules/discover' 5 | require 'dockscan/modules/audit' 6 | require 'dockscan/modules/report' 7 | 8 | require 'dockscan/scan/plugin' 9 | 10 | require 'pp' 11 | 12 | module Dockscan 13 | module Scan 14 | class Manage 15 | 16 | attr_accessor :log 17 | 18 | def initialize 19 | @auditoutput=Hash.new 20 | @log=Logger.new MultiDelegator.delegate(:write, :close).to(STDERR) 21 | end 22 | 23 | def check_connection 24 | @log.info("Validating version specified: "+Docker.url) 25 | begin 26 | Docker.validate_version! 27 | rescue 28 | @log.error("Error connecting or validating Docker version") 29 | return false 30 | end 31 | return true 32 | end 33 | 34 | def scan (url, opts, logger) 35 | failed=Array.new 36 | 37 | if url != nil then 38 | Docker.url = url 39 | end 40 | 41 | @log=logger 42 | 43 | if not check_connection then 44 | return 45 | end 46 | 47 | moduledirs=Array.new 48 | if moduledirs.empty? then 49 | moduledirs.push File.expand_path("../modules", File.dirname(__FILE__)) 50 | end 51 | 52 | @log.info("Loading discovery modules...") 53 | moduledirs.each do |moduledir| 54 | @log.debug("Loading dir: #{moduledir}") 55 | Dir["#{moduledir}/discover/*.rb"].each do |f| 56 | @log.info("Loading discovery module: #{f}") 57 | begin 58 | require f 59 | rescue SyntaxError => se 60 | @log.info("Error loading audit module: #{f}") 61 | @log.debug("Error executing audit module: #{se.backtrace}") 62 | failed << f 63 | end 64 | end 65 | end 66 | 67 | @log.info("Running discovery modules...") 68 | Dockscan::Modules::DiscoverModule.modules.each do |modclass| 69 | @log.info("Running discovery module: #{modclass.name}") 70 | begin 71 | mod=modclass.new 72 | mod.scandata=@auditoutput 73 | @auditoutput[mod.class.name]=mod.run 74 | rescue Exception => e 75 | @log.info("Error executing audit module: #{modclass.name}") 76 | @log.debug("Error executing audit module: #{e.backtrace}") 77 | failed << modclass.name 78 | end 79 | # pp @auditoutput[mod.class.name] 80 | end 81 | 82 | 83 | @log.info("Loading audit modules...") 84 | moduledirs.each do |moduledir| 85 | @log.debug("Loading dir: #{moduledir}") 86 | Dir["#{moduledir}/audit/*.rb"].each do |f| 87 | @log.info("Loading audit module: #{f}") 88 | begin 89 | require f 90 | rescue SyntaxError => se 91 | @log.info("Error loading audit module: #{f}") 92 | @log.debug("Error executing audit module: #{se.backtrace}") 93 | failed << f 94 | end 95 | end 96 | end 97 | 98 | @log.info("Running audit modules...") 99 | Dockscan::Modules::AuditModule.modules.each do |modclass| 100 | @log.info("Running audit module: #{modclass.name}") 101 | begin 102 | mod=modclass.new 103 | mod.scandata=@auditoutput 104 | @auditoutput[mod.class.name]=mod.check('test') 105 | rescue Exception => e 106 | @log.info("Error executing audit module: #{modclass.name}") 107 | @log.debug("Error executing audit module: #{e.to_s} #{e.backtrace}") 108 | failed << modclass.name 109 | end 110 | # pp @auditoutput[mod.class.name] 111 | end 112 | 113 | @log.info("Loading report modules...") 114 | moduledirs.each do |moduledir| 115 | Dir["#{moduledir}/report/*.rb"].each do |f| 116 | @log.info("Loading report #{f}") 117 | begin 118 | require f 119 | rescue SyntaxError => se 120 | @log.info("Error loading report module: #{f}") 121 | @log.debug("Error executing report module: #{se.backtrace}") 122 | failed << f 123 | end 124 | end 125 | end 126 | 127 | @log.info("Running report modules...") 128 | Dockscan::Modules::ReportModule.modules.each do |modclass| 129 | @log.info("Running report module: #{modclass.name}") 130 | mod=modclass.new 131 | if opts.key?("report") then 132 | formats=opts["report"].split(",") 133 | else 134 | formats=['stdout'] 135 | end 136 | formats.each do |fmt| 137 | if fmt==mod.format then 138 | begin 139 | mod.scandata=@auditoutput 140 | output=mod.report(nil) 141 | if opts.key?("output") then 142 | reportfilename = opts["output"] 143 | reportfilename << mod.file_extension 144 | File.open(reportfilename, 'w') { |file| file.write(output) } 145 | else 146 | puts output 147 | end 148 | rescue Exception => e 149 | @log.info("Error executing report module: #{modclass.name}") 150 | @log.debug("Error executing report module: #{e.to_s} #{e.backtrace}") 151 | failed << modclass.name 152 | end 153 | @log.debug(output) 154 | else 155 | @log.debug("Skipping report module: #{modclass.name}"); 156 | end 157 | end 158 | end 159 | 160 | if failed.count > 0 161 | failedstr="" 162 | failed.each do |f| 163 | failedstr = f + " " 164 | end 165 | @log.warn("Following modules failed: #{failedstr}") 166 | end 167 | end 168 | 169 | end # Class 170 | end # Scan 171 | end # Dockscan 172 | 173 | -------------------------------------------------------------------------------- /lib/dockscan/scan/plugin.rb: -------------------------------------------------------------------------------- 1 | require 'dockscan/scan/issue' 2 | 3 | module Dockscan 4 | module Scan 5 | class Plugin 6 | attr_accessor :state, :output, :obj, :vuln, :cname 7 | 8 | def initialize 9 | state="untested" 10 | vuln = Dockscan::Scan::Issue.new 11 | end 12 | end # Plugin 13 | end # Scan 14 | end # Dockscan 15 | -------------------------------------------------------------------------------- /lib/dockscan/version.rb: -------------------------------------------------------------------------------- 1 | module Dockscan 2 | VERSION = "0.1.2" 3 | end 4 | -------------------------------------------------------------------------------- /spec/dockscan_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Dockscan do 4 | it 'has a version number' do 5 | expect(Dockscan::VERSION).not_to be nil 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) 2 | require 'dockscan' 3 | --------------------------------------------------------------------------------