-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #301 from mitre/git_test
Advanced Git Resource Example
- Loading branch information
Showing
1 changed file
with
90 additions
and
66 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
|
@@ -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 | ||
|
||
|
@@ -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'` | ||
|
@@ -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 | ||
|
@@ -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 | ||
|
@@ -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 | ||
|
@@ -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' | ||
|
@@ -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 | ||
``` | ||
|
@@ -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' | ||
|
||
|
@@ -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'` | ||
|
@@ -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' | ||
|
||
|
@@ -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 | ||
|
@@ -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 | ||
``` | ||
|
||
|
@@ -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 | ||
``` | ||
|
@@ -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' | ||
|
||
|
@@ -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 | ||
|
@@ -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 | ||
``` | ||
|
@@ -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 | ||
|
@@ -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. |