Skip to content

Add DynamoDB session store (v2 session store gem) #38

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Nov 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
coverage
doc
Gemfile.lock
test/dummy/log/
test/dummy/tmp/
spec/dummy/log/
spec/dummy/tmp/
vendor
101 changes: 87 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
# AWS SDK for Ruby Rails Plugin

[![Build
Status](https://travis-ci.org/aws/aws-sdk-rails.png?branch=master)](https://travis-ci.org/aws/aws-sdk-rails)
[![Code
Climate](https://codeclimate.com/github/aws/aws-sdk-rails.png)](https://codeclimate.com/github/aws/aws-sdk-rails)
[![Gem Version](https://badge.fury.io/rb/aws-sdk-rails.svg)](https://badge.fury.io/rb/aws-sdk-rails) [![Build Status](https://travis-ci.com/aws/aws-sdk-rails.svg?branch=master)](https://travis-ci.com/aws/aws-sdk-rails) [![Github forks](https://img.shields.io/github/forks/aws/aws-sdk-rails.svg)](https://github.com/aws/aws-sdk-rails/network)
[![Github stars](https://img.shields.io/github/stars/aws/aws-sdk-rails.svg)](https://github.com/aws/aws-sdk-rails/stargazers)
[![Gitter](https://badges.gitter.im/aws/aws-sdk-rails.svg)](https://gitter.im/aws/aws-sdk-rails?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)

A Ruby on Rails plugin that integrates AWS services with your application using
the latest version of [AWS SDK For Ruby](https://github.com/aws/aws-sdk-ruby).
Expand All @@ -30,14 +29,14 @@ latest [AWS SDK for Ruby Docs](https://docs.aws.amazon.com/sdk-for-ruby/v3/api/i
for details.

If you're running your Rails application on Amazon EC2, the AWS SDK will
automatically check Amazon EC2 instance metadata for credentials. Learn more:
check Amazon EC2 instance metadata for credentials to load. Learn more:
[IAM Roles for Amazon EC2](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html)

# Features

## AWS SDK uses the Rails logger

The AWS SDK is automatically configured to use the built-in Rails logger for any
The AWS SDK is configured to use the built-in Rails logger for any
SDK log output. The logger is configured to use the `:info` log level. You can
change the log level by setting `:log_level` in the
[Aws.config](https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws.html) hash.
Expand All @@ -48,10 +47,8 @@ Aws.config.update(log_level: :debug)

## Rails 5.2+ Encrypted Credentials

If you are using Rails 5.2+ [Encrypted
Credentials](http://guides.rubyonrails.org/security.html#custom-credentials),
the credentials will be automatically loaded assuming the decrypted contents
are provided as such:
If you are using Rails 5.2+ [Encrypted Credentials](http://guides.rubyonrails.org/security.html#custom-credentials),
the credentials will be decrypted and loaded under the `:aws` top level key:

```yml
# config/credentials.yml.enc
Expand All @@ -61,6 +58,84 @@ aws:
secret_access_key: YOUR_ACCESS_KEY
```

## DynamoDB Session Store

You can configure session storage in Rails to use DynamoDB instead of cookies,
allowing access to sessions from other applications and devices. You will need
to have an existing Amazon DynamoDB session table to use this feature.

You can generate a migration file for the session table using the following
command (<MigrationName> is optional):

```bash
rails generate dynamo_db:session_store_migration <MigrationName>
```

The session store migration generator command will generate two files: a
migration file, `db/migration/#{VERSION}_#{MIGRATION_NAME}.rb`, and a
configuration YAML file, `config/dynamo_db_session_store.yml`.

The migration file will create and delete a table with default options. These
options can be changed prior to running the migration and are documented in the
[Table](https://docs.aws.amazon.com/sdk-for-ruby/aws-sessionstore-dynamodb/api/Aws/SessionStore/DynamoDB/Table.html) class.

To create the table, run migrations as normal with:

```bash
rake db:migrate
```

Next, configure the Rails session store to be `:dynamodb_store` by editing
`config/initializers/session_store.rb` to contain the following:

```ruby
# config/initializers/session_store.rb
Rails.application.config.session_store :dynamodb_store, key: '_your_app_session'
```

You can now start your Rails application with session support.

### Configuration

You can configure the session store with code, YAML files, or ENV, in this order
of precedence. To configure in code, you can directly pass options to your
initializer like so:

```ruby
# config/initializers/session_store.rb
Rails.application.config.session_store :dynamodb_store,
key: '_your_app_session',
table_name: 'foo'
```

Alternatively, you can use the generated YAML configuration file
`config/dynamo_db_session_store.yml`. YAML configuration may also be specified
per environment, with environment configuration having precedence. To do this,
create `config/dynamo_db_session_store/#{Rails.env}.yml` files as needed.

For configuration options, see the [Configuration](https://docs.aws.amazon.com/sdk-for-ruby/aws-sessionstore-dynamodb/api/Aws/SessionStore/DynamoDB/Configuration.html) class.

#### Rack Configuration

DynamoDB session storage is implemented in the [`aws-sessionstore-dynamodb`](https://github.com/aws/aws-sessionstore-dynamodb-ruby)
gem. The Rack middleware inherits from the [`Rack::Session::Abstract::Persisted`](https://www.rubydoc.info/github/rack/rack/Rack/Session/Abstract/Persisted)
class, which also includes additional options (such as `:key`) that can be
passed into the Rails initializer.

### Cleaning old sessions

By default sessions do not expire. See `config/dynamo_db_session_store.yml` to
configure the max age or stale period of a session.

You can use the DynamoDB [Time to Live (TTL) feature](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/TTL.html)
on the `expire_at` attribute to automatically delete expired items.

Alternatively, a Rake task for garbage collection is provided:

```bash
rake dynamo_db:collect_garbage
```

## Amazon Simple Email Service (SES) as an ActionMailer Delivery Method

This gem will automatically register SES as an ActionMailer delivery method. You
Expand All @@ -71,9 +146,7 @@ simply need to configure Rails to use it in your environment configuration:
config.action_mailer.delivery_method = :ses
```

# Other Usage

## Manually setting Action Mailer credentials
### Manually setting credentials

If you need to provide different credentials for Action Mailer, you can call
client-creating actions manually. For example, you can create an initializer
Expand All @@ -98,7 +171,7 @@ Aws::Rails.add_action_mailer_delivery_method(
)
```

## Using ARNs with SES
### Using ARNs with SES

This gem uses [`Aws::SES::Client#send_raw_email`](https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/SES/Client.html#send_raw_email-instance_method)
to send emails. This operation allows you to specify a cross-account identity
Expand Down
10 changes: 6 additions & 4 deletions aws-sdk-rails.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,19 @@ Gem::Specification.new do |spec|
spec.name = 'aws-sdk-rails'
spec.version = version
spec.authors = ['Amazon Web Services']
spec.email = ['[email protected]', '[email protected]']
spec.email = ['[email protected]', '[email protected]']

spec.summary = 'AWS SDK for Ruby on Rails Plugin'
spec.description = 'Integrates the AWS Ruby SDK with Ruby on Rails'
spec.homepage = 'https://github.com/aws/aws-sdk-rails'
spec.license = 'Apache-2.0'

spec.require_paths = ['lib']
spec.files += Dir['lib/**/*.rb', 'lib/aws-sdk-rails.rb']
spec.files += Dir['lib/**/*.rb']

spec.add_dependency('aws-sdk-ses', '~> 1')
spec.add_dependency('railties', '>= 5.2.0')
spec.add_dependency('aws-sdk-ses', '~> 1') # for ActionMailer
spec.add_dependency('aws-sessionstore-dynamodb', '~> 2') # includes dynamo db
spec.add_dependency('railties', '>= 5.2.0') # encrypted credentials

spec.add_development_dependency('rails')
end
32 changes: 32 additions & 0 deletions lib/action_dispatch/session/dynamodb_store.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
require 'aws-sessionstore-dynamodb'

module ActionDispatch
module Session
# Uses the Dynamo DB Session Store implementation to create a class that
# extends ActionDispatch::Session. Rails will create a :dynamodb_store
# configuration for session_store from this class name.
#
# This class will use the Rails secret_key_base unless otherwise provided.
#
# Configuration can also be provided in YAML files from Rails config, either
# in "config/session_store.yml" or "config/session_store/#{Rails.env}.yml".
# Configuration files that are environment-specific will take precedence.
#
# @see https://docs.aws.amazon.com/sdk-for-ruby/aws-sessionstore-dynamodb/api/Aws/SessionStore/DynamoDB/Configuration.html
class DynamodbStore < Aws::SessionStore::DynamoDB::RackMiddleware
def initialize(app, options = {})
options[:config_file] ||= config_file if config_file.exist?
options[:secret_key] ||= Rails.application.secret_key_base
super
end

private

def config_file
file = Rails.root.join("config/dynamo_db_session_store/#{Rails.env}.yml")
file = Rails.root.join('config/dynamo_db_session_store.yml') unless file.exist?
file
end
end
end
end
68 changes: 3 additions & 65 deletions lib/aws-sdk-rails.rb
Original file line number Diff line number Diff line change
@@ -1,69 +1,7 @@
# frozen_string_literal: true

require_relative 'aws/rails/mailer'
require_relative 'aws/rails/notifications_instrument_plugin'
require_relative 'aws/rails/railtie'
require_relative 'aws/rails/notifications_instrument'

module Aws

# Use the Rails namespace.
module Rails

# @api private
class Railtie < ::Rails::Railtie
initializer 'aws-sdk-rails.initialize',
before: :load_config_initializers do
# Initialization Actions
Aws::Rails.use_rails_encrypted_credentials
Aws::Rails.add_action_mailer_delivery_method
Aws::Rails.log_to_rails_logger
end
end

# This is called automatically from the SDK's Railtie, but can be manually
# called if you want to specify options for building the Aws::SES::Client.
#
# @param [Symbol] name The name of the ActionMailer delivery method to
# register.
# @param [Hash] options The options you wish to pass on to the
# Aws::SES::Client initialization method.
def self.add_action_mailer_delivery_method(name = :ses, options = {})
ActiveSupport.on_load(:action_mailer) do
add_delivery_method(name, Aws::Rails::Mailer, options)
end
end

# Configures the AWS SDK for Ruby's logger to use the Rails logger.
def self.log_to_rails_logger
Aws.config[:logger] = ::Rails.logger
nil
end

# Configures the AWS SDK with credentials from Rails encrypted credentials.
def self.use_rails_encrypted_credentials
# limit the config keys we merge to credentials only
aws_credential_keys = %i[access_key_id secret_access_key session_token]

Aws.config.merge!(
::Rails.application
.try(:credentials)
.try(:aws)
.to_h.slice(*aws_credential_keys)
)
end

# Adds ActiveSupport Notifications instrumentation to AWS SDK
# client operations. Each operation will produce an event with a name:
# <operation>.<service>.aws. For example, S3's put_object has an event
# name of: put_object.S3.aws
def self.instrument_sdk_operations
Aws.constants.each do|c|
m = Aws.const_get(c)
if m.is_a?(Module) &&
m.const_defined?(:Client) &&
m.const_get(:Client).superclass == Seahorse::Client::Base
m.const_get(:Client).add_plugin(Aws::Rails::Notifications)
end
end
end
end
end
require_relative 'action_dispatch/session/dynamodb_store'
68 changes: 68 additions & 0 deletions lib/aws/rails/railtie.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# frozen_string_literal: true

module Aws
# Use the Rails namespace.
module Rails
# @api private
class Railtie < ::Rails::Railtie
initializer 'aws-sdk-rails.initialize',
before: :load_config_initializers do
# Initialization Actions
Aws::Rails.use_rails_encrypted_credentials
Aws::Rails.add_action_mailer_delivery_method
Aws::Rails.log_to_rails_logger
end

rake_tasks do
load 'tasks/dynamo_db/session_store/clean.rake'
end
end

# This is called automatically from the SDK's Railtie, but can be manually
# called if you want to specify options for building the Aws::SES::Client.
#
# @param [Symbol] name The name of the ActionMailer delivery method to
# register.
# @param [Hash] options The options you wish to pass on to the
# Aws::SES::Client initialization method.
def self.add_action_mailer_delivery_method(name = :ses, options = {})
ActiveSupport.on_load(:action_mailer) do
add_delivery_method(name, Aws::Rails::Mailer, options)
end
end

# Configures the AWS SDK for Ruby's logger to use the Rails logger.
def self.log_to_rails_logger
Aws.config[:logger] = ::Rails.logger
nil
end

# Configures the AWS SDK with credentials from Rails encrypted credentials.
def self.use_rails_encrypted_credentials
# limit the config keys we merge to credentials only
aws_credential_keys = %i[access_key_id secret_access_key session_token]

Aws.config.merge!(
::Rails.application
.try(:credentials)
.try(:aws)
.to_h.slice(*aws_credential_keys)
)
end

# Adds ActiveSupport Notifications instrumentation to AWS SDK
# client operations. Each operation will produce an event with a name:
# <operation>.<service>.aws. For example, S3's put_object has an event
# name of: put_object.S3.aws
def self.instrument_sdk_operations
Aws.constants.each do|c|
m = Aws.const_get(c)
if m.is_a?(Module) &&
m.const_defined?(:Client) &&
m.const_get(:Client).superclass == Seahorse::Client::Base
m.const_get(:Client).add_plugin(Aws::Rails::Notifications)
end
end
end
end
end
13 changes: 13 additions & 0 deletions lib/generators/dynamo_db/session_store_migration/USAGE
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Description:
Generates a migration file for deleting and a creating a DynamoDB
sessions table, and a configuration file for the session store.

Example:
rails generate dynamo_db:session_store_migration <MIGRATION_NAME>

This will create:
db/migrate/#{VERSION}_#{MIGRATION_NAME}.rb
config/dynamo_db_session_store.yml

The migration will be run when the command rake db:migrate is run
in the command line.
Loading