Add support for authentication via ssh certificates and pub/privatekey #442
|
@ -100,6 +100,5 @@ func runLoginAdd(ctx *cli.Context) error {
|
|||
ctx.String("ssh-certificate-principal"),
|
||||
ctx.String("ssh-key-agent-public-key"),
|
||||
ctx.Bool("insecure"),
|
||||
ctx.Bool("ssh-certificate"),
|
||||
sshKeyAgent)
|
||||
}
|
||||
|
|
4
go.mod
4
go.mod
|
@ -66,4 +66,6 @@ require (
|
|||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
||||
)
|
||||
|
||||
replace code.gitea.io/sdk/gitea => gitea.com/42wim/go-sdk/gitea v0.0.0-20220615192105-8c821f6c3419
|
||||
replace code.gitea.io/sdk/gitea => gitea.com/42wim/go-sdk/gitea v0.0.0-20220616000741-57eaee10e1a9
|
||||
6543 marked this conversation as resolved
Outdated
|
||||
|
||||
// replace code.gitea.io/sdk/gitea => ../go-sdk-main/gitea
|
||||
|
|
4
go.sum
4
go.sum
|
@ -1,7 +1,7 @@
|
|||
code.gitea.io/gitea-vet v0.2.1 h1:b30by7+3SkmiftK0RjuXqFvZg2q4p68uoPGuxhzBN0s=
|
||||
code.gitea.io/gitea-vet v0.2.1/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE=
|
||||
gitea.com/42wim/go-sdk/gitea v0.0.0-20220615192105-8c821f6c3419 h1:nr6/27FiO35Nyd23HS481wh59614hhupte0OSEn383A=
|
||||
gitea.com/42wim/go-sdk/gitea v0.0.0-20220615192105-8c821f6c3419/go.mod h1:aRmrQC3CAHdJAU1LQt0C9zqzqI8tUB/5oQtNE746aYE=
|
||||
gitea.com/42wim/go-sdk/gitea v0.0.0-20220616000741-57eaee10e1a9 h1:fKSuHO9hZIP/UtXRw2NzdX9JfHXcNLCXKMB1o7u93y8=
|
||||
gitea.com/42wim/go-sdk/gitea v0.0.0-20220616000741-57eaee10e1a9/go.mod h1:aRmrQC3CAHdJAU1LQt0C9zqzqI8tUB/5oQtNE746aYE=
|
||||
gitea.com/noerw/unidiff-comments v0.0.0-20201219085024-64aec5658f2b h1:CLYsMGcGLohESQDMth+RgJ4cB3CCHToxnj0zBbvB3sE=
|
||||
gitea.com/noerw/unidiff-comments v0.0.0-20201219085024-64aec5658f2b/go.mod h1:Fc8iyPm4NINRWujeIk2bTfcbGc4ZYY29/oMAAGcr4qI=
|
||||
github.com/AlecAivazis/survey/v2 v2.3.1 h1:lzkuHA60pER7L4eYL8qQJor4bUWlJe4V0gqAT19tdOA=
|
||||
|
|
|
@ -25,12 +25,11 @@ type Login struct {
|
|||
Default bool `yaml:"default"`
|
||||
SSHHost string `yaml:"ssh_host"`
|
||||
// optional path to the private key
|
||||
SSHKey string `yaml:"ssh_key"`
|
||||
Insecure bool `yaml:"insecure"`
|
||||
SSHCert bool `yaml:"ssh_certificate"`
|
||||
SSHCertPrincipal string `yaml:"ssh_certificate_principal"`
|
||||
SSHKeyAgent bool `yaml:"ssh_key_agent"`
|
||||
SSHKeyAgentPub string `yaml:"ssh_key_agent_pub"`
|
||||
SSHKey string `yaml:"ssh_key"`
|
||||
Insecure bool `yaml:"insecure"`
|
||||
SSHCertPrincipal string `yaml:"ssh_certificate_principal"`
|
||||
SSHAgent bool `yaml:"ssh_agent"`
|
||||
SSHKeyFingerprint string `yaml:"ssh_key_agent_pub"`
|
||||
// User is username from gitea
|
||||
User string `yaml:"user"`
|
||||
// Created is auto created unix timestamp
|
||||
|
@ -181,11 +180,12 @@ func (l *Login) Client(options ...gitea.ClientOption) *gitea.Client {
|
|||
|
||||
options = append(options, gitea.SetToken(l.Token), gitea.SetHTTPClient(httpClient))
|
||||
|
||||
if l.SSHCert {
|
||||
options = append(options, gitea.UseSSHCert(l.SSHCertPrincipal))
|
||||
if l.SSHCertPrincipal != "" {
|
||||
options = append(options, gitea.UseSSHCert(l.SSHCertPrincipal, l.SSHKey))
|
||||
}
|
||||
if l.SSHKeyAgent {
|
||||
options = append(options, gitea.UseSSHPubkey(l.SSHKeyAgentPub))
|
||||
|
||||
if l.SSHKeyFingerprint != "" {
|
||||
options = append(options, gitea.UseSSHPubkey(l.SSHKeyFingerprint, l.SSHKey))
|
||||
}
|
||||
|
||||
client, err := gitea.NewClient(l.URL, options...)
|
||||
|
|
|
@ -6,6 +6,7 @@ package interact
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/tea/modules/task"
|
||||
|
@ -15,10 +16,9 @@ import (
|
|||
|
||||
// CreateLogin create an login interactive
|
||||
func CreateLogin() error {
|
||||
var name, token, user, passwd, sshKey, giteaURL, sshCertPrincipal, sshKeyAgentPub string
|
||||
var sshCert = false
|
||||
var name, token, user, passwd, sshKey, giteaURL, sshCertPrincipal, sshKeyFingerprint string
|
||||
var insecure = false
|
||||
var sshKeyAgent = false
|
||||
var sshAgent = false
|
||||
|
||||
promptI := &survey.Input{Message: "URL of Gitea instance: "}
|
||||
if err := survey.AskOne(promptI, &giteaURL, survey.WithValidator(survey.Required)); err != nil {
|
||||
|
@ -40,41 +40,15 @@ func CreateLogin() error {
|
|||
return err
|
||||
}
|
||||
|
||||
promptYN := &survey.Confirm{
|
||||
Message: "Do you want to use your SSH certificate to login? (needs a running ssh-agent with certificate loaded)",
|
||||
Default: false,
|
||||
}
|
||||
if err = survey.AskOne(promptYN, &sshCert); err != nil {
|
||||
loginMethod, err := promptSelect("Login with: ", []string{"token", "ssh-key/certificate"}, "", "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if sshCert {
|
||||
promptI = &survey.Input{Message: "Which SSH certificate principal to use? (if not specified the first one detected will be used): "}
|
||||
if err = survey.AskOne(promptI, &sshCertPrincipal); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !sshCert {
|
||||
promptYN := &survey.Confirm{
|
||||
Message: "Do you want to use your SSH key to login? (needs a running ssh-agent with your key loaded)",
|
||||
Default: false,
|
||||
}
|
||||
if err = survey.AskOne(promptYN, &sshKeyAgent); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if sshKeyAgent {
|
||||
promptI = &survey.Input{Message: "Which SSH key ? (paste your public key or fingerprint): "}
|
||||
if err = survey.AskOne(promptI, &sshKeyAgentPub, survey.WithValidator(survey.Required)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !sshCert && !sshKeyAgent {
|
||||
switch loginMethod {
|
||||
case "token":
|
||||
var hasToken bool
|
||||
promptYN = &survey.Confirm{
|
||||
promptYN := &survey.Confirm{
|
||||
Message: "Do you have an access token?",
|
||||
Default: false,
|
||||
}
|
||||
|
@ -98,10 +72,43 @@ func CreateLogin() error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
case "ssh-key/certificate":
|
||||
promptI = &survey.Input{Message: "SSH Key/Certificate Path (leave empty for auto-discovery in ~/.ssh and ssh-agent):"}
|
||||
if err := survey.AskOne(promptI, &sshKey); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if sshKey == "" {
|
||||
sshKey, err = promptSelect("Select ssh-key: ", task.ListSSHPubkey(), "", "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 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, ".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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var optSettings bool
|
||||
promptYN = &survey.Confirm{
|
||||
promptYN := &survey.Confirm{
|
||||
Message: "Set Optional settings: ",
|
||||
Default: false,
|
||||
}
|
||||
|
@ -123,5 +130,5 @@ func CreateLogin() error {
|
|||
}
|
||||
}
|
||||
|
||||
return task.CreateLogin(name, token, user, passwd, sshKey, giteaURL, sshCertPrincipal, sshKeyAgentPub, insecure, sshCert, sshKeyAgent)
|
||||
return task.CreateLogin(name, token, user, passwd, sshKey, giteaURL, sshCertPrincipal, sshKeyFingerprint, insecure, sshAgent)
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ import (
|
|||
)
|
||||
|
||||
// CreateLogin create a login to be stored in config
|
||||
func CreateLogin(name, token, user, passwd, sshKey, giteaURL, sshCertPrincipal, sshKeyAgentPub string, insecure, sshCert, sshKeyAgent 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,7 +32,7 @@ func CreateLogin(name, token, user, passwd, sshKey, giteaURL, sshCertPrincipal,
|
|||
return fmt.Errorf("token already been used, delete login '%s' first", login.Name)
|
||||
}
|
||||
|
||||
if !sshCert && !sshKeyAgent {
|
||||
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")
|
||||
|
@ -50,19 +50,18 @@ func CreateLogin(name, token, user, passwd, sshKey, giteaURL, sshCertPrincipal,
|
|||
}
|
||||
|
||||
login := config.Login{
|
||||
Name: name,
|
||||
URL: serverURL.String(),
|
||||
Token: token,
|
||||
Insecure: insecure,
|
||||
SSHKey: sshKey,
|
||||
SSHCert: sshCert,
|
||||
SSHKeyAgent: sshKeyAgent,
|
||||
SSHCertPrincipal: sshCertPrincipal,
|
||||
SSHKeyAgentPub: sshKeyAgentPub,
|
||||
Created: time.Now().Unix(),
|
||||
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 && !sshCert && !sshKeyAgent {
|
||||
if len(token) == 0 && sshCertPrincipal == "" && !sshAgent && sshKey == "" {
|
||||
if login.Token, err = generateToken(login, user, passwd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
102
modules/task/login_httpsign.go
Normal file
102
modules/task/login_httpsign.go
Normal file
|
@ -0,0 +1,102 @@
|
|||
// 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"
|
||||
)
|
||||
|
||||
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 + ")"
|
||||
} else {
|
||||
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
|
||||
}
|
Loading…
Reference in New Issue
Block a user
sdk pull got merged
ok pulled in upstream go-sdk back