Quit agent endpoint with config (#14223)
* Add agent/v1/quit endpoint * Closes https://github.com/hashicorp/vault/issues/11089 * Agent quit API behind config setting * Normalise test config whitespace * Document config option Co-authored-by: Rémi Lapeyre <remi.lapeyre@lenstra.fr> Co-authored-by: Ben Ash <32777270+benashz@users.noreply.github.com>
This commit is contained in:
parent
c2d7386be4
commit
3668275903
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:improvement
|
||||||
|
agent: The `agent/v1/quit` endpoint can now be used to stop the Vault Agent remotely
|
||||||
|
```
|
|
@ -715,7 +715,10 @@ func (c *AgentCommand) Run(args []string) int {
|
||||||
|
|
||||||
// Create a muxer and add paths relevant for the lease cache layer
|
// Create a muxer and add paths relevant for the lease cache layer
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
|
quitEnabled := lnConfig.AgentAPI != nil && lnConfig.AgentAPI.EnableQuit
|
||||||
|
|
||||||
mux.Handle(consts.AgentPathCacheClear, leaseCache.HandleCacheClear(ctx))
|
mux.Handle(consts.AgentPathCacheClear, leaseCache.HandleCacheClear(ctx))
|
||||||
|
mux.Handle(consts.AgentPathQuit, c.handleQuit(quitEnabled))
|
||||||
mux.Handle(consts.AgentPathMetrics, c.handleMetrics())
|
mux.Handle(consts.AgentPathMetrics, c.handleMetrics())
|
||||||
mux.Handle("/", muxHandler)
|
mux.Handle("/", muxHandler)
|
||||||
|
|
||||||
|
@ -1047,3 +1050,22 @@ func (c *AgentCommand) handleMetrics() http.Handler {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *AgentCommand) handleQuit(enabled bool) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !enabled {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch r.Method {
|
||||||
|
case http.MethodPost:
|
||||||
|
default:
|
||||||
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.logger.Debug("received quit request")
|
||||||
|
close(c.ShutdownCh)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -510,21 +511,31 @@ cache {
|
||||||
}
|
}
|
||||||
|
|
||||||
listener "tcp" {
|
listener "tcp" {
|
||||||
address = "127.0.0.1:8101"
|
address = "%s"
|
||||||
tls_disable = true
|
tls_disable = true
|
||||||
}
|
}
|
||||||
listener "tcp" {
|
listener "tcp" {
|
||||||
address = "127.0.0.1:8102"
|
address = "%s"
|
||||||
tls_disable = true
|
tls_disable = true
|
||||||
require_request_header = false
|
require_request_header = false
|
||||||
}
|
}
|
||||||
listener "tcp" {
|
listener "tcp" {
|
||||||
address = "127.0.0.1:8103"
|
address = "%s"
|
||||||
tls_disable = true
|
tls_disable = true
|
||||||
require_request_header = true
|
require_request_header = true
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
config = fmt.Sprintf(config, roleIDPath, secretIDPath)
|
listenAddr1 := generateListenerAddress(t)
|
||||||
|
listenAddr2 := generateListenerAddress(t)
|
||||||
|
listenAddr3 := generateListenerAddress(t)
|
||||||
|
config = fmt.Sprintf(
|
||||||
|
config,
|
||||||
|
roleIDPath,
|
||||||
|
secretIDPath,
|
||||||
|
listenAddr1,
|
||||||
|
listenAddr2,
|
||||||
|
listenAddr3,
|
||||||
|
)
|
||||||
configPath := makeTempFile(t, "config.hcl", config)
|
configPath := makeTempFile(t, "config.hcl", config)
|
||||||
defer os.Remove(configPath)
|
defer os.Remove(configPath)
|
||||||
|
|
||||||
|
@ -563,19 +574,19 @@ listener "tcp" {
|
||||||
|
|
||||||
// Test against a listener configuration that omits
|
// Test against a listener configuration that omits
|
||||||
// 'require_request_header', with the header missing from the request.
|
// 'require_request_header', with the header missing from the request.
|
||||||
agentClient := newApiClient("http://127.0.0.1:8101", false)
|
agentClient := newApiClient("http://"+listenAddr1, false)
|
||||||
req = agentClient.NewRequest("GET", "/v1/sys/health")
|
req = agentClient.NewRequest("GET", "/v1/sys/health")
|
||||||
request(t, agentClient, req, 200)
|
request(t, agentClient, req, 200)
|
||||||
|
|
||||||
// Test against a listener configuration that sets 'require_request_header'
|
// Test against a listener configuration that sets 'require_request_header'
|
||||||
// to 'false', with the header missing from the request.
|
// to 'false', with the header missing from the request.
|
||||||
agentClient = newApiClient("http://127.0.0.1:8102", false)
|
agentClient = newApiClient("http://"+listenAddr2, false)
|
||||||
req = agentClient.NewRequest("GET", "/v1/sys/health")
|
req = agentClient.NewRequest("GET", "/v1/sys/health")
|
||||||
request(t, agentClient, req, 200)
|
request(t, agentClient, req, 200)
|
||||||
|
|
||||||
// Test against a listener configuration that sets 'require_request_header'
|
// Test against a listener configuration that sets 'require_request_header'
|
||||||
// to 'true', with the header missing from the request.
|
// to 'true', with the header missing from the request.
|
||||||
agentClient = newApiClient("http://127.0.0.1:8103", false)
|
agentClient = newApiClient("http://"+listenAddr3, false)
|
||||||
req = agentClient.NewRequest("GET", "/v1/sys/health")
|
req = agentClient.NewRequest("GET", "/v1/sys/health")
|
||||||
resp, err := agentClient.RawRequest(req)
|
resp, err := agentClient.RawRequest(req)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -587,7 +598,7 @@ listener "tcp" {
|
||||||
|
|
||||||
// Test against a listener configuration that sets 'require_request_header'
|
// Test against a listener configuration that sets 'require_request_header'
|
||||||
// to 'true', with an invalid header present in the request.
|
// to 'true', with an invalid header present in the request.
|
||||||
agentClient = newApiClient("http://127.0.0.1:8103", false)
|
agentClient = newApiClient("http://"+listenAddr3, false)
|
||||||
h := agentClient.Headers()
|
h := agentClient.Headers()
|
||||||
h[consts.RequestHeaderName] = []string{"bogus"}
|
h[consts.RequestHeaderName] = []string{"bogus"}
|
||||||
agentClient.SetHeaders(h)
|
agentClient.SetHeaders(h)
|
||||||
|
@ -602,7 +613,7 @@ listener "tcp" {
|
||||||
|
|
||||||
// Test against a listener configuration that sets 'require_request_header'
|
// Test against a listener configuration that sets 'require_request_header'
|
||||||
// to 'true', with the proper header present in the request.
|
// to 'true', with the proper header present in the request.
|
||||||
agentClient = newApiClient("http://127.0.0.1:8103", true)
|
agentClient = newApiClient("http://"+listenAddr3, true)
|
||||||
req = agentClient.NewRequest("GET", "/v1/sys/health")
|
req = agentClient.NewRequest("GET", "/v1/sys/health")
|
||||||
request(t, agentClient, req, 200)
|
request(t, agentClient, req, 200)
|
||||||
}
|
}
|
||||||
|
@ -613,16 +624,17 @@ listener "tcp" {
|
||||||
func TestAgent_RequireAutoAuthWithForce(t *testing.T) {
|
func TestAgent_RequireAutoAuthWithForce(t *testing.T) {
|
||||||
logger := logging.NewVaultLogger(hclog.Trace)
|
logger := logging.NewVaultLogger(hclog.Trace)
|
||||||
// Create a config file
|
// Create a config file
|
||||||
config := `
|
config := fmt.Sprintf(`
|
||||||
cache {
|
cache {
|
||||||
use_auto_auth_token = "force"
|
use_auto_auth_token = "force"
|
||||||
}
|
}
|
||||||
|
|
||||||
listener "tcp" {
|
listener "tcp" {
|
||||||
address = "127.0.0.1:8101"
|
address = "%s"
|
||||||
tls_disable = true
|
tls_disable = true
|
||||||
}
|
}
|
||||||
`
|
`, generateListenerAddress(t))
|
||||||
|
|
||||||
configPath := makeTempFile(t, "config.hcl", config)
|
configPath := makeTempFile(t, "config.hcl", config)
|
||||||
defer os.Remove(configPath)
|
defer os.Remove(configPath)
|
||||||
|
|
||||||
|
@ -1623,7 +1635,7 @@ func TestAgent_Cache_Retry(t *testing.T) {
|
||||||
cache {
|
cache {
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
listenAddr := "127.0.0.1:18123"
|
listenAddr := generateListenerAddress(t)
|
||||||
listenConfig := fmt.Sprintf(`
|
listenConfig := fmt.Sprintf(`
|
||||||
listener "tcp" {
|
listener "tcp" {
|
||||||
address = "%s"
|
address = "%s"
|
||||||
|
@ -1861,7 +1873,7 @@ func TestAgent_TemplateConfig_ExitOnRetryFailure(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
listenAddr := "127.0.0.1:18123"
|
listenAddr := generateListenerAddress(t)
|
||||||
listenConfig := fmt.Sprintf(`
|
listenConfig := fmt.Sprintf(`
|
||||||
listener "tcp" {
|
listener "tcp" {
|
||||||
address = "%s"
|
address = "%s"
|
||||||
|
@ -2021,14 +2033,15 @@ func TestAgent_Metrics(t *testing.T) {
|
||||||
serverClient := cluster.Cores[0].Client
|
serverClient := cluster.Cores[0].Client
|
||||||
|
|
||||||
// Create a config file
|
// Create a config file
|
||||||
config := `
|
listenAddr := generateListenerAddress(t)
|
||||||
|
config := fmt.Sprintf(`
|
||||||
cache {}
|
cache {}
|
||||||
|
|
||||||
listener "tcp" {
|
listener "tcp" {
|
||||||
address = "127.0.0.1:8101"
|
address = "%s"
|
||||||
tls_disable = true
|
tls_disable = true
|
||||||
}
|
}
|
||||||
`
|
`, listenAddr)
|
||||||
configPath := makeTempFile(t, "config.hcl", config)
|
configPath := makeTempFile(t, "config.hcl", config)
|
||||||
defer os.Remove(configPath)
|
defer os.Remove(configPath)
|
||||||
|
|
||||||
|
@ -2062,7 +2075,7 @@ listener "tcp" {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
conf := api.DefaultConfig()
|
conf := api.DefaultConfig()
|
||||||
conf.Address = "http://127.0.0.1:8101"
|
conf.Address = "http://" + listenAddr
|
||||||
agentClient, err := api.NewClient(conf)
|
agentClient, err := api.NewClient(conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
|
@ -2082,3 +2095,133 @@ listener "tcp" {
|
||||||
"Points",
|
"Points",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAgent_Quit(t *testing.T) {
|
||||||
|
//----------------------------------------------------
|
||||||
|
// Start the server and agent
|
||||||
|
//----------------------------------------------------
|
||||||
|
logger := logging.NewVaultLogger(hclog.Error)
|
||||||
|
cluster := vault.NewTestCluster(t,
|
||||||
|
&vault.CoreConfig{
|
||||||
|
Logger: logger,
|
||||||
|
CredentialBackends: map[string]logical.Factory{
|
||||||
|
"approle": credAppRole.Factory,
|
||||||
|
},
|
||||||
|
LogicalBackends: map[string]logical.Factory{
|
||||||
|
"kv": logicalKv.Factory,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&vault.TestClusterOptions{
|
||||||
|
NumCores: 1,
|
||||||
|
})
|
||||||
|
cluster.Start()
|
||||||
|
defer cluster.Cleanup()
|
||||||
|
|
||||||
|
vault.TestWaitActive(t, cluster.Cores[0].Core)
|
||||||
|
serverClient := cluster.Cores[0].Client
|
||||||
|
|
||||||
|
// Unset the environment variable so that agent picks up the right test
|
||||||
|
// cluster address
|
||||||
|
defer os.Setenv(api.EnvVaultAddress, os.Getenv(api.EnvVaultAddress))
|
||||||
|
err := os.Unsetenv(api.EnvVaultAddress)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
listenAddr := generateListenerAddress(t)
|
||||||
|
listenAddr2 := generateListenerAddress(t)
|
||||||
|
config := fmt.Sprintf(`
|
||||||
|
vault {
|
||||||
|
address = "%s"
|
||||||
|
tls_skip_verify = true
|
||||||
|
}
|
||||||
|
|
||||||
|
listener "tcp" {
|
||||||
|
address = "%s"
|
||||||
|
tls_disable = true
|
||||||
|
}
|
||||||
|
|
||||||
|
listener "tcp" {
|
||||||
|
address = "%s"
|
||||||
|
tls_disable = true
|
||||||
|
agent_api {
|
||||||
|
enable_quit = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cache {}
|
||||||
|
`, serverClient.Address(), listenAddr, listenAddr2)
|
||||||
|
|
||||||
|
configPath := makeTempFile(t, "config.hcl", config)
|
||||||
|
defer os.Remove(configPath)
|
||||||
|
|
||||||
|
// Start the agent
|
||||||
|
_, cmd := testAgentCommand(t, logger)
|
||||||
|
cmd.startedCh = make(chan struct{})
|
||||||
|
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
cmd.Run([]string{"-config", configPath})
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-cmd.startedCh:
|
||||||
|
case <-time.After(5 * time.Second):
|
||||||
|
t.Errorf("timeout")
|
||||||
|
}
|
||||||
|
client, err := api.NewClient(api.DefaultConfig())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
client.SetToken(serverClient.Token())
|
||||||
|
client.SetMaxRetries(0)
|
||||||
|
err = client.SetAddress("http://" + listenAddr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// First try on listener 1 where the API should be disabled.
|
||||||
|
resp, err := client.RawRequest(client.NewRequest(http.MethodPost, "/agent/v1/quit"))
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected error")
|
||||||
|
}
|
||||||
|
if resp != nil && resp.StatusCode != http.StatusNotFound {
|
||||||
|
t.Fatalf("expected %d but got: %d", http.StatusNotFound, resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now try on listener 2 where the quit API should be enabled.
|
||||||
|
err = client.SetAddress("http://" + listenAddr2)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = client.RawRequest(client.NewRequest(http.MethodPost, "/agent/v1/quit"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-cmd.ShutdownCh:
|
||||||
|
case <-time.After(5 * time.Second):
|
||||||
|
t.Errorf("timeout")
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a randomly assigned port and then free it again before returning it.
|
||||||
|
// There is still a race when trying to use it, but should work better
|
||||||
|
// than a static port.
|
||||||
|
func generateListenerAddress(t *testing.T) string {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
ln1, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
listenAddr := ln1.Addr().String()
|
||||||
|
ln1.Close()
|
||||||
|
return listenAddr
|
||||||
|
}
|
||||||
|
|
|
@ -796,6 +796,9 @@ listener "tcp" {
|
||||||
profiling {
|
profiling {
|
||||||
unauthenticated_pprof_access = true
|
unauthenticated_pprof_access = true
|
||||||
}
|
}
|
||||||
|
agent_api {
|
||||||
|
enable_quit = true
|
||||||
|
}
|
||||||
}`))
|
}`))
|
||||||
|
|
||||||
config := Config{
|
config := Config{
|
||||||
|
@ -833,6 +836,9 @@ listener "tcp" {
|
||||||
Profiling: configutil.ListenerProfiling{
|
Profiling: configutil.ListenerProfiling{
|
||||||
UnauthenticatedPProfAccess: true,
|
UnauthenticatedPProfAccess: true,
|
||||||
},
|
},
|
||||||
|
AgentAPI: &configutil.AgentAPI{
|
||||||
|
EnableQuit: true,
|
||||||
|
},
|
||||||
CustomResponseHeaders: DefaultCustomHeaders,
|
CustomResponseHeaders: DefaultCustomHeaders,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -93,6 +93,8 @@ type Listener struct {
|
||||||
SocketUser string `hcl:"socket_user"`
|
SocketUser string `hcl:"socket_user"`
|
||||||
SocketGroup string `hcl:"socket_group"`
|
SocketGroup string `hcl:"socket_group"`
|
||||||
|
|
||||||
|
AgentAPI *AgentAPI `hcl:"agent_api"`
|
||||||
|
|
||||||
Telemetry ListenerTelemetry `hcl:"telemetry"`
|
Telemetry ListenerTelemetry `hcl:"telemetry"`
|
||||||
Profiling ListenerProfiling `hcl:"profiling"`
|
Profiling ListenerProfiling `hcl:"profiling"`
|
||||||
InFlightRequestLogging ListenerInFlightRequestLogging `hcl:"inflight_requests_logging"`
|
InFlightRequestLogging ListenerInFlightRequestLogging `hcl:"inflight_requests_logging"`
|
||||||
|
@ -111,6 +113,11 @@ type Listener struct {
|
||||||
CustomResponseHeadersRaw interface{} `hcl:"custom_response_headers"`
|
CustomResponseHeadersRaw interface{} `hcl:"custom_response_headers"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AgentAPI allows users to select which parts of the Agent API they want enabled.
|
||||||
|
type AgentAPI struct {
|
||||||
|
EnableQuit bool `hcl:"enable_quit"`
|
||||||
|
}
|
||||||
|
|
||||||
func (l *Listener) GoString() string {
|
func (l *Listener) GoString() string {
|
||||||
return fmt.Sprintf("*%#v", *l)
|
return fmt.Sprintf("*%#v", *l)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,3 +7,6 @@ const AgentPathCacheClear = "/agent/v1/cache-clear"
|
||||||
// AgentPathMetrics is the path the the agent will use to expose its internal
|
// AgentPathMetrics is the path the the agent will use to expose its internal
|
||||||
// metrics.
|
// metrics.
|
||||||
const AgentPathMetrics = "/agent/v1/metrics"
|
const AgentPathMetrics = "/agent/v1/metrics"
|
||||||
|
|
||||||
|
// AgentPathQuit is the path that the agent will use to trigger stopping it.
|
||||||
|
const AgentPathQuit = "/agent/v1/quit"
|
||||||
|
|
|
@ -109,6 +109,22 @@ Vault Agent allows client-side caching of responses containing newly created tok
|
||||||
and responses containing leased secrets generated off of these newly created tokens.
|
and responses containing leased secrets generated off of these newly created tokens.
|
||||||
Please see the [Caching docs][caching] for information.
|
Please see the [Caching docs][caching] for information.
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
### Quit
|
||||||
|
|
||||||
|
This endpoints triggers shutdown of the agent. By default, it is disabled, and can
|
||||||
|
be enabled per listener using the [`agent_api`][agent-api] stanza. It is recommended
|
||||||
|
to only enable this on trusted interfaces, as it does not require any authorization to use.
|
||||||
|
|
||||||
|
| Method | Path |
|
||||||
|
| :----- | :--------------- |
|
||||||
|
| `POST` | `/agent/v1/quit` |
|
||||||
|
|
||||||
|
### Cache
|
||||||
|
|
||||||
|
See the [caching](/docs/agent/caching#api) page for details on the cache API.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
These are the currently-available general configuration option:
|
These are the currently-available general configuration option:
|
||||||
|
@ -214,7 +230,7 @@ to address in the future.
|
||||||
|
|
||||||
Agent supports one or more [listener][listener_main] stanzas. In addition to
|
Agent supports one or more [listener][listener_main] stanzas. In addition to
|
||||||
the standard listener configuration, an Agent's listener configuration also
|
the standard listener configuration, an Agent's listener configuration also
|
||||||
supports an additional optional entry:
|
supports the following:
|
||||||
|
|
||||||
- `require_request_header` `(bool: false)` - Require that all incoming HTTP
|
- `require_request_header` `(bool: false)` - Require that all incoming HTTP
|
||||||
requests on this listener must have an `X-Vault-Request: true` header entry.
|
requests on this listener must have an `X-Vault-Request: true` header entry.
|
||||||
|
@ -222,6 +238,12 @@ supports an additional optional entry:
|
||||||
Request Forgery attacks. Requests on the listener that do not have the proper
|
Request Forgery attacks. Requests on the listener that do not have the proper
|
||||||
`X-Vault-Request` header will fail, with a HTTP response status code of `412: Precondition Failed`.
|
`X-Vault-Request` header will fail, with a HTTP response status code of `412: Precondition Failed`.
|
||||||
|
|
||||||
|
- `agent_api` <code>([agent_api][agent-api]: <optional\>)</code> - Manages optional Agent API endpoints.
|
||||||
|
|
||||||
|
#### agent_api Stanza
|
||||||
|
|
||||||
|
- `enable_quit` `(bool: false)` - If set to `true`, the agent will enable the [quit](/docs/agent#quit) API.
|
||||||
|
|
||||||
### telemetry Stanza
|
### telemetry Stanza
|
||||||
|
|
||||||
Vault Agent supports the [telemetry][telemetry] stanza and collects various
|
Vault Agent supports the [telemetry][telemetry] stanza and collects various
|
||||||
|
@ -334,6 +356,7 @@ template {
|
||||||
[persistent-cache]: /docs/agent/caching/persistent-caches
|
[persistent-cache]: /docs/agent/caching/persistent-caches
|
||||||
[template]: /docs/agent/template
|
[template]: /docs/agent/template
|
||||||
[template-config]: /docs/agent/template-config
|
[template-config]: /docs/agent/template-config
|
||||||
|
[agent-api]: /docs/agent/#agent_api-stanza
|
||||||
[listener]: /docs/agent#listener-stanza
|
[listener]: /docs/agent#listener-stanza
|
||||||
[listener_main]: /docs/configuration/listener/tcp
|
[listener_main]: /docs/configuration/listener/tcp
|
||||||
[winsvc]: /docs/agent/winsvc
|
[winsvc]: /docs/agent/winsvc
|
||||||
|
|
Loading…
Reference in New Issue