2018-10-30 16:17:19 +00:00
|
|
|
package agent
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2018-10-30 18:09:04 +00:00
|
|
|
"encoding/json"
|
2019-04-01 20:27:54 +00:00
|
|
|
"fmt"
|
2018-10-30 16:17:19 +00:00
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
log "github.com/hashicorp/go-hclog"
|
|
|
|
"github.com/hashicorp/vault/api"
|
|
|
|
credAppRole "github.com/hashicorp/vault/builtin/credential/approle"
|
|
|
|
"github.com/hashicorp/vault/command/agent/auth"
|
|
|
|
agentapprole "github.com/hashicorp/vault/command/agent/auth/approle"
|
|
|
|
"github.com/hashicorp/vault/command/agent/sink"
|
|
|
|
"github.com/hashicorp/vault/command/agent/sink/file"
|
|
|
|
vaulthttp "github.com/hashicorp/vault/http"
|
2019-04-13 07:44:06 +00:00
|
|
|
"github.com/hashicorp/vault/sdk/helper/logging"
|
2019-04-12 21:54:35 +00:00
|
|
|
"github.com/hashicorp/vault/sdk/logical"
|
2018-10-30 16:17:19 +00:00
|
|
|
"github.com/hashicorp/vault/vault"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestAppRoleEndToEnd(t *testing.T) {
|
2020-09-15 14:01:26 +00:00
|
|
|
t.Parallel()
|
2018-11-02 20:22:15 +00:00
|
|
|
|
2019-04-01 20:27:54 +00:00
|
|
|
testCases := []struct {
|
|
|
|
removeSecretIDFile bool
|
|
|
|
bindSecretID bool
|
|
|
|
secretIDLess bool
|
|
|
|
expectToken bool
|
|
|
|
}{
|
2021-04-08 16:43:39 +00:00
|
|
|
// default behaviour => token expected
|
2019-04-01 20:27:54 +00:00
|
|
|
{false, true, false, true},
|
|
|
|
{true, true, false, true},
|
|
|
|
|
|
|
|
//bindSecretID=false, wrong secret provided => token expected
|
|
|
|
//(vault ignores the supplied secret_id if bind_secret_id=false)
|
|
|
|
{false, false, false, true},
|
|
|
|
{true, false, false, true},
|
|
|
|
|
2021-04-08 16:43:39 +00:00
|
|
|
// bindSecretID=false, secret not provided => token expected
|
2019-04-01 20:27:54 +00:00
|
|
|
{false, false, true, true},
|
|
|
|
{true, false, true, true},
|
|
|
|
|
2021-04-08 16:43:39 +00:00
|
|
|
// bindSecretID=true, secret not provided => token not expected
|
2019-04-01 20:27:54 +00:00
|
|
|
{false, true, true, false},
|
|
|
|
{true, true, true, false},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range testCases {
|
|
|
|
secretFileAction := "preserve"
|
|
|
|
if tc.removeSecretIDFile {
|
|
|
|
secretFileAction = "remove"
|
|
|
|
}
|
|
|
|
t.Run(fmt.Sprintf("%s_secret_id_file bindSecretID=%v secretIDLess=%v expectToken=%v", secretFileAction, tc.bindSecretID, tc.secretIDLess, tc.expectToken), func(t *testing.T) {
|
2020-09-15 14:01:26 +00:00
|
|
|
t.Parallel()
|
2019-04-01 20:27:54 +00:00
|
|
|
testAppRoleEndToEnd(t, tc.removeSecretIDFile, tc.bindSecretID, tc.secretIDLess, tc.expectToken)
|
|
|
|
})
|
|
|
|
}
|
2018-10-30 18:09:04 +00:00
|
|
|
}
|
|
|
|
|
2019-04-01 20:27:54 +00:00
|
|
|
func testAppRoleEndToEnd(t *testing.T, removeSecretIDFile bool, bindSecretID bool, secretIDLess bool, expectToken bool) {
|
2018-10-30 16:17:19 +00:00
|
|
|
var err error
|
|
|
|
logger := logging.NewVaultLogger(log.Trace)
|
|
|
|
coreConfig := &vault.CoreConfig{
|
|
|
|
DisableMlock: true,
|
|
|
|
DisableCache: true,
|
|
|
|
Logger: log.NewNullLogger(),
|
|
|
|
CredentialBackends: map[string]logical.Factory{
|
|
|
|
"approle": credAppRole.Factory,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
|
|
|
|
HandlerFunc: vaulthttp.Handler,
|
|
|
|
})
|
|
|
|
|
|
|
|
cluster.Start()
|
|
|
|
defer cluster.Cleanup()
|
|
|
|
|
|
|
|
cores := cluster.Cores
|
|
|
|
|
|
|
|
vault.TestWaitActive(t, cores[0].Core)
|
|
|
|
|
|
|
|
client := cores[0].Client
|
|
|
|
|
|
|
|
err = client.Sys().EnableAuthWithOptions("approle", &api.EnableAuthOptions{
|
|
|
|
Type: "approle",
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2019-04-01 20:27:54 +00:00
|
|
|
_, err = client.Logical().Write("auth/approle/role/test1", addConstraints(!bindSecretID, map[string]interface{}{
|
|
|
|
"bind_secret_id": bindSecretID,
|
2019-05-08 16:10:48 +00:00
|
|
|
"token_ttl": "6s",
|
2018-10-30 18:09:04 +00:00
|
|
|
"token_max_ttl": "10s",
|
2019-04-01 20:27:54 +00:00
|
|
|
}))
|
|
|
|
|
|
|
|
logger.Trace("vault configured with", "bind_secret_id", bindSecretID)
|
2018-10-30 16:17:19 +00:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2019-04-01 20:27:54 +00:00
|
|
|
secret := ""
|
|
|
|
secretID1 := ""
|
|
|
|
secretID2 := ""
|
|
|
|
if bindSecretID {
|
|
|
|
resp, err := client.Logical().Write("auth/approle/role/test1/secret-id", nil)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
secretID1 = resp.Data["secret_id"].(string)
|
|
|
|
} else {
|
|
|
|
logger.Trace("skipped write to auth/approle/role/test1/secret-id")
|
|
|
|
}
|
|
|
|
resp, err := client.Logical().Read("auth/approle/role/test1/role-id")
|
2018-10-30 16:17:19 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2018-10-30 18:09:04 +00:00
|
|
|
roleID1 := resp.Data["role_id"].(string)
|
2018-10-30 16:17:19 +00:00
|
|
|
|
2019-04-01 20:27:54 +00:00
|
|
|
_, err = client.Logical().Write("auth/approle/role/test2", addConstraints(!bindSecretID, map[string]interface{}{
|
|
|
|
"bind_secret_id": bindSecretID,
|
2019-05-08 16:10:48 +00:00
|
|
|
"token_ttl": "6s",
|
2018-10-30 18:09:04 +00:00
|
|
|
"token_max_ttl": "10s",
|
2019-04-01 20:27:54 +00:00
|
|
|
}))
|
2018-10-30 16:17:19 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2019-04-01 20:27:54 +00:00
|
|
|
if bindSecretID {
|
|
|
|
resp, err = client.Logical().Write("auth/approle/role/test2/secret-id", nil)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
secretID2 = resp.Data["secret_id"].(string)
|
|
|
|
} else {
|
|
|
|
logger.Trace("skipped write to auth/approle/role/test2/secret-id")
|
2018-10-30 16:17:19 +00:00
|
|
|
}
|
2018-10-30 18:09:04 +00:00
|
|
|
resp, err = client.Logical().Read("auth/approle/role/test2/role-id")
|
2018-10-30 16:17:19 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2018-10-30 18:09:04 +00:00
|
|
|
roleID2 := resp.Data["role_id"].(string)
|
2018-10-30 16:17:19 +00:00
|
|
|
|
2018-10-30 18:09:04 +00:00
|
|
|
rolef, err := ioutil.TempFile("", "auth.role-id.test.")
|
|
|
|
if err != nil {
|
2018-10-30 16:17:19 +00:00
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2018-10-30 18:09:04 +00:00
|
|
|
role := rolef.Name()
|
|
|
|
rolef.Close() // WriteFile doesn't need it open
|
|
|
|
defer os.Remove(role)
|
|
|
|
t.Logf("input role_id_file_path: %s", role)
|
2019-04-01 20:27:54 +00:00
|
|
|
if bindSecretID {
|
|
|
|
secretf, err := ioutil.TempFile("", "auth.secret-id.test.")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
secret = secretf.Name()
|
|
|
|
secretf.Close()
|
|
|
|
defer os.Remove(secret)
|
|
|
|
t.Logf("input secret_id_file_path: %s", secret)
|
|
|
|
} else {
|
|
|
|
logger.Trace("skipped writing tempfile auth.secret-id.test.")
|
2018-10-30 18:09:04 +00:00
|
|
|
}
|
2018-10-30 16:17:19 +00:00
|
|
|
// We close these right away because we're just basically testing
|
|
|
|
// permissions and finding a usable file name
|
|
|
|
ouf, err := ioutil.TempFile("", "auth.tokensink.test.")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
out := ouf.Name()
|
|
|
|
ouf.Close()
|
|
|
|
os.Remove(out)
|
|
|
|
t.Logf("output: %s", out)
|
|
|
|
|
2020-09-30 01:03:09 +00:00
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
2018-10-30 16:17:19 +00:00
|
|
|
|
2019-04-01 20:27:54 +00:00
|
|
|
secretFromAgent := secret
|
|
|
|
if secretIDLess {
|
|
|
|
secretFromAgent = ""
|
|
|
|
}
|
|
|
|
if !bindSecretID && !secretIDLess {
|
|
|
|
logger.Trace("agent is providing an invalid secret that should be ignored")
|
|
|
|
secretf, err := ioutil.TempFile("", "auth.secret-id.test.")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
secretFromAgent = secretf.Name()
|
|
|
|
secretf.Close()
|
|
|
|
defer os.Remove(secretFromAgent)
|
2021-04-08 16:43:39 +00:00
|
|
|
// if the token is empty, auth.approle would fail reporting the error
|
|
|
|
if err := ioutil.WriteFile(secretFromAgent, []byte("wrong-secret"), 0o600); err != nil {
|
2019-04-01 20:27:54 +00:00
|
|
|
t.Fatal(err)
|
|
|
|
} else {
|
|
|
|
logger.Trace("wrote secret_id_file_path with wrong-secret", "path", secretFromAgent)
|
|
|
|
}
|
|
|
|
}
|
2018-10-30 18:09:04 +00:00
|
|
|
conf := map[string]interface{}{
|
|
|
|
"role_id_file_path": role,
|
2019-04-01 20:27:54 +00:00
|
|
|
"secret_id_file_path": secretFromAgent,
|
2018-10-30 18:09:04 +00:00
|
|
|
}
|
2019-04-01 20:27:54 +00:00
|
|
|
logger.Trace("agent configured with", "conf", conf)
|
2018-10-30 18:09:04 +00:00
|
|
|
if !removeSecretIDFile {
|
|
|
|
conf["remove_secret_id_file_after_reading"] = removeSecretIDFile
|
|
|
|
}
|
2018-10-30 16:17:19 +00:00
|
|
|
am, err := agentapprole.NewApproleAuthMethod(&auth.AuthConfig{
|
|
|
|
Logger: logger.Named("auth.approle"),
|
|
|
|
MountPath: "auth/approle",
|
2018-10-30 18:09:04 +00:00
|
|
|
Config: conf,
|
2018-10-30 16:17:19 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
ahConfig := &auth.AuthHandlerConfig{
|
|
|
|
Logger: logger.Named("auth.handler"),
|
|
|
|
Client: client,
|
|
|
|
}
|
|
|
|
ah := auth.NewAuthHandler(ahConfig)
|
2020-09-30 01:03:09 +00:00
|
|
|
errCh := make(chan error)
|
|
|
|
go func() {
|
|
|
|
errCh <- ah.Run(ctx, am)
|
|
|
|
}()
|
2018-10-30 16:17:19 +00:00
|
|
|
defer func() {
|
2020-09-30 01:03:09 +00:00
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
case err := <-errCh:
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
2018-10-30 16:17:19 +00:00
|
|
|
}()
|
|
|
|
|
|
|
|
config := &sink.SinkConfig{
|
|
|
|
Logger: logger.Named("sink.file"),
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"path": out,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
fs, err := file.NewFileSink(config)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
config.Sink = fs
|
|
|
|
|
|
|
|
ss := sink.NewSinkServer(&sink.SinkServerConfig{
|
|
|
|
Logger: logger.Named("sink.server"),
|
|
|
|
Client: client,
|
|
|
|
})
|
2020-09-30 01:03:09 +00:00
|
|
|
|
|
|
|
go func() {
|
|
|
|
errCh <- ss.Run(ctx, ah.OutputCh, []*sink.SinkConfig{config})
|
|
|
|
}()
|
2018-10-30 16:17:19 +00:00
|
|
|
defer func() {
|
2020-09-30 01:03:09 +00:00
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
case err := <-errCh:
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
2018-10-30 16:17:19 +00:00
|
|
|
}()
|
|
|
|
|
2020-09-30 01:03:09 +00:00
|
|
|
// This has to be after the other defers so it happens first. It allows
|
|
|
|
// successful test runs to immediately cancel all of the runner goroutines
|
|
|
|
// and unblock any of the blocking defer calls by the runner's DoneCh that
|
|
|
|
// comes before this and avoid successful tests from taking the entire
|
|
|
|
// timeout duration.
|
|
|
|
defer cancel()
|
2018-10-30 16:17:19 +00:00
|
|
|
|
|
|
|
// Check that no sink file exists
|
|
|
|
_, err = os.Lstat(out)
|
|
|
|
if err == nil {
|
|
|
|
t.Fatal("expected err")
|
|
|
|
}
|
|
|
|
if !os.IsNotExist(err) {
|
|
|
|
t.Fatal("expected notexist err")
|
|
|
|
}
|
|
|
|
|
2021-04-08 16:43:39 +00:00
|
|
|
if err := ioutil.WriteFile(role, []byte(roleID1), 0o600); err != nil {
|
2018-10-30 18:09:04 +00:00
|
|
|
t.Fatal(err)
|
|
|
|
} else {
|
|
|
|
logger.Trace("wrote test role 1", "path", role)
|
|
|
|
}
|
|
|
|
|
2019-04-01 20:27:54 +00:00
|
|
|
if bindSecretID {
|
2021-04-08 16:43:39 +00:00
|
|
|
if err := ioutil.WriteFile(secret, []byte(secretID1), 0o600); err != nil {
|
2019-04-01 20:27:54 +00:00
|
|
|
t.Fatal(err)
|
|
|
|
} else {
|
|
|
|
logger.Trace("wrote test secret 1", "path", secret)
|
|
|
|
}
|
2018-10-30 18:09:04 +00:00
|
|
|
} else {
|
2019-04-01 20:27:54 +00:00
|
|
|
logger.Trace("skipped writing test secret 1")
|
2018-10-30 18:09:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
checkToken := func() string {
|
2018-11-02 20:22:15 +00:00
|
|
|
timeout := time.Now().Add(10 * time.Second)
|
2018-10-30 18:09:04 +00:00
|
|
|
for {
|
|
|
|
if time.Now().After(timeout) {
|
2019-04-01 20:27:54 +00:00
|
|
|
if expectToken {
|
|
|
|
t.Fatal("did not find a written token after timeout")
|
|
|
|
}
|
|
|
|
return ""
|
2018-10-30 18:09:04 +00:00
|
|
|
}
|
|
|
|
val, err := ioutil.ReadFile(out)
|
|
|
|
if err == nil {
|
|
|
|
os.Remove(out)
|
|
|
|
if len(val) == 0 {
|
|
|
|
t.Fatal("written token was empty")
|
|
|
|
}
|
2019-04-01 20:27:54 +00:00
|
|
|
if !secretIDLess {
|
|
|
|
_, err = os.Stat(secretFromAgent)
|
|
|
|
switch {
|
|
|
|
case removeSecretIDFile && err == nil:
|
|
|
|
t.Fatal("secret file exists but was supposed to be removed")
|
|
|
|
case !removeSecretIDFile && err != nil:
|
|
|
|
t.Fatal("secret ID file does not exist but was not supposed to be removed")
|
|
|
|
}
|
2018-10-30 18:09:04 +00:00
|
|
|
}
|
|
|
|
client.SetToken(string(val))
|
|
|
|
secret, err := client.Auth().Token().LookupSelf()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
return secret.Data["entity_id"].(string)
|
|
|
|
}
|
|
|
|
time.Sleep(250 * time.Millisecond)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
origEntity := checkToken()
|
2019-04-01 20:27:54 +00:00
|
|
|
if !expectToken && origEntity != "" {
|
|
|
|
t.Fatal("did not expect a token to be written: " + origEntity)
|
|
|
|
}
|
|
|
|
if !expectToken && origEntity == "" {
|
|
|
|
logger.Trace("skipping entities comparison as we are not expecting tokens to be written")
|
|
|
|
return
|
|
|
|
}
|
2018-10-30 18:09:04 +00:00
|
|
|
|
|
|
|
// Make sure it gets renewed
|
|
|
|
timeout := time.Now().Add(4 * time.Second)
|
|
|
|
for {
|
|
|
|
if time.Now().After(timeout) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
secret, err := client.Auth().Token().LookupSelf()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
ttl, err := secret.Data["ttl"].(json.Number).Int64()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2019-05-08 16:10:48 +00:00
|
|
|
if ttl > 6 {
|
2018-10-30 18:09:04 +00:00
|
|
|
t.Fatalf("unexpected ttl: %v", secret.Data["ttl"])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write new values
|
2021-04-08 16:43:39 +00:00
|
|
|
if err := ioutil.WriteFile(role, []byte(roleID2), 0o600); err != nil {
|
2018-10-30 16:17:19 +00:00
|
|
|
t.Fatal(err)
|
2018-10-30 18:09:04 +00:00
|
|
|
} else {
|
|
|
|
logger.Trace("wrote test role 2", "path", role)
|
2018-10-30 16:17:19 +00:00
|
|
|
}
|
2018-10-30 18:09:04 +00:00
|
|
|
|
2019-04-01 20:27:54 +00:00
|
|
|
if bindSecretID {
|
2021-04-08 16:43:39 +00:00
|
|
|
if err := ioutil.WriteFile(secret, []byte(secretID2), 0o600); err != nil {
|
2019-04-01 20:27:54 +00:00
|
|
|
t.Fatal(err)
|
|
|
|
} else {
|
|
|
|
logger.Trace("wrote test secret 2", "path", secret)
|
|
|
|
}
|
2018-10-30 18:09:04 +00:00
|
|
|
} else {
|
2019-04-01 20:27:54 +00:00
|
|
|
logger.Trace("skipped writing test secret 2")
|
2018-10-30 18:09:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
newEntity := checkToken()
|
|
|
|
if newEntity == origEntity {
|
|
|
|
t.Fatal("found same entity")
|
|
|
|
}
|
|
|
|
|
|
|
|
timeout = time.Now().Add(4 * time.Second)
|
|
|
|
for {
|
|
|
|
if time.Now().After(timeout) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
secret, err := client.Auth().Token().LookupSelf()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
ttl, err := secret.Data["ttl"].(json.Number).Int64()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2019-05-08 16:10:48 +00:00
|
|
|
if ttl > 6 {
|
2018-10-30 18:09:04 +00:00
|
|
|
t.Fatalf("unexpected ttl: %v", secret.Data["ttl"])
|
|
|
|
}
|
2018-10-30 16:17:19 +00:00
|
|
|
}
|
|
|
|
}
|
2018-10-31 00:53:49 +00:00
|
|
|
|
|
|
|
func TestAppRoleWithWrapping(t *testing.T) {
|
2019-04-01 20:27:54 +00:00
|
|
|
testCases := []struct {
|
|
|
|
bindSecretID bool
|
|
|
|
secretIDLess bool
|
|
|
|
expectToken bool
|
|
|
|
}{
|
2021-04-08 16:43:39 +00:00
|
|
|
// default behaviour => token expected
|
2019-04-01 20:27:54 +00:00
|
|
|
{true, false, true},
|
|
|
|
|
|
|
|
//bindSecretID=false, wrong secret provided, wrapping_path provided => token not expected
|
|
|
|
//(wrapping token is not valid or does not exist)
|
|
|
|
{false, false, false},
|
|
|
|
|
2021-04-08 16:43:39 +00:00
|
|
|
// bindSecretID=false, no secret provided, wrapping_path provided but ignored => token expected
|
2019-04-01 20:27:54 +00:00
|
|
|
{false, true, true},
|
|
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
|
|
t.Run(fmt.Sprintf("bindSecretID=%v secretIDLess=%v expectToken=%v", tc.bindSecretID, tc.secretIDLess, tc.expectToken), func(t *testing.T) {
|
|
|
|
testAppRoleWithWrapping(t, tc.bindSecretID, tc.secretIDLess, tc.expectToken)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func testAppRoleWithWrapping(t *testing.T, bindSecretID bool, secretIDLess bool, expectToken bool) {
|
2018-10-31 00:53:49 +00:00
|
|
|
var err error
|
|
|
|
logger := logging.NewVaultLogger(log.Trace)
|
|
|
|
coreConfig := &vault.CoreConfig{
|
|
|
|
DisableMlock: true,
|
|
|
|
DisableCache: true,
|
|
|
|
Logger: log.NewNullLogger(),
|
|
|
|
CredentialBackends: map[string]logical.Factory{
|
|
|
|
"approle": credAppRole.Factory,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
|
|
|
|
HandlerFunc: vaulthttp.Handler,
|
|
|
|
})
|
|
|
|
|
|
|
|
cluster.Start()
|
|
|
|
defer cluster.Cleanup()
|
|
|
|
|
|
|
|
cores := cluster.Cores
|
|
|
|
|
|
|
|
vault.TestWaitActive(t, cores[0].Core)
|
|
|
|
|
|
|
|
client := cores[0].Client
|
|
|
|
origToken := client.Token()
|
|
|
|
|
|
|
|
err = client.Sys().EnableAuthWithOptions("approle", &api.EnableAuthOptions{
|
|
|
|
Type: "approle",
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2019-04-01 20:27:54 +00:00
|
|
|
_, err = client.Logical().Write("auth/approle/role/test1", addConstraints(!bindSecretID, map[string]interface{}{
|
|
|
|
"bind_secret_id": bindSecretID,
|
2019-05-08 16:10:48 +00:00
|
|
|
"token_ttl": "6s",
|
2018-10-31 00:53:49 +00:00
|
|
|
"token_max_ttl": "10s",
|
2019-04-01 20:27:54 +00:00
|
|
|
}))
|
2018-10-31 00:53:49 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
client.SetWrappingLookupFunc(func(operation, path string) string {
|
|
|
|
if path == "auth/approle/role/test1/secret-id" {
|
|
|
|
return "10s"
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
})
|
|
|
|
|
2019-04-01 20:27:54 +00:00
|
|
|
secret := ""
|
|
|
|
secretID1 := ""
|
|
|
|
if bindSecretID {
|
|
|
|
resp, err := client.Logical().Write("auth/approle/role/test1/secret-id", nil)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
secretID1 = resp.WrapInfo.Token
|
|
|
|
} else {
|
|
|
|
logger.Trace("skipped write to auth/approle/role/test1/secret-id")
|
2018-10-31 00:53:49 +00:00
|
|
|
}
|
2019-04-01 20:27:54 +00:00
|
|
|
resp, err := client.Logical().Read("auth/approle/role/test1/role-id")
|
2018-10-31 00:53:49 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
roleID1 := resp.Data["role_id"].(string)
|
|
|
|
|
|
|
|
rolef, err := ioutil.TempFile("", "auth.role-id.test.")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
role := rolef.Name()
|
|
|
|
rolef.Close() // WriteFile doesn't need it open
|
|
|
|
defer os.Remove(role)
|
|
|
|
t.Logf("input role_id_file_path: %s", role)
|
|
|
|
|
2019-04-01 20:27:54 +00:00
|
|
|
if bindSecretID {
|
|
|
|
secretf, err := ioutil.TempFile("", "auth.secret-id.test.")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
secret = secretf.Name()
|
|
|
|
secretf.Close()
|
|
|
|
defer os.Remove(secret)
|
|
|
|
t.Logf("input secret_id_file_path: %s", secret)
|
|
|
|
} else {
|
|
|
|
logger.Trace("skipped writing tempfile auth.secret-id.test.")
|
2018-10-31 00:53:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// We close these right away because we're just basically testing
|
|
|
|
// permissions and finding a usable file name
|
|
|
|
ouf, err := ioutil.TempFile("", "auth.tokensink.test.")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
out := ouf.Name()
|
|
|
|
ouf.Close()
|
|
|
|
os.Remove(out)
|
|
|
|
t.Logf("output: %s", out)
|
|
|
|
|
2020-09-30 01:03:09 +00:00
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
2018-10-31 00:53:49 +00:00
|
|
|
|
2019-04-01 20:27:54 +00:00
|
|
|
secretFromAgent := secret
|
|
|
|
if secretIDLess {
|
|
|
|
secretFromAgent = ""
|
|
|
|
}
|
|
|
|
if !bindSecretID && !secretIDLess {
|
|
|
|
logger.Trace("agent is providing an invalid secret that should be ignored")
|
|
|
|
secretf, err := ioutil.TempFile("", "auth.secret-id.test.")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
secretFromAgent = secretf.Name()
|
|
|
|
secretf.Close()
|
|
|
|
defer os.Remove(secretFromAgent)
|
2021-04-08 16:43:39 +00:00
|
|
|
// if the token is empty, auth.approle would fail reporting the error
|
|
|
|
if err := ioutil.WriteFile(secretFromAgent, []byte("wrong-secret"), 0o600); err != nil {
|
2019-04-01 20:27:54 +00:00
|
|
|
t.Fatal(err)
|
|
|
|
} else {
|
|
|
|
logger.Trace("wrote secret_id_file_path with wrong-secret", "path", secretFromAgent)
|
|
|
|
}
|
|
|
|
}
|
2018-10-31 00:53:49 +00:00
|
|
|
conf := map[string]interface{}{
|
|
|
|
"role_id_file_path": role,
|
2019-04-01 20:27:54 +00:00
|
|
|
"secret_id_file_path": secretFromAgent,
|
2018-10-31 00:53:49 +00:00
|
|
|
"secret_id_response_wrapping_path": "auth/approle/role/test1/secret-id",
|
|
|
|
"remove_secret_id_file_after_reading": true,
|
|
|
|
}
|
2019-04-01 20:27:54 +00:00
|
|
|
logger.Trace("agent configured with", "conf", conf)
|
2018-10-31 00:53:49 +00:00
|
|
|
|
|
|
|
am, err := agentapprole.NewApproleAuthMethod(&auth.AuthConfig{
|
|
|
|
Logger: logger.Named("auth.approle"),
|
|
|
|
MountPath: "auth/approle",
|
|
|
|
Config: conf,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
ahConfig := &auth.AuthHandlerConfig{
|
|
|
|
Logger: logger.Named("auth.handler"),
|
|
|
|
Client: client,
|
|
|
|
}
|
|
|
|
ah := auth.NewAuthHandler(ahConfig)
|
2020-09-30 01:03:09 +00:00
|
|
|
errCh := make(chan error)
|
|
|
|
go func() {
|
|
|
|
errCh <- ah.Run(ctx, am)
|
|
|
|
}()
|
2018-10-31 00:53:49 +00:00
|
|
|
defer func() {
|
2020-09-30 01:03:09 +00:00
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
case err := <-errCh:
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
2018-10-31 00:53:49 +00:00
|
|
|
}()
|
|
|
|
|
|
|
|
config := &sink.SinkConfig{
|
|
|
|
Logger: logger.Named("sink.file"),
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"path": out,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
fs, err := file.NewFileSink(config)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
config.Sink = fs
|
|
|
|
|
|
|
|
ss := sink.NewSinkServer(&sink.SinkServerConfig{
|
|
|
|
Logger: logger.Named("sink.server"),
|
|
|
|
Client: client,
|
|
|
|
})
|
2020-09-30 01:03:09 +00:00
|
|
|
go func() {
|
|
|
|
errCh <- ss.Run(ctx, ah.OutputCh, []*sink.SinkConfig{config})
|
|
|
|
}()
|
|
|
|
|
2018-10-31 00:53:49 +00:00
|
|
|
defer func() {
|
2020-09-30 01:03:09 +00:00
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
case err := <-errCh:
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
2018-10-31 00:53:49 +00:00
|
|
|
}()
|
|
|
|
|
2020-09-30 01:03:09 +00:00
|
|
|
// This has to be after the other defers so it happens first. It allows
|
|
|
|
// successful test runs to immediately cancel all of the runner goroutines
|
|
|
|
// and unblock any of the blocking defer calls by the runner's DoneCh that
|
|
|
|
// comes before this and avoid successful tests from taking the entire
|
|
|
|
// timeout duration.
|
|
|
|
defer cancel()
|
2018-10-31 00:53:49 +00:00
|
|
|
|
|
|
|
// Check that no sink file exists
|
|
|
|
_, err = os.Lstat(out)
|
|
|
|
if err == nil {
|
|
|
|
t.Fatal("expected err")
|
|
|
|
}
|
|
|
|
if !os.IsNotExist(err) {
|
|
|
|
t.Fatal("expected notexist err")
|
|
|
|
}
|
|
|
|
|
2021-04-08 16:43:39 +00:00
|
|
|
if err := ioutil.WriteFile(role, []byte(roleID1), 0o600); err != nil {
|
2018-10-31 00:53:49 +00:00
|
|
|
t.Fatal(err)
|
|
|
|
} else {
|
|
|
|
logger.Trace("wrote test role 1", "path", role)
|
|
|
|
}
|
|
|
|
|
2019-04-01 20:27:54 +00:00
|
|
|
if bindSecretID {
|
|
|
|
logger.Trace("WRITING TO auth.secret-id.test.", "secret", secret, "secretID1", secretID1)
|
|
|
|
|
2021-04-08 16:43:39 +00:00
|
|
|
if err := ioutil.WriteFile(secret, []byte(secretID1), 0o600); err != nil {
|
2019-04-01 20:27:54 +00:00
|
|
|
t.Fatal(err)
|
|
|
|
} else {
|
|
|
|
logger.Trace("wrote test secret 1", "path", secret)
|
|
|
|
}
|
2018-10-31 00:53:49 +00:00
|
|
|
} else {
|
2019-04-01 20:27:54 +00:00
|
|
|
logger.Trace("skipped writing test secret 1")
|
2018-10-31 00:53:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
checkToken := func() string {
|
2018-11-06 19:06:06 +00:00
|
|
|
timeout := time.Now().Add(10 * time.Second)
|
2018-10-31 00:53:49 +00:00
|
|
|
for {
|
|
|
|
if time.Now().After(timeout) {
|
2019-04-01 20:27:54 +00:00
|
|
|
if expectToken {
|
|
|
|
t.Fatal("did not find a written token after timeout")
|
|
|
|
}
|
|
|
|
return ""
|
2018-10-31 00:53:49 +00:00
|
|
|
}
|
|
|
|
val, err := ioutil.ReadFile(out)
|
|
|
|
if err == nil {
|
|
|
|
os.Remove(out)
|
|
|
|
if len(val) == 0 {
|
|
|
|
t.Fatal("written token was empty")
|
|
|
|
}
|
2019-04-01 20:27:54 +00:00
|
|
|
if !secretIDLess {
|
|
|
|
if _, err := os.Stat(secret); err == nil {
|
|
|
|
t.Fatal("secret ID file does not exist but was not supposed to be removed")
|
|
|
|
}
|
2018-10-31 00:53:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
client.SetToken(string(val))
|
|
|
|
secret, err := client.Auth().Token().LookupSelf()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
return secret.Data["entity_id"].(string)
|
|
|
|
}
|
|
|
|
time.Sleep(250 * time.Millisecond)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
origEntity := checkToken()
|
2019-04-01 20:27:54 +00:00
|
|
|
logger.Trace("cheking token", "origEntity", origEntity)
|
|
|
|
|
|
|
|
if !expectToken && origEntity != "" {
|
|
|
|
t.Fatal("did not expect a token to be written: " + origEntity)
|
|
|
|
}
|
|
|
|
if !expectToken && origEntity == "" {
|
|
|
|
logger.Trace("skipping entities comparison as we are not expecting tokens to be written")
|
|
|
|
return
|
|
|
|
}
|
2018-10-31 00:53:49 +00:00
|
|
|
|
|
|
|
// Make sure it gets renewed
|
|
|
|
timeout := time.Now().Add(4 * time.Second)
|
|
|
|
for {
|
|
|
|
if time.Now().After(timeout) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
secret, err := client.Auth().Token().LookupSelf()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
ttl, err := secret.Data["ttl"].(json.Number).Int64()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2019-05-08 16:10:48 +00:00
|
|
|
if ttl > 6 {
|
2018-10-31 00:53:49 +00:00
|
|
|
t.Fatalf("unexpected ttl: %v", secret.Data["ttl"])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write new values
|
|
|
|
client.SetToken(origToken)
|
2019-04-01 20:27:54 +00:00
|
|
|
logger.Trace("origToken set into client", "origToken", origToken)
|
|
|
|
|
|
|
|
if bindSecretID {
|
|
|
|
resp, err = client.Logical().Write("auth/approle/role/test1/secret-id", nil)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
secretID2 := resp.WrapInfo.Token
|
2021-04-08 16:43:39 +00:00
|
|
|
if err := ioutil.WriteFile(secret, []byte(secretID2), 0o600); err != nil {
|
2019-04-01 20:27:54 +00:00
|
|
|
t.Fatal(err)
|
|
|
|
} else {
|
|
|
|
logger.Trace("wrote test secret 2", "path", secret)
|
|
|
|
}
|
2018-10-31 00:53:49 +00:00
|
|
|
} else {
|
2019-04-01 20:27:54 +00:00
|
|
|
logger.Trace("skipped writing test secret 2")
|
2018-10-31 00:53:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
newEntity := checkToken()
|
|
|
|
if newEntity != origEntity {
|
|
|
|
t.Fatal("did not find same entity")
|
|
|
|
}
|
|
|
|
|
|
|
|
timeout = time.Now().Add(4 * time.Second)
|
|
|
|
for {
|
|
|
|
if time.Now().After(timeout) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
secret, err := client.Auth().Token().LookupSelf()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
ttl, err := secret.Data["ttl"].(json.Number).Int64()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2019-05-08 16:10:48 +00:00
|
|
|
if ttl > 6 {
|
2018-10-31 00:53:49 +00:00
|
|
|
t.Fatalf("unexpected ttl: %v", secret.Data["ttl"])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-04-01 20:27:54 +00:00
|
|
|
|
|
|
|
func addConstraints(add bool, cfg map[string]interface{}) map[string]interface{} {
|
|
|
|
if add {
|
2021-04-08 16:43:39 +00:00
|
|
|
// extraConstraints to add when bind_secret_id=false (otherwise Vault would fail with: "at least one constraint should be enabled on the role")
|
2019-04-01 20:27:54 +00:00
|
|
|
extraConstraints := map[string]interface{}{
|
|
|
|
"secret_id_bound_cidrs": "127.0.0.1/32",
|
|
|
|
"token_bound_cidrs": "127.0.0.1/32",
|
|
|
|
}
|
|
|
|
for k, v := range extraConstraints {
|
|
|
|
cfg[k] = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return cfg
|
|
|
|
}
|