package datasource import ( "errors" "regexp" "strings" "time" "github.com/grafana/grafana-plugin-sdk-go/backend" ) // Guardrail errors var ( ErrEmptyItemIDs = errors.New("itemids cannot be empty for item ID query mode") ErrInvalidItemID = errors.New("itemid must be a valid numeric value") ErrInvalidTimeRange = errors.New("invalid time range: 'from' must be before 'to'") ErrTimeRangeTooLarge = errors.New("time range exceeds maximum allowed duration") ErrTooManyItems = errors.New("query would return too many items") ErrAPIMethodNotAllowed = errors.New("API method is not allowed") ) // Default guardrail limits // These limits help prevent performance issues and excessive resource consumption. // Based on Zabbix best practices: // - Zabbix "Max period for time selector" defaults to 2 years (range: 1-10 years) // See: https://www.zabbix.com/documentation/current/en/manual/web_interface/frontend_sections/administration/general // - Zabbix search_limit parameter defaults to 1000 items // See: https://www.zabbix.com/documentation/current/en/manual/api/reference/settings/object // // Our limits are conservative to ensure good performance: const ( DefaultMaxTimeRangeDays = 365 // 1 year DefaultMaxItems = 500 ) // ValidateItemIDs validates that itemids string is not empty and contains valid numeric IDs func ValidateItemIDs(itemids string) error { trimmed := strings.TrimSpace(itemids) if trimmed == "" { return backend.DownstreamError(ErrEmptyItemIDs) } // Split and validate each ID ids := strings.Split(trimmed, ",") numericPattern := regexp.MustCompile(`^\d+$`) hasValidID := false for _, id := range ids { id = strings.TrimSpace(id) if id == "" { continue // Skip empty entries from trailing commas } if !numericPattern.MatchString(id) { return backend.DownstreamError(ErrInvalidItemID) } hasValidID = true } if !hasValidID { return backend.DownstreamError(ErrEmptyItemIDs) } return nil } // ValidateTimeRange validates that the time range is valid (from < to) func ValidateTimeRange(timeRange backend.TimeRange) error { if !timeRange.From.Before(timeRange.To) { return backend.DownstreamError(ErrInvalidTimeRange) } return nil } // ValidateTimeRangeDuration validates that the time range doesn't exceed the maximum allowed duration func ValidateTimeRangeDuration(timeRange backend.TimeRange, maxDays int) error { if maxDays <= 0 { maxDays = DefaultMaxTimeRangeDays } duration := timeRange.To.Sub(timeRange.From) maxDuration := time.Duration(maxDays) * 24 * time.Hour if duration > maxDuration { return backend.DownstreamError(ErrTimeRangeTooLarge) } return nil } // ValidateItemCount validates that the number of items doesn't exceed the maximum allowed func ValidateItemCount(count int, maxItems int) error { if maxItems <= 0 { maxItems = DefaultMaxItems } if count > maxItems { return backend.DownstreamError(ErrTooManyItems) } return nil } // AllowedAPIMethods defines the Zabbix API methods that are allowed to be called. // This list should be kept in sync with src/datasource/zabbix/types.ts (zabbixMethodName type) var AllowedAPIMethods = map[string]bool{ "alert.get": true, "apiinfo.version": true, "application.get": true, "event.acknowledge": true, "event.get": true, "history.get": true, "host.get": true, "hostgroup.get": true, "item.get": true, "problem.get": true, "proxy.get": true, "script.execute": true, "script.get": true, "service.get": true, "service.getsla": true, "sla.get": true, "sla.getsli": true, "trend.get": true, "trigger.get": true, "user.get": true, "usermacro.get": true, "valuemap.get": true, } // ValidateAPIMethod checks if the API method is in the allowed list func ValidateAPIMethod(method string) error { if _, allowed := AllowedAPIMethods[method]; !allowed { return backend.DownstreamError(ErrAPIMethodNotAllowed) } return nil }