package cloud import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "encoding/xml" "fmt" "io" "net/http" "net/url" "sort" "strings" "time" ) // AWSProvider 通过 EC2 Query API 同步 AWS 区域与实例。 // Logan.Li type AWSProvider struct { accessKey string secretKey string } // NewAWSProvider 创建 AWS Provider。 // Logan.Li func NewAWSProvider(accessKey, secretKey string) *AWSProvider { return &AWSProvider{accessKey: accessKey, secretKey: secretKey} } // Name 返回 AWS 厂商标识。 // Logan.Li func (p *AWSProvider) Name() string { return "aws" } // call 使用 AWS Signature V4 调用 EC2 Query API。 // Logan.Li func (p *AWSProvider) call(region, action string, values url.Values) ([]byte, error) { if region == "" { region = "us-east-1" } values.Set("Action", action) values.Set("Version", "2016-11-15") now := time.Now().UTC() date := now.Format("20060102") host := "ec2." + region + ".amazonaws.com" payload := values.Encode() canonicalHeaders := "content-type:application/x-www-form-urlencoded; charset=utf-8\nhost:" + host + "\nx-amz-date:" + now.Format("20060102T150405Z") + "\n" signedHeaders := "content-type;host;x-amz-date" canonicalRequest := "POST\n/\n\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + sha256Hex(payload) credentialScope := date + "/" + region + "/ec2/aws4_request" stringToSign := "AWS4-HMAC-SHA256\n" + now.Format("20060102T150405Z") + "\n" + credentialScope + "\n" + sha256Hex(canonicalRequest) kDate := hmacSHA256([]byte("AWS4"+p.secretKey), date) kRegion := hmacSHA256(kDate, region) kService := hmacSHA256(kRegion, "ec2") signature := hex.EncodeToString(hmacSHA256(hmacSHA256(kService, "aws4_request"), stringToSign)) req, err := http.NewRequest(http.MethodPost, "https://"+host+"/", strings.NewReader(payload)) if err != nil { return nil, fmt.Errorf("创建 AWS 请求失败: %w", err) } req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=utf-8") req.Header.Set("X-Amz-Date", now.Format("20060102T150405Z")) req.Header.Set("Authorization", "AWS4-HMAC-SHA256 Credential="+p.accessKey+"/"+credentialScope+", SignedHeaders="+signedHeaders+", Signature="+signature) resp, err := (&http.Client{Timeout: 30 * time.Second}).Do(req) if err != nil { return nil, fmt.Errorf("请求 AWS API 失败: %w", err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("读取 AWS 响应失败: %w", err) } if resp.StatusCode < 200 || resp.StatusCode >= 300 { return nil, fmt.Errorf("AWS API 返回 HTTP %d: %s", resp.StatusCode, string(body)) } return body, nil } // ListRegions 获取当前凭据可见的 AWS 区域。 // Logan.Li func (p *AWSProvider) ListRegions() ([]CloudRegion, error) { body, err := p.call("us-east-1", "DescribeRegions", url.Values{}) if err != nil { return nil, err } var result struct { Regions []struct { Name string `xml:"regionName"` Endpoint string `xml:"regionEndpoint"` } `xml:"regionInfo>item"` } if err := xml.Unmarshal(body, &result); err != nil { return nil, fmt.Errorf("解析 AWS 区域响应失败: %w", err) } regions := make([]CloudRegion, 0, len(result.Regions)) for _, region := range result.Regions { regions = append(regions, CloudRegion{RegionID: region.Name, LocalName: region.Name, Endpoint: "https://" + region.Endpoint, Status: "active"}) } sort.Slice(regions, func(i, j int) bool { return regions[i].RegionID < regions[j].RegionID }) return regions, nil } // ListInstances 获取一个 AWS 区域中的 EC2 实例。 // Logan.Li func (p *AWSProvider) ListInstances(regionID string) ([]CloudInstance, error) { body, err := p.call(regionID, "DescribeInstances", url.Values{}) if err != nil { return nil, err } var result struct { Reservations []struct { Instances []struct { ID string `xml:"instanceId"` Type string `xml:"instanceType"` State string `xml:"instanceState>name"` PrivateIP string `xml:"privateIpAddress"` PublicIP string `xml:"ipAddress"` Launch string `xml:"launchTime"` Placement string `xml:"placement>availabilityZone"` Platform string `xml:"platform"` Tags []struct { Key string `xml:"key"` Value string `xml:"value"` } `xml:"tagSet>item"` } `xml:"instancesSet>item"` } `xml:"reservationSet>item"` } if err := xml.Unmarshal(body, &result); err != nil { return nil, fmt.Errorf("解析 AWS 实例响应失败: %w", err) } instances := []CloudInstance{} for _, reservation := range result.Reservations { for _, item := range reservation.Instances { name := item.ID for _, tag := range item.Tags { if tag.Key == "Name" && tag.Value != "" { name = tag.Value break } } osType := "linux" if strings.EqualFold(item.Platform, "windows") { osType = "windows" } instances = append(instances, CloudInstance{InstanceID: item.ID, InstanceName: name, Hostname: name, OSType: osType, Status: awsState(item.State), RegionID: regionID, ZoneID: item.Placement, PrivateIP: item.PrivateIP, PublicIP: item.PublicIP, InstanceType: item.Type, CreationTime: item.Launch}) } } return instances, nil } // awsState 统一 AWS 实例状态,便于复用现有资产导入逻辑。 // Logan.Li func awsState(state string) string { if state == "running" { return "Running" } return state } // sha256Hex 计算签名所需的 SHA256 十六进制摘要。 // Logan.Li func sha256Hex(value string) string { sum := sha256.Sum256([]byte(value)) return hex.EncodeToString(sum[:]) } // hmacSHA256 计算签名链中的 HMAC-SHA256。 // Logan.Li func hmacSHA256(key []byte, value string) []byte { mac := hmac.New(sha256.New, key) mac.Write([]byte(value)) return mac.Sum(nil) }