Allow auto_auth with templates without specifying a sink (#8812)

For situations where you want the Vault agent to handle one or more templates but do not require the acquired credentials elsewhere.

Modify the logic in SyncServer so that if there are no sinks, ignore any new credentials. Since SyncServer is responsible for shutting down the agent, make sure it still properly shuts down in this new situation.

Solves #7988
This commit is contained in:
Thomas L. Kula 2020-05-26 13:52:14 -04:00 committed by GitHub
parent 1dd2113755
commit 3ce9615992
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 240 additions and 51 deletions

View File

@ -156,8 +156,10 @@ func LoadConfig(path string) (*Config, error) {
}
if result.AutoAuth != nil {
if len(result.AutoAuth.Sinks) == 0 && (result.Cache == nil || !result.Cache.UseAutoAuthToken) {
return nil, fmt.Errorf("auto_auth requires at least one sink or cache.use_auto_auth_token=true ")
if len(result.AutoAuth.Sinks) == 0 &&
(result.Cache == nil || !result.Cache.UseAutoAuthToken) &&
len(result.Templates) == 0 {
return nil, fmt.Errorf("auto_auth requires at least one sink or at least one template or cache.use_auto_auth_token=true")
}
}

View File

@ -254,6 +254,13 @@ func TestLoadConfigFile_Bad_AutoAuth_Wrapped_Multiple_Sinks(t *testing.T) {
}
}
func TestLoadConfigFile_Bad_AutoAuth_Nosinks_Nocache_Notemplates(t *testing.T) {
_, err := LoadConfig("./test-fixtures/bad-config-auto_auth-nosinks-nocache-notemplates.hcl")
if err == nil {
t.Fatal("LoadConfig should return an error when auto_auth configured and there are no sinks, caches or templates")
}
}
func TestLoadConfigFile_Bad_AutoAuth_Both_Wrapping_Types(t *testing.T) {
_, err := LoadConfig("./test-fixtures/bad-config-method-wrapping-and-sink-wrapping.hcl")
if err == nil {
@ -539,3 +546,98 @@ func TestLoadConfigFile_Template(t *testing.T) {
})
}
}
// TestLoadConfigFile_Template_NoSinks tests template definitions without sinks in Vault Agent
func TestLoadConfigFile_Template_NoSinks(t *testing.T) {
testCases := map[string]struct {
fixturePath string
expectedTemplates []*ctconfig.TemplateConfig
}{
"min": {
fixturePath: "./test-fixtures/config-template-min-nosink.hcl",
expectedTemplates: []*ctconfig.TemplateConfig{
&ctconfig.TemplateConfig{
Source: pointerutil.StringPtr("/path/on/disk/to/template.ctmpl"),
Destination: pointerutil.StringPtr("/path/on/disk/where/template/will/render.txt"),
},
},
},
"full": {
fixturePath: "./test-fixtures/config-template-full-nosink.hcl",
expectedTemplates: []*ctconfig.TemplateConfig{
&ctconfig.TemplateConfig{
Backup: pointerutil.BoolPtr(true),
Command: pointerutil.StringPtr("restart service foo"),
CommandTimeout: pointerutil.TimeDurationPtr("60s"),
Contents: pointerutil.StringPtr("{{ keyOrDefault \"service/redis/maxconns@east-aws\" \"5\" }}"),
CreateDestDirs: pointerutil.BoolPtr(true),
Destination: pointerutil.StringPtr("/path/on/disk/where/template/will/render.txt"),
ErrMissingKey: pointerutil.BoolPtr(true),
LeftDelim: pointerutil.StringPtr("<<"),
Perms: pointerutil.FileModePtr(0655),
RightDelim: pointerutil.StringPtr(">>"),
SandboxPath: pointerutil.StringPtr("/path/on/disk/where"),
Wait: &ctconfig.WaitConfig{
Min: pointerutil.TimeDurationPtr("10s"),
Max: pointerutil.TimeDurationPtr("40s"),
},
},
},
},
"many": {
fixturePath: "./test-fixtures/config-template-many-nosink.hcl",
expectedTemplates: []*ctconfig.TemplateConfig{
&ctconfig.TemplateConfig{
Source: pointerutil.StringPtr("/path/on/disk/to/template.ctmpl"),
Destination: pointerutil.StringPtr("/path/on/disk/where/template/will/render.txt"),
ErrMissingKey: pointerutil.BoolPtr(false),
CreateDestDirs: pointerutil.BoolPtr(true),
Command: pointerutil.StringPtr("restart service foo"),
Perms: pointerutil.FileModePtr(0600),
},
&ctconfig.TemplateConfig{
Source: pointerutil.StringPtr("/path/on/disk/to/template2.ctmpl"),
Destination: pointerutil.StringPtr("/path/on/disk/where/template/will/render2.txt"),
Backup: pointerutil.BoolPtr(true),
Perms: pointerutil.FileModePtr(0755),
Wait: &ctconfig.WaitConfig{
Min: pointerutil.TimeDurationPtr("2s"),
Max: pointerutil.TimeDurationPtr("10s"),
},
},
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
config, err := LoadConfig(tc.fixturePath)
if err != nil {
t.Fatalf("err: %s", err)
}
expected := &Config{
SharedConfig: &configutil.SharedConfig{
PidFile: "./pidfile",
},
AutoAuth: &AutoAuth{
Method: &Method{
Type: "aws",
MountPath: "auth/aws",
Namespace: "my-namespace/",
Config: map[string]interface{}{
"role": "foobar",
},
},
Sinks: nil,
},
Templates: tc.expectedTemplates,
}
if diff := deep.Equal(config, expected); diff != nil {
t.Fatal(diff)
}
})
}
}

View File

@ -0,0 +1,10 @@
pid_file = "./pidfile"
auto_auth {
method "aws" {
mount_path = "auth/aws"
config = {
role = "foobar"
}
}
}

View File

@ -0,0 +1,37 @@
pid_file = "./pidfile"
auto_auth {
method {
type = "aws"
namespace = "/my-namespace"
config = {
role = "foobar"
}
}
}
template {
destination = "/path/on/disk/where/template/will/render.txt"
create_dest_dirs = true
contents = "{{ keyOrDefault \"service/redis/maxconns@east-aws\" \"5\" }}"
command = "restart service foo"
command_timeout = "60s"
error_on_missing_key = true
perms = 0655
backup = true
left_delimiter = "<<"
right_delimiter = ">>"
sandbox_path = "/path/on/disk/where"
wait {
min = "5s"
max = "30s"
}
wait {
min = "10s"
max = "40s"
}
}

View File

@ -0,0 +1,38 @@
pid_file = "./pidfile"
auto_auth {
method {
type = "aws"
namespace = "/my-namespace"
config = {
role = "foobar"
}
}
}
template {
source = "/path/on/disk/to/template.ctmpl"
destination = "/path/on/disk/where/template/will/render.txt"
create_dest_dirs = true
command = "restart service foo"
error_on_missing_key = false
perms = 0600
}
template {
source = "/path/on/disk/to/template2.ctmpl"
destination = "/path/on/disk/where/template/will/render2.txt"
perms = 0755
backup = true
wait {
min = "2s"
max = "10s"
}
}

View File

@ -0,0 +1,17 @@
pid_file = "./pidfile"
auto_auth {
method {
type = "aws"
namespace = "/my-namespace"
config = {
role = "foobar"
}
}
}
template {
source = "/path/on/disk/to/template.ctmpl"
destination = "/path/on/disk/where/template/will/render.txt"
}

View File

@ -114,27 +114,34 @@ func (ss *SinkServer) Run(ctx context.Context, incoming chan string, sinks []*Si
return
case token := <-incoming:
if token != *latestToken {
if len(sinks) > 0 {
if token != *latestToken {
// Drain the existing funcs
drainLoop:
for {
select {
case <-sinkCh:
atomic.AddInt32(ss.remaining, -1)
default:
break drainLoop
// Drain the existing funcs
drainLoop:
for {
select {
case <-sinkCh:
atomic.AddInt32(ss.remaining, -1)
default:
break drainLoop
}
}
*latestToken = token
for _, s := range sinks {
atomic.AddInt32(ss.remaining, 1)
sinkCh <- sinkToken{s, token}
}
}
*latestToken = token
for _, s := range sinks {
atomic.AddInt32(ss.remaining, 1)
sinkCh <- sinkToken{s, token}
} else {
ss.logger.Trace("no sinks, ignoring new token")
if ss.exitAfterAuth {
ss.logger.Trace("no sinks, exitAfterAuth, bye")
return
}
}
case st := <-sinkCh:
atomic.AddInt32(ss.remaining, -1)
select {

View File

@ -489,10 +489,6 @@ func TestAgent_RequireRequestHeader(t *testing.T) {
defer os.Remove(roleIDPath)
defer os.Remove(secretIDPath)
// Get a temp file path we can use for the sink
sinkPath := makeTempFile(t, "sink.txt", "")
defer os.Remove(sinkPath)
// Create a config file
config := `
auto_auth {
@ -530,7 +526,7 @@ listener "tcp" {
require_request_header = true
}
`
config = fmt.Sprintf(config, roleIDPath, secretIDPath, sinkPath)
config = fmt.Sprintf(config, roleIDPath, secretIDPath)
configPath := makeTempFile(t, "config.hcl", config)
defer os.Remove(configPath)
@ -743,10 +739,6 @@ func TestAgent_Template_Basic(t *testing.T) {
}`)
request(t, serverClient, req, 200)
// Get a temp file path we can use for the sink
sinkPath := makeTempFile(t, "sink.txt", "")
defer os.Remove(sinkPath)
// make a temp directory to hold renders. Each test will create a temp dir
// inside this one
tmpDirRoot, err := ioutil.TempDir("", "agent-test-renders")
@ -760,10 +752,6 @@ func TestAgent_Template_Basic(t *testing.T) {
templateCount int
exitAfterAuth bool
}{
"zero": {},
"zero-with-exit": {
exitAfterAuth: true,
},
"one": {
templateCount: 1,
},
@ -819,12 +807,6 @@ auto_auth {
remove_secret_id_file_after_reading = false
}
}
sink "file" {
config = {
path = "%s"
}
}
}
%s
@ -841,7 +823,7 @@ auto_auth {
// flatten the template configs
templateConfig := strings.Join(templateConfigStrings, " ")
config = fmt.Sprintf(config, serverClient.Address(), roleIDPath, secretIDPath, sinkPath, templateConfig, exitAfterAuth)
config = fmt.Sprintf(config, serverClient.Address(), roleIDPath, secretIDPath, templateConfig, exitAfterAuth)
configPath := makeTempFile(t, "config.hcl", config)
defer os.Remove(configPath)
@ -1032,10 +1014,6 @@ func TestAgent_Template_ExitCounter(t *testing.T) {
}`)
request(t, serverClient, req, 200)
// Get a temp file path we can use for the sink
sinkPath := makeTempFile(t, "sink.txt", "")
defer os.Remove(sinkPath)
// make a temp directory to hold renders. Each test will create a temp dir
// inside this one
tmpDirRoot, err := ioutil.TempDir("", "agent-test-renders")
@ -1066,12 +1044,6 @@ auto_auth {
remove_secret_id_file_after_reading = false
}
}
sink "file" {
config = {
path = "%s"
}
}
}
template {
@ -1100,7 +1072,7 @@ EOF
exit_after_auth = true
`
config = fmt.Sprintf(config, serverClient.Address(), roleIDPath, secretIDPath, sinkPath, tmpDir, tmpDir, tmpDir)
config = fmt.Sprintf(config, serverClient.Address(), roleIDPath, secretIDPath, tmpDir, tmpDir, tmpDir)
configPath := makeTempFile(t, "config.hcl", config)
defer os.Remove(configPath)

View File

@ -15,7 +15,7 @@ wide variety of environments.
## Functionality
Auto-Auth consists of two parts: a Method, which is the authentication method
that should be used in the current environment; and one or more Sinks, which
that should be used in the current environment; and any number of Sinks, which
are locations where the agent should write a token any time the current token
value has changed.
@ -103,7 +103,7 @@ The top level `auto_auth` block has two configuration entries:
- `method` `(object: required)` - Configuration for the method
- `sinks` `(array of objects: required)` - Configuration for the sinks
- `sinks` `(array of objects: optional)` - Configuration for the sinks
### Configuration (Method)

View File

@ -120,3 +120,7 @@ template {
destination = "/tmp/agent/render.txt"
}
```
If you only want to use the Vault agent to render one or more templates and do
not need to sink the acquired credentials, you can omit the `sink` stanza from
the `auto_auth` stanza in the agent configuration.