Files
dingtalk-workspace-cli/internal/security/fingerprint.go
2026-07-15 11:41:30 +08:00

89 lines
2.3 KiB
Go

// Copyright 2026 Alibaba Group
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package security
import (
"fmt"
"net"
"slices"
"strings"
)
// virtualMACPrefixes lists MAC address prefixes commonly used by
// hypervisors, Docker, and VPN adapters.
var virtualMACPrefixes = []string{
"00:00:00",
"02:42:",
"02:00:",
"fe:ff:",
"00:50:56",
"00:0c:29",
"00:1c:42",
"08:00:27",
"52:54:00",
}
var networkInterfaces = net.Interfaces
func isVirtualMAC(mac string) bool {
for _, p := range virtualMACPrefixes {
if strings.HasPrefix(mac, p) {
return true
}
}
return false
}
// selectMAC picks the best MAC from the given interface list.
// Physical NICs are preferred; virtual MACs are used as fallback.
func selectMAC(ifaces []net.Interface) (string, error) {
var physical, virtual []string
for _, iface := range ifaces {
if iface.Flags&net.FlagLoopback != 0 {
continue
}
hw := iface.HardwareAddr
if len(hw) == 0 {
continue
}
s := strings.ToLower(hw.String())
if isVirtualMAC(s) {
virtual = append(virtual, s)
} else {
physical = append(physical, s)
}
}
if len(physical) > 0 {
slices.Sort(physical)
return physical[0], nil
}
if len(virtual) > 0 {
slices.Sort(virtual)
return virtual[0], nil
}
return "", fmt.Errorf("no NIC MAC address found (no network interfaces available)")
}
// GetMACAddress returns the first physical (non-loopback, non-virtual) MAC
// address, sorted lexicographically for determinism. If no physical NIC is
// found (e.g. inside a Docker container), it falls back to the first
// non-loopback virtual MAC address so that token encryption still works.
func GetMACAddress() (string, error) {
ifaces, err := networkInterfaces()
if err != nil {
return "", fmt.Errorf("enumerating network interfaces: %w", err)
}
return selectMAC(ifaces)
}