| 1 | package rootaws |
| 2 | |
| 3 | import ( |
| 4 | "bytes" |
| 5 | "fmt" |
| 6 | "strings" |
| 7 | |
| 8 | "github.com/aws/aws-sdk-go-v2/aws" |
| 9 | ) |
| 10 | |
| 11 | const MaxCredentialsSize = 64 << 10 |
| 12 | |
| 13 | // ParseStaticCredentials accepts only one default profile with literal keys. |
| 14 | // Errors never include credential values or executable configuration text. |
| 15 | func ParseStaticCredentials(body []byte) (aws.Credentials, error) { |
| 16 | var empty aws.Credentials |
| 17 | if len(body) > MaxCredentialsSize || bytes.IndexByte(body, 0) >= 0 { |
| 18 | return empty, fmt.Errorf("invalid credentials size or encoding") |
| 19 | } |
| 20 | profile := false |
| 21 | values := make(map[string]string) |
| 22 | for _, line := range strings.Split(string(body), "\n") { |
| 23 | line = strings.TrimSpace(line) |
| 24 | if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, ";") { |
| 25 | continue |
| 26 | } |
| 27 | if strings.HasPrefix(line, "[") { |
| 28 | if line != "[default]" || profile { |
| 29 | return empty, fmt.Errorf("credentials require one default profile") |
| 30 | } |
| 31 | profile = true |
| 32 | continue |
| 33 | } |
| 34 | // Same order as the SDK: cut the inline comment, then trim, then |
| 35 | // remove one matching pair of quotes. |
| 36 | key, value, found := strings.Cut(trimInlineComment(line), "=") |
| 37 | key, value = strings.TrimSpace(key), unquote(strings.TrimSpace(value)) |
| 38 | if !profile || !found || value == "" { |
| 39 | return empty, fmt.Errorf("invalid static credentials entry") |
| 40 | } |
| 41 | switch key { |
| 42 | case "aws_access_key_id", "aws_secret_access_key", "aws_session_token": |
| 43 | default: |
| 44 | return empty, fmt.Errorf("unsupported credentials key") |
| 45 | } |
| 46 | if _, exists := values[key]; exists || strings.ContainsAny(value, "\r\t ") { |
| 47 | return empty, fmt.Errorf("duplicate or invalid static credentials entry") |
| 48 | } |
| 49 | values[key] = value |
| 50 | } |
| 51 | if values["aws_access_key_id"] == "" || values["aws_secret_access_key"] == "" { |
| 52 | return empty, fmt.Errorf("credentials require access and secret keys") |
| 53 | } |
| 54 | return aws.Credentials{AccessKeyID: values["aws_access_key_id"], SecretAccessKey: values["aws_secret_access_key"], SessionToken: values["aws_session_token"], Source: "CarlosStaticCredentials"}, nil |
| 55 | } |
| 56 | |
| 57 | // trimInlineComment matches the SDK's property comments: only a # or ; after |
| 58 | // a space or tab starts one, so values may contain either character. |
| 59 | func trimInlineComment(s string) string { |
| 60 | for _, marker := range []string{" #", " ;", "\t#", "\t;"} { |
| 61 | s, _, _ = strings.Cut(s, marker) |
| 62 | } |
| 63 | return s |
| 64 | } |
| 65 | |
| 66 | func unquote(s string) string { |
| 67 | if len(s) >= 2 && (s[0] == '"' && s[len(s)-1] == '"' || s[0] == '\'' && s[len(s)-1] == '\'') { |
| 68 | return s[1 : len(s)-1] |
| 69 | } |
| 70 | return s |
| 71 | } |
| 72 | |