mirror of
https://gitea.com/gitea/tea.git
synced 2026-08-25 04:44:22 +00:00
fix(login): avoid panic when parsing auto-discovered SSH keys (#1100)
## Problem `tea login add` can panic while auto-discovering SSH keys. The interactive login flow calls `regexp.FindStringSubmatch` and immediately indexes `[1]` without checking whether the regex matched. When the selected key display string does not have the expected format, the returned slice is `nil` and tea crashes with: ``` panic: runtime error: index out of range [1] with length 0 ``` This is the crash reported in #527. ## Root cause `regexp.Regexp.FindStringSubmatch` returns `nil` when the input does not match. Indexing that result with `[1]` assumes a match and causes the panic. The same unchecked pattern exists for SSH certificates and plain public keys in `modules/interact/login.go`. ## Changes - Extract auto-discovered SSH key/certificate display parsing into `parseSSHPubkeySelection`. - Add a `regexpSubmatch` helper that returns an error when a regex does not match, so login fails with a descriptive error instead of panicking. - Add table-driven tests for local/agent keys, local/agent certificates, and malformed input. Fixes #527 --------- Co-authored-by: bircni <bircni@icloud.com> Reviewed-on: https://gitea.com/gitea/tea/pulls/1100 Reviewed-by: bircni <bircni@icloud.com> Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
This commit is contained in:
+37
-19
@@ -200,25 +200,9 @@ func CreateLogin(ctx context.Context) error {
|
||||
}
|
||||
printTitleAndContent("Selected ssh-key:", sshKey)
|
||||
|
||||
// ssh certificate
|
||||
if strings.Contains(sshKey, "principals") {
|
||||
sshCertPrincipal = regexp.MustCompile(`.*?principals: (.*?)[,|\s]`).FindStringSubmatch(sshKey)[1]
|
||||
if strings.Contains(sshKey, "(ssh-agent)") {
|
||||
sshAgent = true
|
||||
sshKey = ""
|
||||
} else {
|
||||
sshKey = regexp.MustCompile(`\((.*?)\)$`).FindStringSubmatch(sshKey)[1]
|
||||
sshKey = strings.TrimSuffix(sshKey, "-cert.pub")
|
||||
}
|
||||
} else {
|
||||
sshKeyFingerprint = regexp.MustCompile(`(SHA256:.*?)\s`).FindStringSubmatch(sshKey)[1]
|
||||
if strings.Contains(sshKey, "(ssh-agent)") {
|
||||
sshAgent = true
|
||||
sshKey = ""
|
||||
} else {
|
||||
sshKey = regexp.MustCompile(`\((.*?)\)$`).FindStringSubmatch(sshKey)[1]
|
||||
sshKey = strings.TrimSuffix(sshKey, ".pub")
|
||||
}
|
||||
sshKey, sshCertPrincipal, sshKeyFingerprint, sshAgent, err = parseSSHPubkeySelection(sshKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -274,6 +258,40 @@ func CreateLogin(ctx context.Context) error {
|
||||
return task.CreateLogin(ctx, name, token, user, passwd, otp, scopes, sshKey, giteaURL, sshCertPrincipal, sshKeyFingerprint, insecure, sshAgent, versionCheck, helper)
|
||||
}
|
||||
|
||||
func parseSSHPubkeySelection(display string) (sshKey, sshCertPrincipal, sshKeyFingerprint string, sshAgent bool, err error) {
|
||||
if strings.Contains(display, "principals") {
|
||||
if sshCertPrincipal, err = regexpSubmatch(regexp.MustCompile(`.*?principals: (.*?)[,|\s]`), display); err != nil {
|
||||
return "", "", "", false, fmt.Errorf("failed to parse SSH certificate principal from %q: %w", display, err)
|
||||
}
|
||||
if strings.HasSuffix(display, "(ssh-agent)") {
|
||||
return "", sshCertPrincipal, "", true, nil
|
||||
}
|
||||
if sshKey, err = regexpSubmatch(regexp.MustCompile(`\((.*?)\)$`), display); err != nil {
|
||||
return "", "", "", false, fmt.Errorf("failed to parse SSH certificate path from %q: %w", display, err)
|
||||
}
|
||||
return strings.TrimSuffix(sshKey, "-cert.pub"), sshCertPrincipal, "", false, nil
|
||||
}
|
||||
|
||||
if sshKeyFingerprint, err = regexpSubmatch(regexp.MustCompile(`(SHA256:.*?)\s`), display); err != nil {
|
||||
return "", "", "", false, fmt.Errorf("failed to parse SSH key fingerprint from %q: %w", display, err)
|
||||
}
|
||||
if strings.HasSuffix(display, "(ssh-agent)") {
|
||||
return "", "", sshKeyFingerprint, true, nil
|
||||
}
|
||||
if sshKey, err = regexpSubmatch(regexp.MustCompile(`\((.*?)\)$`), display); err != nil {
|
||||
return "", "", "", false, fmt.Errorf("failed to parse SSH key path from %q: %w", display, err)
|
||||
}
|
||||
return strings.TrimSuffix(sshKey, ".pub"), "", sshKeyFingerprint, false, nil
|
||||
}
|
||||
|
||||
func regexpSubmatch(re *regexp.Regexp, s string) (string, error) {
|
||||
match := re.FindStringSubmatch(s)
|
||||
if len(match) < 2 {
|
||||
return "", fmt.Errorf("no match")
|
||||
}
|
||||
return match[1], nil
|
||||
}
|
||||
|
||||
var tokenScopeOpts = []string{
|
||||
string(gitea.AccessTokenScopeAll),
|
||||
string(gitea.AccessTokenScopeRepo),
|
||||
|
||||
Reference in New Issue
Block a user