1
0
Fork 0
telegraf/plugins/inputs/radius/radius.go

159 lines
4.1 KiB
Go
Raw Normal View History

//go:generate ../../../tools/readme_config_includer/generator
package radius
import (
"context"
_ "embed"
"errors"
"fmt"
"net"
"sync"
"time"
"layeh.com/radius"
"layeh.com/radius/rfc2865"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/plugins/inputs"
)
//go:embed sample.conf
var sampleConfig string
type Radius struct {
Servers []string `toml:"servers"`
Username config.Secret `toml:"username"`
Password config.Secret `toml:"password"`
Secret config.Secret `toml:"secret"`
ResponseTimeout config.Duration `toml:"response_timeout"`
RequestIP string `toml:"request_ip"`
Log telegraf.Logger `toml:"-"`
client radius.Client
}
func (*Radius) SampleConfig() string {
return sampleConfig
}
func (r *Radius) Init() error {
if len(r.Servers) == 0 {
r.Servers = []string{"127.0.0.1:1812"}
}
r.client = radius.Client{
Retry: 0,
}
if r.RequestIP == "" {
r.RequestIP = "127.0.0.1"
}
if net.ParseIP(r.RequestIP) == nil {
return fmt.Errorf("invalid ip address provided for request_ip: %s", r.RequestIP)
}
return nil
}
func (r *Radius) Gather(acc telegraf.Accumulator) error {
var wg sync.WaitGroup
for _, server := range r.Servers {
wg.Add(1)
go func(server string) {
defer wg.Done()
acc.AddError(r.pollServer(acc, server))
}(server)
}
wg.Wait()
return nil
}
func (r *Radius) pollServer(acc telegraf.Accumulator, server string) error {
// Create the fields for this metric
host, port, err := net.SplitHostPort(server)
if err != nil {
return fmt.Errorf("splitting host and port failed: %w", err)
}
tags := map[string]string{"source": host, "source_port": port}
fields := make(map[string]interface{})
secret, err := r.Secret.Get()
if err != nil {
return fmt.Errorf("getting secret failed: %w", err)
}
defer secret.Destroy()
username, err := r.Username.Get()
if err != nil {
return fmt.Errorf("getting username failed: %w", err)
}
defer username.Destroy()
password, err := r.Password.Get()
if err != nil {
return fmt.Errorf("getting password failed: %w", err)
}
defer password.Destroy()
// Create the radius packet with PAP authentication
packet := radius.New(radius.CodeAccessRequest, secret.Bytes())
if err := rfc2865.UserName_Set(packet, username.Bytes()); err != nil {
return fmt.Errorf("setting username for radius auth failed: %w", err)
}
// The radius client requires the password in a buffer with capacity being
// a multiple of 16 for internal operations. To not expose the password we
// grow the (potentially protected) buffer to the required capacity.
capacity := password.Size()
if capacity%16 != 0 {
password.Grow(capacity + 16 - capacity%16)
}
if err := rfc2865.UserPassword_Set(packet, password.Bytes()[:capacity]); err != nil {
return fmt.Errorf("setting password for radius auth failed: %w", err)
}
if r.RequestIP != "" {
if err := rfc2865.NASIPAddress_Set(packet, net.ParseIP(r.RequestIP)); err != nil {
return fmt.Errorf("setting NAS IP address for radius auth failed: %w", err)
}
}
// Do the radius request
ctx := context.Background()
if r.ResponseTimeout > 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, time.Duration(r.ResponseTimeout))
defer cancel()
}
startTime := time.Now()
response, err := r.client.Exchange(ctx, packet, server)
duration := time.Since(startTime)
if err != nil {
if !errors.Is(err, context.DeadlineExceeded) {
return err
}
fields["responsetime_ms"] = time.Duration(r.ResponseTimeout).Milliseconds()
tags["response_code"] = "timeout"
} else if response.Code != radius.CodeAccessAccept {
fields["responsetime_ms"] = time.Duration(r.ResponseTimeout).Milliseconds()
tags["response_code"] = response.Code.String()
} else {
fields["responsetime_ms"] = duration.Milliseconds()
tags["response_code"] = response.Code.String()
}
acc.AddFields("radius", fields, tags)
return nil
}
func init() {
inputs.Add("radius", func() telegraf.Input {
return &Radius{ResponseTimeout: config.Duration(time.Second * 5)}
})
}