Reputation: 1729
My Application is built using Ruby On Rails
. I'm ask to use Microsoft Azure Key Vault
to store our secrets string.
I know there is Gems available made By the Microsoft Teams:
https://rubygems.org/gems/azure_mgmt_key_vault
https://rubygems.org/gems/azure_key_vault
How do I "extract" or "reference" a Key and pass it to my application?
Upvotes: 3
Views: 1539
Reputation: 1729
With a lot of Work and Sweat I figured out. Even more, I was able to use a Certificate to connect to Microsoft Azure Key Vault. So below I put all my code with 2 ways to get the token. One with a client secret id
and the other with a certificate
.
I found how to generate a self-sign certificate (for debuging purpose) and get the
encode thumbprint
:
Certificat that was upload to Azure was generated with:
openssl req -x509 -newkey rsa:4096 -keyout private_key.pem -out public_certificate.pem -nodes -days 3650
To obtain the x5t
encode base64 thumbprint of the certificate:
echo $(openssl x509 -in public_certificate.pem -fingerprint -noout) | sed 's/SHA1 Fingerprint=//g' | sed 's/://g' | xxd -r -ps | base64
I built a GEM.
I have a Configuration file lib\azurekeyvault\configuration.rb
:
module AzureKeyVault
class Configuration
attr_accessor :azure_tenant_id, :azure_client_id, :azure_client_secret, :azure_subscription_id, :vault_base_url, :api_version, :resource, :azure_certificate_thumbprint, :azure_certificate_private_key_file
def initialize
@azure_tenant_id = nil
@azure_client_id = nil
@azure_client_secret = nil
@azure_subscription_id = nil
@vault_base_url = nil
@api_version = nil
@resource = nil
@azure_certificate_thumbprint = nil
@azure_certificate_private_key_file = nil
end
end
end
This is the file where the magic happen lib\azurekeyvault\extraction.rb
:
module AzureKeyVault
require 'singleton'
class Extraction
include Singleton
def initialize
@configuration = AzureKeyVault.configuration
end
def get_value(secret_name, secret_version = nil)
get_secret(secret_name, secret_version)
end
private
### Get a Secret value from Microsoft Azure Vault
## secret_name: Name of the Key which contain the value
## secret_version (optional): Version of the key value we need, by omitting version the system to use the latest available version
def get_secret(secret_name, secret_version = nil)
# GET {vaultBaseUrl}/secrets/{secret-name}/{secret-version}?api-version=7.1
vault_base_url = @configuration.vault_base_url
api_version = @configuration.api_version
azure_certificate_thumbprint = @configuration.azure_certificate_thumbprint
auth_token = nil
if azure_certificate_thumbprint.nil?
auth_token = get_auth_token()
else
auth_token = get_auth_certificate_token()
end
return nil if auth_token.nil?
url = "#{vault_base_url}/secrets/#{secret_name}/#{secret_version}?api-version=#{api_version}"
headers = { 'Authorization' => "Bearer " + auth_token }
begin
response = HTTParty.get(url, {headers: headers})
return response.parsed_response['value']
rescue HTTParty::Error => e
puts "HTTParty ERROR: #{e.message}"
raise e
rescue Exception => e
puts "ERROR: #{e.message}"
raise e
end
end
def get_auth_token
#Microsoft identity platform and the OAuth 2.0 client credentials flow
# https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow
# https://learn.microsoft.com/en-us/azure/active-directory/azuread-dev/v1-oauth2-client-creds-grant-flow#request-an-access-token
azure_tenant_id = @configuration.azure_tenant_id
azure_client_id = @configuration.azure_client_id
azure_client_secret = @configuration.azure_client_secret
resource = @configuration.resource
authUrl = "https://login.microsoftonline.com/#{azure_tenant_id}/oauth2/token"
data = {
'grant_type': 'client_credentials',
'client_id': azure_client_id,
'client_secret': azure_client_secret,
'resource': resource
}
begin
response= HTTParty.post(authUrl, body: data)
token = nil
if response
#puts response.to_json
token = response.parsed_response['access_token']
end
return token
rescue HTTParty::Error => e
puts "HTTParty ERROR: #{e.message}"
raise e
rescue Exception => e
puts "ERROR: #{e.message}"
raise e
end
end
def get_auth_certificate_token
begin
# Microsoft identity platform and the OAuth 2.0 client credentials flow
#
# Certificat that was upload to Azure was generated with:
# openssl req -x509 -newkey rsa:4096 -keyout private_key.pem -out public_certificate.pem -nodes -days 3650
#
# To obtain the x5t encode base64 thumbprint of the certificate:
# echo $(openssl x509 -in public_certificate.pem -fingerprint -noout) | sed 's/SHA1 Fingerprint=//g' | sed 's/://g' | xxd -r -ps | base64
# https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow
# https://learn.microsoft.com/en-us/azure/active-directory/azuread-dev/v1-oauth2-client-creds-grant-flow#request-an-access-token
azure_tenant_id = @configuration.azure_tenant_id
azure_client_id = @configuration.azure_client_id
resource = @configuration.resource
azure_certificate_thumbprint = @configuration.azure_certificate_thumbprint
azure_certificate_private_key_file = @configuration.azure_certificate_private_key_file
authUrl = "https://login.microsoftonline.com/#{azure_tenant_id}/oauth2/token"
exp = Time.now.to_i + 4 * 3600
nbf = Time.now.to_i - 3600
jti = SecureRandom.uuid
#//x5t THUMBPRINT of Cert
header = {
"alg": "RS256",
"typ": "JWT",
"x5t": azure_certificate_thumbprint
}
#Claim (payload)
payload = {
"aud": authUrl,
"exp": exp,
"iss": azure_client_id,
"jti": jti,
"nbf": nbf,
"sub": azure_client_id
}
token = "#{Base64.strict_encode64(header.to_json)}.#{Base64.strict_encode64(payload.to_json)}"
# Get the private key, from the file
azure_certificate_private_key = OpenSSL::PKey.read(File.read(azure_certificate_private_key_file))
# The hash algorithm, I assume SHA256 is being used
base64_signature = Base64.strict_encode64(azure_certificate_private_key.sign(OpenSSL::Digest::SHA256.new, token))
jwt_client_assertion = "#{token}.#{base64_signature}"
data = {
'grant_type': 'client_credentials',
'client_id': azure_client_id,
'client_assertion_type': 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
'client_assertion': jwt_client_assertion,
'resource': resource
}
response = HTTParty.post(authUrl, body: data)
token = nil
if response
token = response.parsed_response['access_token']
end
return token
rescue HTTParty::Error => e
puts "HTTParty ERROR: #{e.message}"
raise e
rescue Exception => e
puts "ERROR: #{e.message}"
raise e
end
end
end
end
I have also an Initialiser
where I assign value for my configuration variables
AzureKeyVault.configure do |config|
config.azure_tenant_id = ENV["AZURE_VAULT_TENANT_ID"]
config.azure_client_id = ENV["AZURE_VAULT_CLIENT_ID"]
config.azure_client_secret = ENV["AZURE_VAULT_CLIENT_SECRET"]
config.azure_subscription_id = ENV["AZURE_VAULT_SUBSCRIPTION_ID"]
config.vault_base_url = ENV["AZURE_VAULT_BASE_URL"]
config.api_version = ENV["AZURE_VAULT_API_VERSION"]
config.resource = ENV["AZURE_VAULT_RESOURCE"]
# To obtain the x5t encode base64 thumbprint of the certificate:
# echo $(openssl x509 -in public_certificate.pem -fingerprint -noout) | sed 's/SHA1 Fingerprint=//g' | sed 's/://g' | xxd -r -ps | base64
config.azure_certificate_thumbprint = ENV["AZURE_CERTIFICATE_THUMBPRINT"]
#Certificat that was upload to Azure was generated with:
# openssl req -x509 -newkey rsa:4096 -keyout private_key.pem -out public_certificate.pem -nodes
config.azure_certificate_private_key_file = ENV["AZURE_CERTIFICATE_PRIVATE_KEY_FILE"]
end
Note: This post and answer (@Jason Johnston) help me a lot to understand what was going on: Office 365 Rest API - Daemon week authentication
Upvotes: 8