Skip to content

Commit

Permalink
Preliminary support for @DOCKEROPTS
Browse files Browse the repository at this point in the history
@DOCKEROPTS is imagined to make it easier to lift summon secrets into docker. With usage like this in mind: summon docker run @DOCKEROPTS -it alpine
  • Loading branch information
doodlesbykumbi committed Dec 30, 2020
1 parent 6145b9f commit 31f52b1
Show file tree
Hide file tree
Showing 8 changed files with 140 additions and 8 deletions.
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ COPY go.mod go.sum ./

RUN apk add --no-cache bash \
build-base \
docker-cli \
git && \
go mod download && \
go get -u github.com/jstemmer/go-junit-report && \
Expand Down
3 changes: 2 additions & 1 deletion Dockerfile.acceptance
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ RUN apk add --no-cache bash \
git \
libffi-dev \
ruby-bundler \
ruby-dev
ruby-dev \
docker-cli

# Install summon prerequisites
WORKDIR /summon
Expand Down
15 changes: 15 additions & 0 deletions acceptance/features/dockeropts.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Feature: dockeropts

As a developer using summon
I want to be able to use my secrets in a docker container environment
I do not want to add the summon binary to every container

Scenario: Running an dockeropts-consuming command
Given a file named "secrets.yml" with:
"""
DB_PASSWORD: !var very/secret/db-password
"""

And a secret "very/secret/db-password" with "notSoSecret"
When I successfully run `summon -p ./provider docker run --rm @DOCKEROPTS alpine printenv DB_PASSWORD`
Then the output should contain exactly "DB_PASSWORD=notSoSecret\n"
52 changes: 50 additions & 2 deletions internal/command/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package command
import (
"bytes"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
Expand All @@ -11,13 +12,17 @@ import (
"syscall"

"github.com/codegangsta/cli"

prov "github.com/cyberark/summon/provider"
"github.com/cyberark/summon/secretsyml"
)

// ActionConfig is an object that holds all the info needed to run
// a Summon instance
type ActionConfig struct {
StdIn io.Reader
StdOut io.Writer
StdErr io.Writer
Args []string
Provider string
Filepath string
Expand All @@ -31,6 +36,7 @@ type ActionConfig struct {
}

const ENV_FILE_MAGIC = "@SUMMONENVFILE"
const DOCKER_OPTS_MAGIC = "@DOCKEROPTS"
const SUMMON_ENV_KEY_NAME = "SUMMON_ENV"

// Action is the runner for the main program logic
Expand Down Expand Up @@ -121,6 +127,9 @@ func runAction(ac *ActionConfig) error {
results := make(chan Result, len(secrets))
var wg sync.WaitGroup

var dockerOpts []string
var dockerOptsMutex sync.Mutex

for key, spec := range secrets {
wg.Add(1)
go func(key string, spec secretsyml.SecretSpec) {
Expand All @@ -143,6 +152,16 @@ func runAction(ac *ActionConfig) error {
}

envvar := formatForEnv(key, value, spec, &tempFactory)

// Generate Docker options
dockerOptsMutex.Lock()
defer dockerOptsMutex.Unlock()
if spec.IsFile() {
fileValue := strings.SplitN(envvar, "=", 2)[1]
dockerOpts = append(dockerOpts, "-v", fileValue+":"+fileValue)
}
dockerOpts = append(dockerOpts, "-e", key)

results <- Result{envvar, nil}
wg.Done()
}(key, spec)
Expand Down Expand Up @@ -173,9 +192,38 @@ EnvLoop:
env = append(env, fmt.Sprintf("%s=%s", SUMMON_ENV_KEY_NAME, ac.Environment))
}

// Setup Docker options
var argsWithDockerOpts []string
for _, arg := range ac.Args {
//idx := strings.Index(arg, DOCKER_OPTS_MAGIC)
if arg == DOCKER_OPTS_MAGIC {
// Replace argument with slice of docker options
argsWithDockerOpts = append(argsWithDockerOpts, dockerOpts...)
continue
}

//if idx >= 0 {
// // Replace argument with slice of docker options
// argsWithDockerOpts = append(
// argsWithDockerOpts,
// strings.Replace(arg, DOCKER_OPTS_MAGIC, strings.Join(dockerOpts, " "), -1),
// )
// continue
//}

argsWithDockerOpts = append(argsWithDockerOpts, arg)
}
ac.Args = argsWithDockerOpts

setupEnvFile(ac.Args, env, &tempFactory)

return runSubcommand(ac.Args, append(os.Environ(), env...))
return runSubcommand(
ac.Args,
append(os.Environ(), env...),
ac.StdIn,
ac.StdOut,
ac.StdErr,
)
}

// formatForEnv returns a string in %k=%v format, where %k=namespace of the secret and
Expand Down Expand Up @@ -230,7 +278,7 @@ func findInParentTree(secretsFile string, leafDir string) (string, error) {
}
}

// scans arguments for the magic string; if found,
// scans arguments for the envfile magic string; if found,
// creates a tempfile to which all the environment mappings are dumped
// and replaces the magic string with its path.
// Returns the path if so, returns an empty string otherwise.
Expand Down
49 changes: 48 additions & 1 deletion internal/command/action_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package command

import (
"bytes"
"errors"
"fmt"
"io/ioutil"
Expand All @@ -12,9 +13,10 @@ import (
"testing"
"time"

"github.com/cyberark/summon/secretsyml"
. "github.com/smartystreets/goconvey/convey"
_ "golang.org/x/net/context"

"github.com/cyberark/summon/secretsyml"
)

func TestConvertSubsToMap(t *testing.T) {
Expand Down Expand Up @@ -123,6 +125,51 @@ func TestRunAction(t *testing.T) {

So(string(content), ShouldEqual, expectedValue)
})

Convey("Docker options correctly injected", t, func() {
var buff bytes.Buffer
err := runAction(&ActionConfig{
Provider: "/bin/echo",
StdOut: &buff,
Args: []string{
"docker", "run", "--rm", "@DOCKEROPTS", "alpine", "sh", "-c",
`
echo "$(printenv A)
$(printenv B)
$(cat "$(printenv C)")
$(cat "$(printenv D)")";
`,
},
YamlInline: `
A: |
A's multiple line
value
B: !var B_value
C: !file C_value
D: !var:file D_value
`,
})

code, err := returnStatusOfError(err)
So(err, ShouldBeNil)
So(code, ShouldEqual, 0)

if err != nil || code != 0 {
return
}

So(err, ShouldBeNil)
if err != nil {
return
}

So(string(buff.String()), ShouldEqual, `A's multiple line
value
B_value
C_value
D_value
`)
})
}

func TestDefaultVariableResolution(t *testing.T) {
Expand Down
26 changes: 22 additions & 4 deletions internal/command/subcommand.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package command

import (
"io"
"os"
"os/exec"
"os/signal"
Expand All @@ -11,16 +12,33 @@ import (
// of an environment populated with secret values. Since we have to
// clean up our temp directories, we remain resident and shuffle
// signals around to the chld and back
func runSubcommand(command []string, env []string) error {
func runSubcommand(
command []string,
env []string,
Stdin io.Reader,
Stdout io.Writer,
Stderr io.Writer,
) error {
binary, lookupErr := exec.LookPath(command[0])
if lookupErr != nil {
return lookupErr
}

runner := exec.Command(binary, command[1:]...)
runner.Stdin = os.Stdin
runner.Stdout = os.Stdout
runner.Stderr = os.Stderr

if Stdin == nil {
Stdin = os.Stdin
}
if Stdout == nil {
Stdout = os.Stdout
}
if Stderr == nil {
Stderr = os.Stderr
}

runner.Stdin = Stdin
runner.Stdout = Stdout
runner.Stderr = Stderr
runner.Env = env

signalChannel := make(chan os.Signal, 1)
Expand Down
1 change: 1 addition & 0 deletions test_acceptance
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ docker build --pull -t summon-acceptance -f Dockerfile.acceptance .

docker run --rm -t \
-v "$OUTPUT_DIR:/summon/acceptance/output" \
-v "/var/run/docker.sock:/var/run/docker.sock" \
summon-acceptance bash -c 'cucumber --format junit --out ./output'

echo "Results are in $OUTPUT_DIR"
1 change: 1 addition & 0 deletions test_unit
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ rm -f "output/*.xml"

docker run --rm -t \
-v "$PWD/output:/summon/output" \
-v "/var/run/docker.sock:/var/run/docker.sock" \
summon-builder bash -c "$TEST_CMD;
gocov convert output/c.out | gocov-xml > output/coverage.xml;
"

0 comments on commit 31f52b1

Please sign in to comment.