1require 'aws-sdk' |
|
2require 'ipaddr' |
|
4class SignatureLogs |
|
5 class Log |
|
6 PATTERN = /(?<ip>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(?:, (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}))* - .{0}- \[(?<day>[\d]{2})\/(?<month>[\w]+)\/(?<year>[\d]{4})\:(?<hour>[\d]{2})\:(?<min>[\d]{2})\:(?<sec>[\d]{2}) [^$]+\] "(?<method>GET|POST|PUT|DELETE) (?<uri>[^\s]+?) HTTP\/1\.1" (?<response>[\d]+) [\d]+ "(?<referrer>[^\s]+?)" "(?<agent>[^\"]+?)"/ |
|
7 MONTHS = %w[Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec] |
|
8 attr_reader :message, :data |
|
10 def initialize(message) |
|
11 @message = message |
|
12 @data = message.match(PATTERN) |
|
13 end |
|
15 def blank? |
|
16 data.nil?
|
|
17 end |
|
19 def ip_address |
|
20 if present? |
|
21 @ip_address ||= data && ::IPAddr.new(data["ip"]) |
|
22 end |
|
23 end |
|
25 def timestamp |
|
26 if present? |
|
27 @timestamp ||= data && ::Time.utc(year, month, day, hour, min, sec).in_time_zone |
|
28 end |
|
29 end |
|
31 def method |
|
32 data["method"] if present? |
|
33 end |
|
35 def uri |
|
36 data["uri"] if present? |
|
37 end |
|
39 def response |
|
40 data["response"] if present? |
|
41 end |
|
43 def referrer |
|
44 data["referrer"] if present? |
|
45 end |
|
47 def agent |
|
48 data["agent"] if present? |
|
49 end |
|
51 def ==(other) |
|
52 return false unless other.is_a?(self.class) |
|
53 message == other.message
|
|
54 end |
|
56 private
|
|
58 def year; data["year"].to_i; end |
|
59 def month; MONTHS.index(data["month"]) + 1; end |
|
60 def day; data["day"].to_i; end |
|
61 def hour; data["hour"].to_i; end |
|
62 def min; data["min"].to_i; end |
|
63 def sec; data["sec"].to_i; end |
|
64 end |
|
66 include Enumerable |
|
68 attr_reader :signature |
|
70 delegate :created_at, to: :signature |
|
71 delegate :ip_address, to: :signature |
|
72 delegate :validated_at, to: :signature |
|
73 delegate :validated_ip, to: :signature |
|
74 delegate :size, :empty?, to: :logs |
|
76 class << self |
|
77 def find(id) |
|
78 new(id)
|
|
79 end |
|
80 end |
|
|
82 def initialize(id) |
83 @signature = Signature.find(id) |
|
84 end |
|
|
86 def each(&block) |
87 logs.each { |log| yield log } |
|
88 end |
|
90 private
|
|
|
92 def client |
93 @client ||= Aws::CloudWatchLogs::Client.new |
|
94 end |
|
|
96 def log_group_name |
97 ENV.fetch("NGINX_LOG_GROUP_NAME", "nginx-access-logs") |
|
98 end |
|
|
100 def ms(time) |
101 (time.to_f * 1000).to_i |
|
102 end |
|
|
104 def logs |
105 @logs ||= fetch_events.map { |e| Log.new(e.message) }.reject(&:blank?).sort_by(&:timestamp) |
|
106 end |
|
|
108 def fetch_events |
109 if overlapping? |
|
110 fetch_combined_events
|
|
111 else |
|
112 fetch_create_events + fetch_validate_events
|
|
113 end |
|
114 end |
|
|
116 def overlapping? |
117 return false unless validated_at |
|
118 return false unless validated_ip |
|
119 return false unless ip_address == validated_ip |
|
121 (created_at + 5.minutes) >= (validated_at - 5.minutes) |
|
122 end |
|
|
124 def fetch_create_events |
125 request = {
|
|
126 log_group_name: log_group_name, |
|
127 start_time: ms(created_at - 5.minutes), |
|
128 end_time: ms(created_at + 5.minutes), |
|
129 filter_pattern: ip_address, |
|
130 interleaved: true |
|
131 }
|
|
133 client.filter_log_events(request).events
|
|
135 rescue Aws::CloudWatchLogs::Errors::ServiceError => e |
|
136 []
|
|
137 end |
|
|
139 def fetch_validate_events |
140 return [] unless validated_at |
|
141 return [] unless validated_ip |
|
143 request = {
|
|
144 log_group_name: log_group_name, |
|
145 start_time: ms(validated_at - 5.minutes), |
|
146 end_time: ms(validated_at + 5.minutes), |
|
147 filter_pattern: validated_ip, |
|
148 interleaved: true |
|
149 }
|
|
151 client.filter_log_events(request).events
|
|
153 rescue Aws::CloudWatchLogs::Errors::ServiceError => e |
|
154 []
|
|
155 end |
|
|
157 def fetch_combined_events |
158 request = {
|
|
159 log_group_name: log_group_name, |
|
160 start_time: ms(created_at - 5.minutes), |
|
161 end_time: ms(validated_at + 5.minutes), |
|
162 filter_pattern: ip_address, |
|
163 interleaved: true |
|
164 }
|
|
166 client.filter_log_events(request).events
|
|
168 rescue Aws::CloudWatchLogs::Errors::ServiceError => e |
|
169 []
|
|
170 end |
|
171end |