【问题标题】: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 获取令牌并从 Vault 中读取值
【发布时间】:2020-10-15 03:18:28
【问题描述】:

我的应用程序是使用Ruby On Rails 构建的。我被要求使用Microsoft Azure Key Vault 来存储我们的秘密字符串。

我知道 Microsoft 团队提供 Gems:

https://rubygems.org/gems/azure_mgmt_key_vault

https://rubygems.org/gems/azure_key_vault

如何“提取”或“引用”密钥并将其传递给我的应用程序?

【问题讨论】:

    标签: ruby-on-rails ruby azure azure-keyvault


    【解决方案1】:

    经过大量的工作和汗水,我想通了。更重要的是,我能够使用证书连接到 Microsoft Azure Key Vault。所以下面我把我所有的代码用两种方法来获取令牌。一个带有client secret id,另一个带有certificate

    我找到了如何生成自签名证书(用于调试目的)并获取 编码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
    

    注意:这篇文章和回答 (@Jason Johnston) 对我了解发生的事情有很大帮助:Office 365 Rest API - Daemon week authentication

    【讨论】:

    • 工作正常。谢谢。