discover: broaden discovery to feed the new drivers
Per the standing directive (dynamic discovery per credential type as each driver lands): - env.go: tag DB connection URLs by scheme (postgres/postgresql, mysql/mariadb, redis/rediss) with a user:password authority, routing DATABASE_URL/REDIS_URL/ MYSQL_URL straight to the in-place DB drivers, kept regardless of the secret-name heuristic. - aws.go: emit the driver-ready single-line blob (was the bare secret) and resolve region from env / ~/.aws/config. - ssh.go: content-based key detection (not just id_*) + ~/.ssh/config IdentityFile parsing. - tea.go: new scanner mapping Gitea `tea` CLI config to a driver-ready PAT blob. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
package discover
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"incredigo/internal/vault"
|
||||
)
|
||||
|
||||
func init() { Register(&teaScanner{}) }
|
||||
|
||||
// teaScanner reads the Gitea `tea` CLI config (~/.config/tea/config.yml), which
|
||||
// stores one or more logins as {name, url, user, token}. Because this file is
|
||||
// unambiguously Gitea, entries are tagged Source "gitea" so the gitea rotation
|
||||
// driver Detects them directly.
|
||||
//
|
||||
// The emitted secret is the driver-ready single-line blob
|
||||
// "<scheme>://<user>:<token>@<host>/" (see internal/rotate/gitea.go). The token
|
||||
// name is unknown from tea config, so it is omitted — RevokeOld then degrades to
|
||||
// the guided worklist for the old-token deletion, which is the correct behavior.
|
||||
// Read-only.
|
||||
type teaScanner struct{}
|
||||
|
||||
func (t *teaScanner) Name() string { return "gitea" }
|
||||
|
||||
func (t *teaScanner) path() string {
|
||||
if x := os.Getenv("XDG_CONFIG_HOME"); x != "" {
|
||||
return filepath.Join(x, "tea", "config.yml")
|
||||
}
|
||||
home, _ := os.UserHomeDir()
|
||||
return filepath.Join(home, ".config", "tea", "config.yml")
|
||||
}
|
||||
|
||||
func (t *teaScanner) Available() bool {
|
||||
_, err := os.Stat(t.path())
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (t *teaScanner) Scan(ctx context.Context, v *vault.Vault) ([]Credential, error) {
|
||||
p := t.path()
|
||||
b, err := os.ReadFile(p) // read-only
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fi, _ := os.Stat(p)
|
||||
|
||||
var cfg struct {
|
||||
Logins []struct {
|
||||
Name string `yaml:"name"`
|
||||
URL string `yaml:"url"`
|
||||
User string `yaml:"user"`
|
||||
Token string `yaml:"token"`
|
||||
} `yaml:"logins"`
|
||||
}
|
||||
if err := yaml.Unmarshal(b, &cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var creds []Credential
|
||||
for _, l := range cfg.Logins {
|
||||
if l.Token == "" || l.URL == "" {
|
||||
continue
|
||||
}
|
||||
u, err := url.Parse(l.URL)
|
||||
if err != nil || u.Host == "" {
|
||||
continue
|
||||
}
|
||||
user := l.User
|
||||
if user == "" {
|
||||
user = l.Name
|
||||
}
|
||||
scheme := u.Scheme
|
||||
if scheme == "" {
|
||||
scheme = "https"
|
||||
}
|
||||
// Driver-ready blob; the token lives only here (and the vault).
|
||||
blob := (&url.URL{
|
||||
Scheme: scheme,
|
||||
Host: u.Host,
|
||||
User: url.UserPassword(user, l.Token),
|
||||
Path: "/",
|
||||
}).String()
|
||||
|
||||
creds = append(creds, Credential{
|
||||
Source: t.Name(),
|
||||
Kind: KindToken,
|
||||
Identity: user + " @ " + u.Host,
|
||||
Location: p,
|
||||
Modified: fi.ModTime(),
|
||||
Secret: v.Store([]byte(blob)),
|
||||
})
|
||||
}
|
||||
return creds, nil
|
||||
}
|
||||
Reference in New Issue
Block a user