Skip to content

Commit

Permalink
Merge pull request #301 from mitre/git_test
Browse files Browse the repository at this point in the history
Advanced Git Resource Example
  • Loading branch information
Amndeep7 authored Dec 23, 2024
2 parents a1e575c + 1d4decb commit 436d545
Showing 1 changed file with 90 additions and 66 deletions.
156 changes: 90 additions & 66 deletions src/courses/advanced/05.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,28 +42,51 @@ To write resources, we first need to know what we are testing! In your Codespace
tar xzvf resources/git_test_target.tar.gz
```

This will generate a `git_test` repository which we will use for these examples.
This will generate a `git_test_target` repository which we will use for these examples.

Now let's write some tests and confirm that they run. You can put these tests in the `example.rb` file generated in the `controls` folder of your `my_git` InSpec profile. These tests are written using the `command` resource which is provided by InSpec. We will write a `git` resource in this section to improve this test. **Note that you will need to put the full directory path of the `.git` file from your `git_test_target` repository as the `git_dir` value on line 4 of `example.rb`. To get the full path of your current location in the terminal, use `pwd`.**
Now let's write some tests and confirm that they run. You can put these tests in the `example.rb` file generated in the `controls` folder of your `my_git` InSpec profile. These tests are written using the `command` resource which is provided by InSpec. We will write a `git` resource in this section to improve this test.

```ruby
# encoding: utf-8
# copyright: 2018, The Authors
::: note Inputs review
The `git_dir` input will be used in our profile. It needs to contain the path of the `.git` folder you are assessing. Usually, you'd want to put a default location for that input in your `inspec.yml`, such as `./.git`, that the user of your profile could overwrite with an inputfile, but for expediency's sake we're going to hardcode it to the `.git` folder inside of your `git_test_target` repository. The value in the codeblock below should be correct, but it's always good to doublecheck by using the `cd` command to navigate inside of the `.git` folder in the `git_test_target` repository and then using the `pwd` command to get the full path.
:::

git_dir = "/workspaces/saf-training-lab-environment/git_test_target_target/.git"
::: code-tabs

@tab inspec.yml
```yaml
name: my_git
title: InSpec Profile
maintainer: The Authors
copyright: The Authors
copyright_email: [email protected]
license: Apache-2.0
summary: An InSpec Compliance Profile
version: 0.1.0
supports:
platform: os

inputs:
- name: git_dir
type: String
value: /workspaces/saf-training-lab-environment/git_test_target/.git
```
@tab example.rb
```ruby
git_dir = input('git_dir')

# The following branches should exist
describe command("git --git-dir #{git_dir} branch") do
its('stdout') { should match /main/ }
describe command("git --git-dir #{git_dir} branch --format='%(refname:short)'").stdout.lines.map(&:strip) do
it { should include 'main' }
end

describe command("git --git-dir #{git_dir} branch") do
its('stdout') { should match /testBranch/ }
describe command("git --git-dir #{git_dir} branch --format='%(refname:short)'").stdout.lines.map(&:strip) do
it { should include 'testBranch' }
end

# What is the current branch
describe command("git --git-dir #{git_dir} branch") do
its('stdout') { should match /^\* main/ }
describe command("git --git-dir #{git_dir} branch --show-current").stdout.strip do
it { should eq 'main' }
end

# What is the latest commit
Expand All @@ -76,6 +99,7 @@ describe command("git --git-dir #{git_dir} log --skip=1 -1 --pretty=format:'%h'"
its('stdout') { should match /edc207f/ }
end
```
:::

::: code-tabs

Expand All @@ -96,12 +120,12 @@ Version: 0.1.0
Target: local://
Target ID: 6dcb9e6f-5ede-5474-9521-595fadf5c7ce

Command: `git --git-dir /workspaces/saf-training-lab-environment/git_test_target/.git branch`
stdout is expected to match /main/
Command: `git --git-dir /workspaces/saf-training-lab-environment/git_test_target/.git branch`
stdout is expected to match /testBranch/
Command: `git --git-dir /workspaces/saf-training-lab-environment/git_test_target/.git branch`
stdout is expected to match /^\* main/
["main", "testBranch"]
✔ is expected to include "main"
["main", "testBranch"]
✔ is expected to include "testBranch"
main
✔ is expected to eq "main"
Command: `git --git-dir /workspaces/saf-training-lab-environment/git_test_target/.git log -1 --pretty=format:'%h'`
✔ stdout is expected to match /7a748c6/
Command: `git --git-dir /workspaces/saf-training-lab-environment/git_test_target/.git log --skip=1 -1 --pretty=format:'%h'`
Expand Down Expand Up @@ -149,7 +173,7 @@ inspec exec my_git
```bash
Redirecting to cinc-auditor...

[2023-02-22T03:21:41+00:00] ERROR: Failed to load profile git: Failed to load source for controls/example.rb: undefined method `git' for #<Inspec::ControlEvalContext:0x000000000540af38>
[2023-02-22T03:21:41+00:00] ERROR: Failed to load profile my_git: Failed to load source for controls/example.rb: undefined method `git' for #<Inspec::ControlEvalContext:0x000000000540af38>
Profile: InSpec Profile (my_git)
Version: 0.1.0
Expand All @@ -171,9 +195,6 @@ We should get an error because the git method and resource are not defined yet.
Let's start by creating a new file called `git.rb` in the `libraries` directory. If you do not already have a `libraries` directory, you can make one in the `my_git` InSpec profile directory. The content of the file should look like this:
```ruby
# encoding: utf-8
# copyright: 2019, The Authors
class Git < Inspec.resource(1)
name 'git'
end
Expand All @@ -197,7 +218,7 @@ inspec exec my_git
@tab Output

```bash
[2023-02-22T03:25:57+00:00] ERROR: Failed to load profile git: Failed to load source for controls/example.rb: wrong number of arguments (given 1, expected 0)
[2023-02-22T03:25:57+00:00] ERROR: Failed to load profile my_git: Failed to load source for controls/example.rb: wrong number of arguments (given 1, expected 0)
Profile: InSpec Profile (my_git)
Version: 0.1.0
Expand All @@ -219,9 +240,6 @@ Each resource will require an initialization method.
For our git.rb file let's add that initialization method:
```ruby
# encoding: utf-8
# copyright: 2019, The Authors
class Git < Inspec.resource(1)
name 'git'
Expand Down Expand Up @@ -255,14 +273,14 @@ Target ID: 6dcb9e6f-5ede-5474-9521-595fadf5c7ce
git
× branches
undefined method `branches' for #<#<Class:0x00000000041485a8>:0x00000000043620c8>
Command: `git --git-dir /workspaces/saf-training-lab-environment/git_test_target/.git branch`
stdout is expected to match /testBranch/
Command: `git --git-dir /workspaces/saf-training-lab-environment/git_test_target/.git branch`
stdout is expected to match /^\* main/
["main", "testBranch"]
✔ is expected to include "testBranch"
main
✔ is expected to eq "main"
Command: `git --git-dir /workspaces/saf-training-lab-environment/git_test_target/.git log -1 --pretty=format:'%h'`
✔ stdout is expected to match /edc207f/
✔ stdout is expected to match /7a748c6/
Command: `git --git-dir /workspaces/saf-training-lab-environment/git_test_target/.git log --skip=1 -1 --pretty=format:'%h'`
✔ stdout is expected to match /8c30bff/
✔ stdout is expected to match /edc207f/

Test Summary: 4 successful, 1 failure, 0 skipped
```
Expand All @@ -274,9 +292,6 @@ The test will run but we will get an error saying we do not have a `branches` me
Let's go back to our git.rb file to fix that by adding a `branches` method:

```ruby
# encoding: utf-8
# copyright: 2019, The Authors

class Git < Inspec.resource(1)
name 'git'

Expand Down Expand Up @@ -312,10 +327,10 @@ Target ID: 6dcb9e6f-5ede-5474-9521-595fadf5c7ce
git
× branches is expected to include "main"
expected nil to include "main", but it does not respond to `include?`
Command: `git --git-dir /workspaces/saf-training-lab-environment/git_test_target/.git branch`
stdout is expected to match /testBranch/
Command: `git --git-dir /workspaces/saf-training-lab-environment/git_test_target/.git branch`
stdout is expected to match /^\* main/
["main", "testBranch"]
✔ is expected to include "testBranch"
main
✔ is expected to eq "main"
Command: `git --git-dir /workspaces/saf-training-lab-environment/git_test_target/.git log -1 --pretty=format:'%h'`
✔ stdout is expected to match /7a748c6/
Command: `git --git-dir /workspaces/saf-training-lab-environment/git_test_target/.git log --skip=1 -1 --pretty=format:'%h'`
Expand All @@ -331,9 +346,6 @@ Now the error message says that the `branches` method is returning a null value
To resolve this problem, we can use the `inspec` helper method to invoke the built-in `command` resource to extract this data as shown below:

```ruby
# encoding: utf-8
# copyright: 2019, The Authors

class Git < Inspec.resource(1)
name 'git'

Expand All @@ -342,7 +354,7 @@ class Git < Inspec.resource(1)
end

def branches
inspec.command("git --git-dir #{@path} branch").stdout
inspec.command("git --git-dir #{@path} branch --format='%(refname:short)'").stdout.lines.map(&:strip)
end

end
Expand All @@ -359,7 +371,7 @@ Now let's adjust our test to also check for our second branch that we created ea
describe git(git_dir) do
its('branches') { should include 'main' }
its('branches') { should include 'testBranch' }
its('current_branch') { should cmp 'main' }
its('current_branch') { should eq 'main' }
end
```

Expand All @@ -385,9 +397,9 @@ Target ID: 6dcb9e6f-5ede-5474-9521-595fadf5c7ce
× current_branch
undefined method `current_branch' for #<#<Class:0x0000000005400588>:0x00000000053fd0b8>
Command: `git --git-dir /workspaces/saf-training-lab-environment/git_test_target/.git log -1 --pretty=format:'%h'`
✔ stdout is expected to match /8c30bff/
Command: `git --git-dir /workspaces/saf-training-lab-environment/git_test_target/.git log --skip=1 -1 --pretty=format:'%h'`
✔ stdout is expected to match /7a748c6/
Command: `git --git-dir /workspaces/saf-training-lab-environment/git_test_target/.git log --skip=1 -1 --pretty=format:'%h'`
✔ stdout is expected to match /edc207f/
Test Summary: 6 successful, 1 failure, 0 skipped
```
Expand All @@ -397,9 +409,6 @@ Test Summary: 6 successful, 1 failure, 0 skipped
Let's head over to the git.rb file to create the `current_branch` method we are invoking in the above test:
```ruby
# encoding: utf-8
# copyright: 2019, The Authors

class Git < Inspec.resource(1)
name 'git'

Expand All @@ -408,19 +417,41 @@ class Git < Inspec.resource(1)
end

def branches
inspec.command("git --git-dir #{@path} branch").stdout
inspec.command("git --git-dir #{@path} branch --format='%(refname:short)'").stdout.lines.map(&:strip)
end

def current_branch
branch_name = inspec.command("git --git-dir #{@path} branch").stdout.strip.split("\n").find do |name|
name.start_with?('*')
end
branch_name.gsub(/^\*/,'').strip
inspec.command("git --git-dir #{@path} branch --show-current").stdout.strip
end

end
```

::: tip 1337 h4x0rs
While InSpec controls are intended on being easy to read by both software/cybersecurity engineers AND assessors, InSpec resources are an opportunity to flex those programming muscles and use that arcane sysadmin knowledge to do things in the most optimal way.

Here's an alternative implementation for `current_branch`:
```ruby
def current_branch
branch_name = inspec.command("git --git-dir #{@path} branch").stdout.gsub(" ", "").split("\n").find do |name|
name.start_with?('*')
end
branch_name[1..-1]
end
```

Note how this iterates over the baseline `git branch` output which looks like this:
```bash
$ git branch
* main
testBranch
```

And then we do some fancy stuff with string manipulation while searching for the magic string that starts with an asterisk (which marks the current branch). We're able to leverage the full power of Ruby here!

However, if you're a command line expert (or read the docs!), you'd know that you could just pass a single additional flag to the `git branch` function that spits out the exact answer that we want - don't get carried away when a simple solution exists!
:::

Now we can run the profile again.

::: code-tabs
Expand All @@ -442,11 +473,11 @@ Target ID: 6dcb9e6f-5ede-5474-9521-595fadf5c7ce
git
✔ branches is expected to include "main"
✔ branches is expected to include "testBranch"
✔ current_branch is expected to cmp == "main"
✔ current_branch is expected to eq "main"
Command: `git --git-dir /workspaces/saf-training-lab-environment/git_test_target/.git log -1 --pretty=format:'%h'`
✔ stdout is expected to match /7a748c6/
Command: `git --git-dir /workspaces/saf-training-lab-environment/git_test_target/.git log --skip=1 -1 --pretty=format:'%h'`
✔ stdout is expected to match /8c30bff/
✔ stdout is expected to match /edc207f/

Test Summary: 7 successful, 0 failures, 0 skipped
```
Expand All @@ -473,7 +504,7 @@ Invoking the InSpec shell with `inspec shell` will give you access to all the co

@tab Command
```sh
inspec shell --depends git
inspec shell --depends my_git
```
@tab Output
```sh
Expand All @@ -494,16 +525,9 @@ inspec> git('/workspaces/saf-training-lab-environment/git_test_target/.git').cur
:::

::: warning
Note that we are passing in the _profile_ directory to the `--depends` flag, and not the profile's `libraries` directory. In our example, it's
``` sh
inspec shell --depends my_git
```
and not
``` sh
inspec shell --depends my_git/libraries
```
Note that we are passing in the _profile_ directory to the `--depends` flag, and not the profile's `libraries` directory. In our example, it's `inspec shell --depends my_git` and not `inspec shell --depends my_git/libraries`.
:::

If you edit the resource class file, you'll need to exit the shell and re-launch it for the updates to be available.

From here, we can examine our custom resource in a sandbox in the same way that we do with core resources.
From here, we can examine our custom resource in a sandbox in the same way that we do with core resources.

0 comments on commit 436d545

Please sign in to comment.