Skip to content

Commit

Permalink
Eventbridge multiregion (#2683)
Browse files Browse the repository at this point in the history
  • Loading branch information
alextwoods authored Apr 7, 2022
1 parent 9b1f540 commit 1efcbec
Show file tree
Hide file tree
Showing 5 changed files with 248 additions and 1 deletion.
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,38 @@ Simply downcase the service module name for the helper:
* `ec2` => `#<Aws::EC2::Client>`
* etc

## Functionality requiring AWS Common Runtime (CRT)

The AWS SDK for Ruby has optional functionality that requires the
[AWS Common Runtime (CRT)](https://docs.aws.amazon.com/sdkref/latest/guide/common-runtime.html)
bindings to be included as a dependency with your application. This functionality includes:
* [Amazon S3 Multi-Region Access Points](https://docs.aws.amazon.com/AmazonS3/latest/userguide/MultiRegionAccessPoints.html)
* CRC-32c support for [S3 Additional Checksums](https://aws.amazon.com/blogs/aws/new-additional-checksum-algorithms-for-amazon-s3/)

If the required AWS Common Runtime dependency is not installed you will receive an error
indicating that the required dependency is missing to use the associated functionality. To install this dependency follow
the provided [instructions](#installing-the-aws-common-runtime-crt-dependency).

### Installing the AWS Common Runtime (CRT) Dependency
AWS CRT bindings are in developer preview and are available in the
the [aws-crt](https://rubygems.org/gems/aws-crt/) gem. You can install them
by adding the `aws-crt` gem to your Gemfile.

[Sigv4a](https://docs.aws.amazon.com/general/latest/gr/signing_aws_api_requests.html)
is an extension to Sigv4 that allows signatures that are valid in more than one region.
Sigv4a is required to use some services/operations such as
[S3 Multi-Region Access Points](https://docs.aws.amazon.com/AmazonS3/latest/userguide/MultiRegionAccessPoints.html)
and Amazon EventBridge Global Endpoints.
Currently sigv4a requires the [aws-crt](https://rubygems.org/gems/aws-crt/) gem and a version of the
[aws-sigv4](https://rubygems.org/gems/aws-sigv4/versions/1.4.1.crt) gem built on top of aws-crt -
these versions end with "-crt". To install and use a CRT enabled version, we recommend pinning the
specific version of `aws-sigv4` in your Gemfile (this will also install the `aws-crt` gem):

```ruby
gem 'aws-sdk-s3', '~> 1'
gem 'aws-sigv4', '1.4.1.crt'
```

## Getting Help

Please use any of these resources for getting help:
Expand Down
2 changes: 2 additions & 0 deletions gems/aws-sdk-eventbridge/lib/aws-sdk-eventbridge/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
require 'aws-sdk-core/plugins/recursion_detection.rb'
require 'aws-sdk-core/plugins/signature_v4.rb'
require 'aws-sdk-core/plugins/protocols/json_rpc.rb'
require 'aws-sdk-eventbridge/plugins/multi_region_endpoint.rb'

Aws::Plugins::GlobalConfiguration.add_identifier(:eventbridge)

Expand Down Expand Up @@ -81,6 +82,7 @@ class Client < Seahorse::Client::Base
add_plugin(Aws::Plugins::RecursionDetection)
add_plugin(Aws::Plugins::SignatureV4)
add_plugin(Aws::Plugins::Protocols::JsonRpc)
add_plugin(Aws::EventBridge::Plugins::MultiRegionEndpoint)

# @overload initialize(options)
# @param [Hash] options
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# frozen_string_literal: true


module Aws
module EventBridge
module Plugins
# Resolve Multi-Region Endpoints
class MultiRegionEndpoint < Seahorse::Client::Plugin

def add_handlers(handlers, _config)
handlers.add(Handler, operations: [:put_events])
end

# After extracting out any ARN input, resolve a new URL with it.
class Handler < Seahorse::Client::Handler
def call(context)
if (multi_region_endpoint_id = context.params[:endpoint_id])

validate_multi_region_endpoint!(multi_region_endpoint_id)
validate_config!(context)

url = context.http_request.endpoint
region = context.config.region

# if regional_endpoint is false, a custom endpoint was provided
# customer provided endpoints should be used as is
if context.config.regional_endpoint
sfx = Aws::Partitions::EndpointProvider.dns_suffix_for(
region, 'events', { dualstack: context.config.use_dualstack_endpoint }
)
url.host = "#{multi_region_endpoint_id}.endpoint.events.#{sfx}"
end

context.config.sigv4_signer = sigv4a_signer(context)
end

@handler.call(context)
end

private

def validate_multi_region_endpoint!(multi_region_endpoint_id)
unless !multi_region_endpoint_id.empty? &&
valid_hostname?(multi_region_endpoint_id)
raise ArgumentError, 'multi_region_endpoint_id must be a valid host label.'
end
end

def valid_hostname?(str)
str =~ /^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$/
end

def validate_config!(context)
if context.config.use_fips_endpoint
raise ArgumentError,
'FIPS is not supported with EventBridge multi-region endpoints.'
end
end

def sigv4a_signer(context)
existing = context.config.sigv4_signer
Aws::Sigv4::Signer.new(
service: existing.service,
region: '*',
credentials_provider: existing.credentials_provider,
signing_algorithm: :sigv4a,
uri_escape_path: true,
unsigned_headers: existing.unsigned_headers
)
end
end
end
end
end
end
135 changes: 135 additions & 0 deletions gems/aws-sdk-eventbridge/spec/plugins/multi_region_endpoint_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
require_relative '../spec_helper'

module Aws
module EventBridge
describe Client do
let(:region) { 'us-east-1' }
let(:use_fips_endpoint) { false }
let(:use_dualstack_endpoint) { false }
let(:client) do
Client.new(
stub_responses: true, region: region,
use_dualstack_endpoint: use_dualstack_endpoint,
use_fips_endpoint: use_fips_endpoint
)
end
let(:event) do
{
time: Time.now,
source: "source",
}
end
let(:entries) { [event] }

def expect_sigv4a_signer(region='*')
mock_signature = Aws::Sigv4::Signature.new(headers: {})
mock_signer = double('sigv4a_signer', sign_request: mock_signature)

# a base signer is always created
# multi-region endpoints result in a second signer being created with :sigv4a
allow(Aws::Sigv4::Signer).to receive(:new).and_call_original
allow(Aws::Sigv4::Signer).to receive(:new).with(hash_including(region: region, signing_algorithm: :sigv4a)).and_return(mock_signer)
end

it 'does not update the endpoint when endpoint_id is not set' do
resp = client.put_events(entries: entries)
expect(resp.context.http_request.endpoint.host).to eq('events.us-east-1.amazonaws.com')
end

it 'it updates the endpoint, signs with sigv4a and uses the global region when endpoint_id is set ' do
expect_sigv4a_signer('*')
resp = client.put_events(entries: entries, endpoint_id: 'abc123.456def')
expect(resp.context.http_request.endpoint.host).to eq('abc123.456def.endpoint.events.amazonaws.com')
end

it 'raises when given an invalid endpoint_id' do
expect do
client.put_events(entries: entries, endpoint_id: 'badactor.com?foo=bar')
end.to raise_error(ArgumentError)
end

it 'raises when given an empty endpoint_id' do
expect do
client.put_events(entries: entries, endpoint_id: '')
end.to raise_error(ArgumentError)
end

context 'use_dualstack_endpoint' do
let(:use_dualstack_endpoint) { true }

it 'does not update the endpoint when endpoint_id is not set' do
resp = client.put_events(entries: entries)
expect(resp.context.http_request.endpoint.host).to eq('events.us-east-1.api.aws')
end

it 'uses the dualstack dnsSuffix when endpoint_id is set' do
expect_sigv4a_signer('*')
resp = client.put_events(entries: entries, endpoint_id: 'abc123.456def')
expect(resp.context.http_request.endpoint.host).to eq('abc123.456def.endpoint.events.api.aws')
end
end

context 'use_fips_endpoint' do
let(:use_fips_endpoint) { true }

it 'does not update the endpoint when endpoint_id is not set' do
resp = client.put_events(entries: entries)
expect(resp.context.http_request.endpoint.host).to eq('events-fips.us-east-1.amazonaws.com')
end

it 'raises when endpoint_id and use_fips_endpoint are set' do
expect do
client.put_events(entries: entries, endpoint_id: 'abc123.456def')
end.to raise_error(ArgumentError)
end
end

context 'use_fips_endpoint and use_dualstack_endpoint' do
let(:use_fips_endpoint) { true }
let(:use_dualstack_endpoint) { true }

it 'does not update the endpoint when endpoint_id is not set' do
resp = client.put_events(entries: entries)
expect(resp.context.http_request.endpoint.host).to eq('events-fips.us-east-1.api.aws')
end

it 'raises when endpoint_id and use_fips_endpoint are set' do
expect do
client.put_events(entries: entries, endpoint_id: 'abc123.456def')
end.to raise_error(ArgumentError)
end
end

context 'aws-iso partition' do
let(:region) { 'us-iso-east-1' }

it 'does not update the endpoint when endpoint_id is not set' do
resp = client.put_events(entries: entries)
expect(resp.context.http_request.endpoint.host).to eq('events.us-iso-east-1.c2s.ic.gov')
end

it 'it updates the endpoint, signs with sigv4a and uses the global region when endpoint_id is set ' do
expect_sigv4a_signer('*')
resp = client.put_events(entries: entries, endpoint_id: 'abc123.456def')
expect(resp.context.http_request.endpoint.host).to eq('abc123.456def.endpoint.events.c2s.ic.gov')
end
end

context 'custom endpoint' do
let(:custom_endpoint) { 'https://example.org' }
let(:client) { Client.new(endpoint: custom_endpoint, stub_responses: true, region: region) }

it 'uses the custom endpoint when endpoint_id is not set' do
resp = client.put_events(entries: entries)
expect(resp.context.http_request.endpoint.host).to eq('example.org')
end

it 'does not modify the custom endpoint when endpoint_id is set' do
expect_sigv4a_signer('*')
resp = client.put_events(entries: entries, endpoint_id: 'abc123.456def')
expect(resp.context.http_request.endpoint.host).to eq('example.org')
end
end
end
end
end
5 changes: 4 additions & 1 deletion services.json
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,10 @@
"models": "es/2015-01-01"
},
"EventBridge": {
"models": "eventbridge/2015-10-07"
"models": "eventbridge/2015-10-07",
"addPlugins": [
"Aws::EventBridge::Plugins::MultiRegionEndpoint"
]
},
"FIS": {
"models": "fis/2020-12-01"
Expand Down

0 comments on commit 1efcbec

Please sign in to comment.