Skip to content

Commit 11753c5

Browse files
committed
Add support for AKS workload identity
1 parent 457a4e7 commit 11753c5

File tree

4 files changed

+72
-18
lines changed

4 files changed

+72
-18
lines changed

Gemfile.lock

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ GEM
8282
base64 (0.2.0)
8383
bcrypt_pbkdf (1.1.1)
8484
bcrypt_pbkdf (1.1.1-arm64-darwin)
85+
bcrypt_pbkdf (1.1.1-x86_64-darwin)
8586
benchmark (0.4.0)
8687
bigdecimal (3.1.9)
8788
builder (3.3.0)
@@ -97,6 +98,7 @@ GEM
9798
erb (5.0.1)
9899
erubi (1.13.1)
99100
ffi (1.17.2-arm64-darwin)
101+
ffi (1.17.2-x86_64-darwin)
100102
ffi (1.17.2-x86_64-linux-gnu)
101103
globalid (1.2.1)
102104
activesupport (>= 6.1)
@@ -142,9 +144,11 @@ GEM
142144
net-protocol
143145
net-ssh (7.2.3)
144146
nio4r (2.7.4)
145-
nokogiri (1.18.8-arm64-darwin)
147+
nokogiri (1.18.10-arm64-darwin)
146148
racc (~> 1.4)
147-
nokogiri (1.18.8-x86_64-linux-gnu)
149+
nokogiri (1.18.10-x86_64-darwin)
150+
racc (~> 1.4)
151+
nokogiri (1.18.10-x86_64-linux-gnu)
148152
racc (~> 1.4)
149153
parallel (1.24.0)
150154
parser (3.3.1.0)
@@ -244,6 +248,7 @@ GEM
244248
logger
245249
securerandom (0.4.1)
246250
sqlite3 (2.6.0-arm64-darwin)
251+
sqlite3 (2.6.0-x86_64-darwin)
247252
sqlite3 (2.6.0-x86_64-linux-gnu)
248253
stringio (3.1.7)
249254
strscan (3.1.0)
@@ -262,6 +267,7 @@ GEM
262267

263268
PLATFORMS
264269
arm64-darwin-24
270+
x86_64-darwin-23
265271
x86_64-linux
266272

267273
DEPENDENCIES

lib/azure_blob/identity_token.rb

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,14 @@
1+
require_relative "instance_metadata_service"
2+
require_relative "workload_identity"
13
require "json"
24

35
module AzureBlob
46
class IdentityToken
5-
RESOURCE_URI = "https://storage.azure.com/"
67
EXPIRATION_BUFFER = 600 # 10 minutes
78

8-
IDENTITY_ENDPOINT = ENV["IDENTITY_ENDPOINT"] || "http://169.254.169.254/metadata/identity/oauth2/token"
9-
API_VERSION = ENV["IDENTITY_ENDPOINT"] ? "2019-08-01" : "2018-02-01"
10-
119
def initialize(principal_id: nil)
12-
@identity_uri = URI.parse(IDENTITY_ENDPOINT)
13-
params = {
14-
'api-version': API_VERSION,
15-
resource: RESOURCE_URI,
16-
}
17-
params[:principal_id] = principal_id if principal_id
18-
@identity_uri.query = URI.encode_www_form(params)
10+
@service = AzureBlob::WorkloadIdentity.federated_token? ?
11+
AzureBlob::WorkloadIdentity.new : AzureBlob::InstanceMetadataService.new(principal_id: principal_id)
1912
end
2013

2114
def to_s
@@ -31,13 +24,11 @@ def expired?
3124

3225
def refresh
3326
return unless expired?
34-
headers = { "Metadata" => "true" }
35-
headers["X-IDENTITY-HEADER"] = ENV["IDENTITY_HEADER"] if ENV["IDENTITY_HEADER"]
3627

3728
attempt = 0
3829
begin
3930
attempt += 1
40-
response = JSON.parse(AzureBlob::Http.new(identity_uri, headers).get)
31+
response = JSON.parse(service.request)
4132
rescue AzureBlob::Http::Error => error
4233
if should_retry?(error, attempt)
4334
attempt = 1 if error.status == 410
@@ -48,7 +39,7 @@ def refresh
4839
raise
4940
end
5041
@token = response["access_token"]
51-
@expiration = Time.at(response["expires_on"].to_i)
42+
@expiration = Time.at((response["expires_on"] || response["expires_in"]).to_i)
5243
end
5344

5445
def should_retry?(error, attempt)
@@ -61,6 +52,6 @@ def exponential_backoff(error, attempt)
6152
end
6253
EXPONENTIAL_BACKOFF = [ 2, 6, 14, 30 ]
6354

64-
attr_reader :identity_uri, :expiration, :token
55+
attr_reader :service, :expiration, :token
6556
end
6657
end
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
module AzureBlob
2+
class InstanceMetadataService # Azure Instance Metadata Service (IMDS)
3+
IDENTITY_ENDPOINT = ENV["IDENTITY_ENDPOINT"] || "http://169.254.169.254/metadata/identity/oauth2/token"
4+
API_VERSION = ENV["IDENTITY_ENDPOINT"] ? "2019-08-01" : "2018-02-01"
5+
RESOURCE_URI = "https://storage.azure.com/"
6+
7+
def initialize(principal_id: nil)
8+
@identity_uri = URI.parse(IDENTITY_ENDPOINT)
9+
params = {
10+
'api-version': API_VERSION,
11+
resource: AzureBlob::IdentityToken::RESOURCE_URI
12+
}
13+
params[:principal_id] = principal_id if principal_id
14+
@identity_uri.query = URI.encode_www_form(params)
15+
end
16+
17+
def request
18+
headers = { "Metadata" => "true" }
19+
headers["X-IDENTITY-HEADER"] = ENV["IDENTITY_HEADER"] if ENV["IDENTITY_HEADER"]
20+
21+
AzureBlob::Http.new(@identity_uri, headers).get
22+
end
23+
end
24+
end
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
module AzureBlob
2+
class WorkloadIdentity # Azure AD Workload Identity
3+
IDENTITY_ENDPOINT = "https://login.microsoftonline.com/#{ENV['AZURE_TENANT_ID']}/oauth2/v2.0/token"
4+
CLIENT_ID = ENV['AZURE_CLIENT_ID']
5+
SCOPE = "https://storage.azure.com/.default"
6+
GRANT_TYPE = "client_credentials"
7+
CLIENT_ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
8+
9+
FEDERATED_TOKEN_FILE = ENV["AZURE_FEDERATED_TOKEN_FILE"].to_s
10+
11+
def self.federated_token?
12+
!FEDERATED_TOKEN_FILE.empty?
13+
end
14+
15+
def request
16+
AzureBlob::Http.new(URI.parse(IDENTITY_ENDPOINT)).post(
17+
URI.encode_www_form(
18+
client_id: CLIENT_ID,
19+
scope: SCOPE,
20+
client_assertion_type: CLIENT_ASSERTION_TYPE,
21+
client_assertion: federated_token,
22+
grant_type: GRANT_TYPE
23+
)
24+
)
25+
end
26+
27+
private
28+
29+
def federated_token
30+
File.read(FEDERATED_TOKEN_FILE).strip
31+
end
32+
end
33+
end

0 commit comments

Comments
 (0)