HashiCorp Packer Golden Image testing with custom mondoo policies


Bicycle

Why another article on that topic?

We were already talking about mondoo in a previous article. Mondoo is brought to you by the team who gave you InSpec and the DevSec Project with a passion to make IT operations human readable and executable.

So, why are we revisiting this topic? Because we want to show you how to test your golden images with Mondoo based on another article we have written about HashiCorp Packer. And the reason for this is simple: Mondoo is a great tool to test your golden images for security vulnerabilities, and kitchen terraform is getting deprecated.

Testing of a golden image with mondoo and terratest

When you have defined a certain set of machine templates that you want to use for your infrastructure, you want to make sure that these templates are secure. This is where mondoo comes into play. Mondoo is a tool that allows you to test your golden images for security vulnerabilities, but also to test your infrastructure for compliance with certain security standards and organization standards. We can use that with custom policies to replace the deprecated kitchen terraform setup that was using inspec for this purpose.

The missing link between these technologies is how to spin up your golden image to address the testing by Mondoo. This is where terratest comes into play. Terratest is a Go library that makes it easier to write automated tests for your infrastructure code. It is a great tool to spin up your golden image and test it with Mondoo. You also find here a great article about how to use terratest with terraform.

How to test a golden image with Mondoo and terratest

The actual go code to test a golden image with Mondoo and terratest is quite simple. You can see the full code in the following snippet:

 1package test
 2
 3import (
 4//...
 5)
 6
 7func TestMondooBased(t *testing.T) {
 8
 9	expectedName := fmt.Sprintf("terratest-mondoo-%s", random.UniqueId())
10	cloud := os.Getenv("CLOUD")
11
12	terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
13		TerraformDir: fmt.Sprintf("./fixtures/%s/", cloud),
14		Vars: map[string]interface{}{
15			"vm_image_id": os.Getenv("PACKER_IMAGE_ID"),
16			"location":    os.Getenv("PACKER_LOCATION"),
17			"subnet_id":   os.Getenv("PACKER_SUBNET_ID"),
18			"vpc_id":      os.Getenv("PACKER_VPC_ID"),
19			"vm_name":     expectedName,
20		},
21	})
22
23	defer terraform.Destroy(t, terraformOptions)
24
25    terraform.InitAndApply(t, terraformOptions)
26
27	target_ipaddress := terraform.Output(t, terraformOptions, "target_ipaddress")
28
29	assert.NotEmpty(t, target_ipaddress)
30
31	runMondooTests(t, target_ipaddress, expectedName, "core")
32	runMondooTests(t, target_ipaddress, expectedName, os.Getenv("TESTNAME"))
33
34}

We have a test function that is using embedded test function to run certain tests. The runMondooTests function is the one that is running the mondoo tests. In our example setup we have core policyset that all machine templates must pass. And there also exist dedicated tests related to the actual provisioning of the machine template. And so in the end we are running the Mondoo tests for the core policyset and the dedicated tests for the machine template - and both must be passed.

The actual fixtures definied are still the same we used in the previous article. The core policyset is defined in a file core.mql.yaml and the dedicated tests are defined in a file ${TESTNAME}.mql.yaml. And we just translated the tests from the kitchen terraform setup to the Mondoo setup.

The runMondooTests function is defined as following snippet shows:

 1func runMondooTests(t *testing.T, target_address string, expectedName string, profile string) {
 2
 3	policyBundleFile := fmt.Sprintf("./%s.mql.yaml", profile)
 4	if _, err := os.Stat(policyBundleFile); err != nil {
 5		t.Logf("policy bundle file not found: %s", policyBundleFile)
 6		return
 7	}
 8	logFile := fmt.Sprintf("%s_%s.log", expectedName, profile)
 9
10	var policyFilters []string
11
12	assetConfig := &inventory.Config{
13		Type:        "ssh",
14		Host:        target_address,
15		Port:        int32(22),
16		Options:     map[string]string{},
17		Credentials: []*vault.Credential{},
18	}
19
20	bundleLoader := policy.DefaultBundleLoader()
21	policyBundle, err := bundleLoader.BundleFromPaths(policyBundleFile)
22	if err != nil {
23		t.Error(errors.Wrap(err, "could not load policy bundle from "+policyBundleFile))
24		return
25	}
26
27	conf := inventory.New(inventory.WithAssets(&inventory.Asset{
28		Name:        expectedName,
29		Connections: []*inventory.Config{assetConfig},
30		Annotations: map[string]string{},
31		Labels:      map[string]string{},
32	}))
33
34	scanJob := &scan.Job{
35		Inventory:     conf,
36		Bundle:        policyBundle,
37		PolicyFilters: policyFilters,
38		ReportType:    scan.ReportType_FULL,
39	}
40
41	scanService := scan.NewLocalScanner()
42	res, err := scanService.RunIncognito(context.Background(), scanJob)
43	if err != nil {
44		t.Error("scan failed: " + err.Error())
45		return
46	}
47
48	if res == nil || res.GetFull() == nil {
49		t.Error("scan failed: no result returned, use `debug: true` logging for more details")
50		return
51	}
52
53	report := res.GetFull()
54	handlerConf := reporter.HandlerConfig{
55		Format:       "compact",
56		OutputTarget: logFile,
57		Incognito:    true,
58	}
59	outputHandler, err := reporter.NewOutputHandler(handlerConf)
60	if err != nil {
61		t.Error("failed to create an output handler: " + err.Error())
62		return
63	}
64
65	buf := &bytes.Buffer{}
66	if x, ok := outputHandler.(*reporter.Reporter); ok {
67		x.WithOutput(buf)
68	}
69
70	if err := outputHandler.WriteReport(context.Background(), report); err != nil {
71		t.Error("failed to write report to output target: " + err.Error())
72		return
73	}
74
75	scoreThreshold := uint64(100)
76	if os.Getenv("SCORE_THRESHOLD") != "" {
77		scoreThreshold, _ = strconv.ParseUint(os.Getenv("SCORE_THRESHOLD"), 10, 32)
78	}
79
80	if report.GetWorstScore() < uint32(scoreThreshold) {
81		t.Errorf("scan %s has completed with %d score, does not pass score threshold %d", profile, report.GetWorstScore(), scoreThreshold)
82		return
83	}
84
85	t.Logf("scan %s has completed with %d score, passed score threshold %d", profile, report.GetWorstScore(), scoreThreshold)
86}

This function is running the Mondoo tests for a certain policyset. It is loading the policy bundle from a file, and then it is running the tests against the machine that was created by terratest setup. The result of the tests is written to a log file. And in the end the function is checking if the tests have passed or not, based on a certain score threshold.

Conclusion

The definition of machine templates is a crucial part of your infrastructure setup. You want to make sure that these templates are secure and compliant with certain standards. Mondoo is a great tool to test your golden images for security vulnerabilities, but also to test your infrastructure for compliance with certain security standards and organization standards. And terratest becomes very useful to spin up your golden image and test it with Mondoo. In this article we have shown you how to test your golden images with Mondoo and terratest. We hope this article was helpful to you and you learned something new.

Thank you for reading!

Go Back explore our courses

We are here for you

You are interested in our courses or you simply have a question that needs answering? You can contact us at anytime! We will do our best to answer all your questions.

Contact us