diff --git a/lib/fog/aws/parsers/sts/assume_role_with_saml.rb b/lib/fog/aws/parsers/sts/assume_role_with_saml.rb new file mode 100644 index 000000000..34ade15b8 --- /dev/null +++ b/lib/fog/aws/parsers/sts/assume_role_with_saml.rb @@ -0,0 +1,26 @@ +module Fog + module Parsers + module AWS + module STS + class AssumeRoleWithSAML < Fog::Parsers::Base + def reset + @response = {} + end + + def end_element(name) + case name + when 'SessionToken', 'SecretAccessKey', 'Expiration', 'AccessKeyId' + @response[name] = @value.strip + when 'Arn', 'AssumedRoleId' + @response[name] = @value.strip + 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/assume_role_with_saml.rb b/lib/fog/aws/requests/sts/assume_role_with_saml.rb new file mode 100644 index 000000000..9d0665ee6 --- /dev/null +++ b/lib/fog/aws/requests/sts/assume_role_with_saml.rb @@ -0,0 +1,44 @@ +module Fog + module AWS + class STS + class Real + require 'fog/aws/parsers/sts/assume_role_with_saml' + + # Assume Role with SAML + # + # ==== Parameters + # * role_arn<~String> - The ARN of the role the caller is assuming. + # * principal_arn<~String> - The Amazon Resource Name (ARN) of the SAML provider in IAM that describes the IdP. + # * saml_assertion<~String> - The base-64 encoded SAML authentication response provided by the IdP. + # * policy<~String> - An optional JSON policy document + # * duration<~Integer> - Duration (of seconds) for the assumed role credentials to be valid (default 3600) + # + # ==== Returns + # * response<~Excon::Response>: + # * body<~Hash>: + # * 'Arn'<~String>: The ARN of the assumed role/user + # * 'AccessKeyId'<~String>: The AWS access key of the temporary credentials for the assumed role + # * 'SecretAccessKey'<~String>: The AWS secret key of the temporary credentials for the assumed role + # * 'SessionToken'<~String>: The AWS session token of the temporary credentials for the assumed role + # * 'Expiration'<~Time>: The expiration time of the temporary credentials for the assumed role + # + # ==== See Also + # http://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithSAML.html + # + + def assume_role_with_saml(role_arn, principal_arn, saml_assertion, policy=nil, duration=3600) + request_unsigned({ + 'Action' => 'AssumeRoleWithSAML', + 'RoleArn' => role_arn, + 'PrincipalArn' => principal_arn, + 'SAMLAssertion' => saml_assertion, + 'Policy' => policy && Fog::JSON.encode(policy), + 'DurationSeconds' => duration, + :idempotent => true, + :parser => Fog::Parsers::AWS::STS::AssumeRoleWithSAML.new + }) + end + end + end + end +end diff --git a/lib/fog/aws/sts.rb b/lib/fog/aws/sts.rb index c5c0b8ca7..8391c67bb 100644 --- a/lib/fog/aws/sts.rb +++ b/lib/fog/aws/sts.rb @@ -7,14 +7,15 @@ module Fog class EntityAlreadyExists < Fog::AWS::STS::Error; end class ValidationError < Fog::AWS::STS::Error; end + class AwsAccessKeysMissing < Fog::AWS::STS::Error; end - requires :aws_access_key_id, :aws_secret_access_key - recognizes :host, :path, :port, :scheme, :persistent, :aws_session_token, :use_iam_profile, :aws_credentials_expire_at, :instrumentor, :instrumentor_name + recognizes :aws_access_key_id, :aws_secret_access_key, :host, :path, :port, :scheme, :persistent, :aws_session_token, :use_iam_profile, :aws_credentials_expire_at, :instrumentor, :instrumentor_name request_path 'fog/aws/requests/sts' request :get_federation_token request :get_session_token request :assume_role + request :assume_role_with_saml class Mock def self.data @@ -99,10 +100,16 @@ module Fog @aws_session_token = options[:aws_session_token] @aws_credentials_expire_at = options[:aws_credentials_expire_at] - @signer = Fog::AWS::SignatureV4.new(@aws_access_key_id, @aws_secret_access_key, 'us-east-1', 'sts') + if (@aws_access_key_id && @aws_secret_access_key) + @signer = Fog::AWS::SignatureV4.new(@aws_access_key_id, @aws_secret_access_key, 'us-east-1', 'sts') + end end def request(params) + if (@signer == nil) + raise AwsAccessKeysMissing.new("Can't make unsigned requests, need aws_access_key_id and aws_secret_access_key") + end + idempotent = params.delete(:idempotent) parser = params.delete(:parser) @@ -129,6 +136,30 @@ module Fog end end + def request_unsigned(params) + idempotent = params.delete(:idempotent) + parser = params.delete(:parser) + + params['Version'] = '2011-06-15' + + headers = { 'Content-Type' => 'application/x-www-form-urlencoded', 'Host' => @host } + body = '' + for key in params.keys.sort + unless (value = params[key]).nil? + body << "#{key}=#{Fog::AWS.escape(value.to_s)}&" + end + end + body.chop! + + if @instrumentor + @instrumentor.instrument("#{@instrumentor_name}.request", params) do + _request(body, headers, idempotent, parser) + end + else + _request(body, headers, idempotent, parser) + end + end + def _request(body, headers, idempotent, parser) @connection.request({ :body => body, diff --git a/tests/aws/requests/sts/assume_role_with_saml_tests.rb b/tests/aws/requests/sts/assume_role_with_saml_tests.rb new file mode 100644 index 000000000..e482365d6 --- /dev/null +++ b/tests/aws/requests/sts/assume_role_with_saml_tests.rb @@ -0,0 +1,18 @@ +Shindo.tests('AWS::STS | assume role with SAML', ['aws']) do + + @policy = {"Statement" => [{"Effect" => "Allow", "Action" => "*", "Resource" => "*"}]} + + @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 +end