github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/secrets/pgpass/pgpass.go (about) 1 // Copyright 2025 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package pgpass provides an extractor for identifying secrets in .pgpass files. 16 package pgpass 17 18 import ( 19 "bufio" 20 "context" 21 "fmt" 22 "path/filepath" 23 "regexp" 24 "strings" 25 26 "github.com/google/osv-scalibr/extractor/filesystem" 27 "github.com/google/osv-scalibr/inventory" 28 "github.com/google/osv-scalibr/plugin" 29 ) 30 31 // Pgpass is a Veles Secret that holds relevant information for a [Postgres Pgpass](https://www.postgresql.org/docs/current/libpq-pgpass.html). 32 type Pgpass struct { 33 Hostname string 34 Port string 35 Database string 36 Username string 37 Password string 38 } 39 40 const ( 41 // Name is the unique name of this extractor. 42 Name = "secrets/pgpass" 43 ) 44 45 var ( 46 // pgpassRe is a regular expression that matches the content of a pgpass file entry 47 // 48 // Reference: 49 // - https://www.postgresql.org/docs/current/libpq-pgpass.html 50 // 51 // Every entry in the pgpass file is composed by the following fields: 52 // hostname:port:database:username:password 53 // 54 // - hostname: matches any character except the `:` (that is currently used for separating fields) 55 // - port: matches numbers until 5 digits and * (wildcard) 56 // this group can match ports > 65535 but it is a compromise for regex performance 57 // - database: same as hostname 58 // - username: same as hostname 59 // - password: can match any allowed characters but colons must be escaped 60 pgpassRe = regexp.MustCompile(`^([ -9;-~]+):(\*|[0-9]{1,5}):([ -9;-~]+):([ -9;-~]+):((?:\\:|[ -9;-~])+)$`) 61 ) 62 63 // Extractor extracts postres credentials from .pgpass files. 64 type Extractor struct{} 65 66 // New returns a pgpass extractor. 67 func New() filesystem.Extractor { return &Extractor{} } 68 69 // Name of the extractor. 70 func (e Extractor) Name() string { return Name } 71 72 // Version of the extractor. 73 func (e Extractor) Version() int { return 0 } 74 75 // Requirements of the extractor. 76 func (e Extractor) Requirements() *plugin.Capabilities { 77 return &plugin.Capabilities{} 78 } 79 80 // FileRequired returns true if the specified file is a .pgpass file. 81 func (e Extractor) FileRequired(api filesystem.FileAPI) bool { 82 return filepath.Base(api.Path()) == ".pgpass" 83 } 84 85 // Extract extracts PostgreSQL credentials from .pgpass file. 86 func (e Extractor) Extract(ctx context.Context, input *filesystem.ScanInput) (inventory.Inventory, error) { 87 scanner := bufio.NewScanner(input.Reader) 88 var secrets []*inventory.Secret 89 90 for scanner.Scan() { 91 line := strings.TrimSpace(scanner.Text()) 92 // Skip empty lines and comments. 93 if line == "" || strings.HasPrefix(line, "#") { 94 continue 95 } 96 97 matches := pgpassRe.FindStringSubmatch(line) 98 99 if len(matches) == 6 { 100 password := matches[5] 101 // Skip entries where the password is a single '*' 102 if password == "*" { 103 continue 104 } 105 106 pgpassSecret := Pgpass{ 107 Hostname: matches[1], 108 Port: matches[2], 109 Database: matches[3], 110 Username: matches[4], 111 Password: matches[5], 112 } 113 114 secrets = append(secrets, &inventory.Secret{ 115 Secret: pgpassSecret, 116 Location: input.Path, 117 }) 118 } 119 } 120 121 if err := scanner.Err(); err != nil { 122 return inventory.Inventory{}, fmt.Errorf("error reading .pgpass file: %w", err) 123 } 124 125 return inventory.Inventory{Secrets: secrets}, nil 126 }