# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true require 'contrast/agent/telemetry/telemetry' require 'contrast/utils/os' require 'socket' module Contrast module Utils # Tools for supporting the Telemetry feature module Telemetry # Gets info about the instrumented application required to build unique identifiers, # used in the agent's Telemetry. module Identifier MAC_REGEX = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/.cs__freeze LINUX_OS_REG = /hwaddr=.*?(([A-F0-9]{2}:){5}[A-F0-9]{2})/im.cs__freeze MAC_OS_PRIMARY = 'en0'.cs__freeze LINUX_PRIMARY = 'enp'.cs__freeze # Sinatra and Grape both use similar approach to identify the app_name. # Rails has a different way of doing it, but to unify this we'll use this one. # If app_name is changed/renamed during production it would still get the # new folder's name. # # @ return [String] name of the application from the current working directory def self.app_name @_app_name ||= File.basename(Dir.pwd) end # Returns the MAC address of the primary network interface, depending on the used OS. # If the primary is unknown it finds the first available network interface and gets it's # MAC address instead. # # @return [String, nil] MAC address of the primary network interface or # the first available one, or nil if nothing found def self.mac primary = Contrast::Utils::OS.mac? ? MAC_OS_PRIMARY : LINUX_PRIMARY @_mac = find_mac(primary) || find_mac end class << self private # Finds the primary MAC address of all listed network adapters. # If primary is not set or unknown, use the first MAC address found # from the listed adapters. # # @param primary [nil, String] optional param if set look only for primary # network adapter's name # @return [String, nil] MAC address of the first listed network adapter or # nil if not found def find_mac primary = nil result = nil idx = 0 return if interfaces.empty? while idx < interfaces.length addr = interfaces[idx].addr name = interfaces[idx].name # rubocop:disable Security/Module/Name idx += 1 next if primary && !name.include?(primary) # retrieving MAC address from primary network interface or first available mac = retrieve_mac addr next unless mac result = mac if mac && (mac.match? MAC_REGEX) break if result end result end # Retrieves MAC address for primary or any network interface. # This is OS dependent search. # # @param addr [String] address info # example: # # @return mac [nil, String] MAC address of primary network interface, # any network interface, or nil if no interface is found. def retrieve_mac addr # Mac OS allow us to use getnameinfo(sockaddr [, flags]) => [hostname, servicename] # # returned address: # return addr.getnameinfo[0] if Contrast::Utils::OS.mac? # In Linux using Socket::addr#getnameinfo results in ai_family not supported exception. # In this case we are relying on match filtering of addresses. # # returned address: # # return Regexp.last_match(1) if addr.inspect =~ LINUX_OS_REG nil end # Returns array of network interfaces. # This is OS dependent search. # # @return interfaces [Array] Returns an array of interface addresses. # Socket::Ifaddr - represents a result of getifaddrs(). def interfaces @_interfaces ||= [] return @_interfaces unless @_interfaces.empty? arr = Socket.getifaddrs idx = 0 check_family = 0 while idx < arr.length # We need only network adapters MACs. Checking for pfamily of every socket address: # 18 for Mac OS and 17 for Linux. # family should be an address family such as: :INET, :INET6, :UNIX, etc. check_family = 18 if Contrast::Utils::OS.mac? check_family = 17 if Contrast::Utils::OS.linux? if arr[idx].addr.pfamily != check_family idx += 1 next end @_interfaces << arr[idx] idx += 1 end @_interfaces end end end end end end