Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Advanced Class updates #275

Merged
merged 23 commits into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 18 additions & 11 deletions src/courses/advanced/02.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,32 @@ next: 03.md
title: 2. Review the Fundamentals
author: Aaron Lippold
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this author section necessary? I feel like this and the "contributor" section at the bottom are often just completely wrong by now esp considering how much group authorship and editing has been going on which is unattributed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is more like an @aaronlippold question
Non-blocker for this PR

---

## InSpec Content Review

In the [beginner class](../beginner/README.md), we explained the structure and output of InSpec Profiles. Let's review some content, then practice by revisiting, running, and viewing results of an InSpec profile.
Let's do a quick refresh on some of basic InSpec concepts.

In the [beginner class](../beginner/README.md), we explained the structure and output of InSpec profiles and how to run them. Let's review some content, then practice by revisiting, running, and viewing results of an InSpec profile.
karikarshivani marked this conversation as resolved.
Show resolved Hide resolved

### InSpec Profile Structure
karikarshivani marked this conversation as resolved.
Show resolved Hide resolved
Remember that a `profile` is a set of automated tests that usually relates directly back to a Security Requirements Benchmark.
Remember that a `profile` is a set of automated tests that usually relates directly back to some upstream security guidance document.
karikarshivani marked this conversation as resolved.
Show resolved Hide resolved
karikarshivani marked this conversation as resolved.
Show resolved Hide resolved

Profiles have two (2) **required** elements:
karikarshivani marked this conversation as resolved.
Show resolved Hide resolved
- An `inspec.yml` file

- An `inspec.yml` file
- A `controls` directory

and **optional** elements such as:
- A `libraries` directory
and **optional** elements such as:

- A `libraries` directory
- A `files` directory
- An `inputs.yml` file
- An `inputs.yml` file
- A `README.md` file

InSpec can create the profile structure for you using the following command:

```sh
$ inspec init profile my_inspec_profile
inspec init profile my_inspec_profile
```

This will give you the required files along with some optional files.
Expand All @@ -43,7 +48,7 @@ $ tree my_inspec_profile

#### Control File Structure

Let's take a look at the default ruby file in the `controls` directory.
Let's take a look at the default Ruby file in the `controls` directory.

::: code-tabs
@tab controls/example.rb
Expand All @@ -66,6 +71,7 @@ control 'tmp-1.0' do # A unique ID for this control
end
end
```

:::

This example shows two tests. Both tests check for the existence of the `/tmp` directory. The second test provides additional information about the test. Let's break down each component.
Expand All @@ -90,10 +96,10 @@ end
::: tabs

@tab Resources
InSpec uses resources like the `file` resource to aid in control development. These resources can often be used as the `< entity >` in the describe block. Find a list of resources in the [InSpec documentation ](https://docs.chef.io/inspec/resources/)
InSpec uses resources like the `file` resource to aid in control development. These resources can often be used as the `< entity >` in the describe block. Find a list of resources in the [InSpec documentation](https://docs.chef.io/inspec/resources/)

@tab Matchers
InSpec uses matchers like the `cmp` or `eq` to aid in control development. These matchers can often be used as the `< expectation >` in the describe block where the expectation is checking a requirement of that entity. Find a list of matchers in the [InSpec documentation ](https://docs.chef.io/inspec/matchers/)
InSpec uses matchers like the `cmp` or `eq` to aid in control development. These matchers can often be used as the `< expectation >` in the describe block where the expectation is checking a requirement of that entity. Find a list of matchers in the [InSpec documentation](https://docs.chef.io/inspec/matchers/)

:::

Expand Down Expand Up @@ -132,6 +138,7 @@ inputs:
type: < data type of the input (String, Array, Numeric, Hash) >
value: < default value for the input >
```

:::

This example shows default metadata of the InSpec profile along with the optional sections. Find more information about [inputs](../beginner/06.md) and [overlays](../beginner/10.md) in the beginner class.
Expand Down Expand Up @@ -172,4 +179,4 @@ superusers:
- 'kali'
```

:::
:::
32 changes: 23 additions & 9 deletions src/courses/advanced/03.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ author: Aaron Lippold
headerDepth: 3
---
## Revisiting the NGINX Web Server InSpec Profile

In the [beginner class](../beginner/05.md), we wrote and ran an InSpec profile against a test container. We then generated a report on our results and loaded them into Heimdall for analysis. Let's recap this process with some practice.

### The Target

InSpec is a framework which is used to validate the security configuration of a certain target. In this case, we are interested in validating that an NGINX server complies with our requirements.
InSpec is a framework used to validate the security configuration of a target. In this case, we are interested in validating that an NGINX server complies with our requirements.

First let's find our nginx container id using the `docker ps` command:
First, let's find our NGINX container ID using the `docker ps` command:

```shell
docker ps
Expand All @@ -26,13 +27,17 @@ CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8ba6b8av5n7s nginx:latest "/docker.…" 2 weeks ago Up 1 hour 80/tcp nginx
```

We can then use the container name of our nginx container `nginx` to target the inspec validation scans at that container.
We can then use the container name of our nginx container `nginx` to target the InSpec validation scans at that container.
karikarshivani marked this conversation as resolved.
Show resolved Hide resolved

### The Requirements

InSpec profiles are a set of automated tests that relate back to a security requirements benchmark, so the controls are always motivated by the requirements.
InSpec profiles are a set of automated tests that relate back to a security guidance document, so the controls are always motivated by the requirements.

::: details Review

In the beginner class, we worked with a simple requirements set to implement in InSpec.

```sh
1. NGINX should be installed as version 1.27.0 or later.
2. The following NGINX modules should be installed:
* `http_ssl`
Expand All @@ -43,16 +48,18 @@ InSpec profiles are a set of automated tests that relate back to a security requ
* be owned by the `root` user and group.
* not be readable, writeable, or executable by others.
5. The NGINX shell access should be restricted to admin users.
```

:::

### The Controls

InSpec profiles consist of automated tests, that align to security requirements, written in ruby files inside the controls directory.
InSpec profiles consist of automated tests, that align to security requirements, written in Ruby files inside the controls directory.

::: details Review

If you don't have `my_nginx` profile, run the following command to initialize your InSpec profile.
If you don't have the `my_nginx` profile, run the following command to initialize your InSpec profile.

```
inspec init profile my_nginx
```
Expand Down Expand Up @@ -158,6 +165,7 @@ end
```

:::

### Running the Controls

To run `inspec exec` on the target, ensure that you are in the directory that has `my_nginx` profile.
Expand All @@ -169,8 +177,9 @@ To run `inspec exec` on the target, ensure that you are in the directory that ha
```sh
inspec exec my_nginx -t docker://nginx --input-file inputs-linux.yml
```

@tab output

```sh
Profile: InSpec Profile (my_nginx)
Version: 0.1.0
Expand Down Expand Up @@ -199,14 +208,19 @@ inspec exec my_nginx -t docker://nginx --input-file inputs-linux.yml
Profile Summary: 4 successful controls, 1 control failure, 0 controls skipped
Test Summary: 10 successful, 1 failure, 0 skipped
```

:::

### Reporting Results

In the [beginner class](../beginner/08.md), we mentioned that you can specify an InSpec reporter to indicate the format in which you desire the results. If you want to read the results on the command line as well as save them in a JSON file, you can run this command.

```sh
inspec exec my_nginx -t docker://nginx --input-file inputs-linux.yml --reporter cli json:my_nginx_results.json
inspec exec my_nginx -t docker://nginx --input-file inputs-linux.yml --reporter cli json:my_nginx_results.json --enhanced-outcomes
```

### Visualizing Results
You can use this output file to upload and visualize your results in [Heimdall ](https://heimdall-lite.mitre.org/).

You can use this output file to upload and visualize your results in [Heimdall](https://heimdall-lite.mitre.org/).

![NGINX Heimdall Report View](../../assets/img/NGINX_Heimdall_Report_View.png)
151 changes: 81 additions & 70 deletions src/courses/advanced/04.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Now that you have learned about making and running InSpec profiles, let's dig de

As you saw in the [Beginner class](../beginner/README.md), when writing InSpec code, many core resources are available because they are included in the main InSpec code base.
wdower marked this conversation as resolved.
Show resolved Hide resolved

* You can [explore the core InSpec resources](https://www.inspec.io/docs/reference/resources/) to existing resources.
* You can [explore the core InSpec resources](https://www.inspec.io/docs/reference/resources/) on Chef's documentation website.
* You can also [examine the source code](https://github.com/inspec/inspec/tree/master/lib/inspec/resources) to see what's available. For example, you can see how `file` and other InSpec resources are implemented.

### Local Resources
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't reach with the auto suggestion, but like the other day, let's get rid of the pointless tree invocations and only show the results of that call

Expand All @@ -30,30 +30,9 @@ Note that the `libraries` directory is not created by default within a profile w

Once you create and populate a custom resource Ruby file inside the `libraries` directory, it can be utilized inside your local profile just like the core resources.

### 6.1. Resource Overview
### Resource Structure

Resources may be added to profiles in the libraries folder:
```bash
$ tree examples/profile
examples/profile
...
├── libraries
│ └── gordon_config.rb
```

### 6.2. Resource Structure

The smallest possible InSpec resource takes this form:

```ruby
class Tiny < Inspec.resource(1)
name 'tiny'
end
```

This is easy to write, but not particularly useful for testing.

Resources are written as a regular Ruby class, which inherits from the base `inspec.resource` class. The number (1) specifies the version this resource plugin targets. As Chef InSpec evolves, this interface may change and may require a higher version.
Resources are written as regular Ruby classes, which inherit from the base `inspec.resource` class. The number (1) specifies the version of the parent class that this resource plugin uses. As Chef InSpec evolves, this interface may change and may require a higher version.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have not shown the reader an example resource yet so this explanation is basically useless. We need to follow the pattern that we used for introducing an inspec control here as opposed to an inspec profile one since the user needs to look at and then can understand the code.

wdower marked this conversation as resolved.
Show resolved Hide resolved

In addition to the resource name, the following attributes can be configured:

Expand All @@ -67,67 +46,99 @@ The following methods are available to the resource:
- `inspec` - Contains a registry of all other resources to interact with the operating system or target in general.
- `skip_resource` - A resource may call this method to indicate that requirements aren’t met. All tests that use this resource will be marked as skipped.

The following example shows a full resource using attributes and methods to provide simple access to a configuration file:
### The `etc_hosts` example

Let's look at a simple default resource to get an idea how these resources are used. We'll take a look at the [source code](https://github.com/inspec/inspec/blob/526b52657be571ba1573c12d666dc1f6330f2307/lib/inspec/resources/etc_hosts.rb) for the InSpec resource that models an operating system's hostfile, which is a simple file where we can map IP addresses (e.g. 198.162.8.1) to domain names (e.g. my-heimdall-deployment.my-domain.dev) without having to add a record to a DNS server somewhere.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Let's look at a simple default resource to get an idea how these resources are used. We'll take a look at the [source code](https://github.com/inspec/inspec/blob/526b52657be571ba1573c12d666dc1f6330f2307/lib/inspec/resources/etc_hosts.rb) for the InSpec resource that models an operating system's hostfile, which is a simple file where we can map IP addresses (e.g. 198.162.8.1) to domain names (e.g. my-heimdall-deployment.my-domain.dev) without having to add a record to a DNS server somewhere.
Let's look at a relatively simple, core InSpec resource, `etc_hosts`, to get an idea how these resources are written. We'll take a look at the [source code](https://github.com/inspec/inspec/blob/526b52657be571ba1573c12d666dc1f6330f2307/lib/inspec/resources/etc_hosts.rb) for this resource which will allow you to query information about an operating system's hostfile, which is a file where we can map IP addresses (e.g. 198.162.8.1) to domain names (e.g. my-heimdall-deployment.my-domain.dev).

dev got taken by google :shakesfist: so we should probably find a better example ip address and domain name

got rid of the dns server jank cause the only people who'll know what we're talking about don't need the explanation on the difference. getting rid of the jargon makes it easier for folks.


The example shows a full resource using attributes and methods to return data about the modeled file:
wdower marked this conversation as resolved.
Show resolved Hide resolved

```ruby
class GordonConfig < Inspec.resource(1)
name 'gordon_config'

# Restrict to only run on the below platforms (if none were given, all OS's supported)
supports platform_family: 'fedora'
supports platform: 'centos', release: '6.9'
# Supports `*` for wildcard matcher in the release
supports platform: 'centos', release: '7.*'

desc '
Resource description ...
'

example '
describe gordon_config do
its("signal") { should eq "on" }
require "inspec/utils/parser"
require "inspec/utils/file_reader"

module Inspec::Resources
class EtcHosts < Inspec.resource(1)
name "etc_hosts"
supports platform: "unix"
supports platform: "windows"
desc 'Use the etc_hosts InSpec audit resource to find an
ip_address and its associated hosts'
example <<~EXAMPLE
describe etc_hosts.where { ip_address == '127.0.0.1' } do
its('ip_address') { should cmp '127.0.0.1' }
its('primary_name') { should cmp 'localhost' }
its('all_host_names') { should eq [['localhost', 'localhost.localdomain', 'localhost4', 'localhost4.localdomain4']] }
end
EXAMPLE

attr_reader :params

include Inspec::Utils::CommentParser
include FileReader

DEFAULT_UNIX_PATH = "/etc/hosts".freeze
DEFAULT_WINDOWS_PATH = 'C:\windows\system32\drivers\etc\hosts'.freeze

def initialize(hosts_path = nil)
content = read_file_content(hosts_path || default_hosts_file_path)

@params = parse_conf(content.lines)
end
'

# Load the configuration file on initialization
def initialize(path = nil)
@path = path || '/etc/gordon.conf'
@params = SimpleConfig.new( read_content )
end
FilterTable.create
.register_column(:ip_address, field: "ip_address")
.register_column(:primary_name, field: "primary_name")
.register_column(:all_host_names, field: "all_host_names")
.install_filter_methods_on_resource(self, :params)

# Expose all parameters of the configuration file.
def method_missing(name)
@params[name]
end
def to_s
"Hosts File"
end

private

def default_hosts_file_path
inspec.os.windows? ? DEFAULT_WINDOWS_PATH : DEFAULT_UNIX_PATH
end

def parse_conf(lines)
lines.reject(&:empty?).reject(&comment?).map(&parse_data).map(&format_data)
end

def comment?
parse_options = { comment_char: "#", standalone_comments: false }

->(data) { parse_comment_line(data, parse_options).first.empty? }
end

def parse_data
->(data) { [data.split[0], data.split[1], data.split[1..-1]] }
end

private

def read_content
f = inspec.file(@path)
# Test if the path exist and that it's a file
if f.file?
# Retrieve the file's contents
f.content
else
# If the file doesn't exist, skip all tests that use gordon_config
raise Inspec::Exceptions::ResourceSkipped, "Can't read config at #{@path}"
def format_data
->(data) { %w{ip_address primary_name all_host_names}.zip(data).to_h }
end
end
end
```

Let's break down each component of the resource.
If you've ever done object-oriented programming, you might be seeing some familiar concepts in this resource file. Let's break down some of the subcomponents of the resource.

#### require (lines 1 and 2)
The `require` statement pulls in code written in other files or Ruby modules that we will use in this resource. In this case we are importing some simple utility functions defined elsewhere in the InSpec codebase.
#### class
The class is where the Ruby file is defined.
The `class` is where a Ruby class definition is given. Classes define the structure and function of an object that we can instantiate to model something.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

put the stuff about inheritance and the api version in here under a "details" expando thing

#### name
The name is how we will call upon this resource within our controls, in the example above that would be `gordon_config`.
The `name` defines what token we can use to invoke this resource within our controls. Remember all those `describe` blocks we wrote that invoked the `nginx` resource? We used the term `nginx` to invoke the resource because that token is the defined `name` of the resource in its class definition.
#### supports
Supports are used to define or restrict the Ruby resource to work in specific ways, as shown in the example above that is used to restrict our class to specific platforms.
The `supports` keyword is used to define what types of system should be able to use this resource.or restrict the Ruby resource to work in specific ways, as shown in the example above that is used to restrict our class to specific platforms.
wdower marked this conversation as resolved.
Show resolved Hide resolved
#### desc
A simple description of the purpose of this resource.
A simple human-friendly description of the purpose of this resource. This is also what gets printed when you run `<resource> help` in the InSpec shell.
wdower marked this conversation as resolved.
Show resolved Hide resolved
#### examples
A simple use case example. The example is usually a `describe` block using the resource, given as a multi-line comment.
A simple use case example or a set of them. The example is usually a `describe` block using the resource, given as a multi-line comment.
wdower marked this conversation as resolved.
Show resolved Hide resolved
#### private
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is just straight up ruby syntax. we should probably minimize our explanation here and link out to official docs, and then we can remove most of the explanation cause it honestly is not really relevant to resource development (though it is relevant for software engineering/development in general)

This is a keyword that asserts that every function definition that shows up below this line in the class file should be considered _private_, or not accessible to users who instantiate an object out of this class. For example, when using this resource in a control file, you cannot invoke the `parse_data` function, because it is a private function that should really only be invoked by the resource class itself when the object is first created.
#### initialize method
An initilize method is required if your resource needs to be able to accept a parameter when called in a test (e.g. `file('this/path/is/a/parameter')`)
An `initialize` method is required if your resource needs to be able to accept a parameter when called in a test (e.g. `file('this/path/is/a/parameter')`).
wdower marked this conversation as resolved.
Show resolved Hide resolved
#### functionality methods
These methods return data from the resource so that you can use it in tests.
These methods return data from the resource so that you can use it in tests. There can be just a few of them, or there can be a whole bunch. These methods are how we define the custom matchers that can be invoked in an InSpec control file. We'll build some simple examples in the next section.
Loading
Loading