class Gem::S3URISigner - Documentation for Ruby 4.0
Description: class Gem::S3URISigner: docs. aws.
Keywords: ruby,class,Gem::S3URISigner

🔍

No recent searches

# class Gem::S3URISigner

`S3URISigner` implements AWS SigV4 for S3 Source to avoid a dependency on the aws-sdk-* gems More on AWS SigV4: docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html

### Constants

BASE64_URI_TRANSLATE
EC2_IAM_INFO
EC2_IAM_SECURITY_CREDENTIALS
EC2_IAM_TOKEN
S3Config

### Attributes

method [RW]
uri [RW]

### Public Class Methods

new (uri, method)
Source

    # File lib/rubygems/s3_uri_signer.rb, line 35
    def initialize(uri, method)
      @uri = uri
      @method = method
    end

### Public Instance Methods

sign (expiration = 86_400)
Source

    # File lib/rubygems/s3_uri_signer.rb, line 42
    def sign(expiration = 86_400)
      s3_config = fetch_s3_config

      current_time = Time.now.utc
      date_time = current_time.strftime("%Y%m%dT%H%M%SZ")
      date = date_time[0,8]

      credential_info = "#{date}/#{s3_config.region}/s3/aws4_request"
      canonical_host = "#{uri.host}.s3.#{s3_config.region}.amazonaws.com"

      query_params = generate_canonical_query_params(s3_config, date_time, credential_info, expiration)
      canonical_request = generate_canonical_request(canonical_host, query_params)
      string_to_sign = generate_string_to_sign(date_time, credential_info, canonical_request)
      signature = generate_signature(s3_config, date, string_to_sign)

      Gem::URI.parse("https://#{canonical_host}#{uri.path}?#{query_params}&X-Amz-Signature=#{signature}")
    end

Signs S3 `URI` using query-params according to the reference: docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html

### Private Instance Methods

base64_uri_escape (str)
Source

    # File lib/rubygems/s3_uri_signer.rb, line 143
    def base64_uri_escape(str)
      str.gsub(%r{[\+/=\n]}, BASE64_URI_TRANSLATE)
    end

create_request_pool (uri)
Source

    # File lib/rubygems/s3_uri_signer.rb, line 216
    def create_request_pool(uri)
      proxy_uri = Gem::Request.proxy_uri(Gem::Request.get_proxy_from_env(uri.scheme))
      certs = Gem::Request.get_cert_files
      Gem::Request::ConnectionPools.new(proxy_uri, certs).pool_for(uri)
    end

ec2_iam_request (uri, verb)
Source

    # File lib/rubygems/s3_uri_signer.rb, line 211
    def ec2_iam_request(uri, verb)
      @request_pool ||= create_request_pool(uri)
      Gem::Request.new(uri, verb, nil, @request_pool)
    end

ec2_metadata_credentials_imds_v1 ()
Source

    # File lib/rubygems/s3_uri_signer.rb, line 172
    def ec2_metadata_credentials_imds_v1
      iam_info = ec2_metadata_request(EC2_IAM_INFO, token: nil)
      # Expected format: arn:aws:iam::<id>:instance-profile/<role_name>
      role_name = iam_info["InstanceProfileArn"].split("/").last
      ec2_metadata_request(EC2_IAM_SECURITY_CREDENTIALS + role_name, token: nil)
    end

ec2_metadata_credentials_imds_v2 ()
Source

    # File lib/rubygems/s3_uri_signer.rb, line 164
    def ec2_metadata_credentials_imds_v2
      token = ec2_metadata_token
      iam_info = ec2_metadata_request(EC2_IAM_INFO, token:)
      # Expected format: arn:aws:iam::<id>:instance-profile/<role_name>
      role_name = iam_info["InstanceProfileArn"].split("/").last
      ec2_metadata_request(EC2_IAM_SECURITY_CREDENTIALS + role_name, token:)
    end

ec2_metadata_credentials_json ()
Source

    # File lib/rubygems/s3_uri_signer.rb, line 147
    def ec2_metadata_credentials_json
      require_relative "vendored_net_http"
      require_relative "request"
      require_relative "request/connection_pools"
      require "json"

      # First try V2 fallback to V1
      res = nil
      begin
        res = ec2_metadata_credentials_imds_v2
      rescue InstanceProfileError
        alert_warning "Unable to access ec2 credentials via IMDSv2, falling back to IMDSv1"
        res = ec2_metadata_credentials_imds_v1
      end
      res
    end

ec2_metadata_request (url, token:)
Source

    # File lib/rubygems/s3_uri_signer.rb, line 179
    def ec2_metadata_request(url, token:)
      request = ec2_iam_request(Gem::URI(url), Gem::Net::HTTP::Get)

      response = request.fetch do |req|
        if token
          req.add_field "X-aws-ec2-metadata-token", token
        end
      end

      case response
      when Gem::Net::HTTPOK then
        JSON.parse(response.body)
      else
        raise InstanceProfileError.new("Unable to fetch AWS metadata from #{uri}: #{response.message} #{response.code}")
      end
    end

ec2_metadata_token ()
Source

    # File lib/rubygems/s3_uri_signer.rb, line 196
    def ec2_metadata_token
      request = ec2_iam_request(Gem::URI(EC2_IAM_TOKEN), Gem::Net::HTTP::Put)

      response = request.fetch do |req|
        req.add_field "X-aws-ec2-metadata-token-ttl-seconds", 60
      end

      case response
      when Gem::Net::HTTPOK then
        response.body
      else
        raise InstanceProfileError.new("Unable to fetch AWS metadata from #{uri}: #{response.message} #{response.code}")
      end
    end

fetch_s3_config ()
Source

    # File lib/rubygems/s3_uri_signer.rb, line 110
    def fetch_s3_config
      return S3Config.new(uri.user, uri.password, nil, "us-east-1") if uri.user && uri.password

      s3_source = Gem.configuration[:s3_source] || Gem.configuration["s3_source"]
      host = uri.host
      raise ConfigurationError.new("no s3_source key exists in .gemrc") unless s3_source

      auth = s3_source[host] || s3_source[host.to_sym]
      raise ConfigurationError.new("no key for host #{host} in s3_source in .gemrc") unless auth

      provider = auth[:provider] || auth["provider"]
      case provider
      when "env"
        id = ENV["AWS_ACCESS_KEY_ID"]
        secret = ENV["AWS_SECRET_ACCESS_KEY"]
        security_token = ENV["AWS_SESSION_TOKEN"]
      when "instance_profile"
        credentials = ec2_metadata_credentials_json
        id = credentials["AccessKeyId"]
        secret = credentials["SecretAccessKey"]
        security_token = credentials["Token"]
      else
        id = auth[:id] || auth["id"]
        secret = auth[:secret] || auth["secret"]
        security_token = auth[:security_token] || auth["security_token"]
      end

      raise ConfigurationError.new("s3_source for #{host} missing id or secret") unless id && secret

      region = auth[:region] || auth["region"] || "us-east-1"
      S3Config.new(id, secret, security_token, region)
    end

Extracts S3 configuration for S3 bucket

generate_canonical_query_params (s3_config, date_time, credential_info, expiration)
Source

    # File lib/rubygems/s3_uri_signer.rb, line 64
    def generate_canonical_query_params(s3_config, date_time, credential_info, expiration)
      canonical_params = {}
      canonical_params["X-Amz-Algorithm"] = "AWS4-HMAC-SHA256"
      canonical_params["X-Amz-Credential"] = "#{s3_config.access_key_id}/#{credential_info}"
      canonical_params["X-Amz-Date"] = date_time
      canonical_params["X-Amz-Expires"] = expiration.to_s
      canonical_params["X-Amz-SignedHeaders"] = "host"
      canonical_params["X-Amz-Security-Token"] = s3_config.security_token if s3_config.security_token

      # Sorting is required to generate proper signature
      canonical_params.sort.to_h.map do |key, value|
        "#{base64_uri_escape(key)}=#{base64_uri_escape(value)}"
      end.join("&")
    end

generate_canonical_request (canonical_host, query_params)
Source

    # File lib/rubygems/s3_uri_signer.rb, line 79
    def generate_canonical_request(canonical_host, query_params)
      [
        method.upcase,
        uri.path,
        query_params,
        "host:#{canonical_host}",
        "", # empty params
        "host",
        "UNSIGNED-PAYLOAD",
      ].join("\n")
    end

generate_signature (s3_config, date, string_to_sign)
Source

    # File lib/rubygems/s3_uri_signer.rb, line 100
    def generate_signature(s3_config, date, string_to_sign)
      date_key = OpenSSL::HMAC.digest("sha256", "AWS4" + s3_config.secret_access_key, date)
      date_region_key = OpenSSL::HMAC.digest("sha256", date_key, s3_config.region)
      date_region_service_key = OpenSSL::HMAC.digest("sha256", date_region_key, "s3")
      signing_key = OpenSSL::HMAC.digest("sha256", date_region_service_key, "aws4_request")
      OpenSSL::HMAC.hexdigest("sha256", signing_key, string_to_sign)
    end

generate_string_to_sign (date_time, credential_info, canonical_request)
Source

    # File lib/rubygems/s3_uri_signer.rb, line 91
    def generate_string_to_sign(date_time, credential_info, canonical_request)
      [
        "AWS4-HMAC-SHA256",
        date_time,
        credential_info,
        OpenSSL::Digest::SHA256.hexdigest(canonical_request),
      ].join("\n")
    end