master
go 226 lines 5.8 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package panos
4
5 import (
6 "context"
7 _ "embed"
8 "errors"
9 "fmt"
10 "strings"
11 "time"
12
13 "github.com/netdata/netdata/go/plugins/pkg/confopt"
14 "github.com/netdata/netdata/go/plugins/pkg/metrix"
15 "github.com/netdata/netdata/go/plugins/pkg/web"
16 "github.com/netdata/netdata/go/plugins/plugin/framework/collectorapi"
17 )
18
19 //go:embed "config_schema.json"
20 var configSchema string
21
22 //go:embed "charts.yaml"
23 var chartTemplateYAML string
24
25 func init() {
26 collectorapi.Register("panos", collectorapi.Creator{
27 JobConfigSchema: configSchema,
28 Defaults: collectorapi.Defaults{
29 UpdateEvery: 60,
30 },
31 CreateV2: func() collectorapi.CollectorV2 { return New() },
32 Config: func() any { return &Config{} },
33 })
34 }
35
36 func New() *Collector {
37 store := metrix.NewCollectorStore()
38
39 return &Collector{
40 Config: Config{
41 HTTPConfig: web.HTTPConfig{
42 RequestConfig: web.RequestConfig{
43 URL: "https://127.0.0.1",
44 },
45 ClientConfig: web.ClientConfig{
46 Timeout: confopt.Duration(3 * time.Second),
47 },
48 },
49 },
50 store: store,
51 metrics: newCollectorMetrics(store),
52 routingEngine: routingEngineUnknown,
53 newAPIClient: newPangoAPIClient,
54 advancedBGPCommands: advancedBGPPeerCommands,
55 now: time.Now,
56 }
57 }
58
59 type Config struct {
60 Vnode string `yaml:"vnode,omitempty" json:"vnode"`
61 UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
62 AutoDetectionRetry int `yaml:"autodetection_retry,omitempty" json:"autodetection_retry"`
63 web.HTTPConfig `yaml:",inline" json:""`
64 APIKey string `yaml:"api_key,omitempty" json:"api_key"`
65 Vsys string `yaml:"vsys,omitempty" json:"vsys"`
66 }
67
68 type Collector struct {
69 collectorapi.Base
70 Config `yaml:",inline" json:""`
71
72 store metrix.CollectorStore
73 metrics *collectorMetrics
74
75 apiClient panosAPIClient
76
77 routingEngine routingEngine
78 bgpCommand string
79 noBGPProbedAt time.Time
80
81 newAPIClient func(Config) (panosAPIClient, error)
82 advancedBGPCommands []string
83 now func() time.Time
84 }
85
86 func (c *Collector) Configuration() any {
87 return c.Config
88 }
89
90 func (c *Collector) Init(context.Context) error {
91 if err := c.validateConfig(); err != nil {
92 return err
93 }
94
95 client, err := c.newAPIClient(c.Config)
96 if err != nil {
97 return fmt.Errorf("init PAN-OS API client: %w", err)
98 }
99 c.apiClient = client
100
101 c.Debugf("using URL %s", c.URL)
102 c.Debugf("using timeout: %s", c.Timeout)
103
104 return nil
105 }
106
107 func (c *Collector) Check(ctx context.Context) error {
108 if c.apiClient == nil {
109 return errors.New("PAN-OS API client not initialized")
110 }
111 defer c.logSystemInfo()
112
113 if err := contextError(ctx); err != nil {
114 return err
115 }
116 if _, err := c.querySystemInfo(ctx); err != nil {
117 return fmt.Errorf("check system info: %w", err)
118 }
119 return nil
120 }
121
122 func (c *Collector) Collect(ctx context.Context) error {
123 hasMetrics, err := c.collect(ctx)
124 if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
125 return err
126 }
127 if !hasMetrics {
128 return err
129 }
130 if err != nil {
131 c.Limit(logKeyCollectPartialError, 1, recurringLogEvery).
132 Warningf("PAN-OS partial collection error: %v", err)
133 }
134 return nil
135 }
136
137 func (c *Collector) Cleanup(context.Context) {
138 if c.apiClient != nil {
139 c.apiClient.closeIdleConnections()
140 }
141 }
142
143 func (c *Collector) MetricStore() metrix.CollectorStore { return c.store }
144
145 func (c *Collector) ChartTemplateYAML() string { return chartTemplateYAML }
146
147 func (c *Collector) validateConfig() error {
148 if c.URL == "" {
149 return errors.New("config: url not configured")
150 }
151 if c.APIKey == "" && (c.Username == "" || c.Password == "") {
152 return errors.New("config: api_key or username/password must be set")
153 }
154 if c.ForceHTTP2 {
155 return errors.New("config: force_http2 is not supported by the panos collector")
156 }
157 if c.BearerTokenFile != "" {
158 return errors.New("config: bearer_token_file is not supported by the panos collector")
159 }
160 if c.Method != "" {
161 return errors.New("config: method is not supported by the panos collector")
162 }
163 if c.Body != "" {
164 return errors.New("config: body is not supported by the panos collector")
165 }
166 if c.NotFollowRedirect {
167 return errors.New("config: not_follow_redirects is not supported by the panos collector")
168 }
169 if c.ProxyUsername != "" || c.ProxyPassword != "" {
170 return errors.New("config: proxy_username/proxy_password are not supported; include proxy credentials in proxy_url")
171 }
172 if (c.TLSCert != "" && c.TLSKey == "") || (c.TLSKey != "" && c.TLSCert == "") {
173 return errors.New("config: tls_cert and tls_key must both be set")
174 }
175 return nil
176 }
177
178 const (
179 recurringLogEvery = time.Hour
180 logKeyCollectPartialError = "panos:collect:partial_error"
181 logKeySystemInfo = "panos:system_info"
182 logKeyPanorama = "panos:panorama"
183 )
184
185 func (c *Collector) logSystemInfo() {
186 if c.apiClient == nil {
187 return
188 }
189
190 info := c.apiClient.systemInfo()
191 if len(info) == 0 {
192 return
193 }
194
195 hostname := firstNonEmpty(info["hostname"], info["devicename"])
196 model := info["model"]
197 swVersion := info["sw-version"]
198 serial := info["serial"]
199 haState := firstNonEmpty(info["ha-state"], info["state"])
200
201 parts := make([]string, 0, 4)
202 if hostname != "" {
203 parts = append(parts, "hostname="+hostname)
204 }
205 if model != "" {
206 parts = append(parts, "model="+model)
207 }
208 if swVersion != "" {
209 parts = append(parts, "sw_version="+swVersion)
210 }
211 if serial != "" {
212 parts = append(parts, "serial="+serial)
213 }
214 if haState != "" {
215 parts = append(parts, "ha_state="+haState)
216 }
217
218 if len(parts) > 0 {
219 c.Limit(logKeySystemInfo, 1, 0).
220 Infof("connected to PAN-OS device: %s", strings.Join(parts, ", "))
221 }
222 if strings.Contains(strings.ToLower(model), "panorama") {
223 c.Limit(logKeyPanorama, 1, 0).
224 Warningf("PAN-OS device appears to be Panorama (model=%s); Panorama target proxy mode is not supported by this collector version", model)
225 }
226 }