File: credential_fetcher.rb

package info (click to toggle)
ruby-fog-aws 3.8.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 8,092 kB
  • sloc: ruby: 72,795; javascript: 14; makefile: 9; sh: 4
file content (139 lines) | stat: -rw-r--r-- 6,174 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# frozen_string_literal: true

module Fog
  module AWS
    module CredentialFetcher

      INSTANCE_METADATA_HOST = "http://169.254.169.254"
      INSTANCE_METADATA_TOKEN = "/latest/api/token"
      INSTANCE_METADATA_PATH = "/latest/meta-data/iam/security-credentials/"
      INSTANCE_METADATA_AZ = "/latest/meta-data/placement/availability-zone/"

      CONTAINER_CREDENTIALS_HOST = "http://169.254.170.2"

      STS_GLOBAL_ENDPOINT = "https://sts.amazonaws.com"

      module ServiceMethods
        def fetch_credentials(options)
          if options[:use_iam_profile] && Fog.mocking?
            return Fog::AWS::Compute::Mock.data[:iam_role_based_creds]
          end
          if options[:use_iam_profile]
            begin
              role_data = nil
              region = options[:region]

              if ENV["AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"]
                connection = options[:connection] || Excon.new(CONTAINER_CREDENTIALS_HOST)
                credential_path = options[:credential_path] || ENV["AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"]
                role_data = connection.get(:path => credential_path, :idempotent => true, :expects => 200).body
                session = Fog::JSON.decode(role_data)

                if region.nil?
                  connection = options[:metadata_connection] || Excon.new(INSTANCE_METADATA_HOST)
                  token_header = fetch_credentials_token_header(connection, options[:disable_imds_v2])
                  region = connection.get(:path => INSTANCE_METADATA_AZ, :idempotent => true, :expects => 200, :headers => token_header).body[0..-2]
                end
              elsif ENV["AWS_WEB_IDENTITY_TOKEN_FILE"]
                params = {
                  :Action => "AssumeRoleWithWebIdentity",
                  :RoleArn => options[:role_arn] || ENV.fetch("AWS_ROLE_ARN"),
                  :RoleSessionName => options[:role_session_name] || ENV.fetch("AWS_ROLE_SESSION_NAME"),
                  :WebIdentityToken => File.read(options[:aws_web_identity_token_file] || ENV.fetch("AWS_WEB_IDENTITY_TOKEN_FILE")),
                  :Version => "2011-06-15",
                }
                connection = options[:connection] || Excon.new(STS_GLOBAL_ENDPOINT, :query => params)
                document = Nokogiri::XML(connection.get(:idempotent => true, :expects => 200).body)

                session = {
                  "AccessKeyId" => document.css("AccessKeyId").children.text,
                  "SecretAccessKey" => document.css("SecretAccessKey").children.text,
                  "Token" => document.css("SessionToken").children.text,
                  "Expiration" => document.css("Expiration").children.text,
                }

                if region.nil?
                  connection = options[:metadata_connection] || Excon.new(INSTANCE_METADATA_HOST)
                  token_header = fetch_credentials_token_header(connection, options[:disable_imds_v2])
                  region = connection.get(:path => INSTANCE_METADATA_AZ, :idempotent => true, :expects => 200, :headers => token_header).body[0..-2]
                end
              else
                connection = options[:connection] || Excon.new(INSTANCE_METADATA_HOST)
                token_header = fetch_credentials_token_header(connection, options[:disable_imds_v2])
                role_name = connection.get(:path => INSTANCE_METADATA_PATH, :idempotent => true, :expects => 200, :headers => token_header).body
                role_data = connection.get(:path => INSTANCE_METADATA_PATH+role_name, :idempotent => true, :expects => 200, :headers => token_header).body
                session = Fog::JSON.decode(role_data)
                
                region ||= connection.get(:path => INSTANCE_METADATA_AZ, :idempotent => true, :expects => 200, :headers => token_header).body[0..-2]
              end
              
              credentials = {}
              credentials[:aws_access_key_id] = session['AccessKeyId']
              credentials[:aws_secret_access_key] = session['SecretAccessKey']
              credentials[:aws_session_token] = session['Token']
              credentials[:aws_credentials_expire_at] = Time.xmlschema session['Expiration']
              
              # set region by default to the one the instance is in.
              credentials[:region] = region
              #these indicate the metadata service is unavailable or has no profile setup
              credentials
            rescue Excon::Error => e
              Fog::Logger.warning("Unable to fetch credentials: #{e.message}")
              super
            end
          else
            super
          end
        end

        def fetch_credentials_token_header(connection, disable_imds_v2)
          return nil if disable_imds_v2

          token = connection.put(
            :path => INSTANCE_METADATA_TOKEN,
            :idempotent => true,
            :expects => 200,
            :retry_interval => 1,
            :retry_limit => 3,
            :read_timeout => 1,
            :write_timeout => 1,
            :connect_timeout => 1,
            :headers => { "X-aws-ec2-metadata-token-ttl-seconds" => "300" }
          ).body

          { "X-aws-ec2-metadata-token" => token }
        rescue Excon::Error
          nil
        end
      end

      module ConnectionMethods
        def refresh_credentials_if_expired
          refresh_credentials if credentials_expired?
        end

        private

        def credentials_expired?
          @use_iam_profile &&
            (!@aws_credentials_expire_at ||
             (@aws_credentials_expire_at && Fog::Time.now > @aws_credentials_expire_at - 15)) #new credentials become available from around 5 minutes before expiration time
        end

        def refresh_credentials
          if @use_iam_profile
            new_credentials = service.fetch_credentials :use_iam_profile => @use_iam_profile, :region => @region
            if new_credentials.any?
              setup_credentials new_credentials
              return true
            else
              false
            end
          else
            false
          end
        end
      end
    end
  end
end