package discover import ( "bufio" "context" "os" "path/filepath" "strings" "incredigo/internal/vault" ) func init() { Register(&awsScanner{}) } // awsScanner reads ~/.aws/credentials (INI). Read-only: opens O_RDONLY and never // writes back. type awsScanner struct{} func (a *awsScanner) Name() string { return "aws" } func (a *awsScanner) path() string { if p := os.Getenv("AWS_SHARED_CREDENTIALS_FILE"); p != "" { return p } home, _ := os.UserHomeDir() return filepath.Join(home, ".aws", "credentials") } func (a *awsScanner) Available() bool { _, err := os.Stat(a.path()) return err == nil } func (a *awsScanner) Scan(ctx context.Context, v *vault.Vault) ([]Credential, error) { p := a.path() f, err := os.Open(p) // read-only if err != nil { return nil, err } defer f.Close() fi, _ := f.Stat() var creds []Credential var profile, keyID string flush := func(secret string) { if profile == "" || secret == "" { return } creds = append(creds, Credential{ Source: a.Name(), Kind: KindAWSKey, Identity: profile + " / " + redactMiddle(keyID), Location: p, Modified: fi.ModTime(), Secret: v.Store([]byte(secret)), Meta: map[string]string{"access_key_id": redactMiddle(keyID)}, }) keyID = "" } sc := bufio.NewScanner(f) for sc.Scan() { line := strings.TrimSpace(sc.Text()) if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, ";") { continue } if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") { profile = strings.TrimSpace(line[1 : len(line)-1]) keyID = "" continue } k, val, ok := splitKV(line) if !ok { continue } switch strings.ToLower(k) { case "aws_access_key_id": keyID = val case "aws_secret_access_key": flush(val) } } return creds, sc.Err() } func splitKV(line string) (k, v string, ok bool) { i := strings.IndexByte(line, '=') if i < 0 { return "", "", false } return strings.TrimSpace(line[:i]), strings.TrimSpace(line[i+1:]), true } // redactMiddle keeps the first 4 and last 2 chars, masking the middle, so labels // are human-recognizable without leaking the value. func redactMiddle(s string) string { if len(s) <= 8 { return strings.Repeat("*", len(s)) } return s[:4] + strings.Repeat("*", len(s)-6) + s[len(s)-2:] }