diff --git a/src/courses/advanced/06.md b/src/courses/advanced/06.md index 34325b38a..277eb38df 100644 --- a/src/courses/advanced/06.md +++ b/src/courses/advanced/06.md @@ -14,24 +14,25 @@ If you've ever deployed containerized applications before, you might be familiar ### Create a new profile and set up Docker files -First, we need a test target. Check out the `resources/docker-compose.yml` file in Codespaces for what we can test. It looks like this: +First, we need a test target. Luckily, we already have a great one sitting in our Codespaces environment. It's the same Docker Compose file we use in the `build-lab.sh` script to launch the containers we use for this class! It looks like this: ```yaml +# https://docs.docker.com/compose/compose-file/compose-file-v3/ version: '3' services: - workstation: - container_name: workstation - image: learnchef/inspec_workstation - stdin_open: true + operatingsystem: + image: redhat/ubi9 + container_name: redhat9 tty: true - links: - - target - volumes: - - .:/root - target: - image: learnchef/inspec_target stdin_open: true + restart: always + + nginx: + image: nginx:latest + container_name: nginx tty: true + stdin_open: true + restart: always ``` We will continue writing our controls to check against this Compose file. @@ -47,6 +48,8 @@ inspec init profile docker-workstations @tab Output ```bash +Redirecting to cinc-auditor... + ─────────────────────────── InSpec Code Generator ─────────────────────────── Creating new profile at /workspaces/saf-training-lab-environment/docker-workstations @@ -83,6 +86,8 @@ inspec exec docker-workstations @tab Output ```bash +Redirecting to cinc-auditor... + Profile: InSpec Profile (docker-workstations) Version: 0.1.0 Target: local:// @@ -99,9 +104,9 @@ Test Summary: 0 successful, 0 failures, 1 skipped We need to replace the `file_name` above with the location of the `docker-compose.yml` file. We also need to change the `setting` to grab the tag we want to retrieve. Finally we need to change `value` with the actual value as shown in the docker compose file. You can write multiple expectation statements in the describe block. ```ruby -describe yaml('/path/to/docker-compose.yml') do - its(['services', 'workstation', 'image']) { should eq 'learnchef/inspec_workstation' } - its(['services', 'workstation', 'volumes']) { should cmp '.:/root' } +describe yaml('docker-compose.yml') do + its(['services', 'operatingsystem', 'image']) { should eq 'redhat/ubi9' } + its(['services', 'nginx', 'image']) { should cmp 'nginx:latest' } end ``` @@ -118,54 +123,31 @@ inspec exec docker-workstations @tab Output ```bash +Redirecting to cinc-auditor... + Profile: InSpec Profile (docker-workstations) Version: 0.1.0 Target: local:// -Target ID: 6dcb9e6f-5ede-5474-9521-595fadf5c7ce +Target ID: bdf4338d-8fb0-5ca6-a949-ced198781ad5 - YAML /workspaces/saf-training-lab-environment/resources/docker-compose.yml - ✔ ["services", "workstation", "image"] is expected to eq "learnchef/inspec_workstation" - ✔ ["services", "workstation", "volumes"] is expected to cmp == ".:/root" + YAML docker-compose.yml + ✔ ["services", "operatingsystem", "image"] is expected to eq "redhat/ubi9" + ✔ ["services", "nginx", "image"] is expected to cmp == "nginx:latest" Test Summary: 2 successful, 0 failures, 0 skipped ``` ::: -Much like our `git ` example, this series of tests works, but it could be made better. We essentially parsed the Compose file with a simple YAML file parser which is fine for a one-off. However, if anybody else reads this code, it might not be clear what specific system component we are testing. Recall that we want InSpec tests to be extremely intuitive to read, even by people who did not write the code (and even by people who are not InSpec developers!). Furthermore, Compose files are very common! There's a high likelihood that you'd need to assess the contents of one again. Instead of writing a lot of repetitive code, we could create a resource specific to Compose files that exposes relevant attributes in an easy to access manner for reuse by our other controls - or even the broader security community if we choose to publish it publicly and/or get it merged into InSpec proper. +Much like our `git ` example, this series of tests works, but it could be made better. We essentially parsed the Compose file with a simple YAML file parser which is fine for a one-off. However, if anybody else reads this code, it might not be clear what specific system component we are testing. Recall that we want InSpec tests to be extremely intuitive to read, even by people who did not write the code (and even by people who are not InSpec developers!). Furthermore, Compose files are very common! There's a high likelihood that you'd need to assess the contents of one again. -:::danger If you received an error above! - Concept Check - -If you saw this as your output: - -```bash -Profile: InSpec Profile (docker-workstations) -Version: 0.1.0 -Target: local:// -Target ID: 6dcb9e6f-5ede-5474-9521-595fadf5c7ce - - YAML /path/to/docker-compose.yml - ↺ Can't find file: /path/to/docker-compose.yml - -Test Summary: 0 successful, 0 failures, 1 skipped -``` - -It is because you did not give YOUR path to the docker-compose file. You need to replace the path in your `example.rb` file to be something like this: - -```ruby -describe yaml('/workspaces/saf-training-lab-environment/resources/docker-compose.yml') do - its(['services', 'workstation', 'image']) { should eq 'learnchef/inspec_workstation' } - its(['services', 'workstation', 'volumes']) { should cmp '.:/root' } -end -``` - -::: +Instead of writing a lot of repetitive code, we could create a resource specific to Compose files that exposes relevant attributes in an easy to access manner for reuse by our other controls - or even the broader security community if we choose to publish it publicly and/or get it merged into InSpec proper. ### Rewrite test to utilize a new resource Going back to the control, we will write it using a resource that doesn't exist called docker-compose-config that is going to take a path as a parameter. :::details Test Driven Development -Remember the idea of Test Driven Development (TDD), the red-green-clean cycle. This way of development is driven by the tests. In this way, you know when you have succeeded while developing something new! In other words, before writing a solution, first write the test (which will fail - red), so that you know exactly what the expectation should be and when you have achieved it. Then you can write the solution to make the test pass (green). Finally, clean up the solution to make it easy to read and efficient! +Remember the idea of Test Driven Development (TDD), the "red-green-clean" cycle. This way of development is driven by the tests. In this way, you know when you have succeeded while developing something new! In other words, before writing a solution, first write the test (which will fail - red), so that you know exactly what the expectation should be and when you have achieved it. Then you can write the solution to make the test pass (green). Finally, clean up the solution to make it easy to read and efficient! ![Test Driven Development](../../assets/img/TestDrivenDevelopment.png) ::: @@ -174,28 +156,14 @@ Remember the idea of Test Driven Development (TDD), the red-green-clean cycle. T @tab Tests ```ruby -describe yaml('/workspaces/saf-training-lab-environment/resources/docker-compose.yml') do - its(['services', 'workstation', 'image']) { should eq 'learnchef/inspec_workstation' } - its(['services', 'workstation', 'volumes']) { should cmp '.:/root' } +describe yaml('docker-compose.yml') do + its(['services', 'operatingsystem', 'image']) { should eq 'redhat/ubi9' } + its(['services', 'nginx', 'image']) { should cmp 'nginx:latest' } end -describe docker_compose_config('/workspaces/saf-training-lab-environment/resources/docker-compose.yml') do - its('services.workstation.image') { should eq 'learnchef/inspec_workstation' } - its('services.workstation.volumes') { should cmp '.:/root' } -end -``` - -@tab Generic Tests - -```ruby -describe yaml('/path/to/docker-compose.yml') do - its(['services', 'workstation', 'image']) { should eq 'learnchef/inspec_workstation' } - its(['services', 'workstation', 'volumes']) { should cmp '.:/root' } -end - -describe docker_compose_config('/path/to/docker-compose.yml') do - its('services.workstation.image') { should eq 'learnchef/inspec_workstation' } - its('services.workstation.volumes') { should cmp '.:/root' } +describe docker_compose_config('docker-compose.yml') do + its('services.operatingsystem.image') { should eq 'redhat/ubi9' } + its('services.nginx.image') { should cmp 'nginx:latest' } end ``` @@ -214,13 +182,31 @@ inspec exec docker-workstations @tab Output ```bash -[2023-02-22T18:37:03+00:00] ERROR: Failed to load profile docker-workstations: Failed to load source for controls/example.rb: undefined method `docker_compose_config' for # +Redirecting to cinc-auditor... + +Profile: InSpec Profile (docker-workstations) +Version: 0.1.0 +Target: local:// +Target ID: bdf4338d-8fb0-5ca6-a949-ced198781ad5 + + YAML docker-compose.yml + ✔ ["services", "operatingsystem", "image"] is expected to eq "redhat/ubi9" + ✔ ["services", "nginx", "image"] is expected to cmp == "nginx:latest" + +Test Summary: 2 successful, 0 failures, 0 skipped +@wdower ➜ /workspaces/saf-training-lab-environment (answer_key) $ inspec exec docker-workstations/ +Redirecting to cinc-auditor... + +< lots of error text > Profile: InSpec Profile (docker-workstations) Version: 0.1.0 -Failure Message: Failed to load source for controls/example.rb: undefined method `docker_compose_config' for # +Failure Message: Failed to load source for controls/example.rb: undefined method `docker_compose_config' for + +< lots more error text > + Target: local:// -Target ID: 6dcb9e6f-5ede-5474-9521-595fadf5c7ce +Target ID: bdf4338d-8fb0-5ca6-a949-ced198781ad5 No tests executed. @@ -288,13 +274,14 @@ inspec exec docker-workstations @tab Output ```bash -[2023-02-22T18:38:40+00:00] ERROR: Failed to load profile docker-workstations: Failed to load source for controls/example.rb: wrong number of arguments (given 1, expected 0) +Redirecting to cinc-auditor... +[2024-12-09T07:03:10+00:00] ERROR: Failed to load profile docker-workstations: Failed to load source for controls/example.rb: wrong number of arguments (given 1, expected 0) Profile: InSpec Profile (docker-workstations) Version: 0.1.0 Failure Message: Failed to load source for controls/example.rb: wrong number of arguments (given 1, expected 0) Target: local:// -Target ID: 6dcb9e6f-5ede-5474-9521-595fadf5c7ce +Target ID: bdf4338d-8fb0-5ca6-a949-ced198781ad5 No tests executed. @@ -333,19 +320,21 @@ inspec exec docker-workstations @tab Output ```bash +Redirecting to cinc-auditor... + Profile: InSpec Profile (docker-workstations) Version: 0.1.0 Target: local:// -Target ID: 6dcb9e6f-5ede-5474-9521-595fadf5c7ce +Target ID: bdf4338d-8fb0-5ca6-a949-ced198781ad5 - YAML /workspaces/saf-training-lab-environment/resources/docker-compose.yml - ✔ ["services", "workstation", "image"] is expected to eq "learnchef/inspec_workstation" - ✔ ["services", "workstation", "volumes"] is expected to cmp == ".:/root" + YAML docker-compose.yml + ✔ ["services", "operatingsystem", "image"] is expected to eq "redhat/ubi9" + ✔ ["services", "nginx", "image"] is expected to cmp == "nginx:latest" docker_compose_config - × services.workstation.image - undefined method `services' for #<#:0x00000000032cbbd8> - × services.workstation.volumes - undefined method `services' for #<#:0x00000000032cbbd8> + × services.operatingsystem.image + undefined method `services' for #<#:0x000077e66a0c2358 @resource_skipped=false, @resource_failed=false, @supports=nil, @resource_exception_message=nil, @__backend_runner__=Inspec::Backend::Class @transport=Train::Transports::Local::Connection, @__resource_name__="docker_compose_config", @resource_params=["docker-compose.yml"], @path="docker-compose.yml"> + × services.nginx.image + undefined method `services' for #<#:0x000077e66a0c2358 @resource_skipped=false, @resource_failed=false, @supports=nil, @resource_exception_message=nil, @__backend_runner__=Inspec::Backend::Class @transport=Train::Transports::Local::Connection, @__resource_name__="docker_compose_config", @resource_params=["docker-compose.yml"], @path="docker-compose.yml"> Test Summary: 2 successful, 2 failures, 0 skipped ``` @@ -385,19 +374,21 @@ inspec exec docker-workstations @tab Output ```bash +Redirecting to cinc-auditor... + Profile: InSpec Profile (docker-workstations) Version: 0.1.0 Target: local:// -Target ID: 6dcb9e6f-5ede-5474-9521-595fadf5c7ce +Target ID: bdf4338d-8fb0-5ca6-a949-ced198781ad5 - YAML /workspaces/saf-training-lab-environment/resources/docker-compose.yml - ✔ ["services", "workstation", "image"] is expected to eq "learnchef/inspec_workstation" - ✔ ["services", "workstation", "volumes"] is expected to cmp == ".:/root" + YAML docker-compose.yml + ✔ ["services", "operatingsystem", "image"] is expected to eq "redhat/ubi9" + ✔ ["services", "nginx", "image"] is expected to cmp == "nginx:latest" docker_compose_config - × services.workstation.image - undefined method `workstation' for nil:NilClass - × services.workstation.volumes - undefined method `workstation' for nil:NilClass + × services.operatingsystem.image + undefined method `operatingsystem' for nil:NilClass + × services.nginx.image + undefined method `nginx' for nil:NilClass Test Summary: 2 successful, 2 failures, 0 skipped ``` @@ -438,26 +429,28 @@ inspec exec docker-workstations @tab Output ```bash +Redirecting to cinc-auditor... + Profile: InSpec Profile (docker-workstations) Version: 0.1.0 Target: local:// -Target ID: 6dcb9e6f-5ede-5474-9521-595fadf5c7ce +Target ID: bdf4338d-8fb0-5ca6-a949-ced198781ad5 - YAML /workspaces/saf-training-lab-environment/resources/docker-compose.yml - ✔ ["services", "workstation", "image"] is expected to eq "learnchef/inspec_workstation" - ✔ ["services", "workstation", "volumes"] is expected to cmp == ".:/root" + YAML docker-compose.yml + ✔ ["services", "operatingsystem", "image"] is expected to eq "redhat/ubi9" + ✔ ["services", "nginx", "image"] is expected to cmp == "nginx:latest" docker_compose_config - × services.workstation.image - undefined method `workstation' for - × services.workstation.volumes - undefined method `workstation' for + × services.operatingsystem.image + undefined method `operatingsystem' for {"operatingsystem"=>{"image"=>"redhat/ubi9", "container_name"=>"redhat9", "tty"=>true, "stdin_open"=>true, "restart"=>"always"}, "nginx"=>{"image"=>"nginx:latest", "container_name"=>"nginx", "tty"=>true, "stdin_open"=>true, "restart"=>"always"}}:Hash + × services.nginx.image + undefined method `nginx' for {"operatingsystem"=>{"image"=>"redhat/ubi9", "container_name"=>"redhat9", "tty"=>true, "stdin_open"=>true, "restart"=>"always"}, "nginx"=>{"image"=>"nginx:latest", "container_name"=>"nginx", "tty"=>true, "stdin_open"=>true, "restart"=>"always"}}:Hash Test Summary: 2 successful, 2 failures, 0 skipped ``` ::: -You will notice that it parses correctly, but instead of our result we end up getting a hash. We need to convert the hash into an object that appears like other objects so that we may use our dot notation. So we will wrap our hash in a Ruby class called a `Hashie::Mash`. This gives us a quick way to convert a hash into a Ruby object with a number of methods attached to it. You will have to import the Hashie library by running `gem install hashie` and import it in the resource file to be used. It and is written as follows: +You will notice that it parses correctly, but instead of our result we end up getting a giant hash of data that doesn't seem to parse correctly. We need to convert the hash into an object that appears like other objects so that we may use our dot notation. So we will wrap our hash in a Ruby class called a `Hashie::Mash`. This gives us a quick way to convert a hash into a Ruby object with a number of methods attached to it. You will have to import the Hashie library by running `gem install hashie` and import it in the resource file to be used. It and is written as follows: ```ruby # encoding: utf-8 @@ -494,43 +487,23 @@ inspec exec docker-workstations @tab Output ```bash +Redirecting to cinc-auditor... + Profile: InSpec Profile (docker-workstations) Version: 0.1.0 Target: local:// -Target ID: 6dcb9e6f-5ede-5474-9521-595fadf5c7ce +Target ID: bdf4338d-8fb0-5ca6-a949-ced198781ad5 - YAML /workspaces/saf-training-lab-environment/resources/docker-compose.yml - ✔ ["services", "workstation", "image"] is expected to eq "learnchef/inspec_workstation" - ✔ ["services", "workstation", "volumes"] is expected to cmp == ".:/root" + YAML docker-compose.yml + ✔ ["services", "operatingsystem", "image"] is expected to eq "redhat/ubi9" + ✔ ["services", "nginx", "image"] is expected to cmp == "nginx:latest" docker_compose_config - ✔ services.workstation.image is expected to eq "learnchef/inspec_workstation" - ✔ services.workstation.volumes is expected to cmp == ".:/root" + ✔ services.operatingsystem.image is expected to eq "redhat/ubi9" + ✔ services.nginx.image is expected to cmp == "nginx:latest" Test Summary: 4 successful, 0 failures, 0 skipped ``` ::: -Everything passed! - -:::info Check your work -Check your work with the InSpec video below that walks through this docker resource example! -::: - - -
- -
+Everything passed! \ No newline at end of file