class Azure::Utility::Credentials

Credentials

Constants

AZURE_SERVICE_PRINCIPAL
CONFIG_PATH

Public Class Methods

new() click to toggle source
# File lib/azure/utility/credentials.rb, line 128
def initialize
  cli = Options.new
  cli.parse_options
  CustomLogger.log.debug "Command line options: #{cli.config.inspect}"

  username = cli.config[:username] || username_stdin
  password = cli.config[:password] || password_stdin

  # Get Bearer token for user and pass through to main method
  token = azure_authenticate(username, password)
  if token.nil?
    error_message = 'Unable to acquire token from Azure AD provider.'
    CustomLogger.log.error error_message
    raise error_message
  end
  created_credentials = create_all_objects(token, cli.config)
  CustomLogger.log.debug "Credential details: #{created_credentials.inspect}"
  create_file(created_credentials, cli.config)
  CustomLogger.log.info 'Done!'
end

Public Instance Methods

assign_service_principal_to_role_id(subscription_id, token, service_principal_object_id, role_definition_id) click to toggle source
# File lib/azure/utility/credentials.rb, line 327
      def assign_service_principal_to_role_id(subscription_id, token, service_principal_object_id, role_definition_id)
        CustomLogger.log.info 'Attempting to assign service principal to role'
        url = "https://management.azure.com/subscriptions/#{subscription_id}/providers/Microsoft.Authorization/roleAssignments/#{service_principal_object_id}?api-version=2015-07-01"
        payload_json = <<-PAYLOADEOH
        {
            "properties": {
                "roleDefinitionId": "#{role_definition_id}",
                "principalId": "#{service_principal_object_id}"
            }
        }
        PAYLOADEOH
        azure_call(:put, url, payload_json, token)
      end
azure_authenticate(username, password) click to toggle source
# File lib/azure/utility/credentials.rb, line 346
def azure_authenticate(username, password)
  CustomLogger.log.info 'Authenticating to Azure Active Directory'
  url = 'https://login.windows.net/Common/oauth2/token'
  data = "resource=https%3A%2F%2Fmanagement.core.windows.net%2F&client_id=#{AZURE_SERVICE_PRINCIPAL}" \
    "&grant_type=password&username=#{username}&scope=openid&password=#{password}"
  response = http_post(url, data)
  JSON.parse(response.body)['access_token']
end
azure_call(method, url, data, token) click to toggle source
# File lib/azure/utility/credentials.rb, line 368
def azure_call(method, url, data, token)
  uri = URI(url)
  response = nil
  Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
    case method
    when :put
      request = Net::HTTP::Put.new uri
    when :delete
      request = Net::HTTP::Delete.new uri
    when :get
      request = Net::HTTP::Get.new uri
    when :post
      request = Net::HTTP::Post.new uri
    when :patch
      request = Net::HTTP::Patch.new uri
    end
    request.body = data
    request['Authorization'] = "Bearer #{token}"
    request['Content-Type'] = 'application/json'
    CustomLogger.log.debug "Request: #{request.uri} (#{method}) #{data}"
    response = http.request request
    CustomLogger.log.debug "Response: #{response.body}"
  end
  JSON.parse(response.body) unless response.nil?
end
create_all_objects(token, config) click to toggle source
# File lib/azure/utility/credentials.rb, line 234
def create_all_objects(token, config)
  tenant_id = get_tenant_id(token).first['tenantId']
  subscriptions = Array(config[:subscription_id])
  subscriptions = get_subscriptions(token) if subscriptions.empty?
  identifier = SecureRandom.hex(2)
  credentials = []
  subscriptions.each do |subscription|
    new_application_name = "azure_#{identifier}_#{subscription}"

    if config[:type] == 'azurecli' then
      new_client_secret = SecureRandom.uuid
    else
      new_client_secret = SecureRandom.urlsafe_base64(16, true)
    end

    application_id = create_application(tenant_id, token, new_application_name, new_client_secret)['appId']
    service_principal_object_id = create_service_principal(tenant_id, token, application_id)['objectId']
    role_name = config[:role] || 'Contributor'
    role_definition_id = get_role_definition(subscription, token, role_name).first['id']
    success = false
    counter = 0
    until success || counter > 5
      counter += 1
      CustomLogger.log.info "Waiting for service principal to be available in directory (retry #{counter})"
      sleep 2
      assigned_role = assign_service_principal_to_role_id(subscription, token, service_principal_object_id, role_definition_id)
      success = true unless assigned_role['error']
    end
    raise 'Failed to assign Service Principal to Role' unless success
    CustomLogger.log.info "Assigned service principal to role #{role_name} in subscription #{subscription}"
    new_credentials = {}
    new_credentials[:subscription_id] = subscription
    new_credentials[:client_id] = application_id
    new_credentials[:client_secret] = new_client_secret
    new_credentials[:tenant_id] = tenant_id
    credentials.push(new_credentials)
  end
  credentials
end
create_application(tenant_id, token, new_application_name, new_client_secret) click to toggle source
# File lib/azure/utility/credentials.rb, line 291
      def create_application(tenant_id, token, new_application_name, new_client_secret)
        CustomLogger.log.info "Creating application #{new_application_name} in tenant #{tenant_id}"
        url = "https://graph.windows.net/#{tenant_id}/applications?api-version=1.42-previewInternal"
        payload_json = <<-JSONEOH
        {
            "availableToOtherTenants": false,
            "displayName": "#{new_application_name}",
            "homepage": "https://management.core.windows.net",
            "identifierUris": [
                "https://#{tenant_id}/#{new_application_name}"
            ],
            "passwordCredentials": [
                {
                "startDate": "#{Time.now.utc.iso8601}",
                "endDate": "#{(Time.now + (24 * 60 * 60 * 365 * 10)).utc.iso8601}",
                "keyId": "#{SecureRandom.uuid}",
                "value": "#{new_client_secret}"
                }
            ]
        }
        JSONEOH
        azure_call(:post, url, payload_json, token)
      end
create_file(created_credentials, config) click to toggle source
# File lib/azure/utility/credentials.rb, line 159
      def create_file(created_credentials, config)
        file_name = config[:output_file] || './credentials'
        file_name_expanded = File.expand_path(file_name)
        CustomLogger.log.info "Creating credentials file at #{file_name_expanded}"
        output = ''

        style = config[:type] || 'chef'
        case style
        when 'chef' # ref: https://github.com/pendrica/chef-provisioning-azurerm#configuration
          created_credentials.each do |s|
            subscription_template = <<~CHEFEOH
              [#{s[:subscription_id]}]
              client_id = "#{s[:client_id]}"
              client_secret = "#{s[:client_secret]}"
              tenant_id = "#{s[:tenant_id]}"

            CHEFEOH
            output += subscription_template
          end
        when 'terraform' # ref: https://www.terraform.io/docs/providers/azurerm/index.html
          created_credentials.each do |s|
            subscription_template = <<~TFEOH
              provider "azurerm" {
                subscription_id = "#{s[:subscription_id]}"
                client_id       = "#{s[:client_id]}"
                client_secret   = "#{s[:client_secret]}"
                tenant_id       = "#{s[:tenant_id]}"
              }

            TFEOH
            output += subscription_template
          end
        when 'puppet' # ref: https://github.com/puppetlabs/puppetlabs-azure#installing-the-azure-module
          created_credentials.each do |s|
            subscription_template = <<~PPEOH
              azure: {
                subscription_id: "#{s[:subscription_id]}"
                tenant_id: "#{s[:tenant_id]}"
                client_id: "#{s[:client_id]}"
                client_secret: "#{s[:client_secret]}"
              }

            PPEOH
            output += subscription_template
          end
        when 'azurecli'
          created_credentials.each do |s|
            subscription_template = <<~AZURECLIEOH
              [default]
              subscription_id=#{s[:subscription_id]}
              tenant=#{s[:tenant_id]}
              client_id=#{s[:client_id]}
              secret=#{s[:client_secret]}

            AZURECLIEOH
            output += subscription_template
          end
        else # generic credentials output
          created_credentials.each do |s|
            subscription_template = <<~GENERICEOH
              azure_subscription_id = "#{s[:subscription_id]}"
              azure_tenant_id = "#{s[:tenant_id]}"
              azure_client_id = "#{s[:client_id]}"
              azure_client_secret = "#{s[:client_secret]}"

            GENERICEOH
            output += subscription_template
          end
        end
        File.open(file_name_expanded, 'w') do |file|
          file.write(output)
        end
        puts output if config[:out_to_screen]
      end
create_service_principal(tenant_id, token, application_id) click to toggle source
# File lib/azure/utility/credentials.rb, line 315
      def create_service_principal(tenant_id, token, application_id)
        CustomLogger.log.info 'Creating service principal for application'
        url = "https://graph.windows.net/#{tenant_id}/servicePrincipals?api-version=1.42-previewInternal"
        payload_json = <<-PAYLOADEOH
        {
            "appId": "#{application_id}",
            "accountEnabled": true
        }
        PAYLOADEOH
        azure_call(:post, url, payload_json, token)
      end
get_role_definition(tenant_id, token, role_name) click to toggle source
# File lib/azure/utility/credentials.rb, line 341
def get_role_definition(tenant_id, token, role_name)
  role_definitions = azure_call(:get, "https://management.azure.com/subscriptions/#{tenant_id}/providers/Microsoft.Authorization/roleDefinitions?$filter=roleName%20eq%20\'#{role_name}\'&api-version=2015-07-01", nil, token)
  role_definitions['value']
end
get_subscriptions(token) click to toggle source
# File lib/azure/utility/credentials.rb, line 274
def get_subscriptions(token)
  CustomLogger.log.info 'Retrieving subscriptions info'
  subscriptions = []
  subscriptions_call = azure_call(:get, 'https://management.azure.com/subscriptions?api-version=2015-01-01', nil, token)
  subscriptions_call['value'].each do |subscription|
    subscriptions.push subscription['subscriptionId']
  end
  CustomLogger.log.debug "SubscriptionIDs returned: #{subscriptions.inspect}"
  subscriptions
end
get_tenant_id(token) click to toggle source
# File lib/azure/utility/credentials.rb, line 285
def get_tenant_id(token)
  CustomLogger.log.info 'Retrieving tenant info'
  tenants = azure_call(:get, 'https://management.azure.com/tenants?api-version=2015-01-01', nil, token)
  tenants['value']
end
http_post(url, data) click to toggle source
# File lib/azure/utility/credentials.rb, line 355
def http_post(url, data)
  uri = URI(url)
  response = nil
  Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
    request = Net::HTTP::Post.new uri
    CustomLogger.log.debug "Request: #{request.uri} (#{request.method}) #{data}"
    request.body = data
    response = http.request request
    CustomLogger.log.debug "Response: #{response.body}"
  end
  response
end
password_stdin() click to toggle source
# File lib/azure/utility/credentials.rb, line 154
def password_stdin
  print 'Enter your password: '
  STDIN.noecho(&:gets).chomp
end
username_stdin() click to toggle source
# File lib/azure/utility/credentials.rb, line 149
def username_stdin
  print 'Enter your Azure AD username (user@domain.com): '
  STDIN.gets.chomp
end