Skip to content
This repository was archived by the owner on Jan 11, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion azure_sdk/azure_sdk.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,5 @@ Gem::Specification.new do |spec|
spec.add_runtime_dependency 'azure_mgmt_subscriptions', "~>#{version}"
spec.add_runtime_dependency 'azure_mgmt_traffic_manager', "~>#{version}"
spec.add_runtime_dependency 'azure_mgmt_web', "~>#{version}"
spec.add_runtime_dependency 'azure-storage', "~>0.12.1.preview"
spec.add_runtime_dependency 'azure-storage', "~>0.12.3.preview"
end
2 changes: 2 additions & 0 deletions runtime/ms_rest_azure/lib/ms_rest_azure.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
require 'ms_rest_azure/azure_service_client.rb'
require 'ms_rest_azure/cloud_error_data.rb'
require 'ms_rest_azure/credentials/application_token_provider.rb'
require 'ms_rest_azure/credentials/msi_token_provider.rb'
require 'ms_rest_azure/msi_active_directory_service_settings.rb'
require 'ms_rest_azure/polling_state.rb'
require 'ms_rest_azure/sub_resource.rb'
require 'ms_rest_azure/resource.rb'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class ApplicationTokenProvider < MsRest::TokenProvider
# @param tenant_id [String] tenant id (also known as domain).
# @param client_id [String] client id.
# @param client_secret [String] client secret.
# @param settings [ActiveDirectoryServiceSettings] client secret.
# @param settings [ActiveDirectoryServiceSettings] active directory setting.
def initialize(tenant_id, client_id, client_secret, settings = ActiveDirectoryServiceSettings.get_azure_settings)
fail ArgumentError, 'Tenant id cannot be nil' if tenant_id.nil?
fail ArgumentError, 'Client id cannot be nil' if client_id.nil?
Expand Down Expand Up @@ -81,7 +81,7 @@ def token_expired
end

#
# Retrieves a new authenticaion token.
# Retrieves a new authentication token.
#
# @return [String] new authentication token.
def acquire_token
Expand Down
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
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

Copy link
Copy Markdown
Contributor

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?

@vishrutshah vishrutshah Aug 14, 2017

Copy link
Copy Markdown
Contributor Author

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 not https://management.core.windows.net/. but let me verify again because it might be just another audience that works

Copy link
Copy Markdown
Contributor Author

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 ActiveDirectoryServiceSettings also works for acquiring token ...I'll refactor it then. thanks!

settings
end
end
end
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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this expect "MSIActiveDirectoryServiceSettings"?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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
46 changes: 46 additions & 0 deletions runtime/ms_rest_azure/spec/msi_token_provider_spec.rb
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