From 11b7855b1a3aeb117c43efd714b8ecff1e3fd2c6 Mon Sep 17 00:00:00 2001 From: Eugene Howe Date: Tue, 20 Jan 2015 09:42:23 -0500 Subject: [PATCH] get signin token for federation --- .gitignore | 1 + lib/fog/aws.rb | 1 + lib/fog/aws/core.rb | 3 +- lib/fog/aws/federation.rb | 58 +++++++++++++++++++ lib/fog/aws/iam.rb | 23 +++++++- .../sts/assume_role_with_web_identity.rb | 20 +++++++ .../requests/federation/get_signin_token.rb | 20 +++++++ lib/fog/aws/requests/iam/create_role.rb | 24 ++++++++ lib/fog/aws/requests/iam/delete_role.rb | 16 +++++ lib/fog/aws/requests/iam/get_role.rb | 23 ++++++++ lib/fog/aws/requests/iam/list_roles.rb | 22 +++++++ .../sts/assume_role_with_web_identity.rb | 45 ++++++++++++++ lib/fog/aws/sts.rb | 1 + .../federation/get_signin_token_tests.rb | 11 ++++ tests/requests/iam/role_tests.rb | 25 +++----- tests/requests/sts/assume_role_tests.rb | 26 ++++----- .../sts/assume_role_with_saml_tests.rb | 26 ++++----- .../assume_role_with_web_identity_tests.rb | 26 +++++++++ 18 files changed, 325 insertions(+), 46 deletions(-) create mode 100644 lib/fog/aws/federation.rb create mode 100644 lib/fog/aws/parsers/sts/assume_role_with_web_identity.rb create mode 100644 lib/fog/aws/requests/federation/get_signin_token.rb create mode 100644 lib/fog/aws/requests/sts/assume_role_with_web_identity.rb create mode 100644 tests/requests/federation/get_signin_token_tests.rb create mode 100644 tests/requests/sts/assume_role_with_web_identity_tests.rb diff --git a/.gitignore b/.gitignore index ae3fdc298..a7009beb5 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ *.o *.a mkmf.log +tests/.fog diff --git a/lib/fog/aws.rb b/lib/fog/aws.rb index a08911249..f187290a6 100644 --- a/lib/fog/aws.rb +++ b/lib/fog/aws.rb @@ -19,6 +19,7 @@ require 'fog/aws/dynamodb' require 'fog/aws/elasticache' require 'fog/aws/elb' require 'fog/aws/emr' +require 'fog/aws/federation' require 'fog/aws/glacier' require 'fog/aws/iam' require 'fog/aws/rds' diff --git a/lib/fog/aws/core.rb b/lib/fog/aws/core.rb index 3bbc56e1c..bb7e3aaca 100644 --- a/lib/fog/aws/core.rb +++ b/lib/fog/aws/core.rb @@ -21,6 +21,7 @@ module Fog service(:elasticache, 'Elasticache') service(:elb, 'ELB') service(:emr, 'EMR') + service(:federation, 'Federation') service(:glacier, 'Glacier') service(:iam, 'IAM') service(:rds, 'RDS') @@ -109,7 +110,7 @@ module Fog end end body.chop! - + headers['Authorization'] = options[:signer].sign({:method => options[:method], :headers => headers, :body => body, :query => {}, :path => options[:path]}, date) return body, headers diff --git a/lib/fog/aws/federation.rb b/lib/fog/aws/federation.rb new file mode 100644 index 000000000..c720c8783 --- /dev/null +++ b/lib/fog/aws/federation.rb @@ -0,0 +1,58 @@ +require 'fog/aws/core' + +module Fog + module AWS + class Federation < Fog::Service + extend Fog::AWS::CredentialFetcher::ServiceMethods + + recognizes :instrumentor, :instrumentor_name + + request_path 'fog/aws/requests/federation' + + request 'get_signin_token' + + class Mock + def self.data + @data ||= {} + end + + def self.reset + @data = nil + end + + def initialize(options={}) + end + + def data + self.class.data + end + + def reset_data + self.class.reset + end + end + + class Real + include Fog::AWS::CredentialFetcher::ConnectionMethods + + def initialize(options={}) + @instrumentor = options[:instrumentor] + @instrumentor_name = options[:instrumentor_name] || 'fog.aws.federation' + @connection_options = options[:connection_options] || {} + @host = 'signin.aws.amazon.com' + @path = '/federation' + @scheme = 'https' + @connection = Excon.new("#{@scheme}://#{@host}#{@path}") + end + + def request(action, session) + response = @connection.get( + :query => "Action=#{action}&SessionType=json&Session=#{session}", + :expects => 200 + ).body + Fog::JSON.decode(response) + end + end + end + end +end diff --git a/lib/fog/aws/iam.rb b/lib/fog/aws/iam.rb index ea7370273..aacac6849 100644 --- a/lib/fog/aws/iam.rb +++ b/lib/fog/aws/iam.rb @@ -25,7 +25,7 @@ module Fog request :create_role request :create_user request :delete_access_key - request :delete_account_password_policy + request :delete_account_password_policy request :delete_account_alias request :delete_group request :delete_group_policy @@ -120,6 +120,27 @@ module Fog :created_at => Time.now, :policies => {} } + end, + :roles => Hash.new do |rhash, rkey| + rhash[rkey] = { + :role_id => Fog::AWS::Mock.key_id, + :arn => "arn:aws:iam:#{Fog::AWS::Mock.owner_id}:role/#{rkey}", + :create_date => Time.now, + :assume_role_policy_document => { + "Version" => "2012-10-17", + "Statement" => [ + { + "Effect" => "Allow", + "Principal" => { + "Service" => [ + "ec2.amazonaws.com" + ] + }, + "Action" => ["sts:AssumeRole"] + } + ] + }, + } end } end diff --git a/lib/fog/aws/parsers/sts/assume_role_with_web_identity.rb b/lib/fog/aws/parsers/sts/assume_role_with_web_identity.rb new file mode 100644 index 000000000..b90b81d38 --- /dev/null +++ b/lib/fog/aws/parsers/sts/assume_role_with_web_identity.rb @@ -0,0 +1,20 @@ +module Fog + module Parsers + module AWS + module STS + class AssumeRoleWithWithWebIdentity < Fog::Parsers::Base + def reset + @response = {} + end + + def end_element(name) + case name + when 'AssumedRoleUser', 'Audience', 'Credentials', 'PackedPolicySize', 'Provider', 'SubjectFromWebIdentityToken' + @response[name] = @value.strip + end + end + end + end + end + end +end diff --git a/lib/fog/aws/requests/federation/get_signin_token.rb b/lib/fog/aws/requests/federation/get_signin_token.rb new file mode 100644 index 000000000..c886225c9 --- /dev/null +++ b/lib/fog/aws/requests/federation/get_signin_token.rb @@ -0,0 +1,20 @@ +module Fog + module AWS + class Federation + class Real + def get_signin_token(session) + + request('getSigninToken', CGI.escape(Fog::JSON.encode(session))) + end + end + + class Mock + def get_signin_token(session) + { + 'SigninToken' => Fog::Mock.random_base64(752) + } + end + end + end + end +end diff --git a/lib/fog/aws/requests/iam/create_role.rb b/lib/fog/aws/requests/iam/create_role.rb index d37f7df47..1eddc94a6 100644 --- a/lib/fog/aws/requests/iam/create_role.rb +++ b/lib/fog/aws/requests/iam/create_role.rb @@ -50,6 +50,30 @@ module Fog ) end end + + class Mock + def create_role(role_name, assume_role_policy_document, path = '/') + if data[:roles].key?(role_name) + raise Fog::AWS::IAM::EntityAlreadyExists.new("Role with name #{role_name} already exists") + else + data[:roles][role_name][:path] = path + Excon::Response.new.tap do |response| + response.body = { + 'Role' => { + 'Arn' => data[:roles][role_name][:arn].strip, + 'AssumeRolePolicyDocument' => Fog::JSON.encode(data[:roles][role_name][:assume_role_policy_document]), + 'CreateDate' => data[:roles][role_name][:create_date], + 'Path' => path, + 'RoleId' => data[:roles][role_name][:role_id].strip, + 'RoleName' => role_name, + }, + 'RequestId' => Fog::AWS::Mock.request_id + } + response.status = 200 + end + end + end + end end end end diff --git a/lib/fog/aws/requests/iam/delete_role.rb b/lib/fog/aws/requests/iam/delete_role.rb index 02c2c548b..9da27e5ce 100644 --- a/lib/fog/aws/requests/iam/delete_role.rb +++ b/lib/fog/aws/requests/iam/delete_role.rb @@ -25,6 +25,22 @@ module Fog ) end end + + class Mock + def delete_role(role_name) + role = data[:roles][role_name] + + if role + data[:roles].delete(role_name) + Excon::Response.new.tap do |response| + response.status = 200 + response.body = { 'RequestId' => Fog::AWS::Mock.request_id } + end + else + raise Fog::AWS::IAM::NotFound.new("The role with name #{role_name} cannot be found.") + end + end + end end end end diff --git a/lib/fog/aws/requests/iam/get_role.rb b/lib/fog/aws/requests/iam/get_role.rb index c60f8999d..b3fa22af8 100644 --- a/lib/fog/aws/requests/iam/get_role.rb +++ b/lib/fog/aws/requests/iam/get_role.rb @@ -31,6 +31,29 @@ module Fog ) end end + + class Mock + def get_role(role_name) + role = self.data[:roles][role_name] + + raise Fog::AWS::IAM::NotFound.new("The role with name #{role_name} cannot be found") unless role + + Excon::Response.new.tap do |response| + response.body = { + 'Role' => { + 'Arn' => role[:arn].strip, + 'AssumeRolePolicyDocument' => Fog::JSON.encode(role[:assume_role_policy_document]), + 'CreateDate' => role[:create_date], + 'Path' => role[:path], + 'RoleId' => role[:role_id].strip, + 'RoleName' => role_name, + }, + 'RequestId' => Fog::AWS::Mock.request_id + } + response.status = 200 + end + end + end end end end diff --git a/lib/fog/aws/requests/iam/list_roles.rb b/lib/fog/aws/requests/iam/list_roles.rb index 18267c1be..e6cd82dec 100644 --- a/lib/fog/aws/requests/iam/list_roles.rb +++ b/lib/fog/aws/requests/iam/list_roles.rb @@ -36,6 +36,28 @@ module Fog }.merge!(options)) end end + + class Mock + def list_roles(options={}) + Excon::Response.new.tap do |response| + response.body = { + 'Roles' => data[:roles].map do |role, data| + { + 'Arn' => data[:arn].strip, + 'AssumeRolePolicyDocument' => Fog::JSON.encode(data[:assume_role_policy_document]), + 'RoleId' => data[:role_id], + 'Path' => data[:path], + 'RoleName' => role, + 'CreateDate' => data[:create_date], + } + end, + 'RequestId' => Fog::AWS::Mock.request_id, + 'IsTruncated' => false, + } + response.status = 200 + end + end + end end end end diff --git a/lib/fog/aws/requests/sts/assume_role_with_web_identity.rb b/lib/fog/aws/requests/sts/assume_role_with_web_identity.rb new file mode 100644 index 000000000..8a81dcf1b --- /dev/null +++ b/lib/fog/aws/requests/sts/assume_role_with_web_identity.rb @@ -0,0 +1,45 @@ +module Fog + module AWS + class STS + class Real + require 'fog/aws/parsers/sts/assume_role_with_web_identity' + + def assume_role_with_web_identity(role_arn, web_identity_token, role_session_name, options={}) + request_unsigned( + 'Action' => 'AssumeRoleWithWebIdentity', + 'RoleArn' => role_arn, + 'RoleSessionName' => role_session_name, + 'DurationSeconds' => options[:duration] || 3600, + :idempotent => true, + :parser => Fog::Parsers::AWS::STS::AssumeRoleWithWebIdentity.new + ) + end + end + + class Mock + def assume_role_with_web_identity(role_arn, web_identity_token, role_session_name, options={}) + role = options[:iam].data[:roles].values.detect { |r| r[:arn] == role_arn } + + Excon::Response.new.tap do |response| + response.body = { + 'AssumedRoleUser' => { + 'Arn' => role[:arn], + 'AssumedRoleId' => role[:role_id] + }, + 'Audience' => 'fog', + 'Credentials' => { + 'AccessKeyId' => Fog::AWS::Mock.key_id(20), + 'Expiration' => options[:expiration] || Time.now + 3600, + 'SecretAccessKey' => Fog::AWS::Mock.key_id(40), + 'SessionToken' => Fog::Mock.random_hex(8) + }, + 'Provider' => 'fog', + 'SubjectFromWebIdentityToken' => Fog::Mock.random_hex(8) + } + response.status = 200 + end + end + end + end + end +end diff --git a/lib/fog/aws/sts.rb b/lib/fog/aws/sts.rb index 8391c67bb..43e931dc1 100644 --- a/lib/fog/aws/sts.rb +++ b/lib/fog/aws/sts.rb @@ -16,6 +16,7 @@ module Fog request :get_session_token request :assume_role request :assume_role_with_saml + request :assume_role_with_web_identity class Mock def self.data diff --git a/tests/requests/federation/get_signin_token_tests.rb b/tests/requests/federation/get_signin_token_tests.rb new file mode 100644 index 000000000..c85f18b17 --- /dev/null +++ b/tests/requests/federation/get_signin_token_tests.rb @@ -0,0 +1,11 @@ +Shindo.tests('AWS::Federation | signin tokens', ['aws']) do + @signin_token_format = { + 'SigninToken' => String + } + + tests("#get_signin_token").formats(@signin_token_format) do + pending unless Fog.mocking? + + Fog::AWS[:federation].get_signin_token("test_policy") + end +end diff --git a/tests/requests/iam/role_tests.rb b/tests/requests/iam/role_tests.rb index 0c62531a1..0a6cdccdc 100644 --- a/tests/requests/iam/role_tests.rb +++ b/tests/requests/iam/role_tests.rb @@ -2,42 +2,32 @@ Shindo.tests('AWS::IAM | role requests', ['aws']) do tests('success') do @role = { - 'Arn' => String, + 'Arn' => String, 'AssumeRolePolicyDocument' => String, - 'CreateDate' => Time, - 'Path' => String, - 'RoleId' => String, - 'RoleName' => String + 'CreateDate' => Time, + 'Path' => String, + 'RoleId' => String, + 'RoleName' => String } @role_format = { 'Role' => @role, 'RequestId' => String } tests("#create_role('fogrole')").formats(@role_format) do - pending if Fog.mocking? Fog::AWS[:iam].create_role('fogrole', Fog::AWS::IAM::EC2_ASSUME_ROLE_POLICY).body end tests("#get_role('fogrole')").formats(@role_format) do - pending if Fog.mocking? Fog::AWS[:iam].get_role('fogrole').body end @list_roles_format = { - 'Roles' => [{ - 'Arn' => String, - 'AssumeRolePolicyDocument' => String, - 'CreateDate' => Time, - 'Path' => String, - 'RoleId' => String, - 'RoleName' => String - }], - 'RequestId' => String, + 'Roles' => [@role], + 'RequestId' => String, 'IsTruncated' => Fog::Boolean, } tests("#list_roles").formats(@list_roles_format) do - pending if Fog.mocking? body = Fog::AWS[:iam].list_roles.body returns(true){!! body['Roles'].find {|role| role['RoleName'] == 'fogrole'}} body @@ -159,7 +149,6 @@ Shindo.tests('AWS::IAM | role requests', ['aws']) do end tests("#delete_role('fogrole'").formats(AWS::IAM::Formats::BASIC) do - pending if Fog.mocking? Fog::AWS[:iam].delete_role('fogrole').body end end diff --git a/tests/requests/sts/assume_role_tests.rb b/tests/requests/sts/assume_role_tests.rb index cce659fcf..a5af987c6 100644 --- a/tests/requests/sts/assume_role_tests.rb +++ b/tests/requests/sts/assume_role_tests.rb @@ -1,19 +1,19 @@ Shindo.tests('AWS::STS | assume role', ['aws']) do - @policy = {"Statement" => [{"Effect" => "Allow", "Action" => "*", "Resource" => "*"}]} + @policy = {"Statement" => [{"Effect" => "Allow", "Action" => "*", "Resource" => "*"}]} - @response_format = { - 'SessionToken' => String, - 'SecretAccessKey' => String, - 'Expiration' => String, - 'AccessKeyId' => String, - 'Arn' => String, - 'RequestId' => String - } + @response_format = { + 'SessionToken' => String, + 'SecretAccessKey' => String, + 'Expiration' => String, + 'AccessKeyId' => String, + 'Arn' => String, + 'RequestId' => String + } - tests("#assume_role('rolename', 'assumed_role_session', 'external_id', #{@policy.inspect}, 900)").formats(@response_format) do - pending if Fog.mocking? - Fog::AWS[:sts].assume_role("rolename","assumed_role_session","external_id", @policy, 900).body - end + tests("#assume_role('rolename', 'assumed_role_session', 'external_id', #{@policy.inspect}, 900)").formats(@response_format) do + pending if Fog.mocking? + Fog::AWS[:sts].assume_role("rolename","assumed_role_session","external_id", @policy, 900).body + end end diff --git a/tests/requests/sts/assume_role_with_saml_tests.rb b/tests/requests/sts/assume_role_with_saml_tests.rb index e482365d6..5a55d9ed4 100644 --- a/tests/requests/sts/assume_role_with_saml_tests.rb +++ b/tests/requests/sts/assume_role_with_saml_tests.rb @@ -1,18 +1,18 @@ Shindo.tests('AWS::STS | assume role with SAML', ['aws']) do - @policy = {"Statement" => [{"Effect" => "Allow", "Action" => "*", "Resource" => "*"}]} + @policy = {"Statement" => [{"Effect" => "Allow", "Action" => "*", "Resource" => "*"}]} - @response_format = { - 'SessionToken' => String, - 'SecretAccessKey' => String, - 'Expiration' => String, - 'AccessKeyId' => String, - 'Arn' => String, - 'RequestId' => String - } + @response_format = { + 'SessionToken' => String, + 'SecretAccessKey' => String, + 'Expiration' => String, + 'AccessKeyId' => String, + 'Arn' => String, + 'RequestId' => String + } - tests("#assume_role_with_saml('role_arn', 'principal_arn', 'saml_assertion', #{@policy.inspect}, 900)").formats(@response_format) do - pending if Fog.mocking? - Fog::AWS[:sts].assume_role_with_saml("role_arn","principal_arn","saml_assertion", @policy, 900).body - end + tests("#assume_role_with_saml('role_arn', 'principal_arn', 'saml_assertion', #{@policy.inspect}, 900)").formats(@response_format) do + pending if Fog.mocking? + Fog::AWS[:sts].assume_role_with_saml("role_arn","principal_arn","saml_assertion", @policy, 900).body + end end diff --git a/tests/requests/sts/assume_role_with_web_identity_tests.rb b/tests/requests/sts/assume_role_with_web_identity_tests.rb new file mode 100644 index 000000000..0134cc991 --- /dev/null +++ b/tests/requests/sts/assume_role_with_web_identity_tests.rb @@ -0,0 +1,26 @@ +Shindo.tests('AWS::STS | assume role with web identity', ['aws']) do + @sts = Fog::AWS[:sts] + @iam = Fog::AWS[:iam] + @role = @iam.create_role('sts', Fog::AWS::IAM::EC2_ASSUME_ROLE_POLICY).body['Role'] + @token = Fog::AWS::Mock.key_id + + @response_format = { + 'AssumedRoleUser' => { + 'Arn' => String, + 'AssumedRoleId' => String, + }, + 'Audience' => String, + 'Credentials' => { + 'AccessKeyId' => String, + 'Expiration' => Time, + 'SecretAccessKey' => String, + 'SessionToken' => String, + }, + 'Provider' => String, + 'SubjectFromWebIdentityToken' => String, + } + + tests("#assume_role_with_web_identity('#{@role['Arn']}', '#{@token}', 'fog')").formats(@response_format) do + @sts.assume_role_with_web_identity(@role['Arn'], @token, 'fog', :iam => @iam).body + end +end