mirror of
https://gitea.com/gitea/tea.git
synced 2025-09-02 01:48:30 +02:00
Add support for authentication via ssh certificates and pub/privatekey (#442)
This adds support for authentication using a SSH certificate and normal public keys when you've got an ssh-agent running that has this certificate or your public key loaded. First question when creating a new login is to ask about the ssh certificates or public keys, when the answer is yes, we don't need to ask about tokens/usernames anymore. Co-authored-by: Wim <wim@42.be> Reviewed-on: https://gitea.com/gitea/tea/pulls/442 Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com> Reviewed-by: 6543 <6543@obermui.de> Co-authored-by: Wim <42wim@noreply.gitea.io> Co-committed-by: Wim <42wim@noreply.gitea.io>
This commit is contained in:
@ -16,7 +16,7 @@ import (
|
||||
)
|
||||
|
||||
// CreateLogin create a login to be stored in config
|
||||
func CreateLogin(name, token, user, passwd, sshKey, giteaURL string, insecure bool) error {
|
||||
func CreateLogin(name, token, user, passwd, sshKey, giteaURL, sshCertPrincipal, sshKeyFingerprint string, insecure, sshAgent bool) error {
|
||||
// checks ...
|
||||
// ... if we have a url
|
||||
if len(giteaURL) == 0 {
|
||||
@ -32,13 +32,15 @@ func CreateLogin(name, token, user, passwd, sshKey, giteaURL string, insecure bo
|
||||
return fmt.Errorf("token already been used, delete login '%s' first", login.Name)
|
||||
}
|
||||
|
||||
// .. if we have enough information to authenticate
|
||||
if len(token) == 0 && (len(user)+len(passwd)) == 0 {
|
||||
return fmt.Errorf("No token set")
|
||||
} else if len(user) != 0 && len(passwd) == 0 {
|
||||
return fmt.Errorf("No password set")
|
||||
} else if len(user) == 0 && len(passwd) != 0 {
|
||||
return fmt.Errorf("No user set")
|
||||
if !sshAgent && sshCertPrincipal == "" && sshKey == "" {
|
||||
// .. if we have enough information to authenticate
|
||||
if len(token) == 0 && (len(user)+len(passwd)) == 0 {
|
||||
return fmt.Errorf("No token set")
|
||||
} else if len(user) != 0 && len(passwd) == 0 {
|
||||
return fmt.Errorf("No password set")
|
||||
} else if len(user) == 0 && len(passwd) != 0 {
|
||||
return fmt.Errorf("No user set")
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize URL
|
||||
@ -47,16 +49,25 @@ func CreateLogin(name, token, user, passwd, sshKey, giteaURL string, insecure bo
|
||||
return fmt.Errorf("Unable to parse URL: %s", err)
|
||||
}
|
||||
|
||||
login := config.Login{
|
||||
Name: name,
|
||||
URL: serverURL.String(),
|
||||
Token: token,
|
||||
Insecure: insecure,
|
||||
SSHKey: sshKey,
|
||||
Created: time.Now().Unix(),
|
||||
// check if it's a certificate the principal doesn't matter as the user
|
||||
// has explicitly selected this private key
|
||||
if _, err := os.Stat(sshKey + "-cert.pub"); err == nil {
|
||||
sshCertPrincipal = "yes"
|
||||
}
|
||||
|
||||
if len(token) == 0 {
|
||||
login := config.Login{
|
||||
Name: name,
|
||||
URL: serverURL.String(),
|
||||
Token: token,
|
||||
Insecure: insecure,
|
||||
SSHKey: sshKey,
|
||||
SSHCertPrincipal: sshCertPrincipal,
|
||||
SSHKeyFingerprint: sshKeyFingerprint,
|
||||
SSHAgent: sshAgent,
|
||||
Created: time.Now().Unix(),
|
||||
}
|
||||
|
||||
if len(token) == 0 && sshCertPrincipal == "" && !sshAgent && sshKey == "" {
|
||||
if login.Token, err = generateToken(login, user, passwd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
105
modules/task/login_httpsign.go
Normal file
105
modules/task/login_httpsign.go
Normal file
@ -0,0 +1,105 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package task
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"code.gitea.io/tea/modules/utils"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// ListSSHPubkey lists all the ssh keys in the ssh agent and the ~/.ssh/*.pub files
|
||||
// It returns a list of SSH keys in the format of:
|
||||
// "fingerprint keytype comment - principals: principals (ssh-agent or path to pubkey file)"
|
||||
func ListSSHPubkey() []string {
|
||||
var keys []string
|
||||
|
||||
keys = append(keys, getAgentKeys()...)
|
||||
keys = append(keys, getLocalKeys()...)
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
func getAgentKeys() []string {
|
||||
ag, err := gitea.GetAgent()
|
||||
if err != nil {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
akeys, err := ag.List()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var keys []string
|
||||
|
||||
for _, akey := range akeys {
|
||||
if key := parseKeys([]byte(akey.String()), "ssh-agent"); key != "" {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
}
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
func getLocalKeys() []string {
|
||||
var keys []string
|
||||
|
||||
// enumerate ~/.ssh/*.pub files
|
||||
glob, err := utils.AbsPathWithExpansion("~/.ssh/*.pub")
|
||||
if err != nil {
|
||||
return []string{}
|
||||
}
|
||||
localPubkeyPaths, err := filepath.Glob(glob)
|
||||
if err != nil {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
// parse each local key with present privkey & compare fingerprints to online keys
|
||||
for _, pubkeyPath := range localPubkeyPaths {
|
||||
var pubkeyFile []byte
|
||||
pubkeyFile, err = ioutil.ReadFile(pubkeyPath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if key := parseKeys(pubkeyFile, pubkeyPath); key != "" {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
}
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
func parseKeys(pkinput []byte, sshPath string) string {
|
||||
pkey, comment, _, _, err := ssh.ParseAuthorizedKey(pkinput)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
if strings.Contains(pkey.Type(), "cert-v01@openssh.com") {
|
||||
principals := pkey.(*ssh.Certificate).ValidPrincipals
|
||||
return ssh.FingerprintSHA256(pkey) + " " + pkey.Type() + " " + comment +
|
||||
" - principals: " + strings.Join(principals, ",") + " (" + sshPath + ")"
|
||||
}
|
||||
|
||||
return ssh.FingerprintSHA256(pkey) + " " + pkey.Type() + " " + comment + " (" + sshPath + ")"
|
||||
}
|
||||
|
||||
func getCertPrincipals(pkey ssh.PublicKey) []string {
|
||||
var principals []string
|
||||
|
||||
if cert, ok := pkey.(*ssh.Certificate); ok {
|
||||
for _, principal := range cert.ValidPrincipals {
|
||||
principals = append(principals, principal)
|
||||
}
|
||||
}
|
||||
|
||||
return principals
|
||||
}
|
Reference in New Issue
Block a user