logstash-integration-aws 0.1.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.PRE.MERGE.md +658 -0
  3. data/CHANGELOG.md +15 -0
  4. data/CONTRIBUTORS +40 -0
  5. data/Gemfile +11 -0
  6. data/LICENSE +202 -0
  7. data/NOTICE.TXT +5 -0
  8. data/README.md +205 -0
  9. data/docs/codec-cloudfront.asciidoc +53 -0
  10. data/docs/codec-cloudtrail.asciidoc +45 -0
  11. data/docs/index.asciidoc +38 -0
  12. data/docs/input-cloudwatch.asciidoc +320 -0
  13. data/docs/input-s3.asciidoc +346 -0
  14. data/docs/input-sqs.asciidoc +287 -0
  15. data/docs/output-cloudwatch.asciidoc +321 -0
  16. data/docs/output-s3.asciidoc +442 -0
  17. data/docs/output-sns.asciidoc +166 -0
  18. data/docs/output-sqs.asciidoc +242 -0
  19. data/lib/logstash/codecs/cloudfront.rb +84 -0
  20. data/lib/logstash/codecs/cloudtrail.rb +47 -0
  21. data/lib/logstash/inputs/cloudwatch.rb +338 -0
  22. data/lib/logstash/inputs/s3.rb +466 -0
  23. data/lib/logstash/inputs/sqs.rb +196 -0
  24. data/lib/logstash/outputs/cloudwatch.rb +346 -0
  25. data/lib/logstash/outputs/s3/file_repository.rb +121 -0
  26. data/lib/logstash/outputs/s3/path_validator.rb +18 -0
  27. data/lib/logstash/outputs/s3/size_and_time_rotation_policy.rb +24 -0
  28. data/lib/logstash/outputs/s3/size_rotation_policy.rb +26 -0
  29. data/lib/logstash/outputs/s3/temporary_file.rb +71 -0
  30. data/lib/logstash/outputs/s3/temporary_file_factory.rb +129 -0
  31. data/lib/logstash/outputs/s3/time_rotation_policy.rb +26 -0
  32. data/lib/logstash/outputs/s3/uploader.rb +74 -0
  33. data/lib/logstash/outputs/s3/writable_directory_validator.rb +17 -0
  34. data/lib/logstash/outputs/s3/write_bucket_permission_validator.rb +60 -0
  35. data/lib/logstash/outputs/s3.rb +405 -0
  36. data/lib/logstash/outputs/sns.rb +133 -0
  37. data/lib/logstash/outputs/sqs.rb +167 -0
  38. data/lib/logstash/plugin_mixins/aws_config/generic.rb +54 -0
  39. data/lib/logstash/plugin_mixins/aws_config/v2.rb +93 -0
  40. data/lib/logstash/plugin_mixins/aws_config.rb +8 -0
  41. data/logstash-integration-aws.gemspec +52 -0
  42. data/spec/codecs/cloudfront_spec.rb +92 -0
  43. data/spec/codecs/cloudtrail_spec.rb +56 -0
  44. data/spec/fixtures/aws_credentials_file_sample_test.yml +2 -0
  45. data/spec/fixtures/aws_temporary_credentials_file_sample_test.yml +3 -0
  46. data/spec/fixtures/cloudfront.log +4 -0
  47. data/spec/fixtures/compressed.log.gee.zip +0 -0
  48. data/spec/fixtures/compressed.log.gz +0 -0
  49. data/spec/fixtures/compressed.log.gzip +0 -0
  50. data/spec/fixtures/invalid_utf8.gbk.log +2 -0
  51. data/spec/fixtures/json.log +2 -0
  52. data/spec/fixtures/json_with_message.log +2 -0
  53. data/spec/fixtures/multiline.log +6 -0
  54. data/spec/fixtures/multiple_compressed_streams.gz +0 -0
  55. data/spec/fixtures/uncompressed.log +2 -0
  56. data/spec/inputs/cloudwatch_spec.rb +85 -0
  57. data/spec/inputs/s3_spec.rb +610 -0
  58. data/spec/inputs/sincedb_spec.rb +17 -0
  59. data/spec/inputs/sqs_spec.rb +324 -0
  60. data/spec/integration/cloudwatch_spec.rb +25 -0
  61. data/spec/integration/dynamic_prefix_spec.rb +92 -0
  62. data/spec/integration/gzip_file_spec.rb +62 -0
  63. data/spec/integration/gzip_size_rotation_spec.rb +63 -0
  64. data/spec/integration/outputs/sqs_spec.rb +98 -0
  65. data/spec/integration/restore_from_crash_spec.rb +67 -0
  66. data/spec/integration/s3_spec.rb +66 -0
  67. data/spec/integration/size_rotation_spec.rb +59 -0
  68. data/spec/integration/sqs_spec.rb +110 -0
  69. data/spec/integration/stress_test_spec.rb +60 -0
  70. data/spec/integration/time_based_rotation_with_constant_write_spec.rb +60 -0
  71. data/spec/integration/time_based_rotation_with_stale_write_spec.rb +64 -0
  72. data/spec/integration/upload_current_file_on_shutdown_spec.rb +51 -0
  73. data/spec/outputs/cloudwatch_spec.rb +38 -0
  74. data/spec/outputs/s3/file_repository_spec.rb +143 -0
  75. data/spec/outputs/s3/size_and_time_rotation_policy_spec.rb +77 -0
  76. data/spec/outputs/s3/size_rotation_policy_spec.rb +41 -0
  77. data/spec/outputs/s3/temporary_file_factory_spec.rb +89 -0
  78. data/spec/outputs/s3/temporary_file_spec.rb +47 -0
  79. data/spec/outputs/s3/time_rotation_policy_spec.rb +60 -0
  80. data/spec/outputs/s3/uploader_spec.rb +69 -0
  81. data/spec/outputs/s3/writable_directory_validator_spec.rb +40 -0
  82. data/spec/outputs/s3/write_bucket_permission_validator_spec.rb +49 -0
  83. data/spec/outputs/s3_spec.rb +232 -0
  84. data/spec/outputs/sns_spec.rb +160 -0
  85. data/spec/plugin_mixin/aws_config_spec.rb +217 -0
  86. data/spec/spec_helper.rb +8 -0
  87. data/spec/support/helpers.rb +119 -0
  88. data/spec/unit/outputs/sqs_spec.rb +247 -0
  89. metadata +467 -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
@@ -0,0 +1,8 @@
1
+ # encoding: utf-8
2
+
3
+ require 'logstash/devutils/rspec/spec_helper'
4
+ require 'logstash/outputs/sqs'
5
+ require 'logstash/logging/logger'
6
+ require_relative 'support/helpers'
7
+
8
+ LogStash::Logging::Logger::configure_logging("debug") if ENV["DEBUG"]