logstash-integration-aws 7.1.1-java
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.PRE.MERGE.md +658 -0
- data/CHANGELOG.md +33 -0
- data/CONTRIBUTORS +40 -0
- data/Gemfile +11 -0
- data/LICENSE +202 -0
- data/NOTICE.TXT +5 -0
- data/README.md +205 -0
- data/VERSION +1 -0
- data/docs/codec-cloudfront.asciidoc +53 -0
- data/docs/codec-cloudtrail.asciidoc +45 -0
- data/docs/index.asciidoc +36 -0
- data/docs/input-cloudwatch.asciidoc +320 -0
- data/docs/input-s3.asciidoc +346 -0
- data/docs/input-sqs.asciidoc +287 -0
- data/docs/output-cloudwatch.asciidoc +321 -0
- data/docs/output-s3.asciidoc +442 -0
- data/docs/output-sns.asciidoc +166 -0
- data/docs/output-sqs.asciidoc +242 -0
- data/lib/logstash/codecs/cloudfront.rb +84 -0
- data/lib/logstash/codecs/cloudtrail.rb +47 -0
- data/lib/logstash/inputs/cloudwatch.rb +338 -0
- data/lib/logstash/inputs/s3.rb +466 -0
- data/lib/logstash/inputs/sqs.rb +196 -0
- data/lib/logstash/outputs/cloudwatch.rb +346 -0
- data/lib/logstash/outputs/s3/file_repository.rb +193 -0
- data/lib/logstash/outputs/s3/path_validator.rb +18 -0
- data/lib/logstash/outputs/s3/size_and_time_rotation_policy.rb +24 -0
- data/lib/logstash/outputs/s3/size_rotation_policy.rb +26 -0
- data/lib/logstash/outputs/s3/temporary_file.rb +114 -0
- data/lib/logstash/outputs/s3/temporary_file_factory.rb +126 -0
- data/lib/logstash/outputs/s3/time_rotation_policy.rb +26 -0
- data/lib/logstash/outputs/s3/uploader.rb +76 -0
- data/lib/logstash/outputs/s3/writable_directory_validator.rb +17 -0
- data/lib/logstash/outputs/s3/write_bucket_permission_validator.rb +60 -0
- data/lib/logstash/outputs/s3.rb +442 -0
- data/lib/logstash/outputs/sns.rb +133 -0
- data/lib/logstash/outputs/sqs.rb +167 -0
- data/lib/logstash/plugin_mixins/aws_config/generic.rb +54 -0
- data/lib/logstash/plugin_mixins/aws_config/v2.rb +93 -0
- data/lib/logstash/plugin_mixins/aws_config.rb +8 -0
- data/lib/logstash-integration-aws_jars.rb +4 -0
- data/lib/tasks/build.rake +15 -0
- data/logstash-integration-aws.gemspec +55 -0
- data/spec/codecs/cloudfront_spec.rb +92 -0
- data/spec/codecs/cloudtrail_spec.rb +56 -0
- data/spec/fixtures/aws_credentials_file_sample_test.yml +2 -0
- data/spec/fixtures/aws_temporary_credentials_file_sample_test.yml +3 -0
- data/spec/fixtures/cloudfront.log +4 -0
- data/spec/fixtures/compressed.log.gee.zip +0 -0
- data/spec/fixtures/compressed.log.gz +0 -0
- data/spec/fixtures/compressed.log.gzip +0 -0
- data/spec/fixtures/invalid_utf8.gbk.log +2 -0
- data/spec/fixtures/json.log +2 -0
- data/spec/fixtures/json_with_message.log +2 -0
- data/spec/fixtures/multiline.log +6 -0
- data/spec/fixtures/multiple_compressed_streams.gz +0 -0
- data/spec/fixtures/uncompressed.log +2 -0
- data/spec/inputs/cloudwatch_spec.rb +85 -0
- data/spec/inputs/s3_spec.rb +610 -0
- data/spec/inputs/sincedb_spec.rb +17 -0
- data/spec/inputs/sqs_spec.rb +324 -0
- data/spec/integration/cloudwatch_spec.rb +25 -0
- data/spec/integration/dynamic_prefix_spec.rb +92 -0
- data/spec/integration/gzip_file_spec.rb +62 -0
- data/spec/integration/gzip_size_rotation_spec.rb +63 -0
- data/spec/integration/outputs/sqs_spec.rb +98 -0
- data/spec/integration/restore_from_crash_spec.rb +133 -0
- data/spec/integration/s3_spec.rb +66 -0
- data/spec/integration/size_rotation_spec.rb +59 -0
- data/spec/integration/sqs_spec.rb +110 -0
- data/spec/integration/stress_test_spec.rb +60 -0
- data/spec/integration/time_based_rotation_with_constant_write_spec.rb +60 -0
- data/spec/integration/time_based_rotation_with_stale_write_spec.rb +64 -0
- data/spec/integration/upload_current_file_on_shutdown_spec.rb +51 -0
- data/spec/outputs/cloudwatch_spec.rb +38 -0
- data/spec/outputs/s3/file_repository_spec.rb +143 -0
- data/spec/outputs/s3/size_and_time_rotation_policy_spec.rb +77 -0
- data/spec/outputs/s3/size_rotation_policy_spec.rb +41 -0
- data/spec/outputs/s3/temporary_file_factory_spec.rb +89 -0
- data/spec/outputs/s3/temporary_file_spec.rb +47 -0
- data/spec/outputs/s3/time_rotation_policy_spec.rb +60 -0
- data/spec/outputs/s3/uploader_spec.rb +69 -0
- data/spec/outputs/s3/writable_directory_validator_spec.rb +40 -0
- data/spec/outputs/s3/write_bucket_permission_validator_spec.rb +49 -0
- data/spec/outputs/s3_spec.rb +232 -0
- data/spec/outputs/sns_spec.rb +160 -0
- data/spec/plugin_mixin/aws_config_spec.rb +217 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/support/helpers.rb +121 -0
- data/spec/unit/outputs/sqs_spec.rb +247 -0
- data/vendor/jar-dependencies/org/logstash/plugins/integration/aws/logstash-integration-aws/7.1.1/logstash-integration-aws-7.1.1.jar +0 -0
- metadata +472 -0
@@ -0,0 +1,232 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "logstash/outputs/s3"
|
3
|
+
require "logstash/event"
|
4
|
+
require "logstash/codecs/line"
|
5
|
+
require "stud/temporary"
|
6
|
+
|
7
|
+
describe LogStash::Outputs::S3 do
|
8
|
+
let(:prefix) { "super/%{server}" }
|
9
|
+
let(:region) { "us-east-1" }
|
10
|
+
let(:bucket_name) { "mybucket" }
|
11
|
+
let(:options) { { "region" => region,
|
12
|
+
"bucket" => bucket_name,
|
13
|
+
"prefix" => prefix,
|
14
|
+
"restore" => false,
|
15
|
+
"access_key_id" => "access_key_id",
|
16
|
+
"secret_access_key" => "secret_access_key"
|
17
|
+
} }
|
18
|
+
let(:client) { Aws::S3::Client.new(stub_responses: true) }
|
19
|
+
let(:mock_bucket) { Aws::S3::Bucket.new(:name => bucket_name, :stub_responses => true, :client => client) }
|
20
|
+
let(:event) { LogStash::Event.new({ "server" => "overwatch" }) }
|
21
|
+
let(:event_encoded) { "super hype" }
|
22
|
+
let(:events_and_encoded) { { event => event_encoded } }
|
23
|
+
|
24
|
+
subject { described_class.new(options) }
|
25
|
+
|
26
|
+
before do
|
27
|
+
allow_any_instance_of(LogStash::Outputs::S3::WriteBucketPermissionValidator).to receive(:valid?).and_return(true)
|
28
|
+
end
|
29
|
+
|
30
|
+
context "#register configuration validation" do
|
31
|
+
describe "signature version" do
|
32
|
+
it "should set the signature version if specified" do
|
33
|
+
["v2", "v4"].each do |version|
|
34
|
+
s3 = described_class.new(options.merge({ "signature_version" => version }))
|
35
|
+
expect(s3.full_options).to include(:signature_version => version)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should omit the option completely if not specified" do
|
40
|
+
s3 = described_class.new(options)
|
41
|
+
expect(s3.full_options.has_key?(:signature_version)).to eql(false)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "Access control list" do
|
46
|
+
context "when configured" do
|
47
|
+
["private", "public-read", "public-read-write", "authenticated-read", "aws-exec-read", "bucket-owner-read", "bucket-owner-full-control", "log-delivery-write"].each do |permission|
|
48
|
+
it "should return the configured ACL permissions: #{permission}" do
|
49
|
+
s3 = described_class.new(options.merge({ "canned_acl" => permission }))
|
50
|
+
expect(s3.upload_options).to include(:acl => permission)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context "when not configured" do
|
56
|
+
it "uses private as the default" do
|
57
|
+
s3 = described_class.new(options)
|
58
|
+
expect(s3.upload_options).to include(:acl => "private")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "Multipart upload threshold" do
|
64
|
+
context "when configured" do
|
65
|
+
it "should use the configured threshold" do
|
66
|
+
threshold = 1 * 1024 * 1024
|
67
|
+
s3 = described_class.new(options.merge({ "upload_multipart_threshold" => threshold }))
|
68
|
+
expect(s3.upload_options).to include(:multipart_threshold => threshold)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context "when not configured" do
|
73
|
+
it "should use 15MB as the default" do
|
74
|
+
s3 = described_class.new(options)
|
75
|
+
expect(s3.upload_options).to include(:multipart_threshold => 15 * 1024 * 1024)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe "Service Side Encryption" do
|
81
|
+
|
82
|
+
context "when configured" do
|
83
|
+
it "should be configure" do
|
84
|
+
s3 = described_class.new(options.merge({ "server_side_encryption" => true }))
|
85
|
+
expect(s3.upload_options).to include(:server_side_encryption => "AES256")
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context "when algorithm is configured" do
|
90
|
+
["AES256", "aws:kms"].each do |sse|
|
91
|
+
it "should return the configured SSE: #{sse}" do
|
92
|
+
s3 = described_class.new(options.merge({ "server_side_encryption" => true, "server_side_encryption_algorithm" => sse }))
|
93
|
+
expect(s3.upload_options).to include(:server_side_encryption => sse)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
context "when using SSE with KMS and custom key" do
|
99
|
+
it "should return the configured KMS key" do
|
100
|
+
s3 = described_class.new(options.merge({ "server_side_encryption" => true, "server_side_encryption_algorithm" => "aws:kms", "ssekms_key_id" => "test"}))
|
101
|
+
expect(s3.upload_options).to include(:server_side_encryption => "aws:kms")
|
102
|
+
expect(s3.upload_options).to include(:ssekms_key_id => "test")
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
context "when using SSE with KMS but no custom key" do
|
107
|
+
it "should return the configured KMS key" do
|
108
|
+
s3 = described_class.new(options.merge({ "server_side_encryption" => true, "server_side_encryption_algorithm" => "aws:kms"}))
|
109
|
+
expect(s3.upload_options).to include(:server_side_encryption => "aws:kms")
|
110
|
+
expect(s3.upload_options).to include(:ssekms_key_id => nil)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
context "when not configured" do
|
115
|
+
it "should not be configured" do
|
116
|
+
s3 = described_class.new(options)
|
117
|
+
expect(s3.upload_options).to include(:server_side_encryption => nil)
|
118
|
+
expect(s3.upload_options).to include(:ssekms_key_id => nil)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe "Storage Class" do
|
124
|
+
context "when configured" do
|
125
|
+
["STANDARD", "REDUCED_REDUNDANCY", "STANDARD_IA", "ONEZONE_IA"].each do |storage_class|
|
126
|
+
it "should return the configured storage class: #{storage_class}" do
|
127
|
+
s3 = described_class.new(options.merge({ "storage_class" => storage_class }))
|
128
|
+
expect(s3.upload_options).to include(:storage_class => storage_class)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
context "when not configured" do
|
134
|
+
it "uses STANDARD as the default" do
|
135
|
+
s3 = described_class.new(options)
|
136
|
+
expect(s3.upload_options).to include(:storage_class => "STANDARD")
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
describe "temporary directory" do
|
142
|
+
let(:temporary_directory) { Stud::Temporary.pathname }
|
143
|
+
let(:options) { super().merge({ "temporary_directory" => temporary_directory }) }
|
144
|
+
|
145
|
+
it "creates the directory when it doesn't exist" do
|
146
|
+
expect(Dir.exist?(temporary_directory)).to be_falsey
|
147
|
+
subject.register
|
148
|
+
expect(Dir.exist?(temporary_directory)).to be_truthy
|
149
|
+
end
|
150
|
+
|
151
|
+
it "raises an error if we cannot write to the directory" do
|
152
|
+
expect(LogStash::Outputs::S3::WritableDirectoryValidator).to receive(:valid?).with(temporary_directory).and_return(false)
|
153
|
+
expect { subject.register }.to raise_error(LogStash::ConfigurationError)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
it "validates the prefix" do
|
158
|
+
s3 = described_class.new(options.merge({ "prefix" => "`no\><^" }))
|
159
|
+
expect { s3.register }.to raise_error(LogStash::ConfigurationError)
|
160
|
+
end
|
161
|
+
|
162
|
+
describe "additional_settings" do
|
163
|
+
context "supported settings" do
|
164
|
+
let(:additional_settings) do
|
165
|
+
{ "additional_settings" => { "force_path_style" => 'true', "ssl_verify_peer" => 'false', "profile" => 'logstash' } }
|
166
|
+
end
|
167
|
+
|
168
|
+
it "validates the prefix" do
|
169
|
+
expect(Aws::S3::Bucket).to receive(:new).twice.
|
170
|
+
with(anything, hash_including(:force_path_style => true, :ssl_verify_peer => false, :profile => 'logstash')).
|
171
|
+
and_call_original
|
172
|
+
described_class.new(options.merge(additional_settings)).register
|
173
|
+
end
|
174
|
+
end
|
175
|
+
context "when using a non existing setting" do
|
176
|
+
let(:additional_settings) do
|
177
|
+
{ "additional_settings" => { "doesnt_exist" => true } }
|
178
|
+
end
|
179
|
+
|
180
|
+
it "raises an error" do
|
181
|
+
plugin = described_class.new(options.merge(additional_settings))
|
182
|
+
expect { plugin.register }.to raise_error(ArgumentError)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
it "allow to not validate credentials" do
|
188
|
+
s3 = described_class.new(options.merge({"validate_credentials_on_root_bucket" => false}))
|
189
|
+
expect_any_instance_of(LogStash::Outputs::S3::WriteBucketPermissionValidator).not_to receive(:valid?).with(any_args)
|
190
|
+
s3.register
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
context "receiving events" do
|
195
|
+
before do
|
196
|
+
allow(subject).to receive(:bucket_resource).and_return(mock_bucket)
|
197
|
+
subject.register
|
198
|
+
end
|
199
|
+
|
200
|
+
after do
|
201
|
+
subject.close
|
202
|
+
end
|
203
|
+
|
204
|
+
it "uses `Event#sprintf` for the prefix" do
|
205
|
+
expect(event).to receive(:sprintf).with(prefix).and_return("super/overwatch")
|
206
|
+
subject.multi_receive_encoded(events_and_encoded)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
describe "aws service" do
|
211
|
+
context 'us-east-1' do
|
212
|
+
let(:region) { 'us-east-1' }
|
213
|
+
it "sets endpoint" do
|
214
|
+
expect( subject.send(:bucket_resource).client.config.endpoint.to_s ).to eql 'https://s3.us-east-1.amazonaws.com'
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
context 'ap-east-1' do
|
219
|
+
let(:region) { 'ap-east-1' }
|
220
|
+
it "sets endpoint" do
|
221
|
+
expect( subject.send(:bucket_resource).client.config.endpoint.to_s ).to eql 'https://s3.ap-east-1.amazonaws.com'
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
context 'cn-northwest-1' do
|
226
|
+
let(:region) { 'cn-northwest-1' }
|
227
|
+
it "sets endpoint" do
|
228
|
+
expect( subject.send(:bucket_resource).client.config.endpoint.to_s ).to eql 'https://s3.cn-northwest-1.amazonaws.com.cn'
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "logstash/devutils/rspec/spec_helper"
|
3
|
+
require 'logstash/outputs/sns'
|
4
|
+
require 'logstash/event'
|
5
|
+
require "logstash/plugin_mixins/aws_config"
|
6
|
+
|
7
|
+
require "aws-sdk-sns"
|
8
|
+
|
9
|
+
describe LogStash::Outputs::Sns do
|
10
|
+
let(:arn) { "arn:aws:sns:us-east-1:999999999:logstash-test-sns-topic" }
|
11
|
+
let(:sns_subject) { "The Plain in Spain" }
|
12
|
+
let(:sns_message) { "That's where the rain falls, plainly." }
|
13
|
+
let(:mock_client) { double("Aws::SNS::Client") }
|
14
|
+
let(:instance) {
|
15
|
+
allow(Aws::SNS::Client).to receive(:new).and_return(mock_client)
|
16
|
+
inst = LogStash::Outputs::Sns.new
|
17
|
+
allow(inst).to receive(:publish_boot_message_arn).and_return(nil)
|
18
|
+
inst.register
|
19
|
+
inst
|
20
|
+
}
|
21
|
+
|
22
|
+
describe "receiving an event" do
|
23
|
+
let(:expected_subject) { double("expected_subject")}
|
24
|
+
subject {
|
25
|
+
inst = instance
|
26
|
+
allow(inst).to receive(:send_sns_message).with(any_args)
|
27
|
+
allow(inst).to receive(:event_subject).
|
28
|
+
with(any_args).
|
29
|
+
and_return(expected_subject)
|
30
|
+
inst.receive(event)
|
31
|
+
inst
|
32
|
+
}
|
33
|
+
|
34
|
+
shared_examples("publishing correctly") do
|
35
|
+
it "should send a message to the correct ARN if the event has 'arn' set" do
|
36
|
+
expect(subject).to have_received(:send_sns_message).with(arn, anything, anything)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should send the message" do
|
40
|
+
expect(subject).to have_received(:send_sns_message).with(anything, anything, expected_message)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should send the subject" do
|
44
|
+
expect(subject).to have_received(:send_sns_message).with(anything, expected_subject, anything)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "with an explicit message" do
|
49
|
+
let(:expected_subject) { sns_subject }
|
50
|
+
let(:expected_message) { sns_message }
|
51
|
+
let(:event) { LogStash::Event.new("sns" => arn, "sns_subject" => sns_subject,
|
52
|
+
"sns_message" => sns_message) }
|
53
|
+
include_examples("publishing correctly")
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "without an explicit message" do
|
57
|
+
# Testing codecs sucks. It'd be nice if codecs had to implement some sort of encode_sync method
|
58
|
+
let(:expected_message) {
|
59
|
+
c = subject.codec.clone
|
60
|
+
result = nil;
|
61
|
+
c.on_event {|event, encoded| result = encoded }
|
62
|
+
c.encode(event)
|
63
|
+
result
|
64
|
+
}
|
65
|
+
let(:event) { LogStash::Event.new("sns" => arn, "sns_subject" => sns_subject) }
|
66
|
+
|
67
|
+
include_examples("publishing correctly")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "determining the subject" do
|
72
|
+
it "should return 'sns_subject' when set" do
|
73
|
+
event = LogStash::Event.new("sns_subject" => "foo")
|
74
|
+
expect(subject.send(:event_subject, event)).to eql("foo")
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should return the sns subject as JSON if not a string" do
|
78
|
+
event = LogStash::Event.new("sns_subject" => ["foo", "bar"])
|
79
|
+
expect(subject.send(:event_subject, event)).to eql(LogStash::Json.dump(["foo", "bar"]))
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should return the host if 'sns_subject' not set" do
|
83
|
+
event = LogStash::Event.new("host" => "foo")
|
84
|
+
expect(subject.send(:event_subject, event)).to eql("foo")
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should return the host name (ECS compatibility) if 'sns_subject' not set" do
|
88
|
+
event = LogStash::Event.new
|
89
|
+
event.set('[host][hostname]', 'server1')
|
90
|
+
expect(subject.send(:event_subject, event)).to eql('server1')
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should return the host IP (ECS compatibility) if 'sns_subject' not set" do
|
94
|
+
event = LogStash::Event.new
|
95
|
+
event.set('host.geo.name', 'Vychodne Pobrezie')
|
96
|
+
expect(subject.send(:event_subject, event)).to eql(LogStash::Outputs::Sns::NO_SUBJECT)
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should return no subject when no info in host object (ECS compatibility) if 'sns_subject' not set" do
|
100
|
+
event = LogStash::Event.new
|
101
|
+
event.set('[host][ip]', '192.168.1.111')
|
102
|
+
expect(subject.send(:event_subject, event)).to eql('192.168.1.111')
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should return 'NO SUBJECT' when subject cannot be determined" do
|
106
|
+
event = LogStash::Event.new("foo" => "bar")
|
107
|
+
expect(subject.send(:event_subject, event)).to eql(LogStash::Outputs::Sns::NO_SUBJECT)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
describe "sending an SNS notification" do
|
112
|
+
let(:good_publish_args) {
|
113
|
+
{
|
114
|
+
:topic_arn => arn,
|
115
|
+
:subject => sns_subject,
|
116
|
+
:message => sns_message
|
117
|
+
}
|
118
|
+
}
|
119
|
+
let(:long_message) { "A" * (LogStash::Outputs::Sns::MAX_MESSAGE_SIZE_IN_BYTES + 1) }
|
120
|
+
let(:long_subject) { "S" * (LogStash::Outputs::Sns::MAX_SUBJECT_SIZE_IN_CHARACTERS + 1) }
|
121
|
+
subject { instance }
|
122
|
+
|
123
|
+
it "should raise an ArgumentError if no arn is provided" do
|
124
|
+
expect {
|
125
|
+
subject.send(:send_sns_message, nil, sns_subject, sns_message)
|
126
|
+
}.to raise_error(ArgumentError)
|
127
|
+
end
|
128
|
+
|
129
|
+
it "should send a well formed message through to SNS" do
|
130
|
+
expect(mock_client).to receive(:publish).with(good_publish_args)
|
131
|
+
subject.send(:send_sns_message, arn, sns_subject, sns_message)
|
132
|
+
end
|
133
|
+
|
134
|
+
it "should attempt to publish a boot message" do
|
135
|
+
expect(subject).to have_received(:publish_boot_message_arn).once
|
136
|
+
x = case "foo"
|
137
|
+
when "bar"
|
138
|
+
"hello"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
it "should truncate long messages before sending" do
|
143
|
+
max_size = LogStash::Outputs::Sns::MAX_MESSAGE_SIZE_IN_BYTES
|
144
|
+
expect(mock_client).to receive(:publish) {|args|
|
145
|
+
expect(args[:message].bytesize).to eql(max_size)
|
146
|
+
}
|
147
|
+
|
148
|
+
subject.send(:send_sns_message, arn, sns_subject, long_message)
|
149
|
+
end
|
150
|
+
|
151
|
+
it "should truncate long subjects before sending" do
|
152
|
+
max_size = LogStash::Outputs::Sns::MAX_SUBJECT_SIZE_IN_CHARACTERS
|
153
|
+
expect(mock_client).to receive(:publish) {|args|
|
154
|
+
expect(args[:subject].bytesize).to eql(max_size)
|
155
|
+
}
|
156
|
+
|
157
|
+
subject.send(:send_sns_message, arn, long_subject, sns_message)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
@@ -0,0 +1,217 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "logstash/devutils/rspec/spec_helper"
|
3
|
+
require "logstash/plugin_mixins/aws_config"
|
4
|
+
require 'timecop'
|
5
|
+
|
6
|
+
class DummyInputAwsConfigV2 < LogStash::Inputs::Base
|
7
|
+
include LogStash::PluginMixins::AwsConfig::V2
|
8
|
+
|
9
|
+
def aws_service_endpoint(region)
|
10
|
+
{ :dummy_input_aws_config_region => "#{region}.awswebservice.local" }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class DummyInputAwsConfigV2NoRegionMethod < LogStash::Inputs::Base
|
15
|
+
include LogStash::PluginMixins::AwsConfig::V2
|
16
|
+
end
|
17
|
+
|
18
|
+
describe LogStash::PluginMixins::AwsConfig::V2 do
|
19
|
+
let(:settings) { {} }
|
20
|
+
|
21
|
+
subject { DummyInputAwsConfigV2.new(settings).aws_options_hash }
|
22
|
+
|
23
|
+
describe 'config credential' do
|
24
|
+
subject { DummyInputAwsConfigV2.new(settings).aws_options_hash[:credentials] }
|
25
|
+
|
26
|
+
context 'in credential file' do
|
27
|
+
let(:settings) { { 'aws_credentials_file' => File.join(File.dirname(__FILE__), '..', 'fixtures/aws_credentials_file_sample_test.yml') } }
|
28
|
+
|
29
|
+
it 'should support reading configuration from a yaml file' do
|
30
|
+
expect(subject.access_key_id).to eq("1234")
|
31
|
+
expect(subject.secret_access_key).to eq("secret")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'inline' do
|
36
|
+
context 'temporary credential' do
|
37
|
+
let(:settings) { { 'access_key_id' => '1234', 'secret_access_key' => 'secret', 'session_token' => 'session_token' } }
|
38
|
+
|
39
|
+
it "should support passing as key, value, and session_token" do
|
40
|
+
expect(subject.access_key_id).to eq(settings['access_key_id'])
|
41
|
+
expect(subject.secret_access_key).to eq(settings['secret_access_key'])
|
42
|
+
expect(subject.session_token).to eq(settings['session_token'])
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'normal credential' do
|
47
|
+
let(:settings) { { 'access_key_id' => '1234', 'secret_access_key' => 'secret' } }
|
48
|
+
|
49
|
+
it 'should support passing credentials as key, value' do
|
50
|
+
expect(subject.access_key_id).to eq(settings['access_key_id'])
|
51
|
+
expect(subject.secret_access_key).to eq(settings['secret_access_key'])
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'role arn is provided' do
|
56
|
+
let(:settings) { { 'role_arn' => 'arn:aws:iam::012345678910:role/foo', 'region' => 'us-west-2' } }
|
57
|
+
let(:sts_double) { instance_double(Aws::STS::Client) }
|
58
|
+
let(:now) { Time.now }
|
59
|
+
let(:expiration) { Time.at(now.to_i + 3600) }
|
60
|
+
let(:temp_credentials) {
|
61
|
+
double(credentials:
|
62
|
+
double(
|
63
|
+
access_key_id: '1234',
|
64
|
+
secret_access_key: 'secret',
|
65
|
+
session_token: 'session_token',
|
66
|
+
expiration: expiration.to_s,
|
67
|
+
)
|
68
|
+
)
|
69
|
+
}
|
70
|
+
let(:new_temp_credentials) {
|
71
|
+
double(credentials:
|
72
|
+
double(
|
73
|
+
access_key_id: '5678',
|
74
|
+
secret_access_key: 'secret1',
|
75
|
+
session_token: 'session_token1',
|
76
|
+
expiration: expiration.to_s,
|
77
|
+
)
|
78
|
+
)
|
79
|
+
}
|
80
|
+
|
81
|
+
before do
|
82
|
+
allow(Aws::STS::Client).to receive(:new).and_return(sts_double)
|
83
|
+
allow(sts_double).to receive(:assume_role) {
|
84
|
+
if Time.now < expiration
|
85
|
+
temp_credentials
|
86
|
+
else
|
87
|
+
new_temp_credentials
|
88
|
+
end
|
89
|
+
}
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'supports passing role_arn' do
|
93
|
+
Timecop.freeze(now) do
|
94
|
+
expect(subject.credentials.access_key_id).to eq('1234')
|
95
|
+
expect(subject.credentials.secret_access_key).to eq('secret')
|
96
|
+
expect(subject.credentials.session_token).to eq('session_token')
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'rotates the keys once they expire' do
|
101
|
+
Timecop.freeze(Time.at(expiration.to_i + 100)) do
|
102
|
+
expect(subject.credentials.access_key_id).to eq('5678')
|
103
|
+
expect(subject.credentials.secret_access_key).to eq('secret1')
|
104
|
+
expect(subject.credentials.session_token).to eq('session_token1')
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
context 'role arn with credentials' do
|
110
|
+
|
111
|
+
let(:settings) do
|
112
|
+
{
|
113
|
+
'role_arn' => 'arn:aws:iam::012345678910:role/foo',
|
114
|
+
'region' => 'us-west-2',
|
115
|
+
|
116
|
+
'access_key_id' => '12345678',
|
117
|
+
'secret_access_key' => 'secret',
|
118
|
+
'session_token' => 'session_token',
|
119
|
+
|
120
|
+
'proxy_uri' => 'http://a-proxy.net:1234'
|
121
|
+
}
|
122
|
+
end
|
123
|
+
|
124
|
+
let(:aws_options_hash) { DummyInputAwsConfigV2NoRegionMethod.new(settings).aws_options_hash }
|
125
|
+
|
126
|
+
before do
|
127
|
+
allow_any_instance_of(Aws::AssumeRoleCredentials).to receive(:refresh) # called from #initialize
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'uses credentials' do
|
131
|
+
subject = aws_options_hash[:credentials]
|
132
|
+
expect( subject ).to be_a Aws::AssumeRoleCredentials
|
133
|
+
expect( subject.client ).to be_a Aws::STS::Client
|
134
|
+
expect( credentials = subject.client.config.credentials ).to be_a Aws::Credentials
|
135
|
+
expect( credentials.access_key_id ).to eql '12345678'
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'sets up proxy on client and region' do
|
139
|
+
subject = aws_options_hash[:credentials]
|
140
|
+
expect( subject.client.config.http_proxy ).to eql 'http://a-proxy.net:1234'
|
141
|
+
expect( subject.client.config.region ).to eql 'us-west-2' # probably redundant (kept for backwards compat)
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'sets up proxy top level' do # setting in on the client isn't enough!
|
145
|
+
expect( aws_options_hash[:http_proxy] ).to eql 'http://a-proxy.net:1234'
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'sets up region top-level' do
|
149
|
+
# NOTE: this one is required for real with role_arn :
|
150
|
+
expect( aws_options_hash[:region] ).to eql 'us-west-2'
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
describe 'config proxy' do
|
158
|
+
let(:proxy) { "http://localhost:1234" }
|
159
|
+
let(:settings) { { 'access_key_id' => '1234', 'secret_access_key' => 'secret', 'region' => 'us-west-2', 'proxy_uri' => proxy } }
|
160
|
+
|
161
|
+
it "should set the http_proxy option" do
|
162
|
+
expect(subject[:http_proxy]).to eql(proxy)
|
163
|
+
end
|
164
|
+
|
165
|
+
it "should not set the legacy http proxy option" do
|
166
|
+
expect(subject[:proxy_uri]).not_to eql(proxy)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
describe 'config region' do
|
171
|
+
context "when the class implement `#aws_service_endpoint`" do
|
172
|
+
context 'region provided' do
|
173
|
+
let(:settings) { { 'access_key_id' => '1234', 'secret_access_key' => 'secret', 'region' => 'us-west-2' } }
|
174
|
+
|
175
|
+
it 'should use provided region to generate the endpoint configuration' do
|
176
|
+
expect(subject).to include(:dummy_input_aws_config_region => "us-west-2.awswebservice.local")
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
context "region not provided" do
|
181
|
+
let(:settings) { { 'access_key_id' => '1234', 'secret_access_key' => 'secret'} }
|
182
|
+
|
183
|
+
it 'should use default region to generate the endpoint configuration' do
|
184
|
+
expect(subject).to include(:dummy_input_aws_config_region => "us-east-1.awswebservice.local")
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
context "when the classe doesn't implement `#aws_service_endpoint`" do
|
190
|
+
subject { DummyInputAwsConfigV2NoRegionMethod.new(settings).aws_options_hash }
|
191
|
+
|
192
|
+
context 'region provided' do
|
193
|
+
let(:settings) { { 'access_key_id' => '1234', 'secret_access_key' => 'secret', 'region' => 'us-west-2' } }
|
194
|
+
|
195
|
+
it 'should use provided region to generate the endpoint configuration' do
|
196
|
+
expect(subject[:region]).to eq("us-west-2")
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
context "region not provided" do
|
201
|
+
let(:settings) { { 'access_key_id' => '1234', 'secret_access_key' => 'secret'} }
|
202
|
+
|
203
|
+
it 'should use default region to generate the endpoint configuration' do
|
204
|
+
expect(subject[:region]).to eq("us-east-1")
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
context 'when we arent providing credentials' do
|
211
|
+
let(:settings) { {} }
|
212
|
+
it 'should always return a hash' do
|
213
|
+
expect(subject).to eq({ :dummy_input_aws_config_region => "us-east-1.awswebservice.local" })
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
end
|
data/spec/spec_helper.rb
ADDED