Skip to content

Commit

Permalink
updating the docker compose example
Browse files Browse the repository at this point in the history
Signed-off-by: Will <[email protected]>
  • Loading branch information
wdower committed Dec 9, 2024
1 parent 202ad0f commit ddd909c
Showing 1 changed file with 98 additions and 125 deletions.
223 changes: 98 additions & 125 deletions src/courses/advanced/06.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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://
Expand All @@ -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
```
Expand All @@ -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)
:::

Expand All @@ -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
```
Expand All @@ -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 #<Inspec::ControlEvalContext:0x000000000593bb10>
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 #<Inspec::ControlEvalContext:0x000000000593bb10>
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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 #<#<Class:0x00000000050205a8>:0x00000000032cbbd8>
× services.workstation.volumes
undefined method `services' for #<#<Class:0x00000000050205a8>:0x00000000032cbbd8>
× services.operatingsystem.image
undefined method `services' for #<#<Class:0x000077e66a0908d0>: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 #<#<Class:0x000077e66a0908d0>: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
```
Expand Down Expand Up @@ -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
```
Expand Down Expand Up @@ -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 <Hash:0x0000000003abada8>
× services.workstation.volumes
undefined method `workstation' for <Hash:0x0000000003abada8>
× 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
Expand Down Expand Up @@ -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!
:::
<style>
.video-container {
position: relative;
padding-bottom: 56.25%; /* 16:9 */
height: 0;
}
.video-container iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
</style>
<div class="video-container">
<iframe width="1462" height="762" src="https://www.youtube.com/embed/9rbb2RWa9Oo?list=PLSZbtIlMt5rcbXOpMRucKzRMXR7HX7awy" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
Everything passed!

0 comments on commit ddd909c

Please sign in to comment.