Skip to content

Commit 9e49667

Browse files
committed
eks-detection
1 parent 6614f25 commit 9e49667

1 file changed

Lines changed: 233 additions & 49 deletions

File tree

  • common-lib/cloud-provider-identifier/providers

common-lib/cloud-provider-identifier/providers/aws.go

Lines changed: 233 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -22,88 +22,272 @@ import (
2222
"net/http"
2323
"os"
2424
"strings"
25+
"time"
2526

2627
"github.com/devtron-labs/common-lib/cloud-provider-identifier/bean"
2728
"go.uber.org/zap"
2829
)
2930

3031
type instanceIdentityResponse struct {
31-
ImageID string `json:"imageId"`
32-
InstanceID string `json:"instanceId"`
32+
ImageID string `json:"imageId"`
33+
InstanceID string `json:"instanceId"`
34+
AvailabilityZone string `json:"availabilityZone"`
35+
Region string `json:"region"`
36+
InstanceType string `json:"instanceType"`
3337
}
3438

3539
type IdentifyAmazon struct {
3640
Logger *zap.SugaredLogger
3741
}
3842

43+
// AWS environment variables commonly available in EKS environments
44+
var awsEnvironmentVariables = []string{
45+
"AWS_REGION",
46+
"AWS_DEFAULT_REGION",
47+
"AWS_ROLE_ARN",
48+
"AWS_WEB_IDENTITY_TOKEN_FILE",
49+
"AWS_STS_REGIONAL_ENDPOINTS",
50+
}
51+
52+
// AWS service account token paths for EKS
53+
var awsServiceAccountPaths = []string{
54+
"/var/run/secrets/eks.amazonaws.com/serviceaccount/token",
55+
"/var/run/secrets/kubernetes.io/serviceaccount/token",
56+
}
57+
3958
func (impl *IdentifyAmazon) Identify() (string, error) {
59+
// Try multiple detection methods in order of reliability for EKS environments
60+
61+
// 1. Check AWS environment variables (most reliable for EKS)
62+
if impl.checkAWSEnvironmentVariables() {
63+
impl.Logger.Infow("AWS detected via environment variables")
64+
return bean.Amazon, nil
65+
}
66+
67+
// 2. Check for AWS service account tokens (EKS-specific)
68+
if impl.checkAWSServiceAccountTokens() {
69+
impl.Logger.Infow("AWS detected via service account tokens")
70+
return bean.Amazon, nil
71+
}
72+
73+
// 3. Check /proc/version for AWS-specific information
74+
if impl.checkProcVersion() {
75+
impl.Logger.Infow("AWS detected via /proc/version")
76+
return bean.Amazon, nil
77+
}
78+
79+
// 4. Check DMI system files (backward compatibility for EC2)
80+
if impl.checkDMISystemFiles() {
81+
impl.Logger.Infow("AWS detected via DMI system files")
82+
return bean.Amazon, nil
83+
}
84+
85+
impl.Logger.Debugw("AWS not detected via file-based methods")
86+
return bean.Unknown, nil
87+
}
88+
89+
// checkAWSEnvironmentVariables checks for AWS-specific environment variables
90+
func (impl *IdentifyAmazon) checkAWSEnvironmentVariables() bool {
91+
for _, envVar := range awsEnvironmentVariables {
92+
if value := os.Getenv(envVar); value != "" {
93+
impl.Logger.Debugw("Found AWS environment variable", "variable", envVar, "value", value)
94+
return true
95+
}
96+
}
97+
return false
98+
}
99+
100+
// checkAWSServiceAccountTokens checks for AWS service account tokens (EKS-specific)
101+
func (impl *IdentifyAmazon) checkAWSServiceAccountTokens() bool {
102+
for _, tokenPath := range awsServiceAccountPaths {
103+
if _, err := os.Stat(tokenPath); err == nil {
104+
impl.Logger.Debugw("Found AWS service account token", "path", tokenPath)
105+
return true
106+
}
107+
}
108+
return false
109+
}
110+
111+
// checkProcVersion checks /proc/version for AWS-specific information
112+
func (impl *IdentifyAmazon) checkProcVersion() bool {
113+
data, err := os.ReadFile("/proc/version")
114+
if err != nil {
115+
impl.Logger.Debugw("Could not read /proc/version", "error", err)
116+
return false
117+
}
118+
119+
content := strings.ToLower(string(data))
120+
awsIndicators := []string{"aws", "amazon", "ec2", "xen"}
121+
122+
for _, indicator := range awsIndicators {
123+
if strings.Contains(content, indicator) {
124+
impl.Logger.Debugw("Found AWS indicator in /proc/version", "indicator", indicator)
125+
return true
126+
}
127+
}
128+
return false
129+
}
130+
131+
// checkDMISystemFiles checks DMI system files (backward compatibility)
132+
func (impl *IdentifyAmazon) checkDMISystemFiles() bool {
40133
data, err := os.ReadFile(bean.AmazonSysFile)
41134
if err != nil {
42-
impl.Logger.Errorw("error while reading file", "error", err)
43-
return bean.Unknown, err
135+
impl.Logger.Debugw("Could not read DMI system file", "file", bean.AmazonSysFile, "error", err)
136+
return false
44137
}
138+
45139
if strings.Contains(string(data), bean.AmazonIdentifierString) {
46-
return bean.Amazon, nil
140+
impl.Logger.Debugw("Found AWS identifier in DMI system file", "identifier", bean.AmazonIdentifierString)
141+
return true
47142
}
48-
return bean.Unknown, nil
143+
return false
49144
}
50145

51146
func (impl *IdentifyAmazon) IdentifyViaMetadataServer(detected chan<- string) {
52-
r := instanceIdentityResponse{}
53-
req, err := http.NewRequest("PUT", bean.TokenForAmazonMetadataServerV2, nil)
147+
// Create HTTP client with timeout for metadata service
148+
client := &http.Client{
149+
Timeout: 5 * time.Second,
150+
}
151+
152+
// Try to get IMDSv2 token first
153+
token, err := impl.getIMDSv2Token(client)
54154
if err != nil {
55-
impl.Logger.Errorw("error while creating new request", "error", err)
155+
impl.Logger.Debugw("Failed to get IMDSv2 token, trying without token", "error", err)
156+
// Fallback: try without token (IMDSv1) for backward compatibility
157+
if impl.tryMetadataWithoutToken(client, detected) {
158+
return
159+
}
56160
detected <- bean.Unknown
57161
return
58162
}
163+
164+
// Try with IMDSv2 token
165+
if impl.tryMetadataWithToken(client, token, detected) {
166+
return
167+
}
168+
169+
detected <- bean.Unknown
170+
}
171+
172+
// getIMDSv2Token gets the IMDSv2 token for metadata service access
173+
func (impl *IdentifyAmazon) getIMDSv2Token(client *http.Client) (string, error) {
174+
req, err := http.NewRequest("PUT", bean.TokenForAmazonMetadataServerV2, nil)
175+
if err != nil {
176+
return "", err
177+
}
59178
req.Header.Set("X-aws-ec2-metadata-token-ttl-seconds", "21600")
60-
tokenResp, err := http.DefaultClient.Do(req)
179+
180+
resp, err := client.Do(req)
61181
if err != nil {
62-
impl.Logger.Errorw("error while requesting", "error", err, "request", req)
63-
detected <- bean.Unknown
64-
return
182+
return "", err
65183
}
66-
defer tokenResp.Body.Close()
67-
token, err := io.ReadAll(tokenResp.Body)
184+
defer resp.Body.Close()
185+
186+
if resp.StatusCode != http.StatusOK {
187+
return "", err
188+
}
189+
190+
token, err := io.ReadAll(resp.Body)
68191
if err != nil {
69-
impl.Logger.Errorw("error while reading response body", "error", err, "respBody", tokenResp.Body)
70-
detected <- bean.Unknown
71-
return
192+
return "", err
72193
}
73-
req, err = http.NewRequest("GET", bean.AmazonMetadataServer, nil)
194+
195+
return string(token), nil
196+
}
197+
198+
// tryMetadataWithToken attempts to identify AWS using IMDSv2 with token
199+
func (impl *IdentifyAmazon) tryMetadataWithToken(client *http.Client, token string, detected chan<- string) bool {
200+
req, err := http.NewRequest("GET", bean.AmazonMetadataServer, nil)
74201
if err != nil {
75-
impl.Logger.Errorw("error while creating new request", "error", err)
76-
detected <- bean.Unknown
77-
return
202+
impl.Logger.Debugw("Error creating metadata request", "error", err)
203+
return false
78204
}
79-
req.Header.Set("X-aws-ec2-metadata-token", string(token))
80-
resp, err := http.DefaultClient.Do(req)
205+
req.Header.Set("X-aws-ec2-metadata-token", token)
206+
207+
return impl.processMetadataResponse(client, req, detected)
208+
}
209+
210+
// tryMetadataWithoutToken attempts to identify AWS using IMDSv1 (fallback)
211+
func (impl *IdentifyAmazon) tryMetadataWithoutToken(client *http.Client, detected chan<- string) bool {
212+
req, err := http.NewRequest("GET", bean.AmazonMetadataServer, nil)
81213
if err != nil {
82-
impl.Logger.Errorw("error while requesting", "error", err, "request", req)
83-
detected <- bean.Unknown
84-
return
214+
impl.Logger.Debugw("Error creating metadata request", "error", err)
215+
return false
85216
}
86-
if resp.StatusCode == http.StatusOK {
87-
defer resp.Body.Close()
88-
body, err := io.ReadAll(resp.Body)
89-
if err != nil {
90-
impl.Logger.Errorw("error while reading response body", "error", err, "respBody", resp.Body)
91-
detected <- bean.Unknown
92-
return
93-
}
94-
err = json.Unmarshal(body, &r)
95-
if err != nil {
96-
impl.Logger.Errorw("error while unmarshaling json", "error", err, "body", body)
97-
detected <- bean.Unknown
98-
return
99-
}
100-
if strings.HasPrefix(r.ImageID, "ami-") &&
101-
strings.HasPrefix(r.InstanceID, "i-") {
102-
detected <- bean.Amazon
103-
return
104-
}
105-
} else {
106-
detected <- bean.Unknown
107-
return
217+
218+
return impl.processMetadataResponse(client, req, detected)
219+
}
220+
221+
// processMetadataResponse processes the metadata service response and determines if it's AWS
222+
func (impl *IdentifyAmazon) processMetadataResponse(client *http.Client, req *http.Request, detected chan<- string) bool {
223+
resp, err := client.Do(req)
224+
if err != nil {
225+
impl.Logger.Debugw("Error requesting metadata", "error", err)
226+
return false
227+
}
228+
defer resp.Body.Close()
229+
230+
if resp.StatusCode != http.StatusOK {
231+
impl.Logger.Debugw("Metadata service returned non-200 status", "status", resp.StatusCode)
232+
return false
233+
}
234+
235+
body, err := io.ReadAll(resp.Body)
236+
if err != nil {
237+
impl.Logger.Debugw("Error reading metadata response", "error", err)
238+
return false
239+
}
240+
241+
var r instanceIdentityResponse
242+
err = json.Unmarshal(body, &r)
243+
if err != nil {
244+
impl.Logger.Debugw("Error unmarshaling metadata response", "error", err, "body", string(body))
245+
return false
246+
}
247+
248+
// Enhanced AWS detection logic for EKS compatibility
249+
isAWS := false
250+
251+
// Traditional EC2 detection (backward compatibility)
252+
if strings.HasPrefix(r.ImageID, "ami-") && strings.HasPrefix(r.InstanceID, "i-") {
253+
impl.Logger.Debugw("AWS detected via traditional EC2 metadata", "imageId", r.ImageID, "instanceId", r.InstanceID)
254+
isAWS = true
255+
}
256+
257+
// EKS/Fargate detection - check for AWS region format
258+
if r.Region != "" && impl.isValidAWSRegion(r.Region) {
259+
impl.Logger.Debugw("AWS detected via region metadata", "region", r.Region)
260+
isAWS = true
261+
}
262+
263+
// Check availability zone format (AWS-specific)
264+
if r.AvailabilityZone != "" && impl.isValidAWSAvailabilityZone(r.AvailabilityZone) {
265+
impl.Logger.Debugw("AWS detected via availability zone", "az", r.AvailabilityZone)
266+
isAWS = true
267+
}
268+
269+
if isAWS {
270+
detected <- bean.Amazon
271+
return true
272+
}
273+
274+
return false
275+
}
276+
277+
// isValidAWSRegion checks if the region follows AWS region naming convention
278+
func (impl *IdentifyAmazon) isValidAWSRegion(region string) bool {
279+
// AWS regions follow pattern: us-east-1, eu-west-1, ap-southeast-2, etc.
280+
parts := strings.Split(region, "-")
281+
return len(parts) >= 3 && len(parts[len(parts)-1]) == 1
282+
}
283+
284+
// isValidAWSAvailabilityZone checks if the AZ follows AWS AZ naming convention
285+
func (impl *IdentifyAmazon) isValidAWSAvailabilityZone(az string) bool {
286+
// AWS AZs follow pattern: us-east-1a, eu-west-1b, etc.
287+
if len(az) < 4 {
288+
return false
108289
}
290+
// Should end with a single letter
291+
lastChar := az[len(az)-1]
292+
return lastChar >= 'a' && lastChar <= 'z'
109293
}

0 commit comments

Comments
 (0)