logstash-integration-aws 0.1.0.pre

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 (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"]