zabbix_manager 5.1.4 → 5.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -9
  3. data/LICENSE +1 -1
  4. data/README.md +202 -8
  5. data/lib/zabbix_manager/basic/basic_alias.rb +25 -3
  6. data/lib/zabbix_manager/basic/basic_func.rb +32 -61
  7. data/lib/zabbix_manager/basic/basic_init.rb +28 -13
  8. data/lib/zabbix_manager/basic/basic_logic.rb +172 -128
  9. data/lib/zabbix_manager/classes/actions.rb +21 -11
  10. data/lib/zabbix_manager/classes/applications.rb +12 -17
  11. data/lib/zabbix_manager/classes/configurations.rb +20 -5
  12. data/lib/zabbix_manager/classes/drules.rb +11 -23
  13. data/lib/zabbix_manager/classes/errors.rb +16 -28
  14. data/lib/zabbix_manager/classes/events.rb +6 -2
  15. data/lib/zabbix_manager/classes/graphs.rb +41 -34
  16. data/lib/zabbix_manager/classes/hostgroups.rb +53 -3
  17. data/lib/zabbix_manager/classes/hostinterfaces.rb +172 -12
  18. data/lib/zabbix_manager/classes/hosts.rb +197 -68
  19. data/lib/zabbix_manager/classes/httptests.rb +18 -20
  20. data/lib/zabbix_manager/classes/items.rb +199 -72
  21. data/lib/zabbix_manager/classes/maintenance.rb +6 -2
  22. data/lib/zabbix_manager/classes/mediatypes.rb +7 -63
  23. data/lib/zabbix_manager/classes/problems.rb +75 -57
  24. data/lib/zabbix_manager/classes/proxies.rb +48 -7
  25. data/lib/zabbix_manager/classes/roles.rb +47 -36
  26. data/lib/zabbix_manager/classes/screens.rb +45 -14
  27. data/lib/zabbix_manager/classes/scripts.rb +17 -7
  28. data/lib/zabbix_manager/classes/server.rb +7 -1
  29. data/lib/zabbix_manager/classes/templates.rb +59 -21
  30. data/lib/zabbix_manager/classes/triggers.rb +253 -66
  31. data/lib/zabbix_manager/classes/usergroups.rb +28 -6
  32. data/lib/zabbix_manager/classes/usermacros.rb +119 -33
  33. data/lib/zabbix_manager/classes/users.rb +39 -7
  34. data/lib/zabbix_manager/classes/valuemaps.rb +13 -19
  35. data/lib/zabbix_manager/client.rb +311 -135
  36. data/lib/zabbix_manager/http_transport.rb +192 -0
  37. data/lib/zabbix_manager/log_sanitizer.rb +68 -0
  38. data/lib/zabbix_manager/monitoring.rb +691 -0
  39. data/lib/zabbix_manager/version.rb +1 -1
  40. data/lib/zabbix_manager.rb +118 -40
  41. data/zabbix_manager.gemspec +21 -8
  42. metadata +208 -22
  43. data/lib/zabbix_manager/classes/unusable.rb +0 -11
@@ -0,0 +1,192 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "openssl"
5
+ require "socket"
6
+ require "timeout"
7
+ require "uri"
8
+
9
+ class ZabbixManager
10
+ class HttpTransport
11
+ DEFAULT_TIMEOUT = 60
12
+ DEFAULT_KEEP_ALIVE_TIMEOUT = 30
13
+ NETWORK_ERRORS = [
14
+ Timeout::Error, EOFError, IOError, SystemCallError, SocketError, OpenSSL::SSL::SSLError,
15
+ Net::ProtocolError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError
16
+ ].freeze
17
+
18
+ attr_reader :uri
19
+
20
+ # 解析连接、超时和代理配置,并准备持久会话。
21
+ def initialize(options)
22
+ @options = options
23
+ @uri = parse_uri(options.fetch(:url))
24
+ @timeout = positive_number(options.fetch(:timeout, DEFAULT_TIMEOUT), :timeout)
25
+ @open_timeout = positive_number(options.fetch(:open_timeout, @timeout), :open_timeout)
26
+ @read_timeout = positive_number(options.fetch(:read_timeout, @timeout), :read_timeout)
27
+ @write_timeout = positive_number(options.fetch(:write_timeout, @timeout), :write_timeout)
28
+ @keep_alive_timeout = positive_number(
29
+ options.fetch(:keep_alive_timeout, DEFAULT_KEEP_ALIVE_TIMEOUT), :keep_alive_timeout
30
+ )
31
+ @proxy_uri = proxy_uri(options)
32
+ @mutex = Mutex.new
33
+ @http = nil
34
+ @pid = nil
35
+ end
36
+
37
+ # 串行发送请求,失败时关闭连接但不自动重放写操作。
38
+ def request(body, bearer_token: nil)
39
+ @mutex.synchronize do
40
+ response = active_http.request(build_request(body, bearer_token))
41
+ unless response.is_a?(Net::HTTPSuccess)
42
+ raise TransportError, "HTTP Error: #{response.code} on #{safe_url}"
43
+ end
44
+
45
+ response.body
46
+ rescue TransportError
47
+ disconnect
48
+ raise
49
+ rescue *NETWORK_ERRORS => e
50
+ disconnect
51
+ raise TransportError, "#{e.class}: request failed for #{safe_url}"
52
+ rescue StandardError
53
+ disconnect
54
+ raise
55
+ end
56
+ end
57
+
58
+ # 显式关闭当前持久连接。
59
+ def close
60
+ @mutex.synchronize { disconnect }
61
+ true
62
+ end
63
+
64
+ # 返回不包含 URL 凭据和查询串的安全地址。
65
+ def safe_url
66
+ port = @uri.port == @uri.default_port ? nil : ":#{@uri.port}"
67
+ "#{@uri.scheme}://#{@uri.host}#{port}#{@uri.path}"
68
+ end
69
+
70
+ # 返回不包含代理凭据和请求配置的传输层摘要
71
+ # @return [String]
72
+ # @api private
73
+ def inspect
74
+ "#<#{self.class} url=#{safe_url.inspect} persistent=true>"
75
+ end
76
+
77
+ private
78
+
79
+ # 校验并解析 HTTP 或 HTTPS API 地址。
80
+ def parse_uri(value)
81
+ uri = URI.parse(value.to_s)
82
+ unless %w[http https].include?(uri.scheme) && uri.host && !uri.host.empty?
83
+ raise Invalid, "url must be an absolute HTTP or HTTPS URL"
84
+ end
85
+
86
+ uri
87
+ rescue URI::InvalidURIError
88
+ raise Invalid, "url must be an absolute HTTP or HTTPS URL"
89
+ end
90
+
91
+ # 把超时配置转换为正数。
92
+ def positive_number(value, name)
93
+ number = Float(value)
94
+ raise Invalid unless number.positive?
95
+
96
+ number
97
+ rescue ArgumentError, TypeError
98
+ raise Invalid, "#{name} must be greater than zero"
99
+ end
100
+
101
+ # 按显式配置和环境变量顺序解析代理。
102
+ def proxy_uri(options)
103
+ return if options[:no_proxy]
104
+
105
+ value = options[:proxy] || environment_proxy
106
+ return if value.nil? || value.empty?
107
+
108
+ uri = URI.parse(value)
109
+ unless %w[http https].include?(uri.scheme) && uri.host.present?
110
+ raise Invalid, "proxy must be an absolute HTTP or HTTPS URL"
111
+ end
112
+
113
+ uri
114
+ rescue URI::InvalidURIError
115
+ raise Invalid, "proxy must be a valid URL"
116
+ end
117
+
118
+ # 根据目标协议选择对应的环境代理。
119
+ def environment_proxy
120
+ return ENV["http_proxy"] || ENV["HTTP_PROXY"] unless @uri.scheme == "https"
121
+
122
+ ENV["https_proxy"] || ENV["HTTPS_PROXY"] || ENV["http_proxy"] || ENV["HTTP_PROXY"]
123
+ end
124
+
125
+ # 复用已启动连接,并在 fork 后放弃继承套接字。
126
+ def active_http
127
+ abandon_inherited_connection if @pid && @pid != Process.pid
128
+ @http ||= build_http
129
+ @http.start unless @http.started?
130
+ @pid = Process.pid
131
+ @http
132
+ end
133
+
134
+ # 子进程不能向父进程继承的 TLS 套接字发送 close_notify。
135
+ def abandon_inherited_connection
136
+ @http = nil
137
+ @pid = nil
138
+ end
139
+
140
+ # 创建并配置 Net::HTTP 实例。
141
+ def build_http
142
+ http = @proxy_uri ? build_proxy_http : Net::HTTP.new(@uri.host, @uri.port)
143
+
144
+ http.open_timeout = @open_timeout
145
+ http.read_timeout = @read_timeout
146
+ http.write_timeout = @write_timeout if http.respond_to?(:write_timeout=)
147
+ http.keep_alive_timeout = @keep_alive_timeout if http.respond_to?(:keep_alive_timeout=)
148
+ configure_tls(http)
149
+ http
150
+ end
151
+
152
+ # 创建使用显式代理的 Net::HTTP 实例。
153
+ def build_proxy_http
154
+ Net::HTTP::Proxy(
155
+ @proxy_uri.host,
156
+ @proxy_uri.port,
157
+ @proxy_uri.user,
158
+ @proxy_uri.password
159
+ ).new(@uri.host, @uri.port)
160
+ end
161
+
162
+ # 配置 HTTPS;证书校验默认关闭,按调用方选项开启。
163
+ def configure_tls(http)
164
+ return unless @uri.scheme == "https"
165
+
166
+ http.use_ssl = true
167
+ http.verify_mode = @options[:verify_ssl] == true ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
168
+ http.ca_file = @options[:ca_file] if @options[:ca_file]
169
+ end
170
+
171
+ # 构建 JSON-RPC POST,并在需要时写入 Bearer 或 Basic 认证。
172
+ def build_request(body, bearer_token)
173
+ request = Net::HTTP::Post.new(@uri.request_uri)
174
+ request["Content-Type"] = "application/json-rpc"
175
+ request["Accept"] = "application/json"
176
+ request["Authorization"] = "Bearer #{bearer_token}" if bearer_token
177
+ request.basic_auth(@options[:http_user], @options[:http_password]) if @options[:http_user]
178
+ request.body = body
179
+ request
180
+ end
181
+
182
+ # 安全结束连接并清理当前进程的会话状态。
183
+ def disconnect
184
+ @http.finish if @http&.started?
185
+ rescue IOError, SystemCallError
186
+ nil
187
+ ensure
188
+ @http = nil
189
+ @pid = nil
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+
5
+ class ZabbixManager
6
+ module LogSanitizer
7
+ REDACTED = "[FILTERED]"
8
+ MAX_STRING_LENGTH = 2_000
9
+ SENSITIVE_KEY_NAMES = "auth|authorization|api_token|community|cookie|http_password|password|passwd|" \
10
+ "privatekey|secret|sessionid|snmp_community|snmpv3_authpassphrase|" \
11
+ "snmpv3_privpassphrase|tls_psk|tls_psk_identity|token"
12
+ SENSITIVE_KEYS = /\A(?:#{SENSITIVE_KEY_NAMES})\z/i.freeze
13
+
14
+ module_function
15
+
16
+ # 递归脱敏结构化对象,并限制字符串长度。
17
+ def sanitize(value)
18
+ case value
19
+ when Hash
20
+ value.each_with_object({}) do |(key, child), result|
21
+ result[key] = sensitive_key?(key) ? REDACTED : sanitize(child)
22
+ end
23
+ when Array
24
+ value.map { |child| sanitize(child) }
25
+ when String
26
+ sanitize_string(value)
27
+ else
28
+ value
29
+ end
30
+ end
31
+
32
+ # 移除 URL 中的用户名、密码、查询串和片段。
33
+ def sanitize_url(value)
34
+ uri = URI.parse(value.to_s)
35
+ uri.user = nil
36
+ uri.password = nil
37
+ uri.query = nil
38
+ uri.fragment = nil
39
+ uri.to_s
40
+ rescue URI::InvalidURIError
41
+ sanitize_string(value.to_s)
42
+ end
43
+
44
+ # 清理字符串中的认证头和常见敏感键值。
45
+ def sanitize_string(value)
46
+ sanitized = value.dup
47
+ sanitized.gsub!(/\b(Bearer|Basic)\s+[^\s,;]+/i, "\\1 #{REDACTED}")
48
+ sanitized.gsub!(
49
+ /((?:authorization)["']?\s*(?:=>|:|=)\s*)[^,;}\]]+/i,
50
+ "\\1#{REDACTED}"
51
+ )
52
+ sanitized.gsub!(
53
+ /((?:#{SENSITIVE_KEY_NAMES})["']?\s*(?:=>|:|=)\s*)(["'])(.*?)\2/i,
54
+ "\\1\\2#{REDACTED}\\2"
55
+ )
56
+ sanitized.gsub!(
57
+ /((?:#{SENSITIVE_KEY_NAMES})["']?\s*(?:=>|:|=)\s*)(?!["'])[^,;}\]]+/i,
58
+ "\\1#{REDACTED}"
59
+ )
60
+ sanitized.length > MAX_STRING_LENGTH ? "#{sanitized[0, MAX_STRING_LENGTH]}...[TRUNCATED]" : sanitized
61
+ end
62
+
63
+ # 判断字段名是否属于敏感配置。
64
+ def sensitive_key?(key)
65
+ key.to_s.match?(SENSITIVE_KEYS)
66
+ end
67
+ end
68
+ end