diff --git a/docs/resources/docker.md.erb b/docs/resources/docker.md.erb index 81dc8a8dd..197f7f9b7 100644 --- a/docs/resources/docker.md.erb +++ b/docs/resources/docker.md.erb @@ -148,6 +148,17 @@ Or execute the profile directly via URL: its('sizes') { should_not include "1.41 GB" } end +### plugins + +`plugins` returns information about Docker plugins as returned by [docker plugin ls](https://docs.docker.com/engine/reference/commandline/plugin/). + + describe docker.plugins do + its('names') { should include ["store/weaveworks/net-plugin", "docker4x/cloudstor"] } + its('ids') { should cmp ["6ea8176de74b", "771d3ee7c7ea"] } + its('versions') { should cmp ["2.3.0", "18.03.1-ce-aws1"] } + its('enabled') { should cmp [true, false] } + end + ### info `info` returns the parsed result of [docker info](https://docs.docker.com/engine/reference/commandline/info/) diff --git a/docs/resources/docker_plugin.md.erb b/docs/resources/docker_plugin.md.erb new file mode 100644 index 000000000..61ca42f12 --- /dev/null +++ b/docs/resources/docker_plugin.md.erb @@ -0,0 +1,80 @@ +--- +title: About the docker_plugin Resource +platform: linux +--- + +# docker_plugin + +Use the `docker_plugin` InSpec audit resource to verify a Docker plugin. + +
+ +## Syntax + +A `docker_plugin` resource block declares the plugin: + + describe docker_plugin('rexray/ebs') do + it { should exist } + its('id') { should_not eq '0ac30b93ad40' } + its('version') { should eq '0.11.1' } + it { should be_enabled } + end + +
+ +## Resource Parameter Examples + +The resource allows you to pass in an plugin id: + + describe docker_plugin(id: plugin_id) do + it { should be_enabled } + end + +
+ +## Properties + +### id + +The `id` property returns the full plugin id: + + describe docker_plugin('cloudstor/aws') do + its('id') { should eq '0ac30b93ad40' } + end + +### version + +The `version` property tests the value of plugin version: + + describe docker_plugin('cloudstor/aws') do + its('version') { should eq '0.11.0' } + end + +## Examples + +### Test a Docker plugin + + describe docker_plugin('rexray/ebs') do + it { should exist } + its('id') { should_not eq '0ac30b93ad40' } + its('version') { should eq '0.11.1' } + it { should be_enabled } + end + +
+ +## Matchers + +For a full list of available matchers, please visit our [Universal Matchers](https://www.inspec.io/docs/reference/matchers/). + +### exist + +The `exist` matcher tests if the plugin is available on the node: + + describe docker_plugin('rexray/ebs') do + it { should exist } + end + +### enabled + +The `be_enabled` matches tests if the plugin is enabled diff --git a/lib/inspec/resource.rb b/lib/inspec/resource.rb index 1c09603d3..1a255eeba 100644 --- a/lib/inspec/resource.rb +++ b/lib/inspec/resource.rb @@ -128,6 +128,7 @@ require 'resources/directory' require 'resources/docker' require 'resources/docker_container' require 'resources/docker_image' +require 'resources/docker_plugin' require 'resources/docker_service' require 'resources/elasticsearch' require 'resources/etc_fstab' diff --git a/lib/resources/docker.rb b/lib/resources/docker.rb index 07e03025d..fa048215b 100644 --- a/lib/resources/docker.rb +++ b/lib/resources/docker.rb @@ -52,6 +52,20 @@ module Inspec::Resources end end + class DockerPluginFilter + filter = FilterTable.create + filter.add(:ids, field: 'id') + .add(:names, field: 'name') + .add(:versions, field: 'version') + .add(:enabled, field: 'enabled') + filter.connect(self, :plugins) + + attr_reader :plugins + def initialize(plugins) + @plugins = plugins + end + end + class DockerServiceFilter filter = FilterTable.create filter.register_custom_matcher(:exists?) { |x| !x.entries.empty? } @@ -89,6 +103,10 @@ module Inspec::Resources its('repositories') { should_not include 'inssecure_image' } end + describe docker.plugins.where { name == 'rexray/ebs' } do + it { should exist } + end + describe docker.services do its('images') { should_not include 'inssecure_image' } end @@ -119,6 +137,10 @@ module Inspec::Resources DockerImageFilter.new(parse_images) end + def plugins + DockerPluginFilter.new(parse_plugins) + end + def services DockerServiceFilter.new(parse_services) end @@ -226,5 +248,17 @@ module Inspec::Resources warn 'Could not parse `docker images` output' [] end + + def parse_plugins + plugins = inspec.command('docker plugin ls --format \'{"id": {{json .ID}}, "name": "{{ with split .Name ":"}}{{index . 0}}{{end}}", "version": "{{ with split .Name ":"}}{{index . 1}}{{end}}", "enabled": {{json .Enabled}} }\'').stdout + c_plugins = [] + plugins.each_line { |entry| + c_plugins.push(JSON.parse(entry)) + } + c_plugins + rescue JSON::ParserError => _e + warn 'Could not parse `docker plugin ls` output' + [] + end end end diff --git a/lib/resources/docker_plugin.rb b/lib/resources/docker_plugin.rb new file mode 100644 index 000000000..5b3bc3811 --- /dev/null +++ b/lib/resources/docker_plugin.rb @@ -0,0 +1,63 @@ +# encoding: utf-8 + +module Inspec::Resources + class DockerPlugin < Inspec.resource(1) + name 'docker_plugin' + supports platform: 'unix' + desc 'Retrieves info about docker plugins' + example " + describe docker_plugin('rexray/ebs') do + it { should exist } + its('id') { should_not eq '0ac30b93ad40' } + its('version') { should eq '0.11.1' } + it { should be_enabled } + end + + describe docker_plugin('alpine:latest') do + it { should exist } + end + + describe docker_plugin(id: '4a415e366388') do + it { should exist } + end + " + + def initialize(opts = {}) + # do sanitizion of input values + o = opts.dup + o = { name: opts } if opts.is_a?(String) + @opts = o + end + + def exist? + object_info.entries.size == 1 + end + + def enabled? + object_info.enabled[0] + end + + def id + object_info.ids[0] if object_info.entries.size == 1 + end + + def version + object_info.versions[0] if object_info.entries.size == 1 + end + + def to_s + plugin = @opts[:name] || @opts[:id] + "Docker plugin #{plugin}" + end + + private + + def object_info + return @info if defined?(@info) + opts = @opts + @info = inspec.docker.plugins.where { + (name == opts[:name]) || (!id.nil? && !opts[:id].nil? && (id == opts[:id])) + } + end + end +end diff --git a/test/helper.rb b/test/helper.rb index e502ec7d4..7b0278831 100644 --- a/test/helper.rb +++ b/test/helper.rb @@ -425,6 +425,8 @@ class MockLoader "83c36bfade9375ae1feb91023cd1f7409b786fd992ad4013bf0f2259d33d6406" => cmd.call('docker-images'), # docker services %{docker service ls --format '{"ID": {{json .ID}}, "Name": {{json .Name}}, "Mode": {{json .Mode}}, "Replicas": {{json .Replicas}}, "Image": {{json .Image}}, "Ports": {{json .Ports}}}'} => cmd.call('docker-service-ls'), + # docker plugins + %{docker plugin ls --format '{"id": {{json .ID}}, "name": "{{ with split .Name ":"}}{{index . 0}}{{end}}", "version": "{{ with split .Name ":"}}{{index . 1}}{{end}}", "enabled": {{json .Enabled}} }'} => cmd.call('docker-plugin-ls'), # modprobe for kernel_module "modprobe --showconfig" => cmd.call('modprobe-config'), # get-process cmdlet for processes resource diff --git a/test/unit/mock/cmd/docker-plugin-ls b/test/unit/mock/cmd/docker-plugin-ls new file mode 100644 index 000000000..3393f84b8 --- /dev/null +++ b/test/unit/mock/cmd/docker-plugin-ls @@ -0,0 +1,2 @@ +{"id": "6ea8176de74b", "name": "store/weaveworks/net-plugin", "version": "2.3.0", "enabled": true } +{"id": "771d3ee7c7ea", "name": "docker4x/cloudstor", "version": "18.03.1-ce-aws1", "enabled": false } diff --git a/test/unit/resources/docker_plugin_test.rb b/test/unit/resources/docker_plugin_test.rb new file mode 100644 index 000000000..7cd0ca825 --- /dev/null +++ b/test/unit/resources/docker_plugin_test.rb @@ -0,0 +1,39 @@ +# encoding: utf-8 +# author: Noel Georgi + +require 'helper' +require 'inspec/resource' + +describe 'Inspec::Resources::DockerContainer' do + describe 'docker_plugin' do + it 'check plugin parsing for docker4x/cloudstor' do + resource = load_resource('docker_plugin', 'docker4x/cloudstor') + _(resource.id).must_equal '771d3ee7c7ea' + _(resource.version).must_equal '18.03.1-ce-aws1' + _(resource.enabled?).must_equal false + _(resource.exist?).must_equal true + end + + it 'check plugin parsing for store/weaveworks/net-plugin' do + resource = load_resource('docker_plugin', 'store/weaveworks/net-plugin') + _(resource.id).must_equal '6ea8176de74b' + _(resource.version).must_equal '2.3.0' + _(resource.enabled?).must_equal true + _(resource.exist?).must_equal true + end + + it 'check plugin parsing when there are no plugins' do + resource = load_resource('docker_plugin') + assert_nil resource.id + assert_nil resource.version + assert_nil resource.id + assert_nil resource.enabled? + _(resource.exist?).must_equal false + end + + it 'prints as a docker resource' do + resource = load_resource('docker_plugin', 'store/weaveworks/net-plugin') + resource.to_s.must_equal 'Docker plugin store/weaveworks/net-plugin' + end + end +end diff --git a/test/unit/resources/docker_test.rb b/test/unit/resources/docker_test.rb index 8ef789251..5ffd30586 100644 --- a/test/unit/resources/docker_test.rb +++ b/test/unit/resources/docker_test.rb @@ -24,6 +24,13 @@ describe 'Inspec::Resources::Docker' do _(resource.services.images).must_equal ["foo/image:1.0", "foo/image:1.1", "bar:latest", "bar:latest"] end + it 'check docker plugins parsing' do + _(resource.plugins.ids).must_equal ["6ea8176de74b", "771d3ee7c7ea"] + _(resource.plugins.names).must_equal ["store/weaveworks/net-plugin", "docker4x/cloudstor"] + _(resource.plugins.versions).must_equal ["2.3.0", "18.03.1-ce-aws1"] + _(resource.plugins.enabled).must_equal [true, false] + end + it 'check docker version parsing' do _(resource.version.Server.Version).must_equal '17.03.0-ce' _(resource.version.Client.Version).must_equal '17.03.0-ce'