Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Execute ansible in a sandbox container #656

Open
wants to merge 8 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,15 @@ const DefaultUpgradesConcurrencyLimit = 1000
// DefaultSSHConnectionTimeout is the default timeout for SSH connections
const DefaultSSHConnectionTimeout = 10 * time.Second

// DefaultSandboxWorkDir is the default workdir in the sandbox container where we mount the ansible recipes
const DefaultSandboxWorkDir = "/work/ansible"

// DefaultSandboxOverlayDir is the default directory in the sandbox container where we mount the overlay
const DefaultSandboxOverlayDir = "/work/overlay"

// DefaultSandboxMountAgentSocket is the default location in the sandbox container where we mount the ssh agent socket
const DefaultSandboxMountAgentSocket = "/work/ssh-agent"

// Configuration holds config information filled by Cobra and Viper (see commands package for more information)
type Configuration struct {
Ansible Ansible `yaml:"ansible,omitempty" mapstructure:"ansible"`
Expand Down Expand Up @@ -120,6 +129,9 @@ type DockerSandbox struct {
Command []string `mapstructure:"command"`
Entrypoint []string `mapstructure:"entrypoint"`
Env []string `mapstructure:"env"`
User string `mapstructure:"user"`
Cpus string `mapstructure:"cpus"`
Memory string `mapstructure:"memory"`
}

// HostedOperations holds the configuration for operations executed on the orechestrator host (eg. with an operation_host equals to ORECHESTRATOR)
Expand Down
2 changes: 1 addition & 1 deletion config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ func TestHostedOperations_Format(t *testing.T) {
}{
{"DefaultValues", fields{}, `{UnsandboxedOperationsAllowed:false DefaultSandbox:<nil>}`},
{"AllowUnsandboxed", fields{UnsandboxedOperationsAllowed: true}, `{UnsandboxedOperationsAllowed:true DefaultSandbox:<nil>}`},
{"DefaultSandboxConfigured", fields{DefaultSandbox: &DockerSandbox{Image: "alpine:3.7", Command: []string{"cmd", "arg"}}}, `{UnsandboxedOperationsAllowed:false DefaultSandbox:&{Image:alpine:3.7 Command:[cmd arg] Entrypoint:[] Env:[]}}`},
{"DefaultSandboxConfigured", fields{DefaultSandbox: &DockerSandbox{Image: "alpine:3.7", Command: []string{"cmd", "arg"}, User: "1000:1000", Cpus: "1", Memory: "200m"}}, `{UnsandboxedOperationsAllowed:false DefaultSandbox:&{Image:alpine:3.7 Command:[cmd arg] Entrypoint:[] Env:[] User:1000:1000 Cpus:1 Memory:200m}}`},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ require (
github.com/armon/go-metrics v0.3.0
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 // indirect
github.com/blang/semver v3.5.1+incompatible
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869
github.com/bradleyjkemp/cupaloy v2.3.0+incompatible // indirect
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927 // indirect
Expand Down Expand Up @@ -109,6 +110,7 @@ require (
golang.org/x/net v0.0.0-20200202094626-16171245cfb2
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 // indirect
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
golang.org/x/tools v0.0.0-20200302225559-9b52d559c609
gopkg.in/AlecAivazis/survey.v1 v1.6.3
gopkg.in/cookieo9/resources-go.v2 v2.0.0-20150225115733-d27c04069d0d
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 // indirect
Expand Down
17 changes: 16 additions & 1 deletion helper/sshutil/sshutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"time"

"github.com/pkg/errors"
"github.com/satori/go.uuid"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
"golang.org/x/net/context"
Expand Down Expand Up @@ -268,14 +269,28 @@ func (client *SSHClient) CopyFile(source io.Reader, remotePath string, permissio
return nil
}

// NewSSHAgent allows to return a new SSH Agent
// NewSSHAgent allows to return a new SSH Agent. The agent socket is created at /tmp/ssh-XXXXXXXXXX/agent.<ppid> by default
func NewSSHAgent(ctx context.Context) (*SSHAgent, error) {
return NewSSHAgentWithSocket(ctx, "")
}

// NewSSHAgentWithSocket allows to return a new SSH Agent. If socketDir is specified, create the agent socket at /socketDir/<uuid>/agent
func NewSSHAgentWithSocket(ctx context.Context, socketDir string) (*SSHAgent, error) {
bin, err := exec.LookPath("ssh-agent")
if err != nil {
return nil, errors.Wrap(err, "could not find ssh-agent")
}

cmd := executil.Command(ctx, bin)
if socketDir != "" {
tempDir := filepath.Join(socketDir, uuid.NewV4().String())
if err = os.MkdirAll(tempDir, 0770); err != nil {
return nil, errors.Wrapf(err, "could not create socket directory for ssh-agent at %q", tempDir)
}
bindAddress := filepath.Join(tempDir, "agent")
log.Debugf("Set ssh-agent bind-address to %q", bindAddress)
cmd.Args = append(cmd.Args, "-a", bindAddress)
}
out, err := cmd.Output()
if err != nil {
return nil, errors.Wrap(err, "failed to run ssh-agent")
Expand Down
21 changes: 21 additions & 0 deletions helper/sshutil/sshutil_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import (
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"github.com/stretchr/testify/assert"
"os"
"testing"

"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -67,6 +69,25 @@ func TestSSHAgent(t *testing.T) {
keys, err = sshAg.agent.List()
require.Nil(t, err)
require.Len(t, keys, 0, "no key expected")

// test create more than one agents in a specific socket directory
sshAg1, err := NewSSHAgentWithSocket(context.Background(), "/tmp/ssh-agent")
require.Nil(t, err)
sshAg2, err := NewSSHAgentWithSocket(context.Background(), "/tmp/ssh-agent")
require.Nil(t, err)
require.DirExists(t, "/tmp/ssh-agent")
assert.NotEqual(t, sshAg1.Socket, sshAg2.Socket)
// stop one ssh-agent does not remove the parent socket directory but the given agent socket only
err = sshAg1.Stop()
require.Nil(t, err)
require.DirExists(t, "/tmp/ssh-agent", "stop one ssh-agent removed the parent socket directory")
if _, err := os.Stat(sshAg1.Socket); err == nil {
t.Errorf("failed to remove agent socket when stopping ssh-agent %v", err)
}
require.FileExists(t, sshAg2.Socket, "stop one ssh-agent but removed another agent socket")
err = sshAg2.Stop()
err = os.RemoveAll("/tmp/ssh-agent")
require.Nil(t, err, "failed to clean up /tmp/ssh-agent after test")
}

// BER SSH key is not handled by crypto/ssh
Expand Down
15 changes: 15 additions & 0 deletions pkg/ansible/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Dockerfile for building the sandbox container

FROM alpine:3.10

ENV ANSIBLE_VERSION=2.7.9

RUN apk add --no-cache --progress bash python3 openssl ca-certificates openssh sshpass && \
addgroup -g 1000 ansible && \
adduser -D -s /bin/bash -g ansible -G ansible -u 1000 ansible && \
if [ ! -e /usr/bin/python ]; then ln -sf /usr/bin/python3 /usr/bin/python; fi && \
apk --update add --virtual build-dependencies python3-dev libffi-dev openssl-dev build-base && \
pip3 install --upgrade pip && \
pip3 install ansible==${ANSIBLE_VERSION} netaddr jmespath && \
apk del build-dependencies && \
rm -rf /var/cache/apk/*
3 changes: 3 additions & 0 deletions prov/ansible/consul_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,7 @@ func TestRunConsulAnsiblePackageTests(t *testing.T) {
t.Run("TestLogAnsibleOutputInConsulFromScriptFailure", func(t *testing.T) {
testLogAnsibleOutputInConsulFromScriptFailure(t)
})
t.Run("TestSandbox", func(t *testing.T) {
testCreatesandbox(t)
})
}
Loading