Skip to content

Commit

Permalink
initial mondoo terraform provisioner
Browse files Browse the repository at this point in the history
  • Loading branch information
chris-rock committed Jun 29, 2019
1 parent 8477018 commit 5b8b19f
Show file tree
Hide file tree
Showing 12 changed files with 880 additions and 0 deletions.
6 changes: 6 additions & 0 deletions terraform-provisioner-mondoo/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
dist/*
vendor
.terraform
id_rsa*
*.tfstate
*.tfstate.backup
29 changes: 29 additions & 0 deletions terraform-provisioner-mondoo/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
.PHONY: prep/plugins build/linux build/darwin build/install build test/noop

PLUGINS_DIR=~/.terraform.d/plugins
PROVISIONER_BINARY_NAME=terraform-provisioner-mondoo

prep/plugins:
mkdir -p ${PLUGINS_DIR}

build/darwin:
GO111MODULE=on CGO_ENABLED=0 GOOS=darwin installsuffix=cgo go build -o ./dist/${PROVISIONER_BINARY_NAME}

build/linux:
GO111MODULE=on CGO_ENABLED=0 GOOS=linux installsuffix=cgo go build -o ./dist/${PROVISIONER_BINARY_NAME}

install: prep/plugins
cp ./dist/${PROVISIONER_BINARY_NAME} ${PLUGINS_DIR}/${PROVISIONER_BINARY_NAME}

test:
go test -v

CONN=ssh
USER=ec2-user
HOST=52.31.244.47
test/noop:
pushd ./test/noop && \
terraform init && \
rm terraform.tfstat* && \
terraform apply -auto-approve -var conn=${CONN} -var user=${USER} -var host=${HOST} && \
popd
14 changes: 14 additions & 0 deletions terraform-provisioner-mondoo/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module github.com/mondoolabs/mondoo/terraform-provisioner-mondoo

require (
github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2
github.com/hashicorp/go-hclog v0.7.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hashicorp/hil v0.0.0-20190212132231-97b3a9cdfa93 // indirect
github.com/hashicorp/terraform v0.12.3
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-linereader v0.0.0-20190213213312-1b945b3263eb
github.com/mitchellh/mapstructure v1.1.2
github.com/packer-community/winrmcp v0.0.0-20180921211025-c76d91c1e7db // indirect
github.com/rs/zerolog v1.11.0 // indirect
)
492 changes: 492 additions & 0 deletions terraform-provisioner-mondoo/go.sum

Large diffs are not rendered by default.

15 changes: 15 additions & 0 deletions terraform-provisioner-mondoo/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package main

import (
"github.com/hashicorp/terraform/plugin"
"github.com/hashicorp/terraform/terraform"
"github.com/mondoolabs/mondoo/terraform-provisioner-mondoo/mondoo"
)

func main() {
plugin.Serve(&plugin.ServeOpts{
ProvisionerFunc: func() terraform.ResourceProvisioner {
return mondoo.Provisioner()
},
})
}
57 changes: 57 additions & 0 deletions terraform-provisioner-mondoo/mondoo/mondoo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package mondoo

import (
"context"
"fmt"

"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
)

func validateFn(c *terraform.ResourceConfig) (ws []string, es []error) {
return nil, nil
}

func applyFn(ctx context.Context) error {
s := ctx.Value(schema.ProvRawStateKey).(*terraform.InstanceState)
data := ctx.Value(schema.ProvConfigDataKey).(*schema.ResourceData)
o := ctx.Value(schema.ProvOutputKey).(terraform.UIOutput)

// read ssh connection information
sshConfig, err := tfConnInfo(s)
if err != nil {
return err
}

// build mondoo config
conf := &VulnOpts{
Asset: &VulnOptsAsset{
Connection: fmt.Sprintf("ssh://%s@%s", sshConfig.User, sshConfig.Host),
},
Report: tfReportConfig(data),
}

// run mondoo vuln command
return run(ctx, s, data, o, conf)
}

func Provisioner() terraform.ResourceProvisioner {
return &schema.Provisioner{
Schema: map[string]*schema.Schema{
"reporter": &schema.Schema{
Type: schema.TypeMap,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"format": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
},
},
},
},
ApplyFunc: applyFn,
ValidateFunc: validateFn,
}
}
32 changes: 32 additions & 0 deletions terraform-provisioner-mondoo/mondoo/mondoo_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package mondoo

import (
"testing"

"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/terraform"
)

func resourceConfig(t *testing.T, c map[string]interface{}) *terraform.ResourceConfig {
r, err := config.NewRawConfig(c)
if err != nil {
t.Fatalf("config error: %s", err)
}
return terraform.NewResourceConfig(r)
}

func TestProvisionerValidateOK(t *testing.T) {
c := resourceConfig(t, map[string]interface{}{
"reporter": map[string]interface{}{
"format": "yaml",
},
})

warn, errs := Provisioner().Validate(c)
if len(warn) > 0 {
t.Fatalf("Warnings: %v", warn)
}
if len(errs) > 0 {
t.Fatalf("Errors: %v", errs)
}
}
61 changes: 61 additions & 0 deletions terraform-provisioner-mondoo/mondoo/options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package mondoo

import (
"fmt"

"github.com/hashicorp/terraform/helper/schema"
)

type VulnOpts struct {
Asset *VulnOptsAsset `json:"asset,omitempty" mapstructure:"asset"`
Report *VulnOptsReport `json:"report,omitempty" mapstructure:"report"`
Exit0OnSuccess bool `json:"exit-0-on-success,omitempty" mapstructure:"exit-0-on-success"`
Collector string `json:"collector,omitempty" mapstructure:"collector"`
IdDetector string `json:"id-detector,omitempty" mapstructure:"id-detector"`
}

type VulnOptsAsset struct {
ReferenceID string `json:"referenceid,omitempty" mapstructure:"referenceid"`
AssetMrn string `json:"assetmrn,omitempty" mapstructure:"assetmrn"`
Connection string `json:"connection,omitempty" mapstructure:"connection"`
IdentityFile string `json:"identityfile,omitempty" mapstructure:"identityfile"`
}

type VulnOptsReport struct {
Format string `json:"format,omitempty"`
}

// read the report config
func tfReportConfig(data *schema.ResourceData) *VulnOptsReport {
reportData := StringMap(data.Get("report"))

conf := &VulnOptsReport{
Format: StringValue(reportData, "format"),
}
return conf
}

func StringValue(keymap map[string]interface{}, key string) string {
v, ok := keymap[key]
if ok {
switch v := v.(type) {
case string:
return v
default:
panic(fmt.Sprintf("unsupported type: %T", v))
}
}

return ""
}

func StringMap(v interface{}) map[string]interface{} {
switch v := v.(type) {
case nil:
return make(map[string]interface{})
case map[string]interface{}:
return v
default:
panic(fmt.Sprintf("unsupported type: %T", v))
}
}
107 changes: 107 additions & 0 deletions terraform-provisioner-mondoo/mondoo/run.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package mondoo

import (
"context"
"encoding/json"
"fmt"
"io"
"os"
"os/exec"
"strings"

"github.com/armon/circbuf"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
linereader "github.com/mitchellh/go-linereader"
)

const (
// maxBufSize limits how much output we collect from a local
// invocation. This is to prevent TF memory usage from growing
// to an enormous amount due to a faulty process.
maxBufSize = 8 * 1024
)

func run(
ctx context.Context,
s *terraform.InstanceState,
data *schema.ResourceData,
o terraform.UIOutput,
conf *VulnOpts,
) error {
// prep config for mondoo executable
mondooScanConf, err := json.Marshal(conf)
if err != nil {
return err
}

cmdbin := "mondoo"
cmdargs := []string{"vuln"}

// we use os.Pipe instead of goroutines, see golang.org/issue/18874
pr, pw, err := os.Pipe()
if err != nil {
return fmt.Errorf("failed to initialize pipe for output: %s", err)
}

// setup the mondoo command
cmd := exec.Command(cmdbin, cmdargs...)
cmd.Stderr = pw
cmd.Stdout = pw

// set environment variable for mondod executable to detect that we are in
// terraform mode
cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, "CI=true")
cmd.Env = append(cmd.Env, "CI_TERRAFORM=true")

stdin, err := cmd.StdinPipe()
if err != nil {
return err
}
stdin.Write(mondooScanConf)
stdin.Close()

output, _ := circbuf.NewBuffer(maxBufSize)

// copy cmd output to tf output
tee := io.TeeReader(pr, output)

// copy the tee channel to terraform UI output
copyDoneCh := make(chan struct{})
go copyOutputChan(o, tee, copyDoneCh)

// output that we kick off the scan
o.Output(fmt.Sprintf("Executing: %s", strings.Join(cmdargs, " ")))

// start the command
err = cmd.Start()
if err == nil {
err = cmd.Wait()
}

pw.Close()

// Cancelling the command may block the pipe reader if the file descriptor
// was passed to a child process which hasn't closed it. In this case the
// copyOutput goroutine will just hang out until exit.
select {
case <-copyDoneCh:
case <-ctx.Done():
}

if err != nil {
return fmt.Errorf("Error running command '%s': %v. Output: %s",
strings.Join(cmdargs, " "), err, output.Bytes())
}

return nil
}

func copyOutputChan(o terraform.UIOutput, r io.Reader, doneCh chan<- struct{}) {
defer close(doneCh)
lr := linereader.New(r)
for line := range lr.Ch {
o.Output(line)
}
}
34 changes: 34 additions & 0 deletions terraform-provisioner-mondoo/mondoo/ssh.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package mondoo

import (
"github.com/hashicorp/terraform/communicator/shared"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/mapstructure"
)

type sshConnInfo struct {
User string
Password string
PrivateKey string `mapstructure:"private_key"`
Host string
Port int
}

func tfConnInfo(s *terraform.InstanceState) (*sshConnInfo, error) {
connInfo := &sshConnInfo{}
decConf := &mapstructure.DecoderConfig{
WeaklyTypedInput: true,
Result: connInfo,
}
dec, err := mapstructure.NewDecoder(decConf)
if err != nil {
return nil, err
}
if err := dec.Decode(s.Ephemeral.ConnInfo); err != nil {
return nil, err
}

// format the host if needed, needed for IPv6
connInfo.Host = shared.IpFormat(connInfo.Host)
return connInfo, nil
}
17 changes: 17 additions & 0 deletions terraform-provisioner-mondoo/test/noop/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
provider "null" {}

resource "null_resource" "mondoo" {
provisioner "mondoo" {
reporter = {
format = "yaml"
}

connection {
type = "${var.conn}"
host = "${var.host}"
user = "${var.user}"
}

on_failure = "continue"
}
}
Loading

0 comments on commit 5b8b19f

Please sign in to comment.