27bb03bbc0
* adding copyright header * fix fmt and a test
330 lines
9.1 KiB
Go
330 lines
9.1 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package cache
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hashicorp/vault/builtin/credential/userpass"
|
|
vaulthttp "github.com/hashicorp/vault/http"
|
|
"github.com/hashicorp/vault/sdk/logical"
|
|
"github.com/hashicorp/vault/vault"
|
|
|
|
"github.com/hashicorp/go-hclog"
|
|
"github.com/hashicorp/vault/api"
|
|
"github.com/hashicorp/vault/helper/namespace"
|
|
"github.com/hashicorp/vault/sdk/helper/jsonutil"
|
|
"github.com/hashicorp/vault/sdk/helper/logging"
|
|
)
|
|
|
|
const policyAdmin = `
|
|
path "*" {
|
|
capabilities = ["sudo", "create", "read", "update", "delete", "list"]
|
|
}
|
|
`
|
|
|
|
func TestAPIProxy(t *testing.T) {
|
|
cleanup, client, _, _ := setupClusterAndAgent(namespace.RootContext(nil), t, nil)
|
|
defer cleanup()
|
|
|
|
proxier, err := NewAPIProxy(&APIProxyConfig{
|
|
Client: client,
|
|
Logger: logging.NewVaultLogger(hclog.Trace),
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
r := client.NewRequest("GET", "/v1/sys/health")
|
|
req, err := r.ToHTTP()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
resp, err := proxier.Send(namespace.RootContext(nil), &SendRequest{
|
|
Request: req,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var result api.HealthResponse
|
|
err = jsonutil.DecodeJSONFromReader(resp.Response.Body, &result)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if !result.Initialized || result.Sealed || result.Standby {
|
|
t.Fatalf("bad sys/health response: %#v", result)
|
|
}
|
|
}
|
|
|
|
func TestAPIProxyNoCache(t *testing.T) {
|
|
cleanup, client, _, _ := setupClusterAndAgentNoCache(namespace.RootContext(nil), t, nil)
|
|
defer cleanup()
|
|
|
|
proxier, err := NewAPIProxy(&APIProxyConfig{
|
|
Client: client,
|
|
Logger: logging.NewVaultLogger(hclog.Trace),
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
r := client.NewRequest("GET", "/v1/sys/health")
|
|
req, err := r.ToHTTP()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
resp, err := proxier.Send(namespace.RootContext(nil), &SendRequest{
|
|
Request: req,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var result api.HealthResponse
|
|
err = jsonutil.DecodeJSONFromReader(resp.Response.Body, &result)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if !result.Initialized || result.Sealed || result.Standby {
|
|
t.Fatalf("bad sys/health response: %#v", result)
|
|
}
|
|
}
|
|
|
|
func TestAPIProxy_queryParams(t *testing.T) {
|
|
// Set up an agent that points to a standby node for this particular test
|
|
// since it needs to proxy a /sys/health?standbyok=true request to a standby
|
|
cleanup, client, _, _ := setupClusterAndAgentOnStandby(namespace.RootContext(nil), t, nil)
|
|
defer cleanup()
|
|
|
|
proxier, err := NewAPIProxy(&APIProxyConfig{
|
|
Client: client,
|
|
Logger: logging.NewVaultLogger(hclog.Trace),
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
r := client.NewRequest("GET", "/v1/sys/health")
|
|
req, err := r.ToHTTP()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Add a query parameter for testing
|
|
q := req.URL.Query()
|
|
q.Add("standbyok", "true")
|
|
req.URL.RawQuery = q.Encode()
|
|
|
|
resp, err := proxier.Send(namespace.RootContext(nil), &SendRequest{
|
|
Request: req,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var result api.HealthResponse
|
|
err = jsonutil.DecodeJSONFromReader(resp.Response.Body, &result)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if !result.Initialized || result.Sealed || !result.Standby {
|
|
t.Fatalf("bad sys/health response: %#v", result)
|
|
}
|
|
|
|
if resp.Response.StatusCode != http.StatusOK {
|
|
t.Fatalf("exptected standby to return 200, got: %v", resp.Response.StatusCode)
|
|
}
|
|
}
|
|
|
|
// setupClusterAndAgent is a helper func used to set up a test cluster and
|
|
// caching agent against the active node. It returns a cleanup func that should
|
|
// be deferred immediately along with two clients, one for direct cluster
|
|
// communication and another to talk to the caching agent.
|
|
func setupClusterAndAgent(ctx context.Context, t *testing.T, coreConfig *vault.CoreConfig) (func(), *api.Client, *api.Client, *LeaseCache) {
|
|
return setupClusterAndAgentCommon(ctx, t, coreConfig, false, true)
|
|
}
|
|
|
|
// setupClusterAndAgentNoCache is a helper func used to set up a test cluster and
|
|
// proxying agent against the active node. It returns a cleanup func that should
|
|
// be deferred immediately along with two clients, one for direct cluster
|
|
// communication and another to talk to the caching agent.
|
|
func setupClusterAndAgentNoCache(ctx context.Context, t *testing.T, coreConfig *vault.CoreConfig) (func(), *api.Client, *api.Client, *LeaseCache) {
|
|
return setupClusterAndAgentCommon(ctx, t, coreConfig, false, false)
|
|
}
|
|
|
|
// setupClusterAndAgentOnStandby is a helper func used to set up a test cluster
|
|
// and caching agent against a standby node. It returns a cleanup func that
|
|
// should be deferred immediately along with two clients, one for direct cluster
|
|
// communication and another to talk to the caching agent.
|
|
func setupClusterAndAgentOnStandby(ctx context.Context, t *testing.T, coreConfig *vault.CoreConfig) (func(), *api.Client, *api.Client, *LeaseCache) {
|
|
return setupClusterAndAgentCommon(ctx, t, coreConfig, true, true)
|
|
}
|
|
|
|
func setupClusterAndAgentCommon(ctx context.Context, t *testing.T, coreConfig *vault.CoreConfig, onStandby bool, useCache bool) (func(), *api.Client, *api.Client, *LeaseCache) {
|
|
t.Helper()
|
|
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
|
|
// Handle sane defaults
|
|
if coreConfig == nil {
|
|
coreConfig = &vault.CoreConfig{
|
|
DisableMlock: true,
|
|
DisableCache: true,
|
|
Logger: logging.NewVaultLogger(hclog.Trace),
|
|
}
|
|
}
|
|
|
|
// Always set up the userpass backend since we use that to generate an admin
|
|
// token for the client that will make proxied requests to through the agent.
|
|
if coreConfig.CredentialBackends == nil || coreConfig.CredentialBackends["userpass"] == nil {
|
|
coreConfig.CredentialBackends = map[string]logical.Factory{
|
|
"userpass": userpass.Factory,
|
|
}
|
|
}
|
|
|
|
// Init new test cluster
|
|
cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
|
|
HandlerFunc: vaulthttp.Handler,
|
|
})
|
|
cluster.Start()
|
|
|
|
cores := cluster.Cores
|
|
vault.TestWaitActive(t, cores[0].Core)
|
|
|
|
activeClient := cores[0].Client
|
|
standbyClient := cores[1].Client
|
|
|
|
// clienToUse is the client for the agent to point to.
|
|
clienToUse := activeClient
|
|
if onStandby {
|
|
clienToUse = standbyClient
|
|
}
|
|
|
|
// Add an admin policy
|
|
if err := activeClient.Sys().PutPolicy("admin", policyAdmin); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Set up the userpass auth backend and an admin user. Used for getting a token
|
|
// for the agent later down in this func.
|
|
err := activeClient.Sys().EnableAuthWithOptions("userpass", &api.EnableAuthOptions{
|
|
Type: "userpass",
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err = activeClient.Logical().Write("auth/userpass/users/foo", map[string]interface{}{
|
|
"password": "bar",
|
|
"policies": []string{"admin"},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Set up env vars for agent consumption
|
|
origEnvVaultAddress := os.Getenv(api.EnvVaultAddress)
|
|
os.Setenv(api.EnvVaultAddress, clienToUse.Address())
|
|
|
|
origEnvVaultCACert := os.Getenv(api.EnvVaultCACert)
|
|
os.Setenv(api.EnvVaultCACert, fmt.Sprintf("%s/ca_cert.pem", cluster.TempDir))
|
|
|
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
apiProxyLogger := logging.NewVaultLogger(hclog.Trace).Named("apiproxy")
|
|
|
|
// Create the API proxier
|
|
apiProxy, err := NewAPIProxy(&APIProxyConfig{
|
|
Client: clienToUse,
|
|
Logger: apiProxyLogger,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Create a muxer and add paths relevant for the lease cache layer and API proxy layer
|
|
mux := http.NewServeMux()
|
|
|
|
var leaseCache *LeaseCache
|
|
if useCache {
|
|
cacheLogger := logging.NewVaultLogger(hclog.Trace).Named("cache")
|
|
|
|
// Create the lease cache proxier and set its underlying proxier to
|
|
// the API proxier.
|
|
leaseCache, err = NewLeaseCache(&LeaseCacheConfig{
|
|
Client: clienToUse,
|
|
BaseContext: ctx,
|
|
Proxier: apiProxy,
|
|
Logger: cacheLogger.Named("leasecache"),
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
mux.Handle("/agent/v1/cache-clear", leaseCache.HandleCacheClear(ctx))
|
|
|
|
mux.Handle("/", ProxyHandler(ctx, cacheLogger, leaseCache, nil, true))
|
|
} else {
|
|
mux.Handle("/", ProxyHandler(ctx, apiProxyLogger, apiProxy, nil, true))
|
|
}
|
|
|
|
server := &http.Server{
|
|
Handler: mux,
|
|
ReadHeaderTimeout: 10 * time.Second,
|
|
ReadTimeout: 30 * time.Second,
|
|
IdleTimeout: 5 * time.Minute,
|
|
ErrorLog: apiProxyLogger.StandardLogger(nil),
|
|
}
|
|
go server.Serve(listener)
|
|
|
|
// testClient is the client that is used to talk to the agent for proxying/caching behavior.
|
|
testClient, err := activeClient.Clone()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := testClient.SetAddress("http://" + listener.Addr().String()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Login via userpass method to derive a managed token. Set that token as the
|
|
// testClient's token
|
|
resp, err := testClient.Logical().Write("auth/userpass/login/foo", map[string]interface{}{
|
|
"password": "bar",
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
testClient.SetToken(resp.Auth.ClientToken)
|
|
|
|
cleanup := func() {
|
|
// We wait for a tiny bit for things such as agent renewal to exit properly
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
cluster.Cleanup()
|
|
os.Setenv(api.EnvVaultAddress, origEnvVaultAddress)
|
|
os.Setenv(api.EnvVaultCACert, origEnvVaultCACert)
|
|
listener.Close()
|
|
}
|
|
|
|
return cleanup, clienToUse, testClient, leaseCache
|
|
}
|