@@ -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
3031type 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
3539type 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+
3958func (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
51146func (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