added inspec.io provisioner
This commit is contained in:
parent
65b3e67951
commit
f5b13e3cb5
2
Makefile
2
Makefile
|
@ -11,7 +11,7 @@ GOPATH=$(shell go env GOPATH)
|
|||
# gofmt
|
||||
UNFORMATTED_FILES=$(shell find . -not -path "./vendor/*" -name "*.go" | xargs gofmt -s -l)
|
||||
|
||||
EXECUTABLE_FILES=$(shell find . -type f -executable | egrep -v '^\./(website/[vendor|tmp]|vendor/|\.git|bin/|scripts/|pkg/)' | egrep -v '.*(\.sh|\.bats|\.git)' | egrep -v './provisioner/ansible/test-fixtures/exit1')
|
||||
EXECUTABLE_FILES=$(shell find . -type f -executable | egrep -v '^\./(website/[vendor|tmp]|vendor/|\.git|bin/|scripts/|pkg/)' | egrep -v '.*(\.sh|\.bats|\.git)' | egrep -v './provisioner/(ansible|inspec)/test-fixtures/exit1')
|
||||
|
||||
# Get the git commit
|
||||
GIT_DIRTY=$(shell test -n "`git status --porcelain`" && echo "+CHANGES" || true)
|
||||
|
|
|
@ -71,6 +71,7 @@ import (
|
|||
chefsoloprovisioner "github.com/hashicorp/packer/provisioner/chef-solo"
|
||||
convergeprovisioner "github.com/hashicorp/packer/provisioner/converge"
|
||||
fileprovisioner "github.com/hashicorp/packer/provisioner/file"
|
||||
inspecprovisioner "github.com/hashicorp/packer/provisioner/inspec"
|
||||
powershellprovisioner "github.com/hashicorp/packer/provisioner/powershell"
|
||||
puppetmasterlessprovisioner "github.com/hashicorp/packer/provisioner/puppet-masterless"
|
||||
puppetserverprovisioner "github.com/hashicorp/packer/provisioner/puppet-server"
|
||||
|
@ -130,6 +131,7 @@ var Provisioners = map[string]packer.Provisioner{
|
|||
"chef-solo": new(chefsoloprovisioner.Provisioner),
|
||||
"converge": new(convergeprovisioner.Provisioner),
|
||||
"file": new(fileprovisioner.Provisioner),
|
||||
"inspec": new(inspecprovisioner.Provisioner),
|
||||
"powershell": new(powershellprovisioner.Provisioner),
|
||||
"puppet-masterless": new(puppetmasterlessprovisioner.Provisioner),
|
||||
"puppet-server": new(puppetserverprovisioner.Provisioner),
|
||||
|
|
|
@ -0,0 +1,285 @@
|
|||
package inspec
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// An adapter satisfies SSH requests (from an Inspec client) by delegating SSH
|
||||
// exec and subsystem commands to a packer.Communicator.
|
||||
type adapter struct {
|
||||
done <-chan struct{}
|
||||
l net.Listener
|
||||
config *ssh.ServerConfig
|
||||
ui packer.Ui
|
||||
comm packer.Communicator
|
||||
}
|
||||
|
||||
func newAdapter(done <-chan struct{}, l net.Listener, config *ssh.ServerConfig, ui packer.Ui, comm packer.Communicator) *adapter {
|
||||
return &adapter{
|
||||
done: done,
|
||||
l: l,
|
||||
config: config,
|
||||
ui: ui,
|
||||
comm: comm,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *adapter) Serve() {
|
||||
log.Printf("SSH proxy: serving on %s", c.l.Addr())
|
||||
|
||||
for {
|
||||
// Accept will return if either the underlying connection is closed or if a connection is made.
|
||||
// after returning, check to see if c.done can be received. If so, then Accept() returned because
|
||||
// the connection has been closed.
|
||||
conn, err := c.l.Accept()
|
||||
select {
|
||||
case <-c.done:
|
||||
return
|
||||
default:
|
||||
if err != nil {
|
||||
c.ui.Error(fmt.Sprintf("listen.Accept failed: %v", err))
|
||||
continue
|
||||
}
|
||||
go func(conn net.Conn) {
|
||||
if err := c.Handle(conn, c.ui); err != nil {
|
||||
c.ui.Error(err.Error())
|
||||
}
|
||||
}(conn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *adapter) Handle(conn net.Conn, ui packer.Ui) error {
|
||||
log.Print("SSH proxy: accepted connection")
|
||||
_, chans, reqs, err := ssh.NewServerConn(conn, c.config)
|
||||
if err != nil {
|
||||
return errors.New("failed to handshake")
|
||||
}
|
||||
|
||||
// discard all global requests
|
||||
go ssh.DiscardRequests(reqs)
|
||||
|
||||
// Service the incoming NewChannels
|
||||
for newChannel := range chans {
|
||||
if newChannel.ChannelType() != "session" {
|
||||
newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
|
||||
continue
|
||||
}
|
||||
|
||||
go func(ch ssh.NewChannel) {
|
||||
if err := c.handleSession(ch); err != nil {
|
||||
c.ui.Error(err.Error())
|
||||
}
|
||||
}(newChannel)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *adapter) handleSession(newChannel ssh.NewChannel) error {
|
||||
channel, requests, err := newChannel.Accept()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer channel.Close()
|
||||
|
||||
done := make(chan struct{})
|
||||
|
||||
// Sessions have requests such as "pty-req", "shell", "env", and "exec".
|
||||
// see RFC 4254, section 6
|
||||
go func(in <-chan *ssh.Request) {
|
||||
env := make([]envRequestPayload, 4)
|
||||
for req := range in {
|
||||
switch req.Type {
|
||||
case "pty-req":
|
||||
log.Println("inspec provisioner pty-req request")
|
||||
// accept pty-req requests, but don't actually do anything. Necessary for OpenSSH and sudo.
|
||||
req.Reply(true, nil)
|
||||
|
||||
case "env":
|
||||
req, err := newEnvRequest(req)
|
||||
if err != nil {
|
||||
c.ui.Error(err.Error())
|
||||
req.Reply(false, nil)
|
||||
continue
|
||||
}
|
||||
env = append(env, req.Payload)
|
||||
log.Printf("new env request: %s", req.Payload)
|
||||
req.Reply(true, nil)
|
||||
case "exec":
|
||||
req, err := newExecRequest(req)
|
||||
if err != nil {
|
||||
c.ui.Error(err.Error())
|
||||
req.Reply(false, nil)
|
||||
close(done)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Printf("new exec request: %s", req.Payload)
|
||||
|
||||
if len(req.Payload) == 0 {
|
||||
req.Reply(false, nil)
|
||||
close(done)
|
||||
return
|
||||
}
|
||||
|
||||
go func(channel ssh.Channel) {
|
||||
exit := c.exec(string(req.Payload), channel, channel, channel.Stderr())
|
||||
|
||||
exitStatus := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(exitStatus, uint32(exit))
|
||||
channel.SendRequest("exit-status", false, exitStatus)
|
||||
close(done)
|
||||
}(channel)
|
||||
req.Reply(true, nil)
|
||||
case "subsystem":
|
||||
req, err := newSubsystemRequest(req)
|
||||
if err != nil {
|
||||
c.ui.Error(err.Error())
|
||||
req.Reply(false, nil)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Printf("new subsystem request: %s", req.Payload)
|
||||
|
||||
c.ui.Error(fmt.Sprintf("unsupported subsystem requested: %s", req.Payload))
|
||||
req.Reply(false, nil)
|
||||
default:
|
||||
log.Printf("rejecting %s request", req.Type)
|
||||
req.Reply(false, nil)
|
||||
}
|
||||
}
|
||||
}(requests)
|
||||
|
||||
<-done
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *adapter) Shutdown() {
|
||||
c.l.Close()
|
||||
}
|
||||
|
||||
func (c *adapter) exec(command string, in io.Reader, out io.Writer, err io.Writer) int {
|
||||
var exitStatus int
|
||||
exitStatus = c.remoteExec(command, in, out, err)
|
||||
return exitStatus
|
||||
}
|
||||
|
||||
func (c *adapter) remoteExec(command string, in io.Reader, out io.Writer, err io.Writer) int {
|
||||
cmd := &packer.RemoteCmd{
|
||||
Stdin: in,
|
||||
Stdout: out,
|
||||
Stderr: err,
|
||||
Command: command,
|
||||
}
|
||||
|
||||
if err := c.comm.Start(cmd); err != nil {
|
||||
c.ui.Error(err.Error())
|
||||
return cmd.ExitStatus
|
||||
}
|
||||
|
||||
cmd.Wait()
|
||||
|
||||
return cmd.ExitStatus
|
||||
}
|
||||
|
||||
type envRequest struct {
|
||||
*ssh.Request
|
||||
Payload envRequestPayload
|
||||
}
|
||||
|
||||
type envRequestPayload struct {
|
||||
Name string
|
||||
Value string
|
||||
}
|
||||
|
||||
func (p envRequestPayload) String() string {
|
||||
return fmt.Sprintf("%s=%s", p.Name, p.Value)
|
||||
}
|
||||
|
||||
func newEnvRequest(raw *ssh.Request) (*envRequest, error) {
|
||||
r := new(envRequest)
|
||||
r.Request = raw
|
||||
|
||||
if err := ssh.Unmarshal(raw.Payload, &r.Payload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func sshString(buf io.Reader) (string, error) {
|
||||
var size uint32
|
||||
err := binary.Read(buf, binary.BigEndian, &size)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
b := make([]byte, size)
|
||||
err = binary.Read(buf, binary.BigEndian, b)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
type execRequest struct {
|
||||
*ssh.Request
|
||||
Payload execRequestPayload
|
||||
}
|
||||
|
||||
type execRequestPayload string
|
||||
|
||||
func (p execRequestPayload) String() string {
|
||||
return string(p)
|
||||
}
|
||||
|
||||
func newExecRequest(raw *ssh.Request) (*execRequest, error) {
|
||||
r := new(execRequest)
|
||||
r.Request = raw
|
||||
buf := bytes.NewReader(r.Request.Payload)
|
||||
|
||||
var err error
|
||||
var payload string
|
||||
if payload, err = sshString(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r.Payload = execRequestPayload(payload)
|
||||
return r, nil
|
||||
}
|
||||
|
||||
type subsystemRequest struct {
|
||||
*ssh.Request
|
||||
Payload subsystemRequestPayload
|
||||
}
|
||||
|
||||
type subsystemRequestPayload string
|
||||
|
||||
func (p subsystemRequestPayload) String() string {
|
||||
return string(p)
|
||||
}
|
||||
|
||||
func newSubsystemRequest(raw *ssh.Request) (*subsystemRequest, error) {
|
||||
r := new(subsystemRequest)
|
||||
r.Request = raw
|
||||
buf := bytes.NewReader(r.Request.Payload)
|
||||
|
||||
var err error
|
||||
var payload string
|
||||
if payload, err = sshString(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r.Payload = subsystemRequestPayload(payload)
|
||||
return r, nil
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
package inspec
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
func TestAdapter_Serve(t *testing.T) {
|
||||
|
||||
// done signals the adapter that the provisioner is done
|
||||
done := make(chan struct{})
|
||||
|
||||
acceptC := make(chan struct{})
|
||||
l := listener{done: make(chan struct{}), acceptC: acceptC}
|
||||
|
||||
config := &ssh.ServerConfig{}
|
||||
|
||||
ui := new(packer.NoopUi)
|
||||
|
||||
sut := newAdapter(done, &l, config, newUi(ui), communicator{})
|
||||
go func() {
|
||||
i := 0
|
||||
for range acceptC {
|
||||
i++
|
||||
if i == 4 {
|
||||
close(done)
|
||||
l.Close()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
sut.Serve()
|
||||
}
|
||||
|
||||
type listener struct {
|
||||
done chan struct{}
|
||||
acceptC chan<- struct{}
|
||||
i int
|
||||
}
|
||||
|
||||
func (l *listener) Accept() (net.Conn, error) {
|
||||
log.Println("Accept() called")
|
||||
l.acceptC <- struct{}{}
|
||||
select {
|
||||
case <-l.done:
|
||||
log.Println("done, serving an error")
|
||||
return nil, errors.New("listener is closed")
|
||||
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
l.i++
|
||||
|
||||
if l.i%2 == 0 {
|
||||
c1, c2 := net.Pipe()
|
||||
|
||||
go func(c net.Conn) {
|
||||
<-time.After(100 * time.Millisecond)
|
||||
log.Println("closing c")
|
||||
c.Close()
|
||||
}(c1)
|
||||
|
||||
return c2, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.New("accept error")
|
||||
}
|
||||
|
||||
func (l *listener) Close() error {
|
||||
close(l.done)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *listener) Addr() net.Addr {
|
||||
return addr{}
|
||||
}
|
||||
|
||||
type addr struct{}
|
||||
|
||||
func (a addr) Network() string {
|
||||
return a.String()
|
||||
}
|
||||
|
||||
func (a addr) String() string {
|
||||
return "test"
|
||||
}
|
||||
|
||||
type communicator struct{}
|
||||
|
||||
func (c communicator) Start(*packer.RemoteCmd) error {
|
||||
return errors.New("communicator not supported")
|
||||
}
|
||||
|
||||
func (c communicator) Upload(string, io.Reader, *os.FileInfo) error {
|
||||
return errors.New("communicator not supported")
|
||||
}
|
||||
|
||||
func (c communicator) UploadDir(dst string, src string, exclude []string) error {
|
||||
return errors.New("communicator not supported")
|
||||
}
|
||||
|
||||
func (c communicator) Download(string, io.Writer) error {
|
||||
return errors.New("communicator not supported")
|
||||
}
|
||||
|
||||
func (c communicator) DownloadDir(src string, dst string, exclude []string) error {
|
||||
return errors.New("communicator not supported")
|
||||
}
|
|
@ -0,0 +1,563 @@
|
|||
package inspec
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"unicode"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
)
|
||||
|
||||
var SupportedBackends = map[string]bool{"docker": true, "local": true, "ssh": true, "winrm": true}
|
||||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
ctx interpolate.Context
|
||||
|
||||
// The command to run inspec
|
||||
Command string
|
||||
SubCommand string
|
||||
|
||||
// Extra options to pass to the inspec command
|
||||
ExtraArguments []string `mapstructure:"extra_arguments"`
|
||||
InspecEnvVars []string `mapstructure:"inspec_env_vars"`
|
||||
|
||||
// The profile to execute.
|
||||
Profile string `mapstructure:"profile"`
|
||||
AttributesDirectory string `mapstructure:"attributes_directory"`
|
||||
AttributesFiles []string `mapstructure:"attributes"`
|
||||
Backend string `mapstructure:"backend"`
|
||||
User string `mapstructure:"user"`
|
||||
Host string `mapstructure:"host"`
|
||||
LocalPort string `mapstructure:"local_port"`
|
||||
SSHHostKeyFile string `mapstructure:"ssh_host_key_file"`
|
||||
SSHAuthorizedKeyFile string `mapstructure:"ssh_authorized_key_file"`
|
||||
}
|
||||
|
||||
type Provisioner struct {
|
||||
config Config
|
||||
adapter *adapter
|
||||
done chan struct{}
|
||||
inspecVersion string
|
||||
inspecMajVersion uint
|
||||
}
|
||||
|
||||
func (p *Provisioner) Prepare(raws ...interface{}) error {
|
||||
p.done = make(chan struct{})
|
||||
|
||||
err := config.Decode(&p.config, &config.DecodeOpts{
|
||||
Interpolate: true,
|
||||
InterpolateContext: &p.config.ctx,
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
Exclude: []string{},
|
||||
},
|
||||
}, raws...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Defaults
|
||||
if p.config.Command == "" {
|
||||
p.config.Command = "inspec"
|
||||
}
|
||||
|
||||
if p.config.SubCommand == "" {
|
||||
p.config.SubCommand = "exec"
|
||||
}
|
||||
|
||||
var errs *packer.MultiError
|
||||
err = validateProfileConfig(p.config.Profile)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(errs, err)
|
||||
}
|
||||
|
||||
// Check that the authorized key file exists
|
||||
if len(p.config.SSHAuthorizedKeyFile) > 0 {
|
||||
err = validateFileConfig(p.config.SSHAuthorizedKeyFile, "ssh_authorized_key_file", true)
|
||||
if err != nil {
|
||||
log.Println(p.config.SSHAuthorizedKeyFile, "does not exist")
|
||||
errs = packer.MultiErrorAppend(errs, err)
|
||||
}
|
||||
}
|
||||
if len(p.config.SSHHostKeyFile) > 0 {
|
||||
err = validateFileConfig(p.config.SSHHostKeyFile, "ssh_host_key_file", true)
|
||||
if err != nil {
|
||||
log.Println(p.config.SSHHostKeyFile, "does not exist")
|
||||
errs = packer.MultiErrorAppend(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := SupportedBackends[p.config.Backend]; !ok {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("backend: %s must be a valid backend", p.config.Backend))
|
||||
}
|
||||
|
||||
if p.config.Backend == "" {
|
||||
p.config.Backend = "ssh"
|
||||
}
|
||||
|
||||
if p.config.Host == "" {
|
||||
p.config.Host = "127.0.0.1"
|
||||
}
|
||||
|
||||
if len(p.config.LocalPort) > 0 {
|
||||
if _, err := strconv.ParseUint(p.config.LocalPort, 10, 16); err != nil {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("local_port: %s must be a valid port", p.config.LocalPort))
|
||||
}
|
||||
} else {
|
||||
p.config.LocalPort = "0"
|
||||
}
|
||||
|
||||
if len(p.config.AttributesDirectory) > 0 {
|
||||
err = validateDirectoryConfig(p.config.AttributesDirectory, "attrs")
|
||||
if err != nil {
|
||||
log.Println(p.config.AttributesDirectory, "does not exist")
|
||||
errs = packer.MultiErrorAppend(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
if p.config.User == "" {
|
||||
usr, err := user.Current()
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(errs, err)
|
||||
} else {
|
||||
p.config.User = usr.Username
|
||||
}
|
||||
}
|
||||
if p.config.User == "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("user: could not determine current user from environment."))
|
||||
}
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return errs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) getVersion() error {
|
||||
out, err := exec.Command(p.config.Command, "version").Output()
|
||||
if err != nil {
|
||||
return fmt.Errorf(
|
||||
"Error running \"%s version\": %s", p.config.Command, err.Error())
|
||||
}
|
||||
|
||||
versionRe := regexp.MustCompile(`\w (\d+\.\d+[.\d+]*)`)
|
||||
matches := versionRe.FindStringSubmatch(string(out))
|
||||
if matches == nil {
|
||||
return fmt.Errorf(
|
||||
"Could not find %s version in output:\n%s", p.config.Command, string(out))
|
||||
}
|
||||
|
||||
version := matches[1]
|
||||
log.Printf("%s version: %s", p.config.Command, version)
|
||||
p.inspecVersion = version
|
||||
|
||||
majVer, err := strconv.ParseUint(strings.Split(version, ".")[0], 10, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not parse major version from \"%s\".", version)
|
||||
}
|
||||
p.inspecMajVersion = uint(majVer)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
|
||||
ui.Say("Provisioning with Inspec...")
|
||||
|
||||
for i, envVar := range p.config.InspecEnvVars {
|
||||
envVar, err := interpolate.Render(envVar, &p.config.ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not interpolate inspec env vars: %s", err)
|
||||
}
|
||||
p.config.InspecEnvVars[i] = envVar
|
||||
}
|
||||
|
||||
for i, arg := range p.config.ExtraArguments {
|
||||
arg, err := interpolate.Render(arg, &p.config.ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not interpolate inspec extra arguments: %s", err)
|
||||
}
|
||||
p.config.ExtraArguments[i] = arg
|
||||
}
|
||||
|
||||
for i, arg := range p.config.AttributesFiles {
|
||||
arg, err := interpolate.Render(arg, &p.config.ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not interpolate inspec attributes: %s", err)
|
||||
}
|
||||
p.config.AttributesFiles[i] = arg
|
||||
}
|
||||
|
||||
k, err := newUserKey(p.config.SSHAuthorizedKeyFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hostSigner, err := newSigner(p.config.SSHHostKeyFile)
|
||||
// Remove the private key file
|
||||
if len(k.privKeyFile) > 0 {
|
||||
defer os.Remove(k.privKeyFile)
|
||||
}
|
||||
|
||||
keyChecker := ssh.CertChecker{
|
||||
UserKeyFallback: func(conn ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
|
||||
if user := conn.User(); user != p.config.User {
|
||||
return nil, errors.New(fmt.Sprintf("authentication failed: %s is not a valid user", user))
|
||||
}
|
||||
|
||||
if !bytes.Equal(k.Marshal(), pubKey.Marshal()) {
|
||||
return nil, errors.New("authentication failed: unauthorized key")
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
},
|
||||
}
|
||||
|
||||
config := &ssh.ServerConfig{
|
||||
AuthLogCallback: func(conn ssh.ConnMetadata, method string, err error) {
|
||||
log.Printf("authentication attempt from %s to %s as %s using %s", conn.RemoteAddr(), conn.LocalAddr(), conn.User(), method)
|
||||
},
|
||||
PublicKeyCallback: keyChecker.Authenticate,
|
||||
//NoClientAuth: true,
|
||||
}
|
||||
|
||||
config.AddHostKey(hostSigner)
|
||||
|
||||
localListener, err := func() (net.Listener, error) {
|
||||
port, err := strconv.ParseUint(p.config.LocalPort, 10, 16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tries := 1
|
||||
if port != 0 {
|
||||
tries = 10
|
||||
}
|
||||
for i := 0; i < tries; i++ {
|
||||
l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port))
|
||||
port++
|
||||
if err != nil {
|
||||
ui.Say(err.Error())
|
||||
continue
|
||||
}
|
||||
_, p.config.LocalPort, err = net.SplitHostPort(l.Addr().String())
|
||||
if err != nil {
|
||||
ui.Say(err.Error())
|
||||
continue
|
||||
}
|
||||
return l, nil
|
||||
}
|
||||
return nil, errors.New("Error setting up SSH proxy connection")
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ui = newUi(ui)
|
||||
p.adapter = newAdapter(p.done, localListener, config, ui, comm)
|
||||
|
||||
defer func() {
|
||||
log.Print("shutting down the SSH proxy")
|
||||
close(p.done)
|
||||
p.adapter.Shutdown()
|
||||
}()
|
||||
|
||||
go p.adapter.Serve()
|
||||
|
||||
tf, err := ioutil.TempFile(p.config.AttributesDirectory, "packer-provisioner-inspec.*.yml")
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error preparing packer attributes file: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
|
||||
w := bufio.NewWriter(tf)
|
||||
w.WriteString(fmt.Sprintf("packer_build_name: %s\n", p.config.PackerBuildName))
|
||||
w.WriteString(fmt.Sprintf("packer_builder_type: %s\n", p.config.PackerBuilderType))
|
||||
|
||||
if err := w.Flush(); err != nil {
|
||||
tf.Close()
|
||||
return fmt.Errorf("Error preparing packer attributes file: %s", err)
|
||||
}
|
||||
tf.Close()
|
||||
p.config.AttributesFiles = append(p.config.AttributesFiles, tf.Name())
|
||||
|
||||
if err := p.executeInspec(ui, comm, k.privKeyFile); err != nil {
|
||||
return fmt.Errorf("Error executing Inspec: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
func (p *Provisioner) Cancel() {
|
||||
if p.done != nil {
|
||||
close(p.done)
|
||||
}
|
||||
if p.adapter != nil {
|
||||
p.adapter.Shutdown()
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func (p *Provisioner) executeInspec(ui packer.Ui, comm packer.Communicator, privKeyFile string) error {
|
||||
var envvars []string
|
||||
|
||||
args := []string{p.config.SubCommand, p.config.Profile}
|
||||
args = append(args, "--backend", p.config.Backend)
|
||||
args = append(args, "--host", p.config.Host)
|
||||
|
||||
if p.config.Backend == "ssh" {
|
||||
if len(privKeyFile) > 0 {
|
||||
args = append(args, "--key-files", privKeyFile)
|
||||
}
|
||||
args = append(args, "--user", p.config.User)
|
||||
args = append(args, "--port", p.config.LocalPort)
|
||||
}
|
||||
|
||||
args = append(args, "--attrs")
|
||||
args = append(args, p.config.AttributesFiles...)
|
||||
args = append(args, p.config.ExtraArguments...)
|
||||
|
||||
if len(p.config.InspecEnvVars) > 0 {
|
||||
envvars = append(envvars, p.config.InspecEnvVars...)
|
||||
}
|
||||
|
||||
cmd := exec.Command(p.config.Command, args...)
|
||||
|
||||
cmd.Env = os.Environ()
|
||||
if len(envvars) > 0 {
|
||||
cmd.Env = append(cmd.Env, envvars...)
|
||||
}
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stderr, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
repeat := func(r io.ReadCloser) {
|
||||
reader := bufio.NewReader(r)
|
||||
for {
|
||||
line, err := reader.ReadString('\n')
|
||||
if line != "" {
|
||||
line = strings.TrimRightFunc(line, unicode.IsSpace)
|
||||
ui.Message(line)
|
||||
}
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
} else {
|
||||
ui.Error(err.Error())
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
wg.Done()
|
||||
}
|
||||
wg.Add(2)
|
||||
go repeat(stdout)
|
||||
go repeat(stderr)
|
||||
|
||||
ui.Say(fmt.Sprintf("Executing Inspec: %s", strings.Join(cmd.Args, " ")))
|
||||
if err := cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
wg.Wait()
|
||||
err = cmd.Wait()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Non-zero exit status: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateFileConfig(name string, config string, req bool) error {
|
||||
if req {
|
||||
if name == "" {
|
||||
return fmt.Errorf("%s must be specified.", config)
|
||||
}
|
||||
}
|
||||
info, err := os.Stat(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %s is invalid: %s", config, name, err)
|
||||
} else if info.IsDir() {
|
||||
return fmt.Errorf("%s: %s must point to a file", config, name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateProfileConfig(name string) error {
|
||||
if name == "" {
|
||||
return fmt.Errorf("profile must be specified.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateDirectoryConfig(name string, config string) error {
|
||||
info, err := os.Stat(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %s is invalid: %s", config, name, err)
|
||||
} else if !info.IsDir() {
|
||||
return fmt.Errorf("%s: %s must point to a directory", config, name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type userKey struct {
|
||||
ssh.PublicKey
|
||||
privKeyFile string
|
||||
}
|
||||
|
||||
func newUserKey(pubKeyFile string) (*userKey, error) {
|
||||
userKey := new(userKey)
|
||||
if len(pubKeyFile) > 0 {
|
||||
pubKeyBytes, err := ioutil.ReadFile(pubKeyFile)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to read public key")
|
||||
}
|
||||
userKey.PublicKey, _, _, _, err = ssh.ParseAuthorizedKey(pubKeyBytes)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to parse authorized key")
|
||||
}
|
||||
|
||||
return userKey, nil
|
||||
}
|
||||
|
||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to generate key pair")
|
||||
}
|
||||
userKey.PublicKey, err = ssh.NewPublicKey(key.Public())
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to extract public key from generated key pair")
|
||||
}
|
||||
|
||||
// To support Inspec calling back to us we need to write
|
||||
// this file down
|
||||
privateKeyDer := x509.MarshalPKCS1PrivateKey(key)
|
||||
privateKeyBlock := pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Headers: nil,
|
||||
Bytes: privateKeyDer,
|
||||
}
|
||||
tf, err := ioutil.TempFile("", "packer-provisioner-inspec.*.key")
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to create temp file for generated key")
|
||||
}
|
||||
_, err = tf.Write(pem.EncodeToMemory(&privateKeyBlock))
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to write private key to temp file")
|
||||
}
|
||||
|
||||
err = tf.Close()
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to close private key temp file")
|
||||
}
|
||||
userKey.privKeyFile = tf.Name()
|
||||
|
||||
return userKey, nil
|
||||
}
|
||||
|
||||
type signer struct {
|
||||
ssh.Signer
|
||||
}
|
||||
|
||||
func newSigner(privKeyFile string) (*signer, error) {
|
||||
signer := new(signer)
|
||||
|
||||
if len(privKeyFile) > 0 {
|
||||
privateBytes, err := ioutil.ReadFile(privKeyFile)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to load private host key")
|
||||
}
|
||||
|
||||
signer.Signer, err = ssh.ParsePrivateKey(privateBytes)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to parse private host key")
|
||||
}
|
||||
|
||||
return signer, nil
|
||||
}
|
||||
|
||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to generate server key pair")
|
||||
}
|
||||
|
||||
signer.Signer, err = ssh.NewSignerFromKey(key)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to extract private key from generated key pair")
|
||||
}
|
||||
|
||||
return signer, nil
|
||||
}
|
||||
|
||||
// Ui provides concurrency-safe access to packer.Ui.
|
||||
type Ui struct {
|
||||
sem chan int
|
||||
ui packer.Ui
|
||||
}
|
||||
|
||||
func newUi(ui packer.Ui) packer.Ui {
|
||||
return &Ui{sem: make(chan int, 1), ui: ui}
|
||||
}
|
||||
|
||||
func (ui *Ui) Ask(s string) (string, error) {
|
||||
ui.sem <- 1
|
||||
ret, err := ui.ui.Ask(s)
|
||||
<-ui.sem
|
||||
|
||||
return ret, err
|
||||
}
|
||||
|
||||
func (ui *Ui) Say(s string) {
|
||||
ui.sem <- 1
|
||||
ui.ui.Say(s)
|
||||
<-ui.sem
|
||||
}
|
||||
|
||||
func (ui *Ui) Message(s string) {
|
||||
ui.sem <- 1
|
||||
ui.ui.Message(s)
|
||||
<-ui.sem
|
||||
}
|
||||
|
||||
func (ui *Ui) Error(s string) {
|
||||
ui.sem <- 1
|
||||
ui.ui.Error(s)
|
||||
<-ui.sem
|
||||
}
|
||||
|
||||
func (ui *Ui) Machine(t string, args ...string) {
|
||||
ui.sem <- 1
|
||||
ui.ui.Machine(t, args...)
|
||||
<-ui.sem
|
||||
}
|
||||
|
||||
func (ui *Ui) ProgressBar() packer.ProgressBar {
|
||||
return new(packer.NoopProgressBar)
|
||||
}
|
|
@ -0,0 +1,293 @@
|
|||
package inspec
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
// Be sure to remove the InSpec stub file in each test with:
|
||||
// defer os.Remove(config["command"].(string))
|
||||
func testConfig(t *testing.T) map[string]interface{} {
|
||||
m := make(map[string]interface{})
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
inspec_stub := path.Join(wd, "packer-inspec-stub.sh")
|
||||
|
||||
err = ioutil.WriteFile(inspec_stub, []byte("#!/usr/bin/env bash\necho 2.2.16"), 0777)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
m["command"] = inspec_stub
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func TestProvisioner_Impl(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = &Provisioner{}
|
||||
if _, ok := raw.(packer.Provisioner); !ok {
|
||||
t.Fatalf("must be a Provisioner")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_Defaults(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig(t)
|
||||
defer os.Remove(config["command"].(string))
|
||||
|
||||
err := p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatalf("should have error")
|
||||
}
|
||||
|
||||
hostkey_file, err := ioutil.TempFile("", "hostkey")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(hostkey_file.Name())
|
||||
|
||||
publickey_file, err := ioutil.TempFile("", "publickey")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(publickey_file.Name())
|
||||
|
||||
profile_file, err := ioutil.TempFile("", "test")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(profile_file.Name())
|
||||
|
||||
config["ssh_host_key_file"] = hostkey_file.Name()
|
||||
config["ssh_authorized_key_file"] = publickey_file.Name()
|
||||
config["profile"] = profile_file.Name()
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(profile_file.Name())
|
||||
|
||||
err = os.Unsetenv("USER")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_ProfileFile(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig(t)
|
||||
defer os.Remove(config["command"].(string))
|
||||
|
||||
hostkey_file, err := ioutil.TempFile("", "hostkey")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(hostkey_file.Name())
|
||||
|
||||
publickey_file, err := ioutil.TempFile("", "publickey")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(publickey_file.Name())
|
||||
|
||||
config["ssh_host_key_file"] = hostkey_file.Name()
|
||||
config["ssh_authorized_key_file"] = publickey_file.Name()
|
||||
|
||||
err = p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
profile_file, err := ioutil.TempFile("", "test")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(profile_file.Name())
|
||||
|
||||
config["profile"] = profile_file.Name()
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
test_dir, err := ioutil.TempDir("", "test")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(test_dir)
|
||||
|
||||
config["profile"] = test_dir
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_HostKeyFile(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig(t)
|
||||
defer os.Remove(config["command"].(string))
|
||||
|
||||
publickey_file, err := ioutil.TempFile("", "publickey")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(publickey_file.Name())
|
||||
|
||||
profile_file, err := ioutil.TempFile("", "test")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(profile_file.Name())
|
||||
|
||||
filename := make([]byte, 10)
|
||||
n, err := io.ReadFull(rand.Reader, filename)
|
||||
if n != len(filename) || err != nil {
|
||||
t.Fatal("could not create random file name")
|
||||
}
|
||||
|
||||
config["ssh_host_key_file"] = fmt.Sprintf("%x", filename)
|
||||
config["ssh_authorized_key_file"] = publickey_file.Name()
|
||||
config["profile"] = profile_file.Name()
|
||||
|
||||
err = p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should error if ssh_host_key_file does not exist")
|
||||
}
|
||||
|
||||
hostkey_file, err := ioutil.TempFile("", "hostkey")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(hostkey_file.Name())
|
||||
|
||||
config["ssh_host_key_file"] = hostkey_file.Name()
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_AuthorizedKeyFiles(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig(t)
|
||||
defer os.Remove(config["command"].(string))
|
||||
|
||||
hostkey_file, err := ioutil.TempFile("", "hostkey")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(hostkey_file.Name())
|
||||
|
||||
profile_file, err := ioutil.TempFile("", "test")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(profile_file.Name())
|
||||
|
||||
filename := make([]byte, 10)
|
||||
n, err := io.ReadFull(rand.Reader, filename)
|
||||
if n != len(filename) || err != nil {
|
||||
t.Fatal("could not create random file name")
|
||||
}
|
||||
|
||||
config["ssh_host_key_file"] = hostkey_file.Name()
|
||||
config["profile"] = profile_file.Name()
|
||||
config["ssh_authorized_key_file"] = fmt.Sprintf("%x", filename)
|
||||
|
||||
err = p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Errorf("should error if ssh_authorized_key_file does not exist")
|
||||
}
|
||||
|
||||
publickey_file, err := ioutil.TempFile("", "publickey")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(publickey_file.Name())
|
||||
|
||||
config["ssh_authorized_key_file"] = publickey_file.Name()
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Errorf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_LocalPort(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig(t)
|
||||
defer os.Remove(config["command"].(string))
|
||||
|
||||
hostkey_file, err := ioutil.TempFile("", "hostkey")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(hostkey_file.Name())
|
||||
|
||||
publickey_file, err := ioutil.TempFile("", "publickey")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(publickey_file.Name())
|
||||
|
||||
profile_file, err := ioutil.TempFile("", "test")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(profile_file.Name())
|
||||
|
||||
config["ssh_host_key_file"] = hostkey_file.Name()
|
||||
config["ssh_authorized_key_file"] = publickey_file.Name()
|
||||
config["profile"] = profile_file.Name()
|
||||
|
||||
config["local_port"] = "65537"
|
||||
err = p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
config["local_port"] = "22222"
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInspecGetVersion(t *testing.T) {
|
||||
if os.Getenv("PACKER_ACC") == "" {
|
||||
t.Skip("This test is only run with PACKER_ACC=1 and it requires InSpec to be installed")
|
||||
}
|
||||
|
||||
var p Provisioner
|
||||
p.config.Command = "inspec exec"
|
||||
err := p.getVersion()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInspecGetVersionError(t *testing.T) {
|
||||
var p Provisioner
|
||||
p.config.Command = "./test-fixtures/exit1"
|
||||
err := p.getVersion()
|
||||
if err == nil {
|
||||
t.Fatal("Should return error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "./test-fixtures/exit1 version") {
|
||||
t.Fatal("Error message should include command name")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
exit 1
|
|
@ -25,6 +25,7 @@ still distributed with Packer.
|
|||
## Provisioners
|
||||
|
||||
- File
|
||||
- InSpec
|
||||
- PowerShell
|
||||
- Shell
|
||||
- Windows Restart
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
---
|
||||
description: |
|
||||
The inspec Packer provisioner allows inspec profiles to be run to test the
|
||||
machine.
|
||||
layout: docs
|
||||
page_title: 'InSpec - Provisioners'
|
||||
sidebar_current: 'docs-provisioners-inspec'
|
||||
---
|
||||
|
||||
# InSpec Provisioner
|
||||
|
||||
Type: `inspec`
|
||||
|
||||
The `inspec` Packer provisioner runs InSpec profiles. It dynamically creates a
|
||||
target configured to use SSH, runs an SSH server, executes `inspec exec`, and
|
||||
marshals InSpec tests through the SSH server to the machine being provisioned
|
||||
by Packer.
|
||||
|
||||
## Basic Example
|
||||
|
||||
This is a fully functional template that will test an image on DigitalOcean.
|
||||
Replace the mock `api_token` value with your own.
|
||||
|
||||
``` json
|
||||
{
|
||||
"provisioners": [
|
||||
{
|
||||
"type": "inspec",
|
||||
"profile": "https://github.com/dev-sec/linux-baseline"
|
||||
}
|
||||
],
|
||||
|
||||
"builders": [
|
||||
{
|
||||
"type": "digitalocean",
|
||||
"api_token": "<digital ocean api token>",
|
||||
"image": "ubuntu-14-04-x64",
|
||||
"region": "sfo1"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration Reference
|
||||
|
||||
Required Parameters:
|
||||
|
||||
- `profile` - The profile to be executed by InSpec.
|
||||
|
||||
Optional Parameters:
|
||||
|
||||
- `inspec_env_vars` (array of strings) - Environment variables to set before
|
||||
running InSpec. Usage example:
|
||||
|
||||
``` json
|
||||
{
|
||||
"inspec_env_vars": [ "FOO=bar" ]
|
||||
}
|
||||
```
|
||||
|
||||
- `command` (string) - The command to invoke InSpec. Defaults to `inspec`.
|
||||
|
||||
- `extra_arguments` (array of strings) - Extra arguments to pass to InSpec.
|
||||
These arguments *will not* be passed through a shell and arguments should
|
||||
not be quoted. Usage example:
|
||||
|
||||
``` json
|
||||
{
|
||||
"extra_arguments": [ "--sudo", "--reporter", "json" ]
|
||||
}
|
||||
```
|
||||
|
||||
- `attributes` (array of strings) - Attribute Files used by InSpec which will
|
||||
be passed to the `--attrs` argument of the `inspec` command when this
|
||||
provisioner runs InSpec. Specify this if you want a different location.
|
||||
Note using also `"--attrs"` in `extra_arguments` will override this
|
||||
setting.
|
||||
|
||||
- `attributes_directory` (string) - The directory in which to place the
|
||||
temporary generated InSpec Attributes file. By default, this is the
|
||||
system-specific temporary file location. The fully-qualified name of this
|
||||
temporary file will be passed to the `--attrs` argument of the `inspec`
|
||||
command when this provisioner runs InSpec. Specify this if you want a
|
||||
different location.
|
||||
|
||||
- `backend` (string) - Backend used by InSpec for connection. Defaults to
|
||||
SSH.
|
||||
|
||||
- `host` (string) - Host used for by InSpec for connection. Defaults to
|
||||
localhost.
|
||||
|
||||
- `local_port` (string) - The port on which to attempt to listen for SSH
|
||||
connections. This value is a starting point. The provisioner will attempt to
|
||||
listen for SSH connections on the first available of ten ports, starting at
|
||||
`local_port`. A system-chosen port is used when `local_port` is missing or
|
||||
empty.
|
||||
|
||||
- `ssh_host_key_file` (string) - The SSH key that will be used to run the SSH
|
||||
server on the host machine to forward commands to the target machine.
|
||||
InSpec connects to this server and will validate the identity of the server
|
||||
using the system known\_hosts. The default behavior is to generate and use
|
||||
a onetime key.
|
||||
|
||||
- `ssh_authorized_key_file` (string) - The SSH public key of the InSpec
|
||||
`ssh_user`. The default behavior is to generate and use a onetime key. If
|
||||
this key is generated, the corresponding private key is passed to `inspec`
|
||||
command with the `-i inspec_ssh_private_key_file` option.
|
||||
|
||||
- `user` (string) - The `--user` to use. Defaults to the user running Packer.
|
||||
|
||||
## Default Extra Variables
|
||||
|
||||
In addition to being able to specify extra arguments using the
|
||||
`extra_arguments` configuration, the provisioner automatically defines certain
|
||||
commonly useful InSpec Attributes:
|
||||
|
||||
- `packer_build_name` is set to the name of the build that Packer is running.
|
||||
This is most useful when Packer is making multiple builds and you want to
|
||||
distinguish them slightly when using a common profile.
|
||||
|
||||
- `packer_builder_type` is the type of the builder that was used to create
|
||||
the machine that the script is running on. This is useful if you want to
|
||||
run only certain parts of the profile on systems built with certain
|
||||
builders.
|
||||
|
||||
## Debugging
|
||||
|
||||
To debug underlying issues with InSpec, add `"-l"` to `"extra_arguments"` to
|
||||
enable verbose logging.
|
||||
|
||||
``` json
|
||||
{
|
||||
"extra_arguments": [ "-l", "debug" ]
|
||||
}
|
||||
```
|
|
@ -216,6 +216,9 @@
|
|||
<li<%= sidebar_current("docs-provisioners-file")%>>
|
||||
<a href="/docs/provisioners/file.html">File</a>
|
||||
</li>
|
||||
<li<%= sidebar_current("docs-provisioners-inspec")%>>
|
||||
<a href="/docs/provisioners/inspec.html">InSpec</a>
|
||||
</li>
|
||||
<li<%= sidebar_current("docs-provisioners-powershell")%>>
|
||||
<a href="/docs/provisioners/powershell.html">PowerShell</a>
|
||||
</li>
|
||||
|
|
Loading…
Reference in New Issue