This repository was archived by the owner on Jan 11, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 246
[ms_rest_azure] Introduce support for acquiring token from Managed Service Identity #889
Merged
Merged
Changes from 2 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
e113921
Introduce support for acquring token from Managed Service Identity
vishrutshah dc7b1aa
Update to latest azure-storage gem
vishrutshah 13b2fb2
Reusing ActiveDirectorySericeSettings for MSITokenProvider
vishrutshah 8738e1d
Testing CI from fix from Tank for azure-core gem
vishrutshah 75d4660
Using katmsft master branch
vishrutshah 90c4ef2
Using bundler version 1.14.1
vishrutshah 0205452
Remove use of private fork for azure-core
vishrutshah a5f71dc
Adding Changelog
vishrutshah File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
112 changes: 112 additions & 0 deletions
112
runtime/ms_rest_azure/lib/ms_rest_azure/credentials/msi_token_provider.rb
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,112 @@ | ||
| # encoding: utf-8 | ||
| # Copyright (c) Microsoft Corporation. All rights reserved. | ||
| # Licensed under the MIT License. See License.txt in the project root for license information. | ||
|
|
||
| module MsRestAzure | ||
| # | ||
| # Class that provides access to authentication token via Managed Service Identity. | ||
| # | ||
| class MSITokenProvider < MsRest::TokenProvider | ||
|
|
||
| private | ||
|
|
||
| TOKEN_ACQUIRE_URL = 'http://localhost:{port}/oauth2/token' | ||
| REQUEST_BODY_PATTERN = 'authority={authentication_endpoint}{tenant_id}&resource={resource_uri}' | ||
| DEFAULT_SCHEME = 'Bearer' | ||
|
|
||
| # @return [MSIActiveDirectoryServiceSettings] settings. | ||
| attr_accessor :settings | ||
|
|
||
| # @return [String] tenant id (also known as domain). | ||
| attr_accessor :tenant_id | ||
|
|
||
| # @return [Integer] port number where MSI service is running. | ||
| attr_accessor :port | ||
|
|
||
| # @return [String] auth token. | ||
| attr_accessor :token | ||
|
|
||
| # @return [Time] the date when the current token expires. | ||
| attr_accessor :token_expires_on | ||
|
|
||
| # @return [Integer] the amount of time we refresh token before it expires. | ||
| attr_reader :expiration_threshold | ||
|
|
||
| # @return [String] the type of token. | ||
| attr_reader :token_type | ||
|
|
||
| public | ||
|
|
||
| # | ||
| # Creates and initialize new instance of the MSITokenProvider class. | ||
| # @param tenant_id [String] tenant id (also known as domain). | ||
| # @param port [Integer] port number where MSI service is running. | ||
| # @param settings [MSIActiveDirectoryServiceSettings] MSI active directory setting. | ||
| def initialize(tenant_id, port = 50342, settings = MSIActiveDirectoryServiceSettings.get_azure_settings) | ||
| fail ArgumentError, 'Tenant id cannot be nil' if tenant_id.nil? | ||
| fail ArgumentError, 'Port cannot be nil' if port.nil? | ||
| fail ArgumentError, 'Port must be an Integer' unless port.is_a? Integer | ||
| fail ArgumentError, 'Azure AD settings cannot be nil' if settings.nil? | ||
|
|
||
| @tenant_id = tenant_id | ||
| @port = port | ||
| @settings = settings | ||
|
|
||
| @expiration_threshold = 5 * 60 | ||
| end | ||
|
|
||
| # | ||
| # Returns the string value which needs to be attached | ||
| # to HTTP request header in order to be authorized. | ||
| # | ||
| # @return [String] authentication headers. | ||
| def get_authentication_header | ||
| acquire_token if token_expired | ||
| "#{token_type} #{token}" | ||
| end | ||
|
|
||
| private | ||
|
|
||
| # | ||
| # Checks whether token is about to expire. | ||
| # | ||
| # @return [Bool] True if token is about to expire, false otherwise. | ||
| def token_expired | ||
| @token.nil? || Time.now >= @token_expires_on + expiration_threshold | ||
| end | ||
|
|
||
| # | ||
| # Retrieves a new authentication token. | ||
| # | ||
| # @return [String] new authentication token. | ||
| def acquire_token | ||
| token_acquire_url = TOKEN_ACQUIRE_URL.dup | ||
| token_acquire_url['{port}'] = @port.to_s | ||
|
|
||
| url = URI.parse(token_acquire_url) | ||
|
|
||
| connection = Faraday.new(:url => url, :ssl => MsRest.ssl_options) do |builder| | ||
| builder.adapter Faraday.default_adapter | ||
| end | ||
|
|
||
| request_body = REQUEST_BODY_PATTERN.dup | ||
| request_body['{authentication_endpoint}'] = ERB::Util.url_encode(@settings.authentication_endpoint) | ||
| request_body['{tenant_id}'] = ERB::Util.url_encode(@tenant_id) | ||
| request_body['{resource_uri}'] = ERB::Util.url_encode(@settings.token_audience) | ||
|
|
||
| response = connection.post do |request| | ||
| request.headers['content-type'] = 'application/x-www-form-urlencoded' | ||
| request.body = request_body | ||
| end | ||
|
|
||
| fail AzureOperationError, | ||
| 'Couldn\'t acquire access token from Managed Service Identity, please verify your tenant id, port and settings' unless response.status == 200 | ||
|
|
||
| response_body = JSON.load(response.body) | ||
| @token = response_body['access_token'] | ||
| @token_expires_on = Time.at(Integer(response_body['expires_on'])) | ||
| @token_type = response_body['token_type'] | ||
| end | ||
| end | ||
|
|
||
| end |
25 changes: 25 additions & 0 deletions
25
runtime/ms_rest_azure/lib/ms_rest_azure/msi_active_directory_service_settings.rb
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| # encoding: utf-8 | ||
| # Copyright (c) Microsoft Corporation. All rights reserved. | ||
| # Licensed under the MIT License. See License.txt in the project root for license information. | ||
|
|
||
| module MsRestAzure | ||
| # | ||
| # Class which represents an settings for MSI Azure AD authentication. | ||
| # | ||
| class MSIActiveDirectoryServiceSettings < ActiveDirectoryServiceSettings | ||
|
|
||
| private | ||
|
|
||
| # | ||
| # Returns a set of properties required to login into Azure Cloud via MSI. | ||
| # | ||
| # @param azure_environment [AzureEnvironment] An instance of AzureEnvironment. | ||
| # @return [MSIActiveDirectoryServiceSettings] settings required for authentication. | ||
| def self.get_settings(azure_environment = MsRestAzure::AzureEnvironments::Azure) | ||
| settings = ActiveDirectoryServiceSettings.new | ||
| settings.authentication_endpoint = azure_environment.active_directory_endpoint_url | ||
| settings.token_audience = azure_environment.resource_manager_endpoint_url | ||
| settings | ||
| end | ||
| end | ||
| end | ||
42 changes: 42 additions & 0 deletions
42
runtime/ms_rest_azure/spec/msi_active_directory_service_settings_spec.rb
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| # encoding: utf-8 | ||
| # Copyright (c) Microsoft Corporation. All rights reserved. | ||
| # Licensed under the MIT License. See License.txt in the project root for license information. | ||
|
|
||
| require 'rspec' | ||
| require 'ms_rest_azure' | ||
|
|
||
| module MsRestAzure | ||
| describe 'MSI Active Directory Service Settings' do | ||
| it 'should initialize with Azure Cloud properties' do | ||
| msi_msi_aad_settings = MSIActiveDirectoryServiceSettings.get_azure_settings | ||
|
|
||
| expect(msi_msi_aad_settings).to be_a(ActiveDirectoryServiceSettings) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should this expect "MSIActiveDirectoryServiceSettings"?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch. Supposed to be tested for sub class instead of super. |
||
| expect(msi_msi_aad_settings.authentication_endpoint).to eq(MsRestAzure::AzureEnvironments::AzureCloud.active_directory_endpoint_url) | ||
| expect(msi_msi_aad_settings.token_audience).to eq(MsRestAzure::AzureEnvironments::AzureCloud.resource_manager_endpoint_url) | ||
| end | ||
|
|
||
| it 'should initialize with Azure China Cloud properties' do | ||
| msi_aad_settings = MSIActiveDirectoryServiceSettings.get_azure_china_settings | ||
|
|
||
| expect(msi_aad_settings).to be_a(ActiveDirectoryServiceSettings) | ||
| expect(msi_aad_settings.authentication_endpoint).to eq(MsRestAzure::AzureEnvironments::AzureChinaCloud.active_directory_endpoint_url) | ||
| expect(msi_aad_settings.token_audience).to eq(MsRestAzure::AzureEnvironments::AzureChinaCloud.resource_manager_endpoint_url) | ||
| end | ||
|
|
||
| it 'should initialize with Azure US Government Cloud properties' do | ||
| msi_aad_settings = MSIActiveDirectoryServiceSettings.get_azure_us_government_settings | ||
|
|
||
| expect(msi_aad_settings).to be_a(ActiveDirectoryServiceSettings) | ||
| expect(msi_aad_settings.authentication_endpoint).to eq(MsRestAzure::AzureEnvironments::AzureUSGovernment.active_directory_endpoint_url) | ||
| expect(msi_aad_settings.token_audience).to eq(MsRestAzure::AzureEnvironments::AzureUSGovernment.resource_manager_endpoint_url) | ||
| end | ||
|
|
||
| it 'should initialize with Azure German Cloud properties' do | ||
| msi_aad_settings = MSIActiveDirectoryServiceSettings.get_azure_german_settings | ||
|
|
||
| expect(msi_aad_settings).to be_a(ActiveDirectoryServiceSettings) | ||
| expect(msi_aad_settings.authentication_endpoint).to eq(MsRestAzure::AzureEnvironments::AzureGermanCloud.active_directory_endpoint_url) | ||
| expect(msi_aad_settings.token_audience).to eq(MsRestAzure::AzureEnvironments::AzureGermanCloud.resource_manager_endpoint_url) | ||
| end | ||
| end | ||
| end | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| # encoding: utf-8 | ||
| # Copyright (c) Microsoft Corporation. All rights reserved. | ||
| # Licensed under the MIT License. See License.txt in the project root for license information. | ||
|
|
||
| require 'rspec' | ||
| require 'ms_rest_azure' | ||
|
|
||
| module MsRestAzure | ||
|
|
||
| describe MSITokenProvider do | ||
| it 'should throw error if nil data is passed into constructor' do | ||
| expect { MSITokenProvider.new(nil) }.to raise_error(ArgumentError) | ||
| expect { MSITokenProvider.new('tenant_id',nil) }.to raise_error(ArgumentError) | ||
| expect { MSITokenProvider.new('tenant_id','port') }.to raise_error(ArgumentError) | ||
| expect { MSITokenProvider.new('tenant_id',50431,nil) }.to raise_error(ArgumentError) | ||
| end | ||
|
|
||
| it 'should set defaults for managed service identity' do | ||
| tenant = 'xxxx-xxxx-xxxxx-xxxxx' | ||
| azure_cloud = MsRestAzure::AzureEnvironments::AzureCloud | ||
|
|
||
| token_provider = MSITokenProvider.new(tenant) | ||
| expect(token_provider.send(:tenant_id)).to eq(tenant) | ||
| expect(token_provider.send(:port)).to eq(50342) | ||
| settings = token_provider.send(:settings) | ||
| expect(settings.authentication_endpoint).to eq(azure_cloud.active_directory_endpoint_url) | ||
| expect(settings.token_audience).to eq(azure_cloud.resource_manager_endpoint_url) | ||
| end | ||
|
|
||
| it 'should set customs for managed service identity' do | ||
| tenant = 'xxxx-xxxx-xxxxx-xxxxx' | ||
| port = 50333 | ||
| settings = MSIActiveDirectoryServiceSettings.new() | ||
| settings.authentication_endpoint = 'https://login.microsoftonline.com/' | ||
| settings.token_audience = 'https://vault.azure.net' | ||
|
|
||
| token_provider = MSITokenProvider.new(tenant, port, settings) | ||
| expect(token_provider.send(:tenant_id)).to eq(tenant) | ||
| expect(token_provider.send(:port)).to eq(port) | ||
| settings = token_provider.send(:settings) | ||
| expect(settings.authentication_endpoint).to eq(settings.authentication_endpoint) | ||
| expect(settings.token_audience).to eq(settings.token_audience) | ||
| end | ||
| end | ||
|
|
||
| end |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is the token_audience expected to be different from the ActiveDirectoryServiceSettings? how do we know which one should be?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess yes from the documentation describing the MSI authentication flow, default token audience is
https://management.azure.com/and nothttps://management.core.windows.net/. but let me verify again because it might be just another audience that worksThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As usual, you're right...it's just an another resource token audience. The one we have with
ActiveDirectoryServiceSettingsalso works for acquiring token ...I'll refactor it then. thanks!