azure-blob 0.5.9.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f5b111fda5a789b4fb8c02172220432303a13091d27b3205864cecdbd7ba0aeb
4
- data.tar.gz: d49dfffe286580867b7b9e4f75e2a4a02135ab52e6adc48ee067f94eb0e7e77c
3
+ metadata.gz: 363606d640d9841d39afc55797fde060f8a706bf0bbc85b9c6e2c1d0f51e40bb
4
+ data.tar.gz: 4f6d315afcece7d4499891bf727311c7a79551fd3d8aaca3ed8d85d662c51ae0
5
5
  SHA512:
6
- metadata.gz: '08b2c38410ce15d81566c2759bd9395226e26cdb4bf0a47199e1dac9da4c9c34b92fdbda7069995eb3dd1b6d31214d0d14a38dadf5a5a36429315245e30fa609'
7
- data.tar.gz: d76e2cd0c1c94696a87c769af32125cff0af7b46168b2ff45293b65f8d31447ff90734022595f235b03ca142e734f57cde582ae040bed76a78950320917f5d5e
6
+ metadata.gz: 9ca4c5a7146c9a9d684c53a403e5d3270e1f6c0d51211bd467f297124e5771971840c78faa468ef620158ce98f7353097d6db66b830c59c34b4dab852985674f
7
+ data.tar.gz: 1d452597c9d69a8d2b4dd97f06ab70d1c3d09a7dfe316ea98853dfd709b565bf0f3d6e605dc256fdfa5c73d93cd56643e8f034d054a27c7d8c1dab5914ff00f0
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.6.0] 2025-12-01
4
+
5
+ - Add support for AKS workload identity
6
+
3
7
  ## [0.5.9.1] 2025-06-30
4
8
 
5
9
  - Fix a regression in #13 + fix add a test
data/README.md CHANGED
@@ -26,14 +26,13 @@ microsoft:
26
26
 
27
27
  ### Managed Identity (Entra ID)
28
28
 
29
- AzureBlob supports managed identities on :
29
+ AzureBlob supports managed identities on:
30
30
  - Azure VM
31
31
  - App Service
32
+ - AKS (Azure Kubernetes Service) with workload identity
32
33
  - Azure Functions (Untested but should work)
33
34
  - Azure Containers (Untested but should work)
34
35
 
35
- AKS support will likely require more work. Contributions are welcome.
36
-
37
36
  To authenticate through managed identities instead of a shared key, omit `storage_access_key` from your `storage.yml` file and pass in the identity `principal_id`.
38
37
 
39
38
  ActiveStorage config example:
@@ -46,6 +45,21 @@ prod:
46
45
  principal_id: 71b34410-4c50-451d-b456-95ead1b18cce
47
46
  ```
48
47
 
48
+ #### AKS with Workload Identity
49
+
50
+ ActiveStorage config example:
51
+
52
+ ```
53
+ prod:
54
+ service: AzureBlob
55
+ container: container_name
56
+ storage_account_name: account_name
57
+ use_managed_identities: true
58
+ ```
59
+
60
+ > uses `AZURE_CLIENT_ID`, `AZURE_TENANT_ID` and `AZURE_FEDERATED_TOKEN_FILE` environment variables, made available by AKS cluster when Azure AD Workload Identity is set up properly.
61
+
62
+
49
63
  ### Azurite
50
64
 
51
65
  To use Azurite, pass the `storage_blob_host` config key with the Azurite URL (`http://127.0.0.1:10000/devstoreaccount1` by default)
@@ -126,20 +140,26 @@ A dev environment is supplied through Nix with [devenv](https://devenv.sh/).
126
140
 
127
141
  To test with Entra ID, the `AZURE_ACCESS_KEY` environment variable must be unset and the code must be ran or proxied through a VPS with the proper roles.
128
142
 
129
- For cost saving, the terraform variable `create_vm` and `create_app_service` are false by default.
130
- To create the VPS and App service, Create a var file `var.tfvars` containing:
143
+ For cost saving, the terraform variables `create_vm`, `create_app_service`, and `create_aks` are false by default.
144
+ To create the VM, App Service, and/or AKS cluster, create a var file `var.tfvars` containing:
131
145
 
132
146
  ```
133
147
  create_vm = true
134
148
  create_app_service = true
149
+ create_aks = true
135
150
  ```
136
151
  and re-apply terraform: `terraform apply -var-file=var.tfvars`.
137
152
 
138
- This will create the VPS and required managed identities.
153
+ This will create the infrastructure and required managed identities.
154
+
155
+ **Testing:**
156
+ - `bin/rake test_azure_vm` - Establishes a VPN connection to the Azure VM and runs tests using node identity
157
+ - `bin/rake test_app_service` - Establishes a VPN connection to the App Service container and runs tests
158
+ - `bin/rake test_aks` - Establishes a VPN connection to the AKS cluster and runs tests using workload identity
139
159
 
140
- `bin/rake test_azure_vm` and `bin/rake test_app_service` will establish a VPN connection to the VM or App service container and run the test suite. You might be prompted for a sudo password when the VPN starts (sshuttle).
160
+ You might be prompted for a sudo password when the VPN starts (sshuttle).
141
161
 
142
- After you are done, run terraform again without the var file (`terraform apply`) to destroy the VPS and App service application.
162
+ After you are done, run terraform again without the var file (`terraform apply`) to destroy all resources.
143
163
 
144
164
  #### Cleanup
145
165
 
data/Rakefile CHANGED
@@ -5,6 +5,7 @@ require "minitest/test_task"
5
5
  require "azure_blob"
6
6
  require_relative "test/support/app_service_vpn"
7
7
  require_relative "test/support/azure_vm_vpn"
8
+ require_relative "test/support/aks_vpn"
8
9
  require_relative "test/support/azurite"
9
10
 
10
11
  Minitest::TestTask.create(:test_rails) do
@@ -49,6 +50,16 @@ ensure
49
50
  vpn.kill
50
51
  end
51
52
 
53
+ task :test_aks do |t|
54
+ vpn = AksVpn.new
55
+ ENV["AZURE_CLIENT_ID"] = vpn.client_id
56
+ ENV["AZURE_TENANT_ID"] = vpn.tenant_id
57
+ ENV["AZURE_FEDERATED_TOKEN_FILE"] = vpn.token_file
58
+ Rake::Task["test_entra_id"].execute
59
+ ensure
60
+ vpn.kill
61
+ end
62
+
52
63
  task :test_azurite do |t|
53
64
  azurite = Azurite.new
54
65
  # Azurite well-known credentials
@@ -1,21 +1,14 @@
1
+ require_relative "instance_metadata_service"
2
+ require_relative "workload_identity"
1
3
  require "json"
2
4
 
3
5
  module AzureBlob
4
6
  class IdentityToken
5
- RESOURCE_URI = "https://storage.azure.com/"
6
7
  EXPIRATION_BUFFER = 600 # 10 minutes
7
8
 
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
-
11
9
  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)
19
12
  end
20
13
 
21
14
  def to_s
@@ -31,13 +24,11 @@ module AzureBlob
31
24
 
32
25
  def refresh
33
26
  return unless expired?
34
- headers = { "Metadata" => "true" }
35
- headers["X-IDENTITY-HEADER"] = ENV["IDENTITY_HEADER"] if ENV["IDENTITY_HEADER"]
36
27
 
37
28
  attempt = 0
38
29
  begin
39
30
  attempt += 1
40
- response = JSON.parse(AzureBlob::Http.new(identity_uri, headers).get)
31
+ response = JSON.parse(service.request)
41
32
  rescue AzureBlob::Http::Error => error
42
33
  if should_retry?(error, attempt)
43
34
  attempt = 1 if error.status == 410
@@ -48,7 +39,7 @@ module AzureBlob
48
39
  raise
49
40
  end
50
41
  @token = response["access_token"]
51
- @expiration = Time.at(response["expires_on"].to_i)
42
+ @expiration = service.expiration(response)
52
43
  end
53
44
 
54
45
  def should_retry?(error, attempt)
@@ -61,6 +52,6 @@ module AzureBlob
61
52
  end
62
53
  EXPONENTIAL_BACKOFF = [ 2, 6, 14, 30 ]
63
54
 
64
- attr_reader :identity_uri, :expiration, :token
55
+ attr_reader :service, :expiration, :token
65
56
  end
66
57
  end
@@ -0,0 +1,28 @@
1
+ module AzureBlob
2
+ class InstanceMetadataService # :nodoc:
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: 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
+
24
+ def expiration(response)
25
+ Time.at(response["expires_on"].to_i)
26
+ end
27
+ end
28
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AzureBlob
4
- VERSION = "0.5.9.1"
4
+ VERSION = "0.6.0"
5
5
  end
@@ -0,0 +1,41 @@
1
+ module AzureBlob
2
+ class WorkloadIdentity # :nodoc:
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 initialize
16
+ @identity_uri = URI.parse(IDENTITY_ENDPOINT)
17
+ end
18
+
19
+ def request
20
+ AzureBlob::Http.new(@identity_uri).post(
21
+ URI.encode_www_form(
22
+ client_id: CLIENT_ID,
23
+ scope: SCOPE,
24
+ client_assertion_type: CLIENT_ASSERTION_TYPE,
25
+ client_assertion: federated_token,
26
+ grant_type: GRANT_TYPE
27
+ )
28
+ )
29
+ end
30
+
31
+ def expiration(response)
32
+ Time.now + response["expires_in"].to_i
33
+ end
34
+
35
+ private
36
+
37
+ def federated_token
38
+ File.read(FEDERATED_TOKEN_FILE).strip
39
+ end
40
+ end
41
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: azure-blob
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.9.1
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joé Dupuis
@@ -49,11 +49,13 @@ files:
49
49
  - lib/azure_blob/errors.rb
50
50
  - lib/azure_blob/http.rb
51
51
  - lib/azure_blob/identity_token.rb
52
+ - lib/azure_blob/instance_metadata_service.rb
52
53
  - lib/azure_blob/metadata.rb
53
54
  - lib/azure_blob/shared_key_signer.rb
54
55
  - lib/azure_blob/tags.rb
55
56
  - lib/azure_blob/user_delegation_key.rb
56
57
  - lib/azure_blob/version.rb
58
+ - lib/azure_blob/workload_identity.rb
57
59
  homepage: https://github.com/testdouble/azure-blob
58
60
  licenses:
59
61
  - MIT