Skip to content

Commit 44c805e

Browse files
saketu-awsdljvette
authored andcommitted
[Agent 1.2.1] Add fallback to IMDSv1
Prior to this change, there was no logic for falling back to IMDSv1 if IMDSv2 calls failed. This change adds fallbacks to IMDSv1 if calls fail. * Unit Tests : [Y] * Integration Tests : Successfully had a CodeDeploy deployment using this agent.
1 parent f16c5a3 commit 44c805e

File tree

4 files changed

+130
-53
lines changed

4 files changed

+130
-53
lines changed

bin/install

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,29 @@ class Proxy
2525
end
2626
end
2727

28+
log_file_path = "/tmp/codedeploy-agent.update.log"
29+
30+
require 'logger'
31+
32+
if($stdout.isatty)
33+
# if we are being run in a terminal, log to stdout and the log file.
34+
@log = Logger.new(Proxy.new(File.open(log_file_path, 'a+'), $stdout))
35+
else
36+
# keep at most 2MB of old logs rotating out 1MB at a time
37+
@log = Logger.new(log_file_path, 2, 1048576)
38+
# make sure anything coming out of ruby ends up in the log file
39+
$stdout.reopen(log_file_path, 'a+')
40+
$stderr.reopen(log_file_path, 'a+')
41+
end
42+
43+
@log.level = Logger::INFO
44+
2845
require 'net/http'
2946
require 'json'
3047

48+
TOKEN_PATH = '/latest/api/token'
49+
DOCUMENT_PATH = '/latest/dynamic/instance-identity/document'
50+
3151
class IMDSV2
3252
def self.region
3353
doc['region'].strip
@@ -50,35 +70,25 @@ class IMDSV2
5070
http_request(request)
5171
end
5272

53-
def self.get_request(path, token)
73+
def self.get_request(path, token = nil)
5474
request = Net::HTTP::Get.new(path)
55-
request['X-aws-ec2-metadata-token'] = token
75+
unless token.nil?
76+
request['X-aws-ec2-metadata-token'] = token
77+
end
5678
http_request(request)
5779
end
5880

5981
def self.doc
60-
token = put_request('/latest/api/token')
61-
JSON.parse(get_request('/latest/dynamic/instance-identity/document', token).strip)
82+
begin
83+
token = put_request(TOKEN_PATH)
84+
JSON.parse(get_request(DOCUMENT_PATH, token).strip)
85+
rescue
86+
@log.info("IMDSv2 http request failed, falling back to IMDSv1.")
87+
JSON.parse(get_request(DOCUMENT_PATH).strip)
88+
end
6289
end
6390
end
6491

65-
log_file_path = "/tmp/codedeploy-agent.update.log"
66-
67-
require 'logger'
68-
69-
if($stdout.isatty)
70-
# if we are being run in a terminal, log to stdout and the log file.
71-
@log = Logger.new(Proxy.new(File.open(log_file_path, 'a+'), $stdout))
72-
else
73-
# keep at most 2MB of old logs rotating out 1MB at a time
74-
@log = Logger.new(log_file_path, 2, 1048576)
75-
# make sure anything coming out of ruby ends up in the log file
76-
$stdout.reopen(log_file_path, 'a+')
77-
$stderr.reopen(log_file_path, 'a+')
78-
end
79-
80-
@log.level = Logger::INFO
81-
8292
begin
8393
require 'fileutils'
8494
require 'openssl'

bin/update

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,30 @@ class Proxy
2525
end
2626
end
2727

28+
require 'tmpdir'
29+
require 'logger'
30+
31+
log_file_path = "#{Dir.tmpdir()}/codedeploy-agent.update.log"
32+
33+
if($stdout.isatty)
34+
# if we are being run in a terminal, log to stdout and the log file.
35+
@log = Logger.new(Proxy.new(File.open(log_file_path, 'a+'), $stdout))
36+
else
37+
# keep at most 2MB of old logs rotating out 1MB at a time
38+
@log = Logger.new(log_file_path, 2, 1048576)
39+
# make sure anything coming out of ruby ends up in the log file
40+
$stdout.reopen(log_file_path, 'a+')
41+
$stderr.reopen(log_file_path, 'a+')
42+
end
43+
44+
@log.level = Logger::INFO
45+
2846
require 'net/http'
2947
require 'json'
3048

49+
TOKEN_PATH = '/latest/api/token'
50+
DOCUMENT_PATH = '/latest/dynamic/instance-identity/document'
51+
3152
class IMDSV2
3253
def self.region
3354
doc['region'].strip
@@ -50,36 +71,25 @@ class IMDSV2
5071
http_request(request)
5172
end
5273

53-
def self.get_request(path, token)
74+
def self.get_request(path, token = nil)
5475
request = Net::HTTP::Get.new(path)
55-
request['X-aws-ec2-metadata-token'] = token
76+
unless token.nil?
77+
request['X-aws-ec2-metadata-token'] = token
78+
end
5679
http_request(request)
5780
end
5881

5982
def self.doc
60-
token = put_request('/latest/api/token')
61-
JSON.parse(get_request('/latest/dynamic/instance-identity/document', token).strip)
83+
begin
84+
token = put_request(TOKEN_PATH)
85+
JSON.parse(get_request(DOCUMENT_PATH, token).strip)
86+
rescue
87+
@log.info("IMDSv2 http request failed, falling back to IMDSv1.")
88+
JSON.parse(get_request(DOCUMENT_PATH).strip)
89+
end
6290
end
6391
end
6492

65-
require 'tmpdir'
66-
require 'logger'
67-
68-
log_file_path = "#{Dir.tmpdir()}/codedeploy-agent.update.log"
69-
70-
if($stdout.isatty)
71-
# if we are being run in a terminal, log to stdout and the log file.
72-
@log = Logger.new(Proxy.new(File.open(log_file_path, 'a+'), $stdout))
73-
else
74-
# keep at most 2MB of old logs rotating out 1MB at a time
75-
@log = Logger.new(log_file_path, 2, 1048576)
76-
# make sure anything coming out of ruby ends up in the log file
77-
$stdout.reopen(log_file_path, 'a+')
78-
$stderr.reopen(log_file_path, 'a+')
79-
end
80-
81-
@log.level = Logger::INFO
82-
8393
require 'set'
8494
VALID_TYPES = Set.new ['rpm','zypper','deb','msi']
8595

lib/instance_metadata.rb

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,17 @@ class InstanceMetadata
99
PORT = 80
1010
HTTP_TIMEOUT = 30
1111

12+
PARTITION_PATH = '/latest/meta-data/services/partition'
13+
INSTANCE_ID_PATH = '/latest/meta-data/instance-id'
14+
TOKEN_PATH = '/latest/api/token'
15+
DOCUMENT_PATH = '/latest/dynamic/instance-identity/document'
16+
1217
def self.host_identifier
1318
"arn:#{partition}:ec2:#{doc['region']}:#{doc['accountId']}:instance/#{doc['instanceId']}"
1419
end
1520

1621
def self.partition
17-
get_metadata_wrapper('/latest/meta-data/services/partition').strip
22+
get_metadata_wrapper(PARTITION_PATH).strip
1823
end
1924

2025
def self.region
@@ -23,7 +28,7 @@ def self.region
2328

2429
def self.instance_id
2530
begin
26-
get_metadata_wrapper('/latest/meta-data/instance-id')
31+
get_metadata_wrapper(INSTANCE_ID_PATH)
2732
rescue
2833
return nil
2934
end
@@ -34,12 +39,18 @@ class InstanceMetadataError < StandardError
3439

3540
private
3641
def self.get_metadata_wrapper(path)
37-
token = put_request('/latest/api/token')
38-
get_request(path, token)
42+
begin
43+
token = put_request(TOKEN_PATH)
44+
get_request(path, token)
45+
rescue
46+
InstanceAgent::Log.send(:info, "IMDSv2 http request failed, falling back to IMDSv1.")
47+
get_request(path)
48+
end
49+
3950
end
4051

4152
def self.http_request(request)
42-
Net::HTTP.start('169.254.169.254', 80, :read_timeout => 120, :open_timeout => 120) do |http|
53+
Net::HTTP.start(IP_ADDRESS, PORT, :read_timeout => 120, :open_timeout => 120) do |http|
4354
response = http.request(request)
4455
if response.code.to_i != 200
4556
raise "HTTP error from metadata service: #{response.message}, code #{response.code}"
@@ -54,14 +65,21 @@ def self.put_request(path)
5465
http_request(request)
5566
end
5667

57-
def self.get_request(path, token)
68+
def self.get_request(path, token = nil)
5869
request = Net::HTTP::Get.new(path)
59-
request['X-aws-ec2-metadata-token'] = token
70+
unless token.nil?
71+
request['X-aws-ec2-metadata-token'] = token
72+
end
6073
http_request(request)
6174
end
6275

6376
def self.doc
64-
token = put_request('/latest/api/token')
65-
JSON.parse(get_request('/latest/dynamic/instance-identity/document', token).strip)
77+
begin
78+
token = put_request(TOKEN_PATH)
79+
JSON.parse(get_request(DOCUMENT_PATH, token).strip)
80+
rescue
81+
InstanceAgent::Log.send(:info, "IMDSv2 http request failed, falling back to IMDSv1.")
82+
JSON.parse(get_request(DOCUMENT_PATH).strip)
83+
end
6684
end
6785
end

test/instance_metadata_test.rb

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ class InstanceMetadataTest < InstanceAgentTestCase
99
def self.should_check_status_code(&blk)
1010
should 'raise unless status code is 200' do
1111
stub_request(:get, 'http://169.254.169.254/latest/dynamic/instance-identity/document').
12-
with(headers: {'X-aws-ec2-metadata-token' => @token}).
1312
to_return(status: 503, body: @instance_document, headers: {})
1413
assert_raise(&blk)
1514
end
@@ -51,6 +50,26 @@ def self.should_check_status_code(&blk)
5150
assert_equal(@host_identifier, InstanceMetadata.host_identifier)
5251
end
5352

53+
should 'return the body if IMDSv2 http request status code is not 200' do
54+
stub_request(:get, 'http://169.254.169.254/latest/dynamic/instance-identity/document').
55+
with(headers: {'X-aws-ec2-metadata-token' => @token}).
56+
to_return(status: 503, body: @instance_document, headers: {})
57+
stub_request(:get, 'http://169.254.169.254/latest/dynamic/instance-identity/document').
58+
with(headers: {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Ruby'}).
59+
to_return(status: 200, body: @instance_document, headers: {})
60+
assert_equal(@host_identifier, InstanceMetadata.host_identifier)
61+
end
62+
63+
should 'return the body if IMDSv2 http request errors out' do
64+
stub_request(:get, 'http://169.254.169.254/latest/dynamic/instance-identity/document').
65+
with(headers: {'X-aws-ec2-metadata-token' => @token}).
66+
to_raise(StandardError)
67+
stub_request(:get, 'http://169.254.169.254/latest/dynamic/instance-identity/document').
68+
with(headers: {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Ruby'}).
69+
to_return(status: 200, body: @instance_document, headers: {})
70+
assert_equal(@host_identifier, InstanceMetadata.host_identifier)
71+
end
72+
5473
should 'strip whitesace in the body' do
5574
stub_request(:get, 'http://169.254.169.254/latest/dynamic/instance-identity/document').
5675
with(headers: {'X-aws-ec2-metadata-token' => @token}).
@@ -74,6 +93,26 @@ def self.should_check_status_code(&blk)
7493
assert_equal("us-east-1", InstanceMetadata.region)
7594
end
7695

96+
should 'return the region if IMDSv2 http request status code is not 200' do
97+
stub_request(:get, 'http://169.254.169.254/latest/dynamic/instance-identity/document').
98+
with(headers: {'X-aws-ec2-metadata-token' => @token}).
99+
to_return(status: 503, body: @instance_document, headers: {})
100+
stub_request(:get, 'http://169.254.169.254/latest/dynamic/instance-identity/document').
101+
with(headers: {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Ruby'}).
102+
to_return(status: 200, body: @instance_document, headers: {})
103+
assert_equal("us-east-1", InstanceMetadata.region)
104+
end
105+
106+
should 'return the region if IMDSv2 http request errors out' do
107+
stub_request(:get, 'http://169.254.169.254/latest/dynamic/instance-identity/document').
108+
with(headers: {'X-aws-ec2-metadata-token' => @token}).
109+
to_raise(StandardError)
110+
stub_request(:get, 'http://169.254.169.254/latest/dynamic/instance-identity/document').
111+
with(headers: {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Ruby'}).
112+
to_return(status: 200, body: @instance_document, headers: {})
113+
assert_equal("us-east-1", InstanceMetadata.region)
114+
end
115+
77116
should 'strip whitesace in the body' do
78117
stub_request(:get, 'http://169.254.169.254/latest/dynamic/instance-identity/document').
79118
with(headers: {'X-aws-ec2-metadata-token' => @token}).

0 commit comments

Comments
 (0)