From 0aa2badd9b633a2d3487c7bdb082b4ed9db37397 Mon Sep 17 00:00:00 2001 From: Thom May Date: Sun, 13 Nov 2011 20:16:22 +0000 Subject: [PATCH] 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