Skip to content

Commit

Permalink
v1.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
tmimura39 committed Sep 29, 2024
1 parent 28d945f commit 861d6a7
Show file tree
Hide file tree
Showing 26 changed files with 272 additions and 64 deletions.
20 changes: 17 additions & 3 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,29 @@ on:
push:
branches:
- main

pull_request:
schedule:
- cron: '0 4 * * *'

jobs:
build:
runs-on: ubuntu-latest
name: Ruby ${{ matrix.ruby }}
name: Ruby ${{ matrix.ruby }} / Rails ${{ matrix.rails }}
strategy:
matrix:
ruby:
- '3.3.4'
- '3.3'
- '3.2'
- '3.1'
rails:
- '7.2'
- '7.1'
- '7.0'
include:
- ruby: ruby-head
rails: edge
env:
RAILS_VERSION: ${{ matrix.rails }}

steps:
- uses: actions/checkout@v4
Expand All @@ -23,5 +35,7 @@ jobs:
with:
ruby-version: ${{ matrix.ruby }}
bundler-cache: true
continue-on-error: ${{ (matrix.ruby == 'ruby-head')}}
- name: Run the default task
run: bundle exec rake
continue-on-error: ${{ (matrix.ruby == 'ruby-head')}}
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,12 @@
/spec/reports/
/tmp/

/Gemfile.lock

/spec/dummy/log/*
/spec/dummy/tmp/*
!/spec/dummy/log/.keep
!/spec/dummy/tmp/.keep

# rspec failure tracking
.rspec_status
19 changes: 18 additions & 1 deletion .rubocop.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
require:
- rubocop-performance
- rubocop-rails
- rubocop-rake
- rubocop-rspec
- rubocop-rspec_rails

AllCops:
TargetRubyVersion: 3.0
TargetRubyVersion: 3.1
NewCops: enable

Layout/LineLength:
Enabled: false

RSpec/MultipleExpectations:
Enabled: false

Style/Documentation:
Enabled: false

Style/StringLiterals:
EnforcedStyle: double_quotes
Expand Down
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
## [Unreleased]

## [0.1.0] - 2024-09-29
## [1.0.0] - 2024-09-29

- Initial release
20 changes: 16 additions & 4 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,23 @@

source "https://rubygems.org"

# Specify your gem's dependencies in actionpack-cloudfront_viewer_address.gemspec
gemspec

gem "rake", "~> 13.0"
if ENV["RAILS_VERSION"] == "edge"
gem "rails", github: "rails/rails", branch: "main"
elsif ENV["RAILS_VERSION"]
gem "rails", "~> #{ENV["RAILS_VERSION"]}.0"
else
gem "rails"
end

gem "rspec", "~> 3.0"
gem "rake"

gem "rubocop", "~> 1.21"
gem "rspec", require: false
gem "rspec-rails", require: false
gem "rubocop", require: false
gem "rubocop-performance", require: false
gem "rubocop-rails", require: false
gem "rubocop-rake", require: false
gem "rubocop-rspec", require: false
gem "rubocop-rspec_rails", require: false
54 changes: 45 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,60 @@
# Actionpack::CloudfrontViewerAddress
# ActionPack::CloudfrontViewerAddress

TODO: Delete this and the text below, and describe your gem
Calculate the remote IP ( `request.remote_ip` ) using `CloudFront-Viewer-Address`.

Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/actionpack/cloudfront_viewer_address`. To experiment with that code, run `bin/console` for an interactive prompt.
> [!CAUTION]
> Be sure to configure security groups and other settings so that HTTP requests go through Amazon CloudFront.
> Otherwise, the `CloudFront-Viewer-Address` will be freely modified and IP spoofing.
## Installation
## Why do you need this Gem?

TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
If you use 'Amazon CloudFront' as the Proxy for Rails App, the remote IP ( `request.remote_ip` ) will be set to the CloudFront IP.
To work around this problem, it was necessary to calculate the remote IP address using a combination of `X-Forwarded-For` and the 'IP address of a trusted Proxy'.
Like these Gem...

* https://github.com/dinks/cloudfront-rails
* https://github.com/customink/actionpack-cloudfront

However, CloudFront provides a more concise solution to this problem with `CloudFront-Viewer-Address`.
This Gem is intended to make this functionality more concise for use in Rails applications.

## Usage

### Step1. Amazon CloudFront Setup

Before using this Gem, modify your Amazon CloudFront configuration to enable `CloudFront-Viewer-Address`.
Please refer to the following for details on how to set up.

* https://aws.amazon.com/about-aws/whats-new/2021/10/amazon-cloudfront-client-ip-address-connection-port-header/
* https://dev.classmethod.jp/articles/amazon-cloudfront-client-ip-address-connection-port-header/ (written in Japanese)

### Step2. This `ActionPack::CloudfrontViewerAddress` Gem Install

Install the gem and add to the application's Gemfile by executing:

$ bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
$ bundle add actionpack-cloudfront_viewer_address

If bundler is not being used to manage dependencies, install the gem by executing:

$ gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
$ gem install actionpack-cloudfront_viewer_address

## Usage
### Step3. Rack middleware

**If you are using Rails, omit this as it will be set automatically.**
Otherwise, set the middleware as `middleware.insert_after ActionDispatch::RemoteIp, ActionPack::CloudfrontViewerAddress::RemoteIp`.

### Step4. Use `request.remote_ip`

Otherwise, no special processing is required.
The client's IP address can be obtained by referring to `request.remote_ip` as usual.

## About IP spoofing

This Gem only references the `CloudFront-Viewer-Address` request header.
The `CloudFront-Viewer-Address` header is not improved if the request is made to a Rails application via Amazon CloudFront.
However, if the request is made to a Rails application without going through Amazon CloudFront, the `CloudFront-Viewer-Address` can be freely rewritten and there is a risk of IP spoofing.

TODO: Write usage instructions here
**Please keep this in mind.**

## Development

Expand Down
28 changes: 12 additions & 16 deletions actionpack-cloudfront_viewer_address.gemspec
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
# frozen_string_literal: true

require_relative "lib/actionpack/cloudfront_viewer_address/version"
require_relative "lib/action_pack/cloudfront_viewer_address/version"

Gem::Specification.new do |spec|
spec.name = "actionpack-cloudfront_viewer_address"
spec.version = Actionpack::CloudfrontViewerAddress::VERSION
spec.version = ActionPack::CloudfrontViewerAddress::VERSION
spec.authors = ["Tomohiko Mimura"]
spec.email = ["mito.5525@gmail.com"]

spec.summary = "TODO: Write a short summary, because RubyGems requires one."
spec.description = "TODO: Write a longer description or delete this line."
spec.homepage = "TODO: Put your gem's website or public repo URL here."
spec.summary = "Calculate RemoteIp based on `CloudFront-Viewer-Address` Header"
spec.description = "Utilize `CloudFront-Viewer-Address`, a custom CloudFront header, to calculate RemoteIp more simply"
spec.homepage = "https://github.com/tmimura39/actionpack-cloudfront_viewer_address"
spec.license = "MIT"
spec.required_ruby_version = ">= 3.0.0"
spec.required_ruby_version = ">= 3.1.0"

spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'"
spec.metadata["allowed_push_host"] = "https://rubygems.org"

spec.metadata["homepage_uri"] = spec.homepage
spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here."
spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
spec.metadata["source_code_uri"] = spec.homepage
spec.metadata["changelog_uri"] = "#{spec.homepage}/tree/main/CHANGELOG.md"

spec.metadata["rubygems_mfa_required"] = "true"

# Specify which files should be added to the gem when it is released.
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
gemspec = File.basename(__FILE__)
spec.files = IO.popen(%w[git ls-files -z], chdir: __dir__, err: IO::NULL) do |ls|
ls.readlines("\x0", chomp: true).reject do |f|
Expand All @@ -33,9 +33,5 @@ Gem::Specification.new do |spec|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]

# Uncomment to register a new dependency of your gem
# spec.add_dependency "example-gem", "~> 1.0"

# For more information and examples about making a new gem, check out our
# guide at: https://bundler.io/guides/creating_gem.html
spec.add_dependency "actionpack"
end
11 changes: 11 additions & 0 deletions lib/action_pack/cloudfront_viewer_address.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

require_relative "cloudfront_viewer_address/version"
require_relative "cloudfront_viewer_address/remote_ip"

require_relative "cloudfront_viewer_address/railtie" if defined?(Rails::Railtie)

module ActionPack
module CloudfrontViewerAddress
end
end
13 changes: 13 additions & 0 deletions lib/action_pack/cloudfront_viewer_address/railtie.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# frozen_string_literal: true

require_relative "remote_ip"

module ActionPack
module CloudfrontViewerAddress
class Railtie < ::Rails::Railtie
initializer "actionpack_cloudfront_viewer_address.configure_rails_initialization" do |app|
app.config.middleware.insert_after ActionDispatch::RemoteIp, ActionPack::CloudfrontViewerAddress::RemoteIp
end
end
end
end
27 changes: 27 additions & 0 deletions lib/action_pack/cloudfront_viewer_address/remote_ip.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true

require "action_dispatch"

module ActionPack
module CloudfrontViewerAddress
class RemoteIp
def initialize(app)
@app = app
end

def call(env)
req = ::ActionDispatch::Request.new(env)
if env["HTTP_CLOUDFRONT_VIEWER_ADDRESS"].present?
# IPv4 "HTTP_CLOUDFRONT_VIEWER_ADDRESS" = "1.1.1.1:3000"
# IPV6 "HTTP_CLOUDFRONT_VIEWER_ADDRESS" = "0000:0000:0000:0000:0000:0000:0000:0000:3000"
req.remote_ip = req.env["HTTP_CLOUDFRONT_VIEWER_ADDRESS"].sub(/:\d+\z/, "") # remove `port(:0000)` part
end
app.call(req.env)
end

private

attr_reader :app
end
end
end
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module Actionpack
module ActionPack
module CloudfrontViewerAddress
VERSION = "0.1.0"
VERSION = "1.0.0"
end
end
9 changes: 1 addition & 8 deletions lib/actionpack/cloudfront_viewer_address.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,3 @@
# frozen_string_literal: true

require_relative "cloudfront_viewer_address/version"

module Actionpack
module CloudfrontViewerAddress
class Error < StandardError; end
# Your code goes here...
end
end
require "action_pack/cloudfront_viewer_address"
6 changes: 0 additions & 6 deletions sig/actionpack/cloudfront_viewer_address.rbs

This file was deleted.

33 changes: 33 additions & 0 deletions spec/action_pack/cloudfront_viewer_address/remote_ip_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# frozen_string_literal: true

RSpec.describe ActionPack::CloudfrontViewerAddress::RemoteIp do
context "when CloudFront-Viewer-Address(IPv4 Address + Port) specified" do
it "action_dispatch.remote_ip = IPv4 Address" do
remote_ip = nil
described_class
.new(->(env) { remote_ip = env["action_dispatch.remote_ip"] })
.call({ "action_dispatch.remote_ip" => "0.0.0.0", "HTTP_CLOUDFRONT_VIEWER_ADDRESS" => "1.1.1.1:3000" })
expect(remote_ip).to eq "1.1.1.1"
end
end

context "when CloudFront-Viewer-Address(IPv6 Address + Port) specified" do
it "action_dispatch.remote_ip = IPv6 Address" do
remote_ip = nil
described_class
.new(->(env) { remote_ip = env["action_dispatch.remote_ip"] })
.call({ "action_dispatch.remote_ip" => "0.0.0.0", "HTTP_CLOUDFRONT_VIEWER_ADDRESS" => "1111:1111:1111:1111:1111:1111:1111:1111:3000" })
expect(remote_ip).to eq "1111:1111:1111:1111:1111:1111:1111:1111"
end
end

context "when CloudFront-Viewer-Address unspecified" do
it "action_dispatch.remote_ip is unchanged" do
remote_ip = nil
described_class
.new(->(env) { remote_ip = env["action_dispatch.remote_ip"] })
.call({ "action_dispatch.remote_ip" => "0.0.0.0" })
expect(remote_ip).to eq "0.0.0.0"
end
end
end
7 changes: 7 additions & 0 deletions spec/action_pack/cloudfront_viewer_address_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

RSpec.describe ActionPack::CloudfrontViewerAddress do
it "has a version number" do
expect(ActionPack::CloudfrontViewerAddress::VERSION).not_to be_nil
end
end
11 changes: 0 additions & 11 deletions spec/actionpack/cloudfront_viewer_address_spec.rb

This file was deleted.

7 changes: 7 additions & 0 deletions spec/dummy/app/controllers/remote_ip_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

class RemoteIpController < ActionController::API
def show
render plain: request.remote_ip
end
end
6 changes: 6 additions & 0 deletions spec/dummy/config.ru
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# frozen_string_literal: true

require_relative "config/environment"

run Rails.application
Rails.application.load_server
14 changes: 14 additions & 0 deletions spec/dummy/config/application.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# frozen_string_literal: true

require_relative "boot"

require "rails"
require "action_controller/railtie"

require "actionpack/cloudfront_viewer_address"

module Dummy
class Application < Rails::Application
config.eager_load = true
end
end
Loading

0 comments on commit 861d6a7

Please sign in to comment.