open-vault/builtin/logical/consul/backend_test.go

502 lines
12 KiB
Go

package consul
import (
"bufio"
"encoding/base64"
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"reflect"
"strings"
"testing"
"time"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/logical/framework"
logicaltest "github.com/hashicorp/vault/logical/testing"
"github.com/mitchellh/mapstructure"
)
func createBackend() *backend {
var b backend
b.Backend = &framework.Backend{
PathsSpecial: &logical.Paths{
Root: []string{
"config/*",
},
},
Paths: []*framework.Path{
pathConfigAccess(),
pathRoles(),
pathToken(&b),
},
Secrets: []*framework.Secret{
secretToken(&b),
},
}
return &b
}
func TestBackend_config_access(t *testing.T) {
if os.Getenv(logicaltest.TestEnvVar) == "" {
t.Skip(fmt.Sprintf("Acceptance tests skipped unless env '%s' set", logicaltest.TestEnvVar))
return
}
accessConfig, process := testStartConsulServer(t)
defer testStopConsulServer(t, process)
config := logical.TestBackendConfig()
storage := &logical.InmemStorage{}
config.StorageView = storage
b := createBackend()
_, err := b.Backend.Setup(config)
if err != nil {
t.Fatal(err)
}
confReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "config/access",
Storage: storage,
Data: accessConfig,
}
resp, err := b.HandleRequest(confReq)
if err != nil || (resp != nil && resp.IsError()) || resp != nil {
t.Fatalf("failed to write configuration: resp:%#v err:%s", resp, err)
}
confReq.Operation = logical.ReadOperation
resp, err = b.HandleRequest(confReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("failed to write configuration: resp:%#v err:%s", resp, err)
}
expected := map[string]interface{}{
"address": "127.0.0.1:8500",
"scheme": "http",
}
if !reflect.DeepEqual(expected, resp.Data) {
t.Fatalf("bad: expected:%#v\nactual:%#v\n", expected, resp.Data)
}
if resp.Data["token"] != nil {
t.Fatalf("token should not be set in the response")
}
}
func TestBackend_basic(t *testing.T) {
if os.Getenv(logicaltest.TestEnvVar) == "" {
t.Skip(fmt.Sprintf("Acceptance tests skipped unless env '%s' set", logicaltest.TestEnvVar))
return
}
config, process := testStartConsulServer(t)
defer testStopConsulServer(t, process)
b, _ := Factory(logical.TestBackendConfig())
logicaltest.Test(t, logicaltest.TestCase{
AcceptanceTest: true,
PreCheck: func() { testAccPreCheck(t) },
Backend: b,
Steps: []logicaltest.TestStep{
testAccStepConfig(t, config),
testAccStepWritePolicy(t, "test", testPolicy, ""),
testAccStepReadToken(t, "test", config),
},
})
}
func TestBackend_renew_revoke(t *testing.T) {
if os.Getenv(logicaltest.TestEnvVar) == "" {
t.Skip(fmt.Sprintf("Acceptance tests skipped unless env '%s' set", logicaltest.TestEnvVar))
return
}
config, process := testStartConsulServer(t)
defer testStopConsulServer(t, process)
beConfig := logical.TestBackendConfig()
beConfig.StorageView = &logical.InmemStorage{}
b, _ := Factory(beConfig)
req := &logical.Request{
Storage: beConfig.StorageView,
Operation: logical.UpdateOperation,
Path: "config/access",
Data: config,
}
resp, err := b.HandleRequest(req)
if err != nil {
t.Fatal(err)
}
req.Path = "roles/test"
req.Data = map[string]interface{}{
"policy": base64.StdEncoding.EncodeToString([]byte(testPolicy)),
"lease": "6h",
}
resp, err = b.HandleRequest(req)
if err != nil {
t.Fatal(err)
}
req.Operation = logical.ReadOperation
req.Path = "creds/test"
resp, err = b.HandleRequest(req)
if err != nil {
t.Fatal(err)
}
if resp == nil || resp.IsError() {
t.Fatal("resp nil or error")
}
generatedSecret := resp.Secret
generatedSecret.IssueTime = time.Now()
generatedSecret.TTL = 6 * time.Hour
var d struct {
Token string `mapstructure:"token"`
}
if err := mapstructure.Decode(resp.Data, &d); err != nil {
t.Fatal(err)
}
log.Printf("[WARN] Generated token: %s", d.Token)
// Build a client and verify that the credentials work
apiConfig := api.DefaultConfig()
apiConfig.Address = config["address"].(string)
apiConfig.Token = d.Token
client, err := api.NewClient(apiConfig)
if err != nil {
t.Fatal(err)
}
log.Printf("[WARN] Verifying that the generated token works...")
_, err = client.KV().Put(&api.KVPair{
Key: "foo",
Value: []byte("bar"),
}, nil)
if err != nil {
t.Fatal(err)
}
req.Operation = logical.RenewOperation
req.Secret = generatedSecret
resp, err = b.HandleRequest(req)
if err != nil {
t.Fatal(err)
}
if resp == nil {
t.Fatal("got nil response from renew")
}
req.Operation = logical.RevokeOperation
resp, err = b.HandleRequest(req)
if err != nil {
t.Fatal(err)
}
log.Printf("[WARN] Verifying that the generated token does not work...")
_, err = client.KV().Put(&api.KVPair{
Key: "foo",
Value: []byte("bar"),
}, nil)
if err == nil {
t.Fatal("expected error")
}
}
func TestBackend_management(t *testing.T) {
if os.Getenv(logicaltest.TestEnvVar) == "" {
t.Skip(fmt.Sprintf("Acceptance tests skipped unless env '%s' set", logicaltest.TestEnvVar))
return
}
config, process := testStartConsulServer(t)
defer testStopConsulServer(t, process)
b, _ := Factory(logical.TestBackendConfig())
logicaltest.Test(t, logicaltest.TestCase{
AcceptanceTest: true,
PreCheck: func() { testAccPreCheck(t) },
Backend: b,
Steps: []logicaltest.TestStep{
testAccStepConfig(t, config),
testAccStepWriteManagementPolicy(t, "test", ""),
testAccStepReadManagementToken(t, "test", config),
},
})
}
func TestBackend_crud(t *testing.T) {
if os.Getenv(logicaltest.TestEnvVar) == "" {
t.Skip(fmt.Sprintf("Acceptance tests skipped unless env '%s' set", logicaltest.TestEnvVar))
return
}
_, process := testStartConsulServer(t)
defer testStopConsulServer(t, process)
b, _ := Factory(logical.TestBackendConfig())
logicaltest.Test(t, logicaltest.TestCase{
AcceptanceTest: true,
PreCheck: func() { testAccPreCheck(t) },
Backend: b,
Steps: []logicaltest.TestStep{
testAccStepWritePolicy(t, "test", testPolicy, ""),
testAccStepReadPolicy(t, "test", testPolicy, 0),
testAccStepDeletePolicy(t, "test"),
},
})
}
func TestBackend_role_lease(t *testing.T) {
if os.Getenv(logicaltest.TestEnvVar) == "" {
t.Skip(fmt.Sprintf("Acceptance tests skipped unless env '%s' set", logicaltest.TestEnvVar))
return
}
_, process := testStartConsulServer(t)
defer testStopConsulServer(t, process)
b, _ := Factory(logical.TestBackendConfig())
logicaltest.Test(t, logicaltest.TestCase{
AcceptanceTest: true,
PreCheck: func() { testAccPreCheck(t) },
Backend: b,
Steps: []logicaltest.TestStep{
testAccStepWritePolicy(t, "test", testPolicy, "6h"),
testAccStepReadPolicy(t, "test", testPolicy, 6*time.Hour),
testAccStepDeletePolicy(t, "test"),
},
})
}
func testStartConsulServer(t *testing.T) (map[string]interface{}, *os.Process) {
if _, err := exec.LookPath("consul"); err != nil {
t.Errorf("consul not found: %s", err)
}
td, err := ioutil.TempDir("", "vault")
if err != nil {
t.Fatalf("err: %s", err)
}
tf, err := ioutil.TempFile("", "vault")
if err != nil {
t.Fatalf("err: %s", err)
}
if _, err := tf.Write([]byte(strings.TrimSpace(testConsulConfig))); err != nil {
t.Fatalf("err: %s", err)
}
tf.Close()
cmd := exec.Command(
"consul", "agent",
"-server",
"-bootstrap",
"-advertise", "127.0.0.1",
"-config-file", tf.Name(),
"-data-dir", td)
stdout, _ := cmd.StdoutPipe()
stderr, _ := cmd.StderrPipe()
stdoutScanner := bufio.NewScanner(stdout)
stderrScanner := bufio.NewScanner(stderr)
stdoutScanFunc := func() {
for stdoutScanner.Scan() {
t.Logf("Consul stdout: %s\n", stdoutScanner.Text())
}
}
stderrScanFunc := func() {
for stderrScanner.Scan() {
t.Logf("Consul stderr: %s\n", stderrScanner.Text())
}
}
if os.Getenv("VAULT_VERBOSE_ACC_TESTS") != "" {
go stdoutScanFunc()
go stderrScanFunc()
}
if err := cmd.Start(); err != nil {
t.Fatalf("error starting Consul: %s", err)
}
// Give Consul time to startup
time.Sleep(2 * time.Second)
config := map[string]interface{}{
"address": "127.0.0.1:8500",
"token": "test",
}
return config, cmd.Process
}
func testStopConsulServer(t *testing.T, p *os.Process) {
p.Kill()
}
func testAccPreCheck(t *testing.T) {
if _, err := exec.LookPath("consul"); err != nil {
t.Fatal("consul must be on PATH")
}
}
func testAccStepConfig(
t *testing.T, config map[string]interface{}) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.UpdateOperation,
Path: "config/access",
Data: config,
}
}
func testAccStepReadToken(
t *testing.T, name string, conf map[string]interface{}) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.ReadOperation,
Path: "creds/" + name,
Check: func(resp *logical.Response) error {
var d struct {
Token string `mapstructure:"token"`
}
if err := mapstructure.Decode(resp.Data, &d); err != nil {
return err
}
log.Printf("[WARN] Generated token: %s", d.Token)
// Build a client and verify that the credentials work
config := api.DefaultConfig()
config.Address = conf["address"].(string)
config.Token = d.Token
client, err := api.NewClient(config)
if err != nil {
return err
}
log.Printf("[WARN] Verifying that the generated token works...")
_, err = client.KV().Put(&api.KVPair{
Key: "foo",
Value: []byte("bar"),
}, nil)
if err != nil {
return err
}
return nil
},
}
}
func testAccStepReadManagementToken(
t *testing.T, name string, conf map[string]interface{}) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.ReadOperation,
Path: "creds/" + name,
Check: func(resp *logical.Response) error {
var d struct {
Token string `mapstructure:"token"`
}
if err := mapstructure.Decode(resp.Data, &d); err != nil {
return err
}
log.Printf("[WARN] Generated token: %s", d.Token)
// Build a client and verify that the credentials work
config := api.DefaultConfig()
config.Address = conf["address"].(string)
config.Token = d.Token
client, err := api.NewClient(config)
if err != nil {
return err
}
log.Printf("[WARN] Verifying that the generated token works...")
_, _, err = client.ACL().Create(&api.ACLEntry{
Type: "management",
Name: "test2",
}, nil)
if err != nil {
return err
}
return nil
},
}
}
func testAccStepWritePolicy(t *testing.T, name string, policy string, lease string) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.UpdateOperation,
Path: "roles/" + name,
Data: map[string]interface{}{
"policy": base64.StdEncoding.EncodeToString([]byte(policy)),
"lease": lease,
},
}
}
func testAccStepWriteManagementPolicy(t *testing.T, name string, lease string) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.UpdateOperation,
Path: "roles/" + name,
Data: map[string]interface{}{
"token_type": "management",
"lease": lease,
},
}
}
func testAccStepReadPolicy(t *testing.T, name string, policy string, lease time.Duration) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.ReadOperation,
Path: "roles/" + name,
Check: func(resp *logical.Response) error {
policyRaw := resp.Data["policy"].(string)
out, err := base64.StdEncoding.DecodeString(policyRaw)
if err != nil {
return err
}
if string(out) != policy {
return fmt.Errorf("mismatch: %s %s", out, policy)
}
leaseRaw := resp.Data["lease"].(string)
l, err := time.ParseDuration(leaseRaw)
if err != nil {
return err
}
if l != lease {
return fmt.Errorf("mismatch: %v %v", l, lease)
}
return nil
},
}
}
func testAccStepDeletePolicy(t *testing.T, name string) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.DeleteOperation,
Path: "roles/" + name,
}
}
const testPolicy = `
key "" {
policy = "write"
}
`
const testConsulConfig = `
{
"datacenter": "test",
"acl_datacenter": "test",
"acl_master_token": "test"
}
`