rails-auth 2.2.0 → 3.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/jruby.yml +31 -0
- data/.github/workflows/mri.yml +27 -0
- data/CHANGES.md +30 -0
- data/lib/rails/auth/config_builder.rb +0 -7
- data/lib/rails/auth/rack.rb +1 -0
- data/lib/rails/auth/rspec/matchers/acl_matchers.rb +2 -2
- data/lib/rails/auth/version.rb +1 -1
- data/lib/rails/auth/x509/filter/pem_urlencoded.rb +17 -0
- data/lib/rails/auth/x509/middleware.rb +8 -29
- data/spec/rails/auth/rspec/matchers/acl_matchers_spec.rb +10 -1
- data/spec/rails/auth/x509/middleware_spec.rb +9 -35
- data/spec/support/create_certs.rb +0 -17
- metadata +6 -4
- data/.travis.yml +0 -24
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ebb7b8700e3c8719cdb65a37efa8e1234b79a35a43fba6dffcb9d427cbf9e2e7
|
4
|
+
data.tar.gz: 46e21ea3735dde1cf16c1242ca1117288bce5a4c4ef5cf0f3669e890fecc5124
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fcec7e972d73c52fdcaeef22da67dee25849efaddbf254964b3e73f5179e849ef1bd32e80c1f6520f5502884f27811c0b6bb66278923da67320b38cdfd42464b
|
7
|
+
data.tar.gz: 12a93fbdbfc37c44da69bdbb8b163554dd145364627fb689b5acc530a4ec176f6ac5ee2fa0b502ab594a998ce0fad8664e111aafdd268e0f04571f942d9ce6e1
|
@@ -0,0 +1,31 @@
|
|
1
|
+
name: CI - JRuby
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches: [ master ]
|
6
|
+
pull_request:
|
7
|
+
branches: [ master ]
|
8
|
+
|
9
|
+
jobs:
|
10
|
+
test:
|
11
|
+
runs-on: ubuntu-latest
|
12
|
+
strategy:
|
13
|
+
matrix:
|
14
|
+
java-version:
|
15
|
+
- 8
|
16
|
+
- 11
|
17
|
+
|
18
|
+
steps:
|
19
|
+
- uses: actions/checkout@v2
|
20
|
+
- name: Set up Java
|
21
|
+
uses: actions/setup-java@v2
|
22
|
+
with:
|
23
|
+
distribution: temurin
|
24
|
+
java-version: ${{ matrix.java-version }}
|
25
|
+
- name: Set up Ruby
|
26
|
+
uses: ruby/setup-ruby@v1
|
27
|
+
with:
|
28
|
+
bundler-cache: true
|
29
|
+
ruby-version: jruby
|
30
|
+
- name: Run tests
|
31
|
+
run: bundle exec rake
|
@@ -0,0 +1,27 @@
|
|
1
|
+
name: CI - MRI
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches: [ master ]
|
6
|
+
pull_request:
|
7
|
+
branches: [ master ]
|
8
|
+
|
9
|
+
jobs:
|
10
|
+
test:
|
11
|
+
runs-on: ubuntu-latest
|
12
|
+
strategy:
|
13
|
+
matrix:
|
14
|
+
ruby-version:
|
15
|
+
- 2.6
|
16
|
+
- 2.7
|
17
|
+
- 3.0
|
18
|
+
|
19
|
+
steps:
|
20
|
+
- uses: actions/checkout@v2
|
21
|
+
- name: Set up Ruby
|
22
|
+
uses: ruby/setup-ruby@v1
|
23
|
+
with:
|
24
|
+
bundler-cache: true
|
25
|
+
ruby-version: ${{ matrix.ruby-version }}
|
26
|
+
- name: Run tests
|
27
|
+
run: bundle exec rake
|
data/CHANGES.md
CHANGED
@@ -1,3 +1,33 @@
|
|
1
|
+
### 3.1.0 (2021-10-26)
|
2
|
+
|
3
|
+
* [#70](https://github.com/square/rails-auth/pull/70)
|
4
|
+
Support URL-encoded PEMs to support new Puma header requirements.
|
5
|
+
([@drcapulet])
|
6
|
+
|
7
|
+
### 3.0.0 (2020-08-10)
|
8
|
+
|
9
|
+
* [#68](https://github.com/square/rails-auth/pull/68)
|
10
|
+
Remove `ca_file` and `require_cert` options to the config builder as we no
|
11
|
+
longer verify the certificate chain.
|
12
|
+
([@drcapulet])
|
13
|
+
|
14
|
+
* [#67](https://github.com/square/rails-auth/pull/67)
|
15
|
+
Remove `ca_file`, `require_cert`, and `truststore` options to X509 middleware
|
16
|
+
as we no longer verify the certificate chain.
|
17
|
+
([@drcapulet])
|
18
|
+
|
19
|
+
### 2.2.2 (2020-07-02)
|
20
|
+
|
21
|
+
* [#65](https://github.com/square/rails-auth/pull/65)
|
22
|
+
Fix error when passing `truststore` instead of `ca_file` to X509 middleware.
|
23
|
+
([@drcapulet])
|
24
|
+
|
25
|
+
### 2.2.1 (2020-01-08)
|
26
|
+
|
27
|
+
* [#63](https://github.com/square/rails-auth/pull/63)
|
28
|
+
Fix `FrozenError` in `permit` matcher description.
|
29
|
+
([@drcapulet])
|
30
|
+
|
1
31
|
### 2.2.0 (2019-12-05)
|
2
32
|
|
3
33
|
* [#55](https://github.com/square/rails-auth/pull/55)
|
@@ -31,22 +31,15 @@ module Rails
|
|
31
31
|
def production(
|
32
32
|
config,
|
33
33
|
cert_filters: nil,
|
34
|
-
require_cert: false,
|
35
|
-
ca_file: nil,
|
36
34
|
error_page: Rails.root.join("public/403.html"),
|
37
35
|
monitor: nil
|
38
36
|
)
|
39
|
-
raise ArgumentError, "no cert_filters given but require_cert is true" if require_cert && !cert_filters
|
40
|
-
raise ArgumentError, "no ca_file given but cert_filters were set" if cert_filters && !ca_file
|
41
|
-
|
42
37
|
error_page_middleware(config, error_page)
|
43
38
|
|
44
39
|
if cert_filters
|
45
40
|
config.middleware.insert_before Rails::Auth::ACL::Middleware,
|
46
41
|
Rails::Auth::X509::Middleware,
|
47
|
-
require_cert: require_cert,
|
48
42
|
cert_filters: cert_filters,
|
49
|
-
ca_file: ca_file,
|
50
43
|
logger: Rails.logger
|
51
44
|
end
|
52
45
|
|
data/lib/rails/auth/rack.rb
CHANGED
@@ -24,6 +24,7 @@ require "rails/auth/monitor/middleware"
|
|
24
24
|
|
25
25
|
require "rails/auth/x509/certificate"
|
26
26
|
require "rails/auth/x509/filter/pem"
|
27
|
+
require "rails/auth/x509/filter/pem_urlencoded"
|
27
28
|
require "rails/auth/x509/filter/java" if defined?(JRUBY_VERSION)
|
28
29
|
require "rails/auth/x509/matcher"
|
29
30
|
require "rails/auth/x509/middleware"
|
@@ -6,9 +6,9 @@ RSpec::Matchers.define(:permit) do |env|
|
|
6
6
|
credentials = Rails::Auth.credentials(env)
|
7
7
|
message = "allow #{method}s by "
|
8
8
|
|
9
|
-
return message
|
9
|
+
return message + "unauthenticated clients" if credentials.count.zero?
|
10
10
|
|
11
|
-
message
|
11
|
+
message + credentials.values.map(&:inspect).join(", ")
|
12
12
|
end
|
13
13
|
|
14
14
|
match { |acl| acl.match(env) }
|
data/lib/rails/auth/version.rb
CHANGED
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rails
|
4
|
+
module Auth
|
5
|
+
module X509
|
6
|
+
module Filter
|
7
|
+
# Extract OpenSSL::X509::Certificates from Privacy Enhanced Mail (PEM) certificates
|
8
|
+
# that are URL encoded ($ssl_client_escaped_cert from Nginx).
|
9
|
+
class PemUrlencoded < Pem
|
10
|
+
def call(encoded_pem)
|
11
|
+
super(URI.decode_www_form_component(encoded_pem))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -3,29 +3,20 @@
|
|
3
3
|
module Rails
|
4
4
|
module Auth
|
5
5
|
module X509
|
6
|
-
#
|
7
|
-
|
8
|
-
|
9
|
-
# Validates X.509 client certificates and adds credential objects for valid
|
10
|
-
# clients to the rack environment as env["rails-auth.credentials"]["x509"]
|
6
|
+
# Extracts X.509 client certificates and adds credential objects to the
|
7
|
+
# rack environment as env["rails-auth.credentials"]["x509"]
|
11
8
|
class Middleware
|
12
9
|
# Create a new X.509 Middleware object
|
13
10
|
#
|
14
|
-
# @param [Object]
|
15
|
-
# @param [Hash]
|
16
|
-
# @param [
|
17
|
-
# @param [OpenSSL::X509::Store] truststore (optional) provide your own truststore (for e.g. CRLs)
|
18
|
-
# @param [Boolean] require_cert causes middleware to raise if certs are unverified
|
11
|
+
# @param [Object] app next app in the Rack middleware chain
|
12
|
+
# @param [Hash] cert_filters maps Rack environment names to cert extractors
|
13
|
+
# @param [Logger] logger place to log certificate extraction issues
|
19
14
|
#
|
20
15
|
# @return [Rails::Auth::X509::Middleware] new X509 middleware instance
|
21
|
-
def initialize(app, cert_filters: {},
|
22
|
-
raise ArgumentError, "no ca_file given" unless ca_file
|
23
|
-
|
16
|
+
def initialize(app, cert_filters: {}, logger: nil)
|
24
17
|
@app = app
|
25
|
-
@logger = logger
|
26
|
-
@truststore = truststore || OpenSSL::X509::Store.new.add_file(ca_file)
|
27
|
-
@require_cert = require_cert
|
28
18
|
@cert_filters = cert_filters
|
19
|
+
@logger = logger
|
29
20
|
|
30
21
|
@cert_filters.each do |key, filter|
|
31
22
|
next unless filter.is_a?(Symbol)
|
@@ -52,17 +43,9 @@ module Rails
|
|
52
43
|
cert = extract_certificate_with_filter(filter, env[key])
|
53
44
|
next unless cert
|
54
45
|
|
55
|
-
|
56
|
-
log("Verified", cert)
|
57
|
-
return Rails::Auth::X509::Certificate.new(cert)
|
58
|
-
else
|
59
|
-
log("Verify FAILED", cert)
|
60
|
-
raise CertificateVerifyFailed, "verify failed: #{subject(cert)}" if @require_cert
|
61
|
-
end
|
46
|
+
return Rails::Auth::X509::Certificate.new(cert)
|
62
47
|
end
|
63
48
|
|
64
|
-
raise CertificateVerifyFailed, "no client certificate in request" if @require_cert
|
65
|
-
|
66
49
|
nil
|
67
50
|
end
|
68
51
|
|
@@ -78,10 +61,6 @@ module Rails
|
|
78
61
|
nil
|
79
62
|
end
|
80
63
|
|
81
|
-
def log(message, cert)
|
82
|
-
@logger.debug("rails-auth: #{message} (#{subject(cert)})") if @logger
|
83
|
-
end
|
84
|
-
|
85
64
|
def subject(cert)
|
86
65
|
cert.subject.to_a.map { |attr, data| "#{attr}=#{data}" }.join(",")
|
87
66
|
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
RSpec.describe "RSpec ACL matchers", acl_spec: true do
|
4
|
-
let(:example_certificate) { x509_certificate_hash(ou: "ponycopter") }
|
5
4
|
let(:another_certificate) { x509_certificate_hash(ou: "derpderp") }
|
5
|
+
let(:example_certificate) { x509_certificate_hash(ou: "ponycopter") }
|
6
6
|
|
7
7
|
subject do
|
8
8
|
Rails::Auth::ACL.from_yaml(
|
@@ -18,5 +18,14 @@ RSpec.describe "RSpec ACL matchers", acl_spec: true do
|
|
18
18
|
it { is_expected.to permit get_request(credentials: example_certificate) }
|
19
19
|
it { is_expected.not_to permit get_request(credentials: another_certificate) }
|
20
20
|
it { is_expected.not_to permit get_request }
|
21
|
+
|
22
|
+
it "has the correct description" do
|
23
|
+
expect(permit(get_request(credentials: example_certificate)).description)
|
24
|
+
.to eq('allow GETs by #<InstanceDouble(Rails::Auth::X509::Certificate) "OU=ponycopter">')
|
25
|
+
expect(permit(get_request(credentials: another_certificate)).description)
|
26
|
+
.to eq('allow GETs by #<InstanceDouble(Rails::Auth::X509::Certificate) "OU=derpderp">')
|
27
|
+
expect(permit(get_request).description)
|
28
|
+
.to eq("allow GETs by unauthenticated clients")
|
29
|
+
end
|
21
30
|
end
|
22
31
|
end
|
@@ -3,41 +3,32 @@
|
|
3
3
|
require "logger"
|
4
4
|
|
5
5
|
RSpec.describe Rails::Auth::X509::Middleware do
|
6
|
-
let(:request) { Rack::MockRequest.env_for("https://www.example.com") }
|
7
6
|
let(:app) { ->(env) { [200, env, "Hello, world!"] } }
|
7
|
+
let(:request) { Rack::MockRequest.env_for("https://www.example.com") }
|
8
8
|
|
9
|
-
let(:
|
10
|
-
let(:
|
11
|
-
let(:
|
12
|
-
let(:cert_filter) { :pem }
|
13
|
-
let(:example_key) { "X-SSL-Client-Cert" }
|
9
|
+
let(:cert_filter) { :pem }
|
10
|
+
let(:cert_pem) { cert_path("valid.crt").read }
|
11
|
+
let(:example_key) { "X-SSL-Client-Cert" }
|
14
12
|
|
15
13
|
let(:middleware) do
|
16
14
|
described_class.new(
|
17
15
|
app,
|
18
|
-
logger: Logger.new(STDERR),
|
19
|
-
ca_file: cert_path("ca.crt").to_s,
|
20
16
|
cert_filters: { example_key => cert_filter },
|
21
|
-
|
17
|
+
logger: Logger.new(STDERR)
|
22
18
|
)
|
23
19
|
end
|
24
20
|
|
25
21
|
context "certificate types" do
|
26
22
|
describe "PEM certificates" do
|
27
23
|
it "extracts Rails::Auth::X509::Certificate from a PEM certificate in the Rack environment" do
|
28
|
-
_response, env = middleware.call(request.merge(example_key =>
|
24
|
+
_response, env = middleware.call(request.merge(example_key => cert_pem))
|
29
25
|
|
30
26
|
credential = Rails::Auth.credentials(env).fetch("x509")
|
31
27
|
expect(credential).to be_a Rails::Auth::X509::Certificate
|
32
28
|
end
|
33
29
|
|
34
|
-
it "ignores unverified certificates" do
|
35
|
-
_response, env = middleware.call(request.merge(example_key => bad_cert_pem))
|
36
|
-
expect(Rails::Auth.credentials(env)).to be_empty
|
37
|
-
end
|
38
|
-
|
39
30
|
it "normalizes abnormal whitespace" do
|
40
|
-
_response, env = middleware.call(request.merge(example_key =>
|
31
|
+
_response, env = middleware.call(request.merge(example_key => cert_pem.tr("\n", "\t")))
|
41
32
|
|
42
33
|
credential = Rails::Auth.credentials(env).fetch("x509")
|
43
34
|
expect(credential).to be_a Rails::Auth::X509::Certificate
|
@@ -46,11 +37,11 @@ RSpec.describe Rails::Auth::X509::Middleware do
|
|
46
37
|
|
47
38
|
# :nocov:
|
48
39
|
describe "Java certificates" do
|
49
|
-
let(:example_key) { "javax.servlet.request.X509Certificate" }
|
50
40
|
let(:cert_filter) { :java }
|
41
|
+
let(:example_key) { "javax.servlet.request.X509Certificate" }
|
51
42
|
|
52
43
|
let(:java_cert) do
|
53
|
-
ruby_cert = OpenSSL::X509::Certificate.new(
|
44
|
+
ruby_cert = OpenSSL::X509::Certificate.new(cert_pem)
|
54
45
|
input_stream = Java::JavaIO::ByteArrayInputStream.new(ruby_cert.to_der.to_java_bytes)
|
55
46
|
java_cert_klass = Java::JavaSecurityCert::CertificateFactory.getInstance("X.509")
|
56
47
|
java_cert_klass.generateCertificate(input_stream)
|
@@ -67,21 +58,4 @@ RSpec.describe Rails::Auth::X509::Middleware do
|
|
67
58
|
end
|
68
59
|
# :nocov:
|
69
60
|
end
|
70
|
-
|
71
|
-
describe "require_cert: true" do
|
72
|
-
let(:cert_required) { true }
|
73
|
-
|
74
|
-
it "functions normally for valid certificates" do
|
75
|
-
_response, env = middleware.call(request.merge(example_key => valid_cert_pem))
|
76
|
-
|
77
|
-
credential = Rails::Auth.credentials(env).fetch("x509")
|
78
|
-
expect(credential).to be_a Rails::Auth::X509::Certificate
|
79
|
-
end
|
80
|
-
|
81
|
-
it "raises Rails::Auth::X509::CertificateVerifyFailed for unverified certificates" do
|
82
|
-
expect do
|
83
|
-
middleware.call(request.merge(example_key => bad_cert_pem))
|
84
|
-
end.to raise_error Rails::Auth::X509::CertificateVerifyFailed
|
85
|
-
end
|
86
|
-
end
|
87
61
|
end
|
@@ -95,20 +95,3 @@ valid_key_with_ext_path = File.join(cert_path, "valid_with_ext.key")
|
|
95
95
|
|
96
96
|
File.write valid_cert_with_ext_path, valid_cert_with_ext.to_pem
|
97
97
|
File.write valid_key_with_ext_path, valid_cert_with_ext.key_material.private_key.to_pem
|
98
|
-
|
99
|
-
#
|
100
|
-
# Create evil MitM self-signed certificate
|
101
|
-
#
|
102
|
-
|
103
|
-
self_signed_cert = CertificateAuthority::Certificate.new
|
104
|
-
self_signed_cert.subject.common_name = "127.0.0.1"
|
105
|
-
self_signed_cert.subject.organizational_unit = "ponycopter"
|
106
|
-
self_signed_cert.serial_number.number = 2
|
107
|
-
self_signed_cert.key_material.generate_key
|
108
|
-
self_signed_cert.sign!
|
109
|
-
|
110
|
-
self_signed_cert_path = File.join(cert_path, "invalid.crt")
|
111
|
-
self_signed_key_path = File.join(cert_path, "invalid.key")
|
112
|
-
|
113
|
-
File.write self_signed_cert_path, self_signed_cert.to_pem
|
114
|
-
File.write self_signed_key_path, self_signed_cert.key_material.private_key.to_pem
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails-auth
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tony Arcieri
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-10-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -67,10 +67,11 @@ executables: []
|
|
67
67
|
extensions: []
|
68
68
|
extra_rdoc_files: []
|
69
69
|
files:
|
70
|
+
- ".github/workflows/jruby.yml"
|
71
|
+
- ".github/workflows/mri.yml"
|
70
72
|
- ".gitignore"
|
71
73
|
- ".rspec"
|
72
74
|
- ".rubocop.yml"
|
73
|
-
- ".travis.yml"
|
74
75
|
- BUG-BOUNTY.md
|
75
76
|
- CHANGES.md
|
76
77
|
- CONDUCT.md
|
@@ -106,6 +107,7 @@ files:
|
|
106
107
|
- lib/rails/auth/x509/certificate.rb
|
107
108
|
- lib/rails/auth/x509/filter/java.rb
|
108
109
|
- lib/rails/auth/x509/filter/pem.rb
|
110
|
+
- lib/rails/auth/x509/filter/pem_urlencoded.rb
|
109
111
|
- lib/rails/auth/x509/matcher.rb
|
110
112
|
- lib/rails/auth/x509/middleware.rb
|
111
113
|
- lib/rails/auth/x509/subject_alt_name_extension.rb
|
@@ -152,7 +154,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
152
154
|
- !ruby/object:Gem::Version
|
153
155
|
version: '0'
|
154
156
|
requirements: []
|
155
|
-
rubygems_version: 3.
|
157
|
+
rubygems_version: 3.1.6
|
156
158
|
signing_key:
|
157
159
|
specification_version: 4
|
158
160
|
summary: Modular resource-oriented authentication and authorization for Rails/Rack
|
data/.travis.yml
DELETED
@@ -1,24 +0,0 @@
|
|
1
|
-
language: ruby
|
2
|
-
sudo: false
|
3
|
-
branches:
|
4
|
-
only:
|
5
|
-
- master
|
6
|
-
|
7
|
-
before_install:
|
8
|
-
- gem install bundler
|
9
|
-
|
10
|
-
bundler_args: --without development
|
11
|
-
|
12
|
-
rvm:
|
13
|
-
- 2.4
|
14
|
-
- 2.5
|
15
|
-
- 2.6
|
16
|
-
matrix:
|
17
|
-
include:
|
18
|
-
- rvm: jruby
|
19
|
-
jdk: openjdk8
|
20
|
-
env: JRUBY_OPTS="--debug" # for simplecov
|
21
|
-
- rvm: jruby
|
22
|
-
jdk: openjdk11
|
23
|
-
env: JRUBY_OPTS="--debug" # for simplecov
|
24
|
-
fast_finish: true
|