Skip to content

WIP: Support for @SUMMONVARNAMES #200

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

Closed
wants to merge 1 commit into from
Closed
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
10 changes: 6 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM golang:1.15
FROM golang:1.15-alpine

WORKDIR /summon

Expand All @@ -7,9 +7,11 @@ ENV GOARCH=amd64

COPY go.mod go.sum ./

RUN apt update -y && \
apt install -y bash \
git && \
RUN apk add --no-cache bash \
build-base \
docker-cli \
git && \
go mod download && \
go mod download && \
go get -u github.com/jstemmer/go-junit-report && \
go get -u github.com/axw/gocov/gocov && \
Expand Down
1 change: 1 addition & 0 deletions Dockerfile.acceptance
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ FROM golang:1.15-alpine

RUN apk add --no-cache bash \
build-base \
docker-cli \
git \
libffi-dev \
ruby-bundler \
Expand Down
72 changes: 67 additions & 5 deletions docs/_includes/docker.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,80 @@ Since Summon has pluggable providers, you aren't locked into any one solution fo
managing your secrets.

Summon makes it easy to inject secrets as environment variables into your Docker
containers by taking advantage of Docker's `--env-file` argument. This is done
on-demand by using the variable `@SUMMONENVFILE` in the arguments of the process
you are running with Summon. This variable points to a memory-mapped file containing
the variables and values from secrets.yml in VAR=VAL format.
containers by taking advantage of Docker's CLI arguments
(`--env-file` or `--env`). There are two options available.It's possible to mix
and match as you see fit.

## Docker --env arguments

This is done on-demand by using the value `@SUMMONVARNAMES` in the subprocess
arguments passed to Summon. This value is replaced by a space separated list of
the environment variables **names** injected by Summon. With the help of `xargs`
and `printf` we can easily generate the `--env` arguments necessary for the
Docker container to pickup the secrets injected by summon.

```bash
$ summon -p keyring.py -D env=dev env SUMMONVARNAMES=@SUMMONVARNAMES sh << EOL
docker run \$(printenv SUMMONVARNAMES | xargs printf -- '--env %s ' | xargs) deployer
EOL
Checking credentials
Deploying application
```

### Docker --env arguments example

Below we provide a complete example of using @SUMMONVARNAMES to generate the
Docker `--env` arguments. For the sake of brevity we use an inline `secrets.yml`
and the `/bin/echo` provider. Some points to note:

1. `summon` is invoking the `docker` CLI as a child process.
2. `@SUMMONVARNAMES` is transformed into to generate instances of the Docker
argument `--env`.

```bash
secretsyml='
A: |-
A_value with
multiple lines
B: B_value
C: !var C_value
'

# The substitution and transformation of @SUMMONVARNAMES in the `docker run`
# command below results in something of the form:
#
# docker run --rm \
# --env A --env B --env C \
# alpine ...
#
# The output from the command is shown below the command.

summon --provider /bin/echo --yaml "${secretsyml}" env SUMMONVARNAMES=@SUMMONVARNAMES sh << EOL
docker run --rm \$(printenv SUMMONVARNAMES | xargs printf -- '--env %s ' | xargs) alpine sh -c '
printenv A;
printenv B;
printenv C;
'
EOL

# A_value with
# multiple lines
# B_value
# C_value
```

## Docker --env-file argument
This is done on-demand by using the variable `@SUMMONENVFILE` in the arguments of
the process you are running with Summon. This variable points to a memory-mapped
file containing the variables and values from secrets.yml in VAR=VAL format.

```sh
$ summon -p keyring.py -D env=dev docker run --env-file @SUMMONENVFILE deployer
Checking credentials
Deploying application
```

## Example
### @SUMMONENVFILE Example

Let's say we have a deploy script that needs to access our application servers on
AWS and pull the latest version of our code. It should record the outcome of the
Expand Down
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ module github.com/cyberark/summon

require (
github.com/codegangsta/cli v1.20.0
github.com/docker/docker v20.10.2+incompatible
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect
github.com/jtolds/gls v4.20.0+incompatible // indirect
github.com/kr/pretty v0.1.0 // indirect
Expand Down
7 changes: 7 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@ github.com/codegangsta/cli v1.20.0 h1:iX1FXEgwzd5+XN6wk5cVHOGQj6Q3Dcp20lUeS4lHNT
github.com/codegangsta/cli v1.20.0/go.mod h1:/qJNoX69yVSKu5o4jLyXAENLRyk1uhi7zkbQ3slBdOA=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docker/docker v1.13.1 h1:IkZjBSIc8hBjLpqeAbeE5mca5mNgeatLHBy3GO78BWo=
github.com/docker/docker v20.10.2+incompatible h1:vFgEHPqWBTp4pTjdLwjAA4bSo3gvIGOYwuJTlEjVBCw=
github.com/docker/docker v20.10.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
Expand Down
51 changes: 42 additions & 9 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 @@ -12,16 +13,21 @@ 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
TmpPath string
YamlInline string
Subs map[string]string
Ignores []string
Expand All @@ -31,8 +37,9 @@ type ActionConfig struct {
ShowProviderVersions bool
}

const ENV_FILE_MAGIC = "@SUMMONENVFILE"
const SUMMON_ENV_KEY_NAME = "SUMMON_ENV"
const EnvFileMagic = "@SUMMONENVFILE"
const VarNamesMagic = "@SUMMONVARNAMES"
const SummonEnvKeyName = "SUMMON_ENV"

// Action is the runner for the main program logic
var Action = func(c *cli.Context) {
Expand Down Expand Up @@ -110,7 +117,7 @@ func runAction(ac *ActionConfig) error {
}

env := make(map[string]string)
tempFactory := NewTempFactory("")
tempFactory := NewTempFactory(ac.TmpPath)
defer tempFactory.Cleanup()

type Result struct {
Expand Down Expand Up @@ -145,6 +152,7 @@ func runAction(ac *ActionConfig) error {
}

k, v := formatForEnv(key, value, spec, &tempFactory)

results <- Result{k, v, nil}
wg.Done()
}(key, spec)
Expand Down Expand Up @@ -172,17 +180,42 @@ EnvLoop:

// Append environment variable if one is specified
if ac.Environment != "" {
env[SUMMON_ENV_KEY_NAME] = ac.Environment
env[SummonEnvKeyName] = ac.Environment
}

setupEnvFile(ac.Args, env, &tempFactory)
setupVarNames(ac.Args, secrets)

var e []string
for k, v := range env {
e = append(e, fmt.Sprintf("%s=%s", k, v))
}

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

func setupVarNames(args []string, secrets secretsyml.SecretsMap) {
var varNames []string
for varName := range secrets {
varNames = append(varNames, varName)
}
sort.Strings(varNames)

// Inject @SUMMONVARNAMES
for idx, arg := range args {
// Replace argument substring
if strings.Contains(arg, VarNamesMagic) {
// Replace substring in argument with slice of docker options
args[idx] = strings.Replace(arg, VarNamesMagic, strings.Join(varNames, " "), -1)
continue
}
}
}

// formatForEnv returns a string in %k=%v format, where %k=namespace of the secret and
Expand All @@ -201,7 +234,7 @@ func joinEnv(env map[string]string) string {
for k, v := range env {
envs = append(envs, fmt.Sprintf("%s=%s", k, v))
}

// Sort to ensure predictable results
sort.Strings(envs)

Expand Down Expand Up @@ -245,20 +278,20 @@ 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.
func setupEnvFile(args []string, env map[string]string, tempFactory *TempFactory) string {
var envFile = ""

for i, arg := range args {
idx := strings.Index(arg, ENV_FILE_MAGIC)
idx := strings.Index(arg, EnvFileMagic)
if idx >= 0 {
if envFile == "" {
envFile = tempFactory.Push(joinEnv(env))
}
args[i] = strings.Replace(arg, ENV_FILE_MAGIC, envFile, -1)
args[i] = strings.Replace(arg, EnvFileMagic, envFile, -1)
}
}

Expand Down
Loading