如何连接到 Microsoft Azure Key Vault 使用 Ruby 在 Rails 上获取令牌并从 Vault 中读取值
How to connect to Microsoft Azure Key Vault get a token and read a value from the Vault with Ruby On Rails
我的应用程序是使用 Ruby On Rails
构建的。我被要求使用 Microsoft Azure Key Vault
来存储我们的秘密字符串。
我知道 Microsoft Teams 提供了可用的 Gems:
https://rubygems.org/gems/azure_mgmt_key_vault
https://rubygems.org/gems/azure_key_vault
如何“提取”或“引用”密钥并将其传递给我的应用程序?
经过大量的工作和汗水,我想通了。更重要的是,我能够使用证书连接到 Microsoft Azure Key Vault。所以在下面我把我所有的代码都用两种方法来获取令牌。一个带有 client secret id
,另一个带有 certificate
.
我找到了如何生成 self-sign 证书(用于调试目的)并获得
编码 thumbprint
:
上传到 Azure 的证书是通过以下方式生成的:
openssl req -x509 -newkey rsa:4096 -keyout private_key.pem -out public_certificate.pem -nodes -days 3650
获取证书的x5t
编码base64指纹:
echo $(openssl x509 -in public_certificate.pem -fingerprint -noout) | sed 's/SHA1 Fingerprint=//g' | sed 's/://g' | xxd -r -ps | base64
我建了一个GEM。
我有一个配置文件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
这是奇迹发生的文件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://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow
# https://docs.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://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow
# https://docs.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
我还有一个 Initialiser
,我在其中为我的配置变量赋值
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
注意:post 和回答 (@Jason Johnston) 对我理解发生了什么有很大帮助:
我的应用程序是使用 Ruby On Rails
构建的。我被要求使用 Microsoft Azure Key Vault
来存储我们的秘密字符串。
我知道 Microsoft Teams 提供了可用的 Gems:
https://rubygems.org/gems/azure_mgmt_key_vault
https://rubygems.org/gems/azure_key_vault
如何“提取”或“引用”密钥并将其传递给我的应用程序?
经过大量的工作和汗水,我想通了。更重要的是,我能够使用证书连接到 Microsoft Azure Key Vault。所以在下面我把我所有的代码都用两种方法来获取令牌。一个带有 client secret id
,另一个带有 certificate
.
我找到了如何生成 self-sign 证书(用于调试目的)并获得
编码 thumbprint
:
上传到 Azure 的证书是通过以下方式生成的:
openssl req -x509 -newkey rsa:4096 -keyout private_key.pem -out public_certificate.pem -nodes -days 3650
获取证书的x5t
编码base64指纹:
echo $(openssl x509 -in public_certificate.pem -fingerprint -noout) | sed 's/SHA1 Fingerprint=//g' | sed 's/://g' | xxd -r -ps | base64
我建了一个GEM。
我有一个配置文件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
这是奇迹发生的文件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://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow
# https://docs.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://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow
# https://docs.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
我还有一个 Initialiser
,我在其中为我的配置变量赋值
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
注意:post 和回答 (@Jason Johnston) 对我理解发生了什么有很大帮助: