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 +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +28 -8
- data/Rakefile +11 -0
- data/lib/azure_blob/identity_token.rb +7 -16
- data/lib/azure_blob/instance_metadata_service.rb +28 -0
- data/lib/azure_blob/version.rb +1 -1
- data/lib/azure_blob/workload_identity.rb +41 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 363606d640d9841d39afc55797fde060f8a706bf0bbc85b9c6e2c1d0f51e40bb
|
|
4
|
+
data.tar.gz: 4f6d315afcece7d4499891bf727311c7a79551fd3d8aaca3ed8d85d662c51ae0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9ca4c5a7146c9a9d684c53a403e5d3270e1f6c0d51211bd467f297124e5771971840c78faa468ef620158ce98f7353097d6db66b830c59c34b4dab852985674f
|
|
7
|
+
data.tar.gz: 1d452597c9d69a8d2b4dd97f06ab70d1c3d09a7dfe316ea98853dfd709b565bf0f3d6e605dc256fdfa5c73d93cd56643e8f034d054a27c7d8c1dab5914ff00f0
|
data/CHANGELOG.md
CHANGED
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
|
|
130
|
-
To create the
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
@
|
|
13
|
-
|
|
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(
|
|
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 =
|
|
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 :
|
|
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
|
data/lib/azure_blob/version.rb
CHANGED
|
@@ -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.
|
|
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
|