agent/hybrid/instance/fingerprint.go (180 lines of code) (raw):
package instance
import (
"crypto/md5"
"encoding/base64"
"fmt"
"net"
"os"
"os/exec"
"strings"
"time"
"unicode/utf8"
"github.com/aliyun/aliyun_assist_client/agent/log"
"github.com/google/uuid"
)
const (
fingerprintVersion = 1
)
var (
fingerprintVersionPrefix = fmt.Sprintf("%03x_", fingerprintVersion)
)
// GenerateFingerprintIgnoreSavedHash generate fingerprint regardless of whether
// a hardware hash already exists.
func GenerateFingerprintIgnoreSavedHash() (string, error) {
return generateFingerprint(true)
}
// GenerateFingerprint generate fingerprint, but if hardware hash already exists
// and it is similar enough to current hardware the fingerprint in hardware hash
// will be returned.
func GenerateFingerprint() (string, error) {
return generateFingerprint(false)
}
func generateFingerprint(ignoreSavedHash bool) (string, error) {
var hardwareHash map[string]string
var savedHwInfo *HardwareInfo
var err error
var hwHashErr error
// retry getting the new hash and compare with the saved hash for 3 times
for attempt := 1; attempt <= 3; attempt++ {
// fetch current hardware hash values
hardwareHash, hwHashErr = CurrentHwHash()
if hwHashErr != nil || !isValidHardwareHash(hardwareHash) {
// sleep 5 seconds until the next retry
time.Sleep(5 * time.Second)
continue
}
if !ignoreSavedHash {
// try to get previously saved hardwareInfo
savedHwInfo, err = ReadHardwareInfo()
if err != nil {
log.GetLogger().Errorf("read saved hardwareInfo failed: %s", err)
continue
}
// first time generation, breakout retry
if !hasFingerprint(savedHwInfo) {
log.GetLogger().Warn("No fingerprint detected in saved hardwareInfo, skipping retry...")
break
}
// stop retry if the hardware hashes are the same
if isSimilarHardwareHash(savedHwInfo.HardwareHash, hardwareHash, defaultMatchPercent) {
log.GetLogger().Info("Calculated hardware hash is same as saved one, returning fingerprint")
return savedHwInfo.Fingerprint, nil
}
log.GetLogger().Warn("Calculated hardware hash is different with saved one, retry to ensure the difference is not cause by the dependency has not been ready")
// sleep 5 seconds until the next retry
time.Sleep(5 * time.Second)
} else {
log.GetLogger().Info("Ignore saved hardwareInfo")
break
}
}
if hwHashErr != nil {
log.GetLogger().Errorf("Error while fetching hardware hashes from instance: %s", hwHashErr)
return "", hwHashErr
} else if !isValidHardwareHash(hardwareHash) {
return "", fmt.Errorf("hardware hash generated contains invalid characters. %s", hardwareHash)
}
if ignoreSavedHash {
log.GetLogger().Info("Ignore saved hardwareInfo, refresh hardware info and generate a new fingerprint")
} else if !hasFingerprint(savedHwInfo) {
log.GetLogger().Info("There is no saved hardwareInfo, generate a new fingerprint")
} else {
log.GetLogger().Info("Current hardwareInfo is different from saved hardwareInfo, generate a new fingerprint")
}
newFingerprint := generateUUID()
// save hardwareInfo
if err = SaveHardwareInfo(newFingerprint, hardwareHash); err != nil {
log.GetLogger().Errorf("Save hardware hash failed: %s", err)
}
return newFingerprint, err
}
func generateUUID() string {
id := uuid.New()
return fingerprintVersionPrefix + strings.Replace(id.String(), "-", "", -1)
}
// isSimilarHardwareHash
func isSimilarHardwareHash(savedHwHash map[string]string, currentHwHash map[string]string, threshold int) bool {
var totalCount, successCount int
isSimilar := true
// similarity check is disabled when threshold is set to -1
if threshold == -1 {
log.GetLogger().Info("Similarity check is disabled, skipping hardware comparison")
return true
}
// check input
if len(savedHwHash) == 0 {
log.GetLogger().Errorf("Invalid saved hardware hash: %v", savedHwHash)
return false
} else if len(currentHwHash) == 0 {
log.GetLogger().Errorf("Invalid current hardware hash: %v", savedHwHash)
return false
}
// check whether hardwareId (uuid/machineid) has changed
// this usually happens during provisioning
if currentHwHash[hardwareID] != savedHwHash[hardwareID] {
log.GetLogger().Infof("MachindId/UUID in current hardwareInfo[%s] is different from saved hardwareInfo[%s], "+
"it's a new instance.", currentHwHash[hardwareID], savedHwHash[hardwareID])
isSimilar = false
} else {
mismatchedKeyMessages := make([]string, 0, len(currentHwHash))
const unmatchedValueFormat = "The '%s' value (%s) has changed from the registered machine configuration value (%s)."
const matchedValueFormat = "The '%s' value matches the registered machine configuration value."
// check whether ipaddress is the same - if the machine key and the IP address have not changed, it's the same instance.
if currentHwHash[ipaddreddInfo] == savedHwHash[ipaddreddInfo] {
log.GetLogger().Infof(matchedValueFormat, "IP Address")
} else {
message := fmt.Sprintf(unmatchedValueFormat, "IP Address", currentHwHash[ipaddreddInfo], savedHwHash[ipaddreddInfo])
mismatchedKeyMessages = append(mismatchedKeyMessages, message)
// identify number of successful matches
for key, currValue := range currentHwHash {
if prevValue, ok := savedHwHash[key]; ok && currValue == prevValue {
log.GetLogger().Infof(matchedValueFormat, key)
successCount++
} else {
message := fmt.Sprintf(unmatchedValueFormat, key, currValue, prevValue)
mismatchedKeyMessages = append(mismatchedKeyMessages, message)
}
}
// check if the changed match exceeds the minimum match percent
totalCount = len(currentHwHash)
similarity := float32(successCount) / float32(totalCount) * 100
if similarity < float32(threshold) {
log.GetLogger().Errorf("The similarity[%.2f%%] between current hardware and saved is less than the "+
"threshold[%.2f%%], it's a new instance.", similarity, float32(threshold))
for _, message := range mismatchedKeyMessages {
log.GetLogger().Warn(message)
}
isSimilar = false
}
}
}
return isSimilar
}
func commandOutputHash(command string, params ...string) (encodedValue string, value string, err error) {
var contentBytes []byte
if contentBytes, err = exec.Command(command, params...).Output(); err == nil {
value = string(contentBytes) // without encoding
sum := md5.Sum(contentBytes)
encodedValue = base64.StdEncoding.EncodeToString(sum[:])
}
return
}
func hostnameInfo() (value string, err error) {
return os.Hostname()
}
func primaryIpInfo() (value string, err error) {
addrs, err := net.InterfaceAddrs()
if err != nil {
return "", err
}
for _, address := range addrs {
// check the address type and if it is not a loopback then return it
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
return ipnet.IP.String(), nil
}
}
}
return "", err
}
func macAddrInfo() (value string, err error) {
ifaces, err := net.Interfaces()
if err != nil {
return "", err
}
for _, i := range ifaces {
if i.HardwareAddr.String() != "" {
return i.HardwareAddr.String(), nil
}
}
return "", nil
}
func isValidHardwareHash(hardwareHash map[string]string) bool {
for _, value := range hardwareHash {
if !utf8.ValidString(value) {
return false
}
}
return true
}
func hasFingerprint(info *HardwareInfo) bool {
return info != nil && info.Fingerprint != ""
}