-
Notifications
You must be signed in to change notification settings - Fork 63
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
Preliminary support for @SUMMONDOCKERARGS #186
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,8 +16,74 @@ 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 | ||
containers by taking advantage of Docker's CLI arguments (`--env-file` or, `--env` and | ||
`--volume`. There are two options available. It's possible to mix and match as you see fit. | ||
|
||
## Docker --env and --volume arguments | ||
|
||
This is done on-demand by using the variable `@SUMMONDOCKERARGS` in the arguments of the | ||
process you are running with Summon. This variable is replaced by combinations of the | ||
Docker arguments `--env` and `--volume` such that the secrets injected by summon are | ||
passed into the Docker container. The `--volume` arguments allow memory-mapped temporary | ||
files from variables with the `!file` tag to be resolvable inside the container. | ||
|
||
**NOTE:** Using the `!file` tag with `@SUMMONDOCKERARGS` assumes that the Docker CLI is | ||
being run on the host that is used to create volume mounts to the container. For when | ||
this is not the case simply avoid using the `!file` tag, but be mindful that in that case | ||
you lose the benefits of memory-mapped temporary files. | ||
|
||
```bash | ||
$ summon -p keyring.py -D env=dev docker run @SUMMONDOCKERARGS deployer | ||
Checking credentials | ||
Deploying application | ||
``` | ||
|
||
### @SUMMONDOCKERARGS Example | ||
|
||
The example below demonstrates the use of @SUMMONDOCKERARGS. For the sake of brevity | ||
we use an inline `secrets.yml` and the `/bin/echo` provider. Some points to note: | ||
|
||
1. `summon` is invoking `docker` as the child process. | ||
2. `@SUMMONDOCKERARGS` is replaced with a combination of `--env` and `--volume` arguments. | ||
3. Variable `D` uses the `!file` tag and therefore is the only one that | ||
results in a `--volume` argument. The path to this variable inside the container | ||
is as it is on the host. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm assuming that a temp file is being created for these There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point. This might get tricky if for host to container you're going from Windows to Linux or vice-versa. I'm wondering now if I should just altogether disregard the case for files since it adds complication. Having There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if we can add some new The syntax proposed in Issue #190 is something like this:
For Issue #190, the For @SUMMONDOCKERARGS, the
I think this would be easy for users to understand. Maybe this can be added as a followup... along with changes to the secretsyml to add this syntax? WDYT? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like the idea of allowing the user to specify the location that Summon creates files. Even though not every platform supports For now I got rid of the implicit
|
||
|
||
```bash | ||
secretsyml=' | ||
A: |- | ||
A_value with | ||
multiple lines | ||
B: B_value | ||
C: !var C_value | ||
D: !var:file D_value | ||
' | ||
|
||
# The substitution of @SUMMONDOCKERARGS the docker run command below results in | ||
# something of the form: | ||
# | ||
# docker run --rm \ | ||
# --env A --env B --env C --env D \ | ||
# --volume /path/to/D:/path/to/D | ||
# alpine ... | ||
# | ||
# The output from the command is shown below the command. | ||
|
||
summon --provider /bin/echo --yaml "${secretsyml}" \ | ||
docker run --rm @SUMMONDOCKERARGS alpine sh -c ' | ||
printenv A; | ||
printenv B; | ||
printenv C; | ||
cat $(printenv D); | ||
' | ||
# A_value with | ||
# multiple lines | ||
# B_value | ||
# C_value | ||
# D_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. | ||
|
||
|
@@ -27,7 +93,7 @@ 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 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,21 +3,27 @@ package command | |
import ( | ||
"bytes" | ||
"fmt" | ||
"io" | ||
"os" | ||
"os/exec" | ||
"path/filepath" | ||
"sort" | ||
"strings" | ||
"sync" | ||
"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 | ||
|
@@ -30,8 +36,9 @@ type ActionConfig struct { | |
ShowProviderVersions bool | ||
} | ||
|
||
const ENV_FILE_MAGIC = "@SUMMONENVFILE" | ||
const SUMMON_ENV_KEY_NAME = "SUMMON_ENV" | ||
const EnvFileMagic = "@SUMMONENVFILE" | ||
const DockerArgsMagic = "@SUMMONDOCKERARGS" | ||
const SummonEnvKeyName = "SUMMON_ENV" | ||
|
||
// Action is the runner for the main program logic | ||
var Action = func(c *cli.Context) { | ||
|
@@ -122,6 +129,9 @@ func runAction(ac *ActionConfig) error { | |
results := make(chan Result, len(secrets)) | ||
var wg sync.WaitGroup | ||
|
||
var dockerArgs []string | ||
var dockerArgsMutex sync.Mutex | ||
|
||
for key, spec := range secrets { | ||
wg.Add(1) | ||
go func(key string, spec secretsyml.SecretSpec) { | ||
|
@@ -144,6 +154,15 @@ func runAction(ac *ActionConfig) error { | |
} | ||
|
||
k, v := formatForEnv(key, value, spec, &tempFactory) | ||
|
||
// Generate @SUMMONDOCKERARGS | ||
dockerArgsMutex.Lock() | ||
defer dockerArgsMutex.Unlock() | ||
if spec.IsFile() { | ||
dockerArgs = append(dockerArgs, "--volume", v+":"+v) | ||
} | ||
dockerArgs = append(dockerArgs, "--env", k) | ||
|
||
results <- Result{k, v, nil} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we be zeroing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably a good idea, I don't know if Summon does any zeroization at all. But I think that piece is out of scope of this PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't |
||
wg.Done() | ||
}(key, spec) | ||
|
@@ -171,17 +190,48 @@ 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) | ||
|
||
// Setup Docker args | ||
var argsWithDockerArgs []string | ||
for _, arg := range ac.Args { | ||
// Replace entire argument | ||
if arg == DockerArgsMagic { | ||
// Replace argument with slice of docker options | ||
argsWithDockerArgs = append(argsWithDockerArgs, dockerArgs...) | ||
continue | ||
} | ||
|
||
// Replace argument substring | ||
idx := strings.Index(arg, DockerArgsMagic) | ||
if idx >= 0 { | ||
// Replace substring in argument with slice of docker options | ||
argsWithDockerArgs = append( | ||
argsWithDockerArgs, | ||
strings.Replace(arg, DockerArgsMagic, strings.Join(dockerArgs, " "), -1), | ||
) | ||
continue | ||
} | ||
|
||
argsWithDockerArgs = append(argsWithDockerArgs, arg) | ||
} | ||
ac.Args = argsWithDockerArgs | ||
|
||
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, | ||
) | ||
} | ||
|
||
// formatForEnv returns a string in %k=%v format, where %k=namespace of the secret and | ||
|
@@ -200,6 +250,10 @@ 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) | ||
doodlesbykumbi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
return strings.Join(envs, "\n") + "\n" | ||
} | ||
|
||
|
@@ -240,20 +294,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) | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be moved up to retain alphabetical listing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done