feat(inputs.tacacs): Add tacacs plugin for simple tacacs auth response time monitoring (#12747)
This commit is contained in:
parent
2ac45b8d25
commit
dae115852c
|
|
@ -268,6 +268,7 @@ following works:
|
|||
- github.com/netsampler/goflow2 [BSD 3-Clause "New" or "Revised" License](https://github.com/netsampler/goflow2/blob/main/LICENSE)
|
||||
- github.com/newrelic/newrelic-telemetry-sdk-go [Apache License 2.0](https://github.com/newrelic/newrelic-telemetry-sdk-go/blob/master/LICENSE.md)
|
||||
- github.com/nsqio/go-nsq [MIT License](https://github.com/nsqio/go-nsq/blob/master/LICENSE)
|
||||
- github.com/nwaples/tacplus [BSD 2-Clause "Simplified" License](https://github.com/nwaples/tacplus/blob/master/LICENSE)
|
||||
- github.com/olivere/elastic [MIT License](https://github.com/olivere/elastic/blob/release-branch.v7/LICENSE)
|
||||
- github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil [Apache License 2.0](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/LICENSE)
|
||||
- github.com/openconfig/gnmi [Apache License 2.0](https://github.com/openconfig/gnmi/blob/master/LICENSE)
|
||||
|
|
|
|||
1
go.mod
1
go.mod
|
|
@ -138,6 +138,7 @@ require (
|
|||
github.com/netsampler/goflow2 v1.3.3
|
||||
github.com/newrelic/newrelic-telemetry-sdk-go v0.8.1
|
||||
github.com/nsqio/go-nsq v1.1.0
|
||||
github.com/nwaples/tacplus v0.0.3
|
||||
github.com/olivere/elastic v6.2.37+incompatible
|
||||
github.com/openconfig/gnmi v0.10.0
|
||||
github.com/opensearch-project/opensearch-go/v2 v2.3.0
|
||||
|
|
|
|||
2
go.sum
2
go.sum
|
|
@ -1182,6 +1182,8 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA
|
|||
github.com/nsqio/go-nsq v1.1.0 h1:PQg+xxiUjA7V+TLdXw7nVrJ5Jbl3sN86EhGCQj4+FYE=
|
||||
github.com/nsqio/go-nsq v1.1.0/go.mod h1:vKq36oyeVXgsS5Q8YEO7WghqidAVXQlcFxzQbQTuDEY=
|
||||
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
|
||||
github.com/nwaples/tacplus v0.0.3 h1:i3v/BUWWrbKq00BzFDrgcPksUF4HwAWvS8Zk63ezYXg=
|
||||
github.com/nwaples/tacplus v0.0.3/go.mod h1:y5ZA9N5V2JbmwO766S+ET9zuu5FtL1OtdfBCYrbTIgw=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
//go:build !custom || inputs || inputs.tacacs
|
||||
|
||||
package all
|
||||
|
||||
import _ "github.com/influxdata/telegraf/plugins/inputs/tacacs" // register plugin
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
# Tacacs Input Plugin
|
||||
|
||||
The Tacacs plugin collects successful tacacs authentication response times
|
||||
from tacacs servers such as Aruba ClearPass, FreeRADIUS or tac_plus (TACACS+).
|
||||
It is primarily meant to monitor how long it takes for the server to fully
|
||||
handle an auth request, including all potential dependent calls (for example
|
||||
to AD servers, or other sources of truth for auth the tacacs server uses).
|
||||
|
||||
## Global configuration options <!-- @/docs/includes/plugin_config.md -->
|
||||
|
||||
In addition to the plugin-specific configuration settings, plugins support
|
||||
additional global and plugin configuration settings. These settings are used to
|
||||
modify metrics, tags, and field or create aliases and configure ordering, etc.
|
||||
See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
|
||||
|
||||
[CONFIGURATION.md]: ../../../docs/CONFIGURATION.md#plugins
|
||||
|
||||
## Configuration
|
||||
|
||||
```toml @sample.conf
|
||||
# Tacacs plugin collects successful tacacs authentication response times.
|
||||
[[inputs.tacacs]]
|
||||
## An array of Server IPs (or hostnames) and ports to gather from. If none specified, defaults to localhost.
|
||||
# servers = ["127.0.0.1:49"]
|
||||
|
||||
## Request source server IP, normally the server running telegraf.
|
||||
# request_ip = "127.0.0.1"
|
||||
|
||||
## Credentials for tacacs authentication.
|
||||
username = "myuser"
|
||||
password = "mypassword"
|
||||
secret = "mysecret"
|
||||
|
||||
## Maximum time to receive response.
|
||||
# response_timeout = "5s"
|
||||
```
|
||||
|
||||
## Metrics
|
||||
|
||||
- tacacs
|
||||
- tags:
|
||||
- source
|
||||
- fields:
|
||||
- response_status (string, [see below](#field-response_status)))
|
||||
- responsetime_ms (int64 [see below](#field-responsetime_ms)))
|
||||
|
||||
### field `response_status`
|
||||
|
||||
The field "response_status" is either a translated raw code returned
|
||||
by the tacacs server, or filled by telegraf in case of a timeout.
|
||||
|
||||
| Field Value | Raw Code | From | responsetime_ms
|
||||
| -------------------- | ------------ | ------------- | ---------------
|
||||
| AuthenStatusPass | 1 (0x1) | tacacs server | real value
|
||||
| AuthenStatusFail | 2 (0x2) | tacacs server | real value
|
||||
| AuthenStatusGetData | 3 (0x3) | tacacs server | real value
|
||||
| AuthenStatusGetUser | 4 (0x4) | tacacs server | real value
|
||||
| AuthenStatusGetPass | 5 (0x5) | tacacs server | real value
|
||||
| AuthenStatusRestart | 6 (0x6) | tacacs server | real value
|
||||
| AuthenStatusError | 7 (0x7) | tacacs server | real value
|
||||
| AuthenStatusFollow | 33 (0x21) | tacacs server | real value
|
||||
| Timeout | Timeout | telegraf | eq. to response_timeout
|
||||
|
||||
### field `responsetime_ms`
|
||||
|
||||
The field responsetime_ms is response time of the tacacs server
|
||||
in miliseconds of the furthest achieved stage of auth.
|
||||
In case of timeout, its filled by telegraf to be the value of
|
||||
the configured response_timeout.
|
||||
|
||||
## Example Output
|
||||
|
||||
```text
|
||||
tacacs,source=127.0.0.1:49 responsetime_ms=311i,response_status="AuthenStatusPass" 1677526200000000000
|
||||
```
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# Tacacs plugin collects successful tacacs authentication response times.
|
||||
[[inputs.tacacs]]
|
||||
## An array of Server IPs (or hostnames) and ports to gather from. If none specified, defaults to localhost.
|
||||
# servers = ["127.0.0.1:49"]
|
||||
|
||||
## Request source server IP, normally the server running telegraf.
|
||||
# request_ip = "127.0.0.1"
|
||||
|
||||
## Credentials for tacacs authentication.
|
||||
username = "myuser"
|
||||
password = "mypassword"
|
||||
secret = "mysecret"
|
||||
|
||||
## Maximum time to receive response.
|
||||
# response_timeout = "5s"
|
||||
|
|
@ -0,0 +1,209 @@
|
|||
//go:generate ../../../tools/readme_config_includer/generator
|
||||
package tacacs
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/nwaples/tacplus"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/config"
|
||||
"github.com/influxdata/telegraf/plugins/inputs"
|
||||
)
|
||||
|
||||
type Tacacs struct {
|
||||
Servers []string `toml:"servers"`
|
||||
Username config.Secret `toml:"username"`
|
||||
Password config.Secret `toml:"password"`
|
||||
Secret config.Secret `toml:"secret"`
|
||||
RequestAddr string `toml:"request_ip"`
|
||||
ResponseTimeout config.Duration `toml:"response_timeout"`
|
||||
Log telegraf.Logger `toml:"-"`
|
||||
clients []tacplus.Client
|
||||
authStart tacplus.AuthenStart
|
||||
}
|
||||
|
||||
//go:embed sample.conf
|
||||
var sampleConfig string
|
||||
|
||||
func (t *Tacacs) SampleConfig() string {
|
||||
return sampleConfig
|
||||
}
|
||||
|
||||
func (t *Tacacs) Init() error {
|
||||
if len(t.Servers) == 0 {
|
||||
t.Servers = []string{"127.0.0.1:49"}
|
||||
}
|
||||
|
||||
if t.Username.Empty() || t.Password.Empty() || t.Secret.Empty() {
|
||||
return errors.New("empty credentials were provided (username, password or secret)")
|
||||
}
|
||||
|
||||
if t.RequestAddr == "" {
|
||||
t.RequestAddr = "127.0.0.1"
|
||||
}
|
||||
if net.ParseIP(t.RequestAddr) == nil {
|
||||
return fmt.Errorf("invalid ip address provided for request_ip: %s", t.RequestAddr)
|
||||
}
|
||||
|
||||
t.clients = make([]tacplus.Client, 0, len(t.Servers))
|
||||
for _, server := range t.Servers {
|
||||
t.clients = append(t.clients, tacplus.Client{
|
||||
Addr: server,
|
||||
ConnConfig: tacplus.ConnConfig{},
|
||||
})
|
||||
}
|
||||
|
||||
t.authStart = tacplus.AuthenStart{
|
||||
Action: tacplus.AuthenActionLogin,
|
||||
AuthenType: tacplus.AuthenTypeASCII,
|
||||
AuthenService: tacplus.AuthenServiceLogin,
|
||||
PrivLvl: 1,
|
||||
Port: "heartbeat",
|
||||
RemAddr: t.RequestAddr,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Tacacs) AuthenReplyToString(code uint8) string {
|
||||
switch code {
|
||||
case tacplus.AuthenStatusPass:
|
||||
return `AuthenStatusPass`
|
||||
case tacplus.AuthenStatusFail:
|
||||
return `AuthenStatusFail`
|
||||
case tacplus.AuthenStatusGetData:
|
||||
return `AuthenStatusGetData`
|
||||
case tacplus.AuthenStatusGetUser:
|
||||
return `AuthenStatusGetUser`
|
||||
case tacplus.AuthenStatusGetPass:
|
||||
return `AuthenStatusGetPass`
|
||||
case tacplus.AuthenStatusRestart:
|
||||
return `AuthenStatusRestart`
|
||||
case tacplus.AuthenStatusError:
|
||||
return `AuthenStatusError`
|
||||
case tacplus.AuthenStatusFollow:
|
||||
return `AuthenStatusFollow`
|
||||
}
|
||||
return "AuthenStatusUnknown(" + strconv.FormatUint(uint64(code), 10) + ")"
|
||||
}
|
||||
|
||||
func (t *Tacacs) Gather(acc telegraf.Accumulator) error {
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for idx := range t.clients {
|
||||
wg.Add(1)
|
||||
go func(client *tacplus.Client) {
|
||||
defer wg.Done()
|
||||
acc.AddError(t.pollServer(acc, client))
|
||||
}(&t.clients[idx])
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Tacacs) pollServer(acc telegraf.Accumulator, client *tacplus.Client) error {
|
||||
// Create the fields for this metric
|
||||
tags := map[string]string{"source": client.Addr}
|
||||
fields := make(map[string]interface{})
|
||||
|
||||
secret, err := t.Secret.Get()
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting secret failed: %w", err)
|
||||
}
|
||||
defer config.ReleaseSecret(secret)
|
||||
|
||||
client.ConnConfig.Secret = secret
|
||||
|
||||
username, err := t.Username.Get()
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting username failed: %w", err)
|
||||
}
|
||||
defer config.ReleaseSecret(username)
|
||||
|
||||
password, err := t.Password.Get()
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting password failed: %w", err)
|
||||
}
|
||||
defer config.ReleaseSecret(password)
|
||||
|
||||
ctx := context.Background()
|
||||
if t.ResponseTimeout > 0 {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, time.Duration(t.ResponseTimeout))
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
startTime := time.Now()
|
||||
reply, session, err := client.SendAuthenStart(ctx, &t.authStart)
|
||||
if err != nil {
|
||||
if !errors.Is(err, context.DeadlineExceeded) && !errors.Is(err, os.ErrDeadlineExceeded) {
|
||||
return fmt.Errorf("error on new tacacs authentication start request to %s : %w", client.Addr, err)
|
||||
}
|
||||
fields["responsetime_ms"] = time.Since(startTime).Milliseconds()
|
||||
fields["response_status"] = "Timeout"
|
||||
acc.AddFields("tacacs", fields, tags)
|
||||
return nil
|
||||
}
|
||||
defer session.Close()
|
||||
if reply.Status != tacplus.AuthenStatusGetUser {
|
||||
fields["responsetime_ms"] = time.Since(startTime).Milliseconds()
|
||||
fields["response_status"] = t.AuthenReplyToString(reply.Status)
|
||||
acc.AddFields("tacacs", fields, tags)
|
||||
return nil
|
||||
}
|
||||
|
||||
reply, err = session.Continue(ctx, string(username))
|
||||
if err != nil {
|
||||
if !errors.Is(err, context.DeadlineExceeded) && !errors.Is(err, os.ErrDeadlineExceeded) {
|
||||
return fmt.Errorf("error on tacacs authentication continue username request to %s : %w", client.Addr, err)
|
||||
}
|
||||
fields["responsetime_ms"] = time.Since(startTime).Milliseconds()
|
||||
fields["response_status"] = "Timeout"
|
||||
acc.AddFields("tacacs", fields, tags)
|
||||
return nil
|
||||
}
|
||||
if reply.Status != tacplus.AuthenStatusGetPass {
|
||||
fields["responsetime_ms"] = time.Since(startTime).Milliseconds()
|
||||
fields["response_status"] = t.AuthenReplyToString(reply.Status)
|
||||
acc.AddFields("tacacs", fields, tags)
|
||||
return nil
|
||||
}
|
||||
|
||||
reply, err = session.Continue(ctx, string(password))
|
||||
if err != nil {
|
||||
if !errors.Is(err, context.DeadlineExceeded) && !errors.Is(err, os.ErrDeadlineExceeded) {
|
||||
return fmt.Errorf("error on tacacs authentication continue password request to %s : %w", client.Addr, err)
|
||||
}
|
||||
fields["responsetime_ms"] = time.Since(startTime).Milliseconds()
|
||||
fields["response_status"] = "Timeout"
|
||||
acc.AddFields("tacacs", fields, tags)
|
||||
return nil
|
||||
}
|
||||
if reply.Status != tacplus.AuthenStatusPass {
|
||||
fields["responsetime_ms"] = time.Since(startTime).Milliseconds()
|
||||
fields["response_status"] = t.AuthenReplyToString(reply.Status)
|
||||
acc.AddFields("tacacs", fields, tags)
|
||||
return nil
|
||||
}
|
||||
|
||||
fields["responsetime_ms"] = time.Since(startTime).Milliseconds()
|
||||
fields["response_status"] = t.AuthenReplyToString(reply.Status)
|
||||
acc.AddFields("tacacs", fields, tags)
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
inputs.Add("tacacs", func() telegraf.Input {
|
||||
return &Tacacs{ResponseTimeout: config.Duration(time.Second * 5)}
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,315 @@
|
|||
package tacacs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/nwaples/tacplus"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/testcontainers/testcontainers-go/wait"
|
||||
|
||||
"github.com/influxdata/telegraf/config"
|
||||
"github.com/influxdata/telegraf/testutil"
|
||||
)
|
||||
|
||||
type testRequestHandler map[string]struct {
|
||||
password string
|
||||
args []string
|
||||
}
|
||||
|
||||
func (t testRequestHandler) HandleAuthenStart(_ context.Context, a *tacplus.AuthenStart, s *tacplus.ServerSession) *tacplus.AuthenReply {
|
||||
user := a.User
|
||||
for user == "" {
|
||||
c, err := s.GetUser(context.Background(), "Username:")
|
||||
if err != nil || c.Abort {
|
||||
return nil
|
||||
}
|
||||
user = c.Message
|
||||
}
|
||||
pass := ""
|
||||
for pass == "" {
|
||||
c, err := s.GetPass(context.Background(), "Password:")
|
||||
if err != nil || c.Abort {
|
||||
return nil
|
||||
}
|
||||
pass = c.Message
|
||||
}
|
||||
if u, ok := t[user]; ok && u.password == pass {
|
||||
return &tacplus.AuthenReply{Status: tacplus.AuthenStatusPass}
|
||||
}
|
||||
return &tacplus.AuthenReply{Status: tacplus.AuthenStatusFail}
|
||||
}
|
||||
|
||||
func (t testRequestHandler) HandleAuthorRequest(_ context.Context, a *tacplus.AuthorRequest, _ *tacplus.ServerSession) *tacplus.AuthorResponse {
|
||||
if u, ok := t[a.User]; ok {
|
||||
return &tacplus.AuthorResponse{Status: tacplus.AuthorStatusPassAdd, Arg: u.args}
|
||||
}
|
||||
return &tacplus.AuthorResponse{Status: tacplus.AuthorStatusFail}
|
||||
}
|
||||
|
||||
func (t testRequestHandler) HandleAcctRequest(_ context.Context, _ *tacplus.AcctRequest, _ *tacplus.ServerSession) *tacplus.AcctReply {
|
||||
return &tacplus.AcctReply{Status: tacplus.AcctStatusSuccess}
|
||||
}
|
||||
|
||||
func TestTacacsInit(t *testing.T) {
|
||||
var testset = []struct {
|
||||
name string
|
||||
testingTimeout config.Duration
|
||||
serversToTest []string
|
||||
usedUsername config.Secret
|
||||
usedPassword config.Secret
|
||||
usedSecret config.Secret
|
||||
requestAddr string
|
||||
errContains string
|
||||
}{
|
||||
{
|
||||
name: "empty_creds",
|
||||
testingTimeout: config.Duration(time.Second * 5),
|
||||
serversToTest: []string{"foo.bar:80"},
|
||||
usedUsername: config.NewSecret([]byte(``)),
|
||||
usedPassword: config.NewSecret([]byte(`testpassword`)),
|
||||
usedSecret: config.NewSecret([]byte(`testsecret`)),
|
||||
errContains: "empty credentials were provided (username, password or secret)",
|
||||
},
|
||||
{
|
||||
name: "wrong_reqaddress",
|
||||
testingTimeout: config.Duration(time.Second * 5),
|
||||
serversToTest: []string{"foo.bar:80"},
|
||||
usedUsername: config.NewSecret([]byte(`testusername`)),
|
||||
usedPassword: config.NewSecret([]byte(`testpassword`)),
|
||||
usedSecret: config.NewSecret([]byte(`testsecret`)),
|
||||
requestAddr: "257.257.257.257",
|
||||
errContains: "invalid ip address provided for request_ip",
|
||||
},
|
||||
{
|
||||
name: "no_reqaddress_no_servers",
|
||||
testingTimeout: config.Duration(time.Second * 5),
|
||||
usedUsername: config.NewSecret([]byte(`testusername`)),
|
||||
usedPassword: config.NewSecret([]byte(`testpassword`)),
|
||||
usedSecret: config.NewSecret([]byte(`testsecret`)),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range testset {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
plugin := &Tacacs{
|
||||
ResponseTimeout: tt.testingTimeout,
|
||||
Servers: tt.serversToTest,
|
||||
Username: tt.usedUsername,
|
||||
Password: tt.usedPassword,
|
||||
Secret: tt.usedSecret,
|
||||
RequestAddr: tt.requestAddr,
|
||||
Log: testutil.Logger{},
|
||||
}
|
||||
|
||||
err := plugin.Init()
|
||||
|
||||
if tt.errContains == "" {
|
||||
require.NoError(t, err)
|
||||
if tt.requestAddr == "" {
|
||||
require.Equal(t, "127.0.0.1", plugin.RequestAddr)
|
||||
}
|
||||
if len(tt.serversToTest) == 0 {
|
||||
require.Equal(t, []string{"127.0.0.1:49"}, plugin.Servers)
|
||||
}
|
||||
} else {
|
||||
require.ErrorContains(t, err, tt.errContains)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTacacsLocal(t *testing.T) {
|
||||
testHandler := tacplus.ServerConnHandler{
|
||||
Handler: &testRequestHandler{
|
||||
"testusername": {
|
||||
password: "testpassword",
|
||||
},
|
||||
},
|
||||
ConnConfig: tacplus.ConnConfig{
|
||||
Secret: []byte(`testsecret`),
|
||||
Mux: true,
|
||||
},
|
||||
}
|
||||
l, err := net.Listen("tcp", "localhost:0")
|
||||
require.NoError(t, err, "local net listen failed to start listening")
|
||||
|
||||
srvLocal := l.Addr().String()
|
||||
|
||||
srv := &tacplus.Server{
|
||||
ServeConn: func(nc net.Conn) {
|
||||
testHandler.Serve(nc)
|
||||
},
|
||||
}
|
||||
|
||||
go func() {
|
||||
err = srv.Serve(l)
|
||||
require.NoError(t, err, "local srv.Serve failed to start serving on "+srvLocal)
|
||||
}()
|
||||
|
||||
var testset = []struct {
|
||||
name string
|
||||
testingTimeout config.Duration
|
||||
serverToTest []string
|
||||
usedUsername config.Secret
|
||||
usedPassword config.Secret
|
||||
usedSecret config.Secret
|
||||
requestAddr string
|
||||
errContains string
|
||||
reqRespStatus string
|
||||
}{
|
||||
{
|
||||
name: "success_timeout_0s",
|
||||
testingTimeout: config.Duration(0),
|
||||
serverToTest: []string{srvLocal},
|
||||
usedUsername: config.NewSecret([]byte(`testusername`)),
|
||||
usedPassword: config.NewSecret([]byte(`testpassword`)),
|
||||
usedSecret: config.NewSecret([]byte(`testsecret`)),
|
||||
requestAddr: "127.0.0.1",
|
||||
reqRespStatus: "AuthenStatusPass",
|
||||
},
|
||||
{
|
||||
name: "wrongpw",
|
||||
testingTimeout: config.Duration(time.Second * 5),
|
||||
serverToTest: []string{srvLocal},
|
||||
usedUsername: config.NewSecret([]byte(`testusername`)),
|
||||
usedPassword: config.NewSecret([]byte(`WRONGPASSWORD`)),
|
||||
usedSecret: config.NewSecret([]byte(`testsecret`)),
|
||||
requestAddr: "127.0.0.1",
|
||||
reqRespStatus: "AuthenStatusFail",
|
||||
},
|
||||
{
|
||||
name: "wrongsecret",
|
||||
testingTimeout: config.Duration(time.Second * 5),
|
||||
serverToTest: []string{srvLocal},
|
||||
usedUsername: config.NewSecret([]byte(`testusername`)),
|
||||
usedPassword: config.NewSecret([]byte(`testpassword`)),
|
||||
usedSecret: config.NewSecret([]byte(`WRONGSECRET`)),
|
||||
requestAddr: "127.0.0.1",
|
||||
errContains: "error on new tacacs authentication start request to " + srvLocal + " : bad secret or packet",
|
||||
},
|
||||
{
|
||||
name: "unreachable",
|
||||
testingTimeout: config.Duration(time.Nanosecond * 1000),
|
||||
serverToTest: []string{"unreachable.test:49"},
|
||||
usedUsername: config.NewSecret([]byte(`testusername`)),
|
||||
usedPassword: config.NewSecret([]byte(`testpassword`)),
|
||||
usedSecret: config.NewSecret([]byte(`testsecret`)),
|
||||
requestAddr: "127.0.0.1",
|
||||
errContains: "error on new tacacs authentication start request to unreachable.test:49 : dial tcp",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range testset {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
plugin := &Tacacs{
|
||||
ResponseTimeout: tt.testingTimeout,
|
||||
Servers: tt.serverToTest,
|
||||
Username: tt.usedUsername,
|
||||
Password: tt.usedPassword,
|
||||
Secret: tt.usedSecret,
|
||||
RequestAddr: tt.requestAddr,
|
||||
Log: testutil.Logger{},
|
||||
}
|
||||
|
||||
var acc testutil.Accumulator
|
||||
|
||||
require.NoError(t, plugin.Init())
|
||||
require.NoError(t, plugin.Gather(&acc))
|
||||
|
||||
if tt.errContains == "" {
|
||||
require.Len(t, acc.Errors, 0)
|
||||
require.Equal(t, true, acc.HasMeasurement("tacacs"))
|
||||
require.Equal(t, true, acc.HasTag("tacacs", "source"))
|
||||
require.Equal(t, srvLocal, acc.TagValue("tacacs", "source"))
|
||||
require.Equal(t, true, acc.HasInt64Field("tacacs", "responsetime_ms"))
|
||||
require.Equal(t, true, acc.HasStringField("tacacs", "response_status"))
|
||||
require.Equal(t, tt.reqRespStatus, acc.Metrics[0].Fields["response_status"])
|
||||
} else {
|
||||
require.Len(t, acc.Errors, 1)
|
||||
require.ErrorContains(t, acc.FirstError(), tt.errContains)
|
||||
require.Equal(t, false, acc.HasTag("tacacs", "source"))
|
||||
require.Equal(t, false, acc.HasInt64Field("tacacs", "responsetime_ms"))
|
||||
require.Equal(t, false, acc.HasStringField("tacacs", "response_status"))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTacacsIntegration(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping integration test in short mode")
|
||||
}
|
||||
|
||||
container := testutil.Container{
|
||||
Image: "dchidell/docker-tacacs",
|
||||
ExposedPorts: []string{"49/tcp"},
|
||||
WaitingFor: wait.ForAll(
|
||||
wait.ForLog("Starting server..."),
|
||||
),
|
||||
}
|
||||
err := container.Start()
|
||||
require.NoError(t, err, "failed to start container")
|
||||
defer container.Terminate()
|
||||
|
||||
port := container.Ports["49"]
|
||||
|
||||
// Define the testset
|
||||
var testset = []struct {
|
||||
name string
|
||||
testingTimeout config.Duration
|
||||
usedPassword string
|
||||
reqRespStatus string
|
||||
}{
|
||||
{
|
||||
name: "timeout_3s",
|
||||
testingTimeout: config.Duration(time.Second * 3),
|
||||
usedPassword: "cisco",
|
||||
reqRespStatus: "AuthenStatusPass",
|
||||
},
|
||||
{
|
||||
name: "timeout_0s",
|
||||
testingTimeout: config.Duration(0),
|
||||
usedPassword: "cisco",
|
||||
reqRespStatus: "AuthenStatusPass",
|
||||
},
|
||||
{
|
||||
name: "wrong_pw",
|
||||
testingTimeout: config.Duration(time.Second * 5),
|
||||
usedPassword: "wrongpass",
|
||||
reqRespStatus: "AuthenStatusFail",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range testset {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Setup the plugin-under-test
|
||||
plugin := &Tacacs{
|
||||
ResponseTimeout: tt.testingTimeout,
|
||||
Servers: []string{container.Address + ":" + port},
|
||||
Username: config.NewSecret([]byte(`iosadmin`)),
|
||||
Password: config.NewSecret([]byte(tt.usedPassword)),
|
||||
Secret: config.NewSecret([]byte(`ciscotacacskey`)),
|
||||
RequestAddr: "127.0.0.1",
|
||||
Log: testutil.Logger{},
|
||||
}
|
||||
var acc testutil.Accumulator
|
||||
|
||||
// Startup the plugin & Gather
|
||||
require.NoError(t, plugin.Init())
|
||||
require.NoError(t, plugin.Gather(&acc))
|
||||
|
||||
require.NoError(t, acc.FirstError())
|
||||
|
||||
require.Equal(t, true, acc.HasMeasurement("tacacs"))
|
||||
require.Equal(t, true, acc.HasStringField("tacacs", "response_status"))
|
||||
require.Equal(t, true, acc.HasInt64Field("tacacs", "responsetime_ms"))
|
||||
require.Equal(t, true, acc.HasTag("tacacs", "source"))
|
||||
|
||||
require.Equal(t, tt.reqRespStatus, acc.Metrics[0].Fields["response_status"])
|
||||
require.Equal(t, container.Address+":"+port, acc.TagValue("tacacs", "source"))
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue