class MsRestAzure::MSITokenProvider
Class that provides access to authentication token via Managed Service Identity.
Constants
- DEFAULT_SCHEME
- IMDS_TOKEN_ACQUIRE_URL
- REQUEST_BODY_PATTERN
- TOKEN_ACQUIRE_URL
- USER_ASSIGNED_IDENTITY
Attributes
@return [String] client id for user assigned managed identity
@return [Integer] the amount of time we refresh token before it expires.
@return [String] ms_res id for user assigned managed identity
@return [String] object id for user assigned managed identity
@return [Integer] port number where MSI service is running.
@return [MSIActiveDirectoryServiceSettings] settings.
@return [String] auth token.
@return [Time] the date when the current token expires.
@return [String] the type of token.
Public Class Methods
Creates and initialize new instance of the MSITokenProvider class. @param port [Integer] port number where MSI service is running. @param settings [ActiveDirectoryServiceSettings] active directory setting. @param msi_id [Hash] MSI id for user assigned managed service identity,
msi_id = {'client_id': 'client id of user assigned identity'}
or
msi_id = {'object_id': 'object id of user assigned identity'}
or
msi_id = {'msi_rest_id': 'resource id of user assigned identity'}
The above key,value pairs are mutually exclusive.
# File lib/ms_rest_azure/credentials/msi_token_provider.rb, line 58 def initialize(port = 50342, settings = ActiveDirectoryServiceSettings.get_azure_settings, msi_id = nil) fail ArgumentError, 'Azure AD settings cannot be nil' if settings.nil? fail ArgumentError, 'msi_id must include either client_id, object_id or msi_res_id exclusively' if (!msi_id.nil? && msi_id.length > 1) warn "The 'port' argument is no longer used, and will be removed in a future release" if port != 50342 @port = port @settings = settings if !msi_id.nil? @client_id = msi_id[:client_id] unless msi_id[:client_id].nil? @object_id = msi_id[:object_id] unless msi_id[:object_id].nil? @msi_res_id = msi_id[:msi_res_id] unless msi_id[:msi_res_id].nil? end @expiration_threshold = 5 * 60 end
Public Instance Methods
Returns the string value which needs to be attached to HTTP request header in order to be authorized.
@return [String] authentication headers.
# File lib/ms_rest_azure/credentials/msi_token_provider.rb, line 79 def get_authentication_header if !ENV['MSI_VM'].nil? && ENV['MSI_VM'].downcase == 'true' acquire_token if token_expired else acquire_token_from_imds_with_retry if token_expired end "#{token_type} #{token}" end
Private Instance Methods
Retrieves a new authentication token.
@return [String] new authentication token.
# File lib/ms_rest_azure/credentials/msi_token_provider.rb, line 163 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['{resource_uri}'] = ERB::Util.url_encode(@settings.token_audience) request_body = set_msi_id(request_body, 'client_id', @client_id) unless @client_id.nil? request_body = set_msi_id(request_body, 'object_id', @object_id) unless @object_id.nil? request_body = set_msi_id(request_body, 'msi_res_id', @msi_res_id) unless @msi_res_id.nil? response = connection.post do |request| request.headers['content-type'] = 'application/x-www-form-urlencoded' request.headers['Metadata'] = 'true' 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
# File lib/ms_rest_azure/credentials/msi_token_provider.rb, line 94 def acquire_token_from_imds_with_retry token_acquire_url = IMDS_TOKEN_ACQUIRE_URL.dup + "?" + append_header('resource', ERB::Util.url_encode(@settings.token_audience)) + '&' + append_header('api-version', '2018-02-01') token_acquire_url = (token_acquire_url + '&' + append_header('client_id', ERB::Util.url_encode(@client_id))) unless @client_id.nil? token_acquire_url = (token_acquire_url + '&' + append_header('object_id', ERB::Util.url_encode(@object_id))) unless @object_id.nil? token_acquire_url = (token_acquire_url + '&' + append_header('msi_res_id', ERB::Util.url_encode(@msi_res_id))) unless @msi_res_id.nil? url = URI.parse(token_acquire_url) connection = Faraday.new(:url => url, :ssl => MsRest.ssl_options) do |builder| builder.adapter Faraday.default_adapter end retry_value = 1 max_retry = 20 response = nil user_defined_time_limit = ENV['USER_DEFINED_IMDS_MAX_RETRY_TIME'].nil? ? 104900:ENV['USER_DEFINED_IMDS_MAX_RETRY_TIME'] total_wait = 0 slots = [] (0..max_retry-1).each do |i| slots << (100 * ((2 << i) - 1) /1000.to_f) end while retry_value <= max_retry && total_wait < user_defined_time_limit response = connection.get do |request| request.headers['Metadata'] = 'true' request.headers['User-Agent'] = "Azure-SDK-For-Ruby/ms_rest_azure/#{MsRestAzure::VERSION}" end if response.status == 410 || response.status == 429 || response.status == 404 || (response.status > 499 && response.status < 600) puts slots.inspect wait = slots[0..retry_value].sample wait = wait < 1 ? 3 : wait wait = (response.status == 410 && wait < 70) ? 70 : wait retry_value += 1 if (retry_value > max_retry) break end wait = wait > user_defined_time_limit ? user_defined_time_limit : wait sleep(wait) total_wait += wait elsif response.status != 200 fail AzureOperationError, "Couldn't acquire access token from Managed Service Identity, please verify your tenant id, port and settings" else break end end if retry_value > max_retry fail AzureOperationError, "MSI: Failed to acquire tokens after #{max_retry} times" end 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
# File lib/ms_rest_azure/credentials/msi_token_provider.rb, line 90 def append_header(name, value) "#{name}=#{value}" end
Sets user assigned identity value in request body @param request_body [String] body of the request used to acquire token @param id_type [String] type of id to send 'client_id', 'object_id' or 'msi_res_id' @param id_value [String] id of the user assigned identity
@return [String] new request body with the addition of <id_type>=<id_value>.
# File lib/ms_rest_azure/credentials/msi_token_provider.rb, line 201 def set_msi_id(request_body, id_type, id_value) user_assigned_identity = USER_ASSIGNED_IDENTITY.dup request_body = [request_body, user_assigned_identity].join(',') request_body['{id_type}'] = id_type request_body['{user_assigned_identity}'] = ERB::Util.url_encode(id_value) return request_body end
Checks whether token is about to expire.
@return [Bool] True if token is about to expire, false otherwise.
# File lib/ms_rest_azure/credentials/msi_token_provider.rb, line 155 def token_expired @token.nil? || Time.now >= @token_expires_on + expiration_threshold end