From 0aa2badd9b633a2d3487c7bdb082b4ed9db37397 Mon Sep 17 00:00:00 2001 From: Thom May Date: Sun, 13 Nov 2011 20:16:22 +0000 Subject: [PATCH 1/4] implement STS support add support for Federated IAM and session tokens. --- lib/fog/aws.rb | 1 + lib/fog/aws/parsers/sts/get_session_token.rb | 31 ++++ .../aws/requests/sts/get_federation_token.rb | 20 +++ lib/fog/aws/requests/sts/get_session_token.rb | 18 +++ lib/fog/aws/sts.rb | 137 ++++++++++++++++++ lib/fog/bin/aws.rb | 2 + tests/aws/requests/sts/session_token_tests.rb | 34 +++++ 7 files changed, 243 insertions(+) create mode 100644 lib/fog/aws/parsers/sts/get_session_token.rb create mode 100644 lib/fog/aws/requests/sts/get_federation_token.rb create mode 100644 lib/fog/aws/requests/sts/get_session_token.rb create mode 100644 lib/fog/aws/sts.rb create mode 100644 tests/aws/requests/sts/session_token_tests.rb diff --git a/lib/fog/aws.rb b/lib/fog/aws.rb index 48b78de34..c02fecd37 100644 --- a/lib/fog/aws.rb +++ b/lib/fog/aws.rb @@ -20,6 +20,7 @@ module Fog service(:simpledb, 'aws/simpledb', 'SimpleDB') service(:sns, 'aws/sns', 'SNS') service(:sqs, 'aws/sqs', 'SQS') + service(:sts, 'aws/sts', 'STS') service(:storage, 'aws/storage', 'Storage') def self.indexed_param(key, values) diff --git a/lib/fog/aws/parsers/sts/get_session_token.rb b/lib/fog/aws/parsers/sts/get_session_token.rb new file mode 100644 index 000000000..ee5947386 --- /dev/null +++ b/lib/fog/aws/parsers/sts/get_session_token.rb @@ -0,0 +1,31 @@ +module Fog + module Parsers + module AWS + module STS + + class GetSessionToken < Fog::Parsers::Base + # http://docs.amazonwebservices.com/IAM/latest/UserGuide/index.html?CreatingFedTokens.html + + def reset + @response = {} + end + + def end_element(name) + case name + when 'SessionToken', 'SecretAccessKey', 'Expiration', 'AccessKeyId' + @response[name] = @value.strip + when 'Arn', 'FederatedUserId' + @response[name] = @value + when 'PackedPolicySize' + @response[name] = @value + when 'RequestId' + @response[name] = @value + end + end + + end + + end + end + end +end diff --git a/lib/fog/aws/requests/sts/get_federation_token.rb b/lib/fog/aws/requests/sts/get_federation_token.rb new file mode 100644 index 000000000..84e96e8e6 --- /dev/null +++ b/lib/fog/aws/requests/sts/get_federation_token.rb @@ -0,0 +1,20 @@ +module Fog + module AWS + class STS + class Real + + require 'fog/aws/parsers/sts/get_session_token' + + def get_federation_token(name, policy, duration=43200) + request({ + 'Action' => 'GetFederationToken', + 'Name' => name, + 'Policy' => MultiJson.encode(policy), + 'DurationSeconds' => duration, + :parser => Fog::Parsers::AWS::STS::GetSessionToken.new + }) + end + end + end + end +end diff --git a/lib/fog/aws/requests/sts/get_session_token.rb b/lib/fog/aws/requests/sts/get_session_token.rb new file mode 100644 index 000000000..b188a22cc --- /dev/null +++ b/lib/fog/aws/requests/sts/get_session_token.rb @@ -0,0 +1,18 @@ +module Fog + module AWS + class STS + class Real + + require 'fog/aws/parsers/sts/get_session_token' + + def get_session_token(duration=43200) + request({ + 'Action' => 'GetSessionToken', + 'DurationSeconds' => duration, + :parser => Fog::Parsers::AWS::STS::GetSessionToken.new + }) + end + end + end + end +end diff --git a/lib/fog/aws/sts.rb b/lib/fog/aws/sts.rb new file mode 100644 index 000000000..bf675dbd0 --- /dev/null +++ b/lib/fog/aws/sts.rb @@ -0,0 +1,137 @@ +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'aws')) + +module Fog + module AWS + class STS < Fog::Service + + class EntityAlreadyExists < Fog::AWS::STS::Error; end + class ValidationError < Fog::AWS::STS::Error; end + + requires :aws_access_key_id, :aws_secret_access_key + recognizes :host, :path, :port, :scheme, :persistent + + request_path 'fog/aws/requests/sts' + request :get_federation_token + request :get_session_token + + class Mock + def self.data + @data ||= Hash.new do |hash, key| + hash[key] = { + :owner_id => Fog::AWS::Mock.owner_id, + :server_certificates => {} + } + end + end + + def self.reset + @data = nil + end + + def self.server_certificate_id + Fog::Mock.random_hex(16) + end + + def initialize(options={}) + @aws_access_key_id = options[:aws_access_key_id] + end + + def data + self.class.data[@aws_access_key_id] + end + + def reset_data + self.class.data.delete(@aws_access_key_id) + end + end + + class Real + + # Initialize connection to STS + # + # ==== Notes + # options parameter must include values for :aws_access_key_id and + # :aws_secret_access_key in order to create a connection + # + # ==== Examples + # iam = STS.new( + # :aws_access_key_id => your_aws_access_key_id, + # :aws_secret_access_key => your_aws_secret_access_key + # ) + # + # ==== Parameters + # * options<~Hash> - config arguments for connection. Defaults to {}. + # + # ==== Returns + # * STS object with connection to AWS. + def initialize(options={}) + require 'fog/core/parser' + require 'multi_json' + + @aws_access_key_id = options[:aws_access_key_id] + @aws_secret_access_key = options[:aws_secret_access_key] + @connection_options = options[:connection_options] || {} + @hmac = Fog::HMAC.new('sha256', @aws_secret_access_key) + @host = options[:host] || 'sts.amazonaws.com' + @path = options[:path] || '/' + @persistent = options[:persistent] || false + @port = options[:port] || 443 + @scheme = options[:scheme] || 'https' + @connection = Fog::Connection.new("#{@scheme}://#{@host}:#{@port}#{@path}", @persistent, @connection_options) + end + + def reload + @connection.reset + end + + private + + def request(params) + idempotent = params.delete(:idempotent) + parser = params.delete(:parser) + + body = Fog::AWS.signed_params( + params, + { + :aws_access_key_id => @aws_access_key_id, + :hmac => @hmac, + :host => @host, + :path => @path, + :port => @port, + :version => '2011-06-15' + } + ) + + begin + response = @connection.request({ + :body => body, + :expects => 200, + :idempotent => idempotent, + :headers => { 'Content-Type' => 'application/x-www-form-urlencoded' }, + :host => @host, + :method => 'POST', + :parser => parser + }) + + response + rescue Excon::Errors::HTTPStatusError => error + if match = error.message.match(/(.*)<\/Code>(?:.*(.*)<\/Message>)?/m) + case match[1] + when 'EntityAlreadyExists', 'KeyPairMismatch', 'LimitExceeded', 'MalformedCertificate', 'ValidationError' + raise Fog::AWS::STS.const_get(match[1]).slurp(error, match[2]) + else + raise Fog::AWS::STS::Error.slurp(error, "#{match[1]} => #{match[2]}") if match[1] + raise + end + else + raise + end + end + + + end + + end + end + end +end diff --git a/lib/fog/bin/aws.rb b/lib/fog/bin/aws.rb index 485b6aa67..55b342bdd 100644 --- a/lib/fog/bin/aws.rb +++ b/lib/fog/bin/aws.rb @@ -35,6 +35,8 @@ class AWS < Fog::Bin Fog::AWS::RDS when :sns Fog::AWS::SNS + when :sts + Fog::AWS::STS else # @todo Replace most instances of ArgumentError with NotImplementedError # @todo For a list of widely supported Exceptions, see: diff --git a/tests/aws/requests/sts/session_token_tests.rb b/tests/aws/requests/sts/session_token_tests.rb new file mode 100644 index 000000000..01b5fec57 --- /dev/null +++ b/tests/aws/requests/sts/session_token_tests.rb @@ -0,0 +1,34 @@ +Shindo.tests('AWS::STS | session tokens', ['aws']) do + + @session_format = { + 'SessionToken' => String, + 'SecretAccessKey' => String, + 'Expiration' => String, + 'AccessKeyId' => String, + 'RequestId' => String + } + + tests("#get_session_token").formats(@session_format) do + pending if Fog.mocking? + Fog::AWS[:sts].get_session_token.body + end + + @policy = {"Statement" => [{"Effect" => "Allow", "Action" => "*", "Resource" => "*"}]} + + @federation_format = { + 'SessionToken' => String, + 'SecretAccessKey' => String, + 'Expiration' => String, + 'AccessKeyId' => String, + 'Arn' => String, + 'FederatedUserId' => String, + 'PackedPolicySize' => String, + 'RequestId' => String + } + + tests("#get_federation_token('test@fog.io', #{@policy.inspect})").formats(@federation_format) do + pending if Fog.mocking? + Fog::AWS[:sts].get_federation_token("test@fog.io", @policy).body + end + +end From fe5880e8ea05f7dbc97031f99ef1747db2792792 Mon Sep 17 00:00:00 2001 From: Thom May Date: Sun, 13 Nov 2011 20:16:46 +0000 Subject: [PATCH 2/4] Allow use of session tokens in AWS Compute --- lib/fog/aws.rb | 4 ++++ lib/fog/aws/compute.rb | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/fog/aws.rb b/lib/fog/aws.rb index c02fecd37..3e0903c0f 100644 --- a/lib/fog/aws.rb +++ b/lib/fog/aws.rb @@ -86,6 +86,10 @@ module Fog 'Version' => options[:version] }) + params.merge!({ + 'SecurityToken' => options[:aws_session_token] + }) if options[:aws_session_token] + body = '' for key in params.keys.sort unless (value = params[key]).nil? diff --git a/lib/fog/aws/compute.rb b/lib/fog/aws/compute.rb index 171da8751..37dc30b75 100644 --- a/lib/fog/aws/compute.rb +++ b/lib/fog/aws/compute.rb @@ -6,7 +6,7 @@ module Fog class AWS < Fog::Service requires :aws_access_key_id, :aws_secret_access_key - recognizes :endpoint, :region, :host, :path, :port, :scheme, :persistent + recognizes :endpoint, :region, :host, :path, :port, :scheme, :persistent, :aws_session_token model_path 'fog/aws/models/compute' model :address @@ -249,6 +249,7 @@ module Fog # * options<~Hash> - config arguments for connection. Defaults to {}. # * region<~String> - optional region to use, in # ['eu-west-1', 'us-east-1', 'us-west-1', 'us-west-2', 'ap-northeast-1', 'ap-southeast-1'] + # * aws_session_token<~String> - when using Session Tokens or Federated Users, a session_token must be presented # # ==== Returns # * EC2 object with connection to aws. @@ -257,6 +258,7 @@ module Fog @aws_access_key_id = options[:aws_access_key_id] @aws_secret_access_key = options[:aws_secret_access_key] + @aws_session_token = options[:aws_session_token] @connection_options = options[:connection_options] || {} @hmac = Fog::HMAC.new('sha256', @aws_secret_access_key) @region = options[:region] ||= 'us-east-1' @@ -306,6 +308,7 @@ module Fog params, { :aws_access_key_id => @aws_access_key_id, + :aws_session_token => @aws_session_token, :hmac => @hmac, :host => @host, :path => @path, From 306f24163d182131da20838d3b4471a5cefde524 Mon Sep 17 00:00:00 2001 From: Thom May Date: Sun, 13 Nov 2011 20:50:09 +0000 Subject: [PATCH 3/4] handle session tokens for SQS and SimpleDB --- lib/fog/aws/simpledb.rb | 4 +++- lib/fog/aws/sqs.rb | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/fog/aws/simpledb.rb b/lib/fog/aws/simpledb.rb index d414d3011..d407fd2ae 100644 --- a/lib/fog/aws/simpledb.rb +++ b/lib/fog/aws/simpledb.rb @@ -5,7 +5,7 @@ module Fog class SimpleDB < Fog::Service requires :aws_access_key_id, :aws_secret_access_key - recognizes :host, :nil_string, :path, :port, :scheme, :persistent, :region + recognizes :host, :nil_string, :path, :port, :scheme, :persistent, :region, :aws_session_token request_path 'fog/aws/requests/simpledb' request :batch_put_attributes @@ -70,6 +70,7 @@ module Fog @aws_access_key_id = options[:aws_access_key_id] @aws_secret_access_key = options[:aws_secret_access_key] + @aws_session_token = options[:aws_session_token] @connection_options = options[:connection_options] || {} @hmac = Fog::HMAC.new('sha256', @aws_secret_access_key) @nil_string = options[:nil_string]|| 'nil' @@ -165,6 +166,7 @@ module Fog params, { :aws_access_key_id => @aws_access_key_id, + :aws_session_token => @aws_session_token, :hmac => @hmac, :host => @host, :path => @path, diff --git a/lib/fog/aws/sqs.rb b/lib/fog/aws/sqs.rb index a7545f0e7..c7ba00721 100644 --- a/lib/fog/aws/sqs.rb +++ b/lib/fog/aws/sqs.rb @@ -5,7 +5,7 @@ module Fog class SQS < Fog::Service requires :aws_access_key_id, :aws_secret_access_key - recognizes :region, :host, :path, :port, :scheme, :persistent + recognizes :region, :host, :path, :port, :scheme, :persistent, :aws_session_token request_path 'fog/aws/requests/sqs' request :change_message_visibility @@ -78,6 +78,7 @@ module Fog def initialize(options={}) @aws_access_key_id = options[:aws_access_key_id] @aws_secret_access_key = options[:aws_secret_access_key] + @aws_session_token = options[:aws_session_token] @connection_options = options[:connection_options] || {} @hmac = Fog::HMAC.new('sha256', @aws_secret_access_key) options[:region] ||= 'us-east-1' @@ -121,6 +122,7 @@ module Fog params, { :aws_access_key_id => @aws_access_key_id, + :aws_session_token => @aws_session_token, :hmac => @hmac, :host => @host, :path => path || @path, From d96b5ff7b95b29c38e5abd60705529c5dc2f3937 Mon Sep 17 00:00:00 2001 From: Thom May Date: Wed, 16 Nov 2011 10:18:55 +0000 Subject: [PATCH 4/4] Split [AWS|STS] tests into separate files per #609 --- .../sts/get_federation_token_tests.rb | 21 +++++++++++++++++++ tests/aws/requests/sts/session_token_tests.rb | 18 ---------------- 2 files changed, 21 insertions(+), 18 deletions(-) create mode 100644 tests/aws/requests/sts/get_federation_token_tests.rb diff --git a/tests/aws/requests/sts/get_federation_token_tests.rb b/tests/aws/requests/sts/get_federation_token_tests.rb new file mode 100644 index 000000000..758f2ab9e --- /dev/null +++ b/tests/aws/requests/sts/get_federation_token_tests.rb @@ -0,0 +1,21 @@ +Shindo.tests('AWS::STS | session tokens', ['aws']) do + + @policy = {"Statement" => [{"Effect" => "Allow", "Action" => "*", "Resource" => "*"}]} + + @federation_format = { + 'SessionToken' => String, + 'SecretAccessKey' => String, + 'Expiration' => String, + 'AccessKeyId' => String, + 'Arn' => String, + 'FederatedUserId' => String, + 'PackedPolicySize' => String, + 'RequestId' => String + } + + tests("#get_federation_token('test@fog.io', #{@policy.inspect})").formats(@federation_format) do + pending if Fog.mocking? + Fog::AWS[:sts].get_federation_token("test@fog.io", @policy).body + end + +end diff --git a/tests/aws/requests/sts/session_token_tests.rb b/tests/aws/requests/sts/session_token_tests.rb index 01b5fec57..ff52b8bb3 100644 --- a/tests/aws/requests/sts/session_token_tests.rb +++ b/tests/aws/requests/sts/session_token_tests.rb @@ -13,22 +13,4 @@ Shindo.tests('AWS::STS | session tokens', ['aws']) do Fog::AWS[:sts].get_session_token.body end - @policy = {"Statement" => [{"Effect" => "Allow", "Action" => "*", "Resource" => "*"}]} - - @federation_format = { - 'SessionToken' => String, - 'SecretAccessKey' => String, - 'Expiration' => String, - 'AccessKeyId' => String, - 'Arn' => String, - 'FederatedUserId' => String, - 'PackedPolicySize' => String, - 'RequestId' => String - } - - tests("#get_federation_token('test@fog.io', #{@policy.inspect})").formats(@federation_format) do - pending if Fog.mocking? - Fog::AWS[:sts].get_federation_token("test@fog.io", @policy).body - end - end