Skip to content

Add basic Credentials wrapper + ManagedNative mixin #20

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 18 commits into from
Oct 12, 2020
Merged
Show file tree
Hide file tree
Changes from 7 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
3 changes: 2 additions & 1 deletion .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ Metrics/BlockLength:
Exclude:
- 'gems/aws-crt/Rakefile'
- '**/*.rake'
- 'gems/aws-crt/spec/**/*.rb'
- 'gems/**/spec/**/*.rb'

Metrics/MethodLength:
Max: 25
Exclude:
- 'gems/aws-crt/ext/compile.rb'

Expand Down
3 changes: 3 additions & 0 deletions gems/aws-crt-auth/lib/aws-crt-auth.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# frozen_string_literal: true

require 'aws-crt'
require_relative 'aws-crt-auth/signer'
require_relative 'aws-crt-auth/credentials'
require_relative 'aws-crt-auth/signing_config'

module Aws
module Crt
Expand Down
103 changes: 103 additions & 0 deletions gems/aws-crt-auth/lib/aws-crt-auth/credentials.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# frozen_string_literal: true

module Aws
module Crt
module Auth
# Utility class for Credentials.
# @api private
class Credentials
UINT64_MAX = 18_446_744_073_709_551_615

# @param [String] access_key_id
# @param [String] secret_access_key
# @param [String] session_token (nil)
# @param [Time|int] expiration (nil)
def initialize(access_key_id, secret_access_key,
session_token = nil, expiration = nil)
if !access_key_id || access_key_id.empty?
raise ArgumentError, 'access_key_id must be set'
end

if !secret_access_key || secret_access_key.empty?
raise ArgumentError, 'secret_access_key must be set'
end

native = Aws::Crt.call do
Aws::Crt::Native.credentials_new(
access_key_id,
secret_access_key,
session_token,
expiration&.to_i || UINT64_MAX
)
end

@native = FFI::AutoPointer.new(native, self.class.method(:on_release))
end

# @return [FFI:Pointer]
attr_reader :native

# @return [String, nil]
def access_key_id
Aws::Crt::Native.credentials_get_access_key_id(@native) if @native
end

# @return [String, nil]
def secret_access_key
Aws::Crt::Native.credentials_get_secret_access_key(@native) if @native
end

# @return [String, nil]
def session_token
Aws::Crt::Native.credentials_get_session_token(@native) if @native
end

# @return [Time,nil]
def expiration
return unless @native

exp = Aws::Crt::Native.credentials_get_expiration(@native)
return if exp == UINT64_MAX

Time.at(exp)
end

# @return [Credentials]
def credentials
self
end

# @return [Boolean] Returns `true` if the access key id and secret
# access key are both set.
def set?
!@native.nil? &&
!access_key_id.nil? &&
!access_key_id.empty? &&
!secret_access_key.nil? &&
!secret_access_key.empty?
end

# Removing the secret access key from the default inspect string.
# @api private
def inspect
"#<#{self.class.name} access_key_id=#{access_key_id.inspect}>"
end

# Immediately release this instance's attachment to the underlying
# resources, without waiting for the garbage collector.
# Note that underlying resources will remain alive until nothing
# else is using them.
def release
return unless @native

@native.free
@native = nil
end

def self.on_release(native)
Aws::Crt::Native.credentials_release(native)
end
end
end
end
end
37 changes: 37 additions & 0 deletions gems/aws-crt-auth/lib/aws-crt-auth/signer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# frozen_string_literal: true

module Aws
module Crt
module Auth
# Utility class for creating AWS signature version 4 signature.
class Signer
def initialize(options = {})
@service = extract_service(options)
@region = extract_region(options)
@credentials_provider = extract_credentials_provider(options)
@unsigned_headers = Set.new((options.fetch(:unsigned_headers, []))
.map(&:downcase))
@unsigned_headers << 'authorization'
@unsigned_headers << 'x-amzn-trace-id'
@unsigned_headers << 'expect'
%i[uri_escape_path apply_checksum_header].each do |opt|
instance_variable_set("@#{opt}",
options.key?(opt) ? options[:opt] : true)
end
end

def sign_request(request)
# TODO
end

def sign_event(prior_signature, payload, encoder)
# TODO
end

def presign_url(options)
# TODO
end
end
end
end
end
78 changes: 78 additions & 0 deletions gems/aws-crt-auth/lib/aws-crt-auth/signing_config.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# frozen_string_literal: true

module Aws
module Crt
module Auth
# Signing Config
# @api private
class SigningConfig
# @param [Hash] options
# @option options [required, Aws::Crt::Native::signing_algorithm]
# :algorithm
# @option options [required, Aws::Crt::Native::signature_type]
# :signature_type
# @option options [required, Credentials] :credentials
# @option options [required, String] :region
# @option options [required, String] :service
# @option options [Time] :date (Time.now)
# @option options [Array<String>|Proc(String->Boolean)]
# :unsigned_headers ([])
# @option options [Boolean] :uri_escape_path (true)
# @option options [Boolean] :apply_checksum_header (true)
def initialize(options = {})
# validation of parameters is handled in signing_config_new

# create a callback function for aws_should_sign_header_fn
sign_header_fn = extract_unsigned_header_fn(
options[:unsigned_headers]
)
puts "TODO: imp #{sign_header_fn}" if sign_header_fn

# ensure we retain a reference to the credentials to avoid GC
@credentials = options[:credentials]
native = Aws::Crt.call do
Aws::Crt::Native.signing_config_new(
options[:algorithm],
options[:signature_type],
options[:region],
options[:service],
extract_date(options),
@credentials&.native
)
end

@native = FFI::AutoPointer.new(native, self.class.method(:on_release))
end

# Immediately release this instance's attachment to the underlying
# resources, without waiting for the garbage collector.
# Note that underlying resources will remain alive until nothing
# else is using them.
def release
return unless @native

@native.free
@native = nil
end

def self.on_release(native)
Aws::Crt::Native.signing_config_release(native)
end

private

def extract_date(options)
(options[:date] || Time.now).to_i
end

def extract_unsigned_header_fn(unsigned_headers)
if unsigned_headers && !unsigned_headers.respond_to?(:call)
unsigned_headers = Set.new(unsigned_headers)
sign_header_fn = proc { |param| unsigned_headers.include? param }
end
sign_header_fn
end
end
end
end
end
112 changes: 112 additions & 0 deletions gems/aws-crt-auth/spec/credentials_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# frozen_string_literal: true

require_relative 'spec_helper'
require 'weakref'

module Aws
module Crt
module Auth #:nodoc:
UINT64_MAX = 18_446_744_073_709_551_615

describe Credentials do
describe '#initilize' do
it 'raises an ArgumentError when missing access_key_id' do
expect { Credentials.new(nil, 'secret') }
.to raise_error(ArgumentError)
end

it 'raises an ArgumentError when missing secret_access_key' do
expect { Credentials.new('akid', nil) }
.to raise_error(ArgumentError)
end

it 'defaults the session token to nil' do
expect(Credentials.new('akid', 'secret').session_token).to be nil
end

it 'defaults the expiration to nil' do
expect(Credentials.new('akid', 'secret').expiration)
.to be_nil
end

it 'accepts a Time for expiration' do
exp = Time.now
creds = Credentials.new('akid', 'secret', 'token', exp)
expect(creds.expiration.to_i).to eq exp.to_i
end

it 'accepts an epoch (integer) for expiration' do
exp = Time.now
creds = Credentials.new('akid', 'secret', 'token', exp.to_i)
expect(creds.expiration.to_i).to eq exp.to_i
end
end

describe 'accessors' do
let(:exp) { Time.now }
let(:creds) { Credentials.new('akid', 'secret', 'token', exp) }

it 'provides access to the access key id' do
expect(creds.access_key_id).to eq('akid')
end

it 'provides access to the secret access key' do
expect(creds.secret_access_key).to eq('secret')
end

it 'provides access to the session token' do
expect(creds.session_token).to eq('token')
end

it 'provides access to the expiration' do
expect(creds.expiration.to_i).to eq exp.to_i
end
end

describe '#set?' do
it 'returns true when the key and secret are both non nil values' do
expect(Credentials.new('akid', 'secret').set?).to be(true)
end

it 'returns false after the credentials have been released' do
creds = Credentials.new('akid', 'secret')
creds.release
expect(creds.set?).to be(false)
end
end

describe '#inspect' do
let(:creds) { Credentials.new('akid', 'secret', 'token') }

it 'does not include the secret_access_key' do
expect(creds.inspect).not_to include 'secret'
end
end

describe '.on_release' do
it 'cleans up with release' do
creds = Credentials.new('akid', 'secret')
expect(creds).to_not be_nil

creds.release
check_for_clean_shutdown
end

if garbage_collect_is_immediate?
it 'cleans up with GC' do
creds = Credentials.new('akid', 'secret', 'session')
weakref = WeakRef.new(creds)
expect(weakref.weakref_alive?).to be true

# force cleanup via GC
creds = nil # rubocop:disable Lint/UselessAssignment
ObjectSpace.garbage_collect
expect(weakref.weakref_alive?).to be_falsey
check_for_clean_shutdown
end
end
end
end
end
end
end
9 changes: 9 additions & 0 deletions gems/aws-crt-auth/spec/signer_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

require_relative 'spec_helper'

require 'tempfile'
require 'base64'

describe Aws::Crt::Auth::Signer do
end
Loading