move azdevicecode
This commit is contained in:
parent
8d56673982
commit
54d1f08ade
13 changed files with 99 additions and 676 deletions
99
plugin/azdevicecode/main.go
Normal file
99
plugin/azdevicecode/main.go
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
|
||||
azidentity "github.com/Azure/azure-sdk-for-go/sdk/azidentity"
|
||||
a "github.com/microsoft/kiota-authentication-azure-go"
|
||||
msgraphsdk "github.com/microsoftgraph/msgraph-sdk-go"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/tg123/sshpiper/libplugin"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
libplugin.CreateAndRunPluginTemplate(&libplugin.PluginTemplate{
|
||||
Name: "azdevicecode",
|
||||
Usage: "sshpiperd azure devicecode plugin, use devicecode to before ssh, see https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-device-code",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "tenant-id",
|
||||
Usage: "Azure AD tenant id",
|
||||
EnvVars: []string{"SSHPIPERD_AZDEVICECODE_TENANT_ID"},
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "client-id",
|
||||
Usage: "Azure AD client id",
|
||||
EnvVars: []string{"SSHPIPERD_AZDEVICECODE_CLIENT_ID"},
|
||||
Required: true,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "no-read-graph",
|
||||
Usage: "disable query user info from user graph",
|
||||
EnvVars: []string{"SSHPIPERD_AZDEVICECODE_NOREADGRAPH"},
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "scope",
|
||||
Usage: "permission scope when querying user info",
|
||||
EnvVars: []string{"SSHPIPERD_AZDEVICECODE_SCOPE"},
|
||||
Value: "User.Read",
|
||||
},
|
||||
},
|
||||
CreateConfig: func(c *cli.Context) (*libplugin.SshPiperPluginConfig, error) {
|
||||
|
||||
return &libplugin.SshPiperPluginConfig{
|
||||
KeyboardInteractiveCallback: func(conn libplugin.ConnMetadata, client libplugin.KeyboardInteractiveChallenge) (*libplugin.Upstream, error) {
|
||||
|
||||
cred, err := azidentity.NewDeviceCodeCredential(&azidentity.DeviceCodeCredentialOptions{
|
||||
TenantID: c.String("tenant-id"),
|
||||
ClientID: c.String("client-id"),
|
||||
UserPrompt: func(ctx context.Context, message azidentity.DeviceCodeMessage) error {
|
||||
_, err := client(message.Message, "", false)
|
||||
return err
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if c.Bool("no-read-graph") {
|
||||
_, err = cred.GetToken(context.Background(), policy.TokenRequestOptions{
|
||||
Scopes: []string{c.String("scope")},
|
||||
})
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
auth, err := a.NewAzureIdentityAuthenticationProviderWithScopes(cred, []string{c.String("scope")})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
adapter, err := msgraphsdk.NewGraphRequestAdapter(auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gsclient := msgraphsdk.NewGraphServiceClient(adapter)
|
||||
result, err := gsclient.Me().Get()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userId := *result.GetId()
|
||||
|
||||
log.Infof("success with challenged username: %s", userId)
|
||||
|
||||
return &libplugin.Upstream{
|
||||
Auth: libplugin.CreateNextPluginAuth(map[string]string{
|
||||
"UserId": *result.GetId(),
|
||||
}),
|
||||
}, nil
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
package authy
|
||||
|
||||
import (
|
||||
"github.com/tg123/sshpiper/sshpiperd/challenger"
|
||||
)
|
||||
|
||||
func (authyClient) GetName() string {
|
||||
return "authy"
|
||||
}
|
||||
|
||||
func (a *authyClient) GetOpts() interface{} {
|
||||
return &a.Config
|
||||
}
|
||||
|
||||
func (a *authyClient) GetHandler() challenger.Handler {
|
||||
return a.challenge
|
||||
}
|
||||
|
||||
func init() {
|
||||
challenger.Register("authy", &authyClient{})
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
package authy
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (a authyClient) findAuthyID(user string) (string, error) {
|
||||
// TODO a better way to handle large database
|
||||
|
||||
file, err := os.Open(a.Config.File)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
fields := strings.Fields(scanner.Text())
|
||||
|
||||
if len(fields) >= 2 {
|
||||
if fields[0] == user {
|
||||
return fields[1], nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("authy id for user not found")
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
package authy
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_findAuthyId(t *testing.T) {
|
||||
|
||||
a := authyClient{}
|
||||
tmpfile, err := ioutil.TempFile("", "authyid")
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("cannot create temp file %v", err)
|
||||
}
|
||||
|
||||
defer os.Remove(tmpfile.Name())
|
||||
a.Config.File = tmpfile.Name()
|
||||
|
||||
if err := ioutil.WriteFile(tmpfile.Name(), []byte(`
|
||||
piper 123
|
||||
hook 456
|
||||
hook 789
|
||||
`), os.ModePerm); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
{
|
||||
id, err := a.findAuthyID("piper")
|
||||
if err != nil {
|
||||
t.Fatalf("findId failed %v", err)
|
||||
}
|
||||
|
||||
if id != "123" {
|
||||
t.Error("find id return wrong value")
|
||||
}
|
||||
|
||||
}
|
||||
{
|
||||
id, err := a.findAuthyID("hook")
|
||||
if err != nil {
|
||||
t.Fatalf("findId failed %v", err)
|
||||
}
|
||||
|
||||
if id != "456" {
|
||||
t.Error("find id return wrong value")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
package authy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
"github.com/dcu/go-authy"
|
||||
)
|
||||
|
||||
type authyClient struct {
|
||||
Config struct {
|
||||
APIKey string `long:"challenger-authy-apikey" description:"Authy API Key" env:"SSHPIPERD_CHALLENGER_AUTHY_APIKEY" ini-name:"challenger-authy-apikey"`
|
||||
// Method string `long:"challenger-authy-method" default:"token" description:"Authy authentication method" env:"SSHPIPERD_CHALLENGER_AUTHY_METHOD" ini-name:"challenger-authy-method" choice:"token" choice:"onetouch"`
|
||||
Method string `long:"challenger-authy-method" default:"token" description:"Authy authentication method" env:"SSHPIPERD_CHALLENGER_AUTHY_METHOD" ini-name:"challenger-authy-method" choice:"token"`
|
||||
File string `long:"challenger-authy-idfile" description:"Path to a file with ssh_name [space] authy_id per line (first line win if duplicate)" env:"SSHPIPERD_CHALLENGER_AUTHY_IDFILE" ini-name:"challenger-authy-idfile"`
|
||||
}
|
||||
|
||||
authyAPI *authy.Authy
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
func (a *authyClient) Init(logger *log.Logger) error {
|
||||
a.logger = logger
|
||||
a.authyAPI = authy.NewAuthyAPI(a.Config.APIKey)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *authyClient) challenge(conn ssh.ConnMetadata, client ssh.KeyboardInteractiveChallenge) (ssh.AdditionalChallengeContext, error) {
|
||||
user := conn.User()
|
||||
|
||||
authyID, err := a.findAuthyID(user)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch a.Config.Method {
|
||||
case "token":
|
||||
|
||||
ans, err := client(user, "", []string{"Please input your Authy token: "}, []bool{true})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
verification, err := a.authyAPI.VerifyToken(authyID, ans[0], url.Values{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if verification.Valid() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
_, err = client(conn.User(), verification.Message, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("failed to auth with authy: %v", verification.Message)
|
||||
|
||||
case "onetouch":
|
||||
_, err = client(conn.User(), "Please verify login on your Authy app", nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
details := authy.Details{
|
||||
"User": user,
|
||||
"ClientIP": conn.RemoteAddr().String(),
|
||||
}
|
||||
|
||||
approvalRequest, err := a.authyAPI.SendApprovalRequest(authyID, "Log to SSH server", details, url.Values{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
status, err := a.authyAPI.WaitForApprovalRequest(approvalRequest.UUID, time.Second*30, url.Values{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if status == authy.OneTouchStatusApproved {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
_, err = client(conn.User(), "Authy OneTouch failed", nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("one touch faild code: %v", status)
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported authy method")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
package azdevicecode
|
||||
|
||||
import (
|
||||
"github.com/tg123/sshpiper/sshpiperd/challenger"
|
||||
)
|
||||
|
||||
func (*authClient) GetName() string {
|
||||
return "azdevicecode"
|
||||
}
|
||||
|
||||
func (c *authClient) GetOpts() interface{} {
|
||||
return &c.Config
|
||||
}
|
||||
|
||||
func (c *authClient) GetHandler() challenger.Handler {
|
||||
return c.challenge
|
||||
}
|
||||
|
||||
func init() {
|
||||
challenger.Register("azdevicecode", &authClient{})
|
||||
}
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
package azdevicecode
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
|
||||
azidentity "github.com/Azure/azure-sdk-for-go/sdk/azidentity"
|
||||
a "github.com/microsoft/kiota-authentication-azure-go"
|
||||
msgraphsdk "github.com/microsoftgraph/msgraph-sdk-go"
|
||||
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||
)
|
||||
|
||||
type authClient struct {
|
||||
Config struct {
|
||||
TenantID string `long:"challenger-azdevicecode-tenantid" description:"Azure AD tenant id" env:"SSHPIPERD_CHALLENGER_AZDEVICECODE_TENANTID" ini-name:"challenger-azdevicecode-tenantid"`
|
||||
ClientID string `long:"challenger-azdevicecode-clientid" description:"Azure AD client id" env:"SSHPIPERD_CHALLENGER_AZDEVICECODE_CLIENTID" ini-name:"challenger-azdevicecode-clientid"`
|
||||
// Env string `long:"challenger-azdevicecode-env" default:"AzurePublicCloud" description:"Azure AD Cloud to request" env:"SSHPIPERD_CHALLENGER_AZDEVICECODE_ENV" ini-name:"challenger-azdevicecode-env"`
|
||||
Scope string `long:"challenger-azdevicecode-scope" default:"User.Read" description:"Permission scope when querying user info" env:"SSHPIPERD_CHALLENGER_AZDEVICECODE_SCOPE" ini-name:"challenger-azdevicecode-scope"`
|
||||
NoReadGraph bool `long:"challenger-azdevicecode-noreadgraph" description:"Disable query user info from user graph" env:"SSHPIPERD_CHALLENGER_AZDEVICECODE_NOREADGRAPH" ini-name:"challenger-azdevicecode-noreadgraph"`
|
||||
}
|
||||
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
func (c *authClient) Init(logger *log.Logger) error {
|
||||
c.logger = logger
|
||||
return nil
|
||||
}
|
||||
|
||||
type aadUser struct {
|
||||
models.Userable
|
||||
}
|
||||
|
||||
func (*aadUser) ChallengerName() string {
|
||||
return "azdevicecode"
|
||||
}
|
||||
|
||||
func (a *aadUser) Meta() interface{} {
|
||||
return a.Userable
|
||||
}
|
||||
|
||||
func (a *aadUser) ChallengedUsername() string {
|
||||
return *a.GetId()
|
||||
}
|
||||
|
||||
// see https://github.com/microsoftgraph/msgraph-sdk-go
|
||||
func (c *authClient) challenge(conn ssh.ConnMetadata, client ssh.KeyboardInteractiveChallenge) (ssh.AdditionalChallengeContext, error) {
|
||||
|
||||
cred, err := azidentity.NewDeviceCodeCredential(&azidentity.DeviceCodeCredentialOptions{
|
||||
TenantID: c.Config.TenantID,
|
||||
ClientID: c.Config.ClientID,
|
||||
UserPrompt: func(ctx context.Context, message azidentity.DeviceCodeMessage) error {
|
||||
_, err := client(conn.User(), message.Message, nil, nil)
|
||||
return err
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if c.Config.NoReadGraph {
|
||||
_, err = cred.GetToken(context.Background(), policy.TokenRequestOptions{
|
||||
Scopes: []string{c.Config.Scope},
|
||||
})
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
auth, err := a.NewAzureIdentityAuthenticationProviderWithScopes(cred, []string{c.Config.Scope})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
adapter, err := msgraphsdk.NewGraphRequestAdapter(auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gsclient := msgraphsdk.NewGraphServiceClient(adapter)
|
||||
result, err := gsclient.Me().Get()
|
||||
|
||||
return &aadUser{result}, err
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
package challenger
|
||||
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type plugin struct {
|
||||
name string
|
||||
init func(logger *log.Logger) error
|
||||
opts interface{}
|
||||
gethandler func() Handler
|
||||
}
|
||||
|
||||
func (p *plugin) GetName() string {
|
||||
return p.name
|
||||
}
|
||||
|
||||
func (p *plugin) GetOpts() interface{} {
|
||||
return p.opts
|
||||
}
|
||||
|
||||
func (p *plugin) GetHandler() Handler {
|
||||
return p.gethandler()
|
||||
}
|
||||
|
||||
func (p *plugin) Init(logger *log.Logger) error {
|
||||
logger.Printf("challenger: %v init", p.name)
|
||||
|
||||
if p.init != nil {
|
||||
return p.init(logger)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewFromHandler creates a Challenger with given functions
|
||||
func NewFromHandler(name string, gethandler func() Handler, opts interface{}, init func(glogger *log.Logger) error) Provider {
|
||||
return &plugin{
|
||||
name: name,
|
||||
init: init,
|
||||
opts: opts,
|
||||
gethandler: gethandler,
|
||||
}
|
||||
}
|
||||
|
|
@ -1,107 +0,0 @@
|
|||
package pome
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
func (p *pome) load(ctx context.Context, id string) (*pipe, error) {
|
||||
req, err := http.NewRequest("GET", p.Config.CheckBaseURL+id, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req = req.WithContext(ctx)
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode > 299 {
|
||||
return nil, fmt.Errorf("bad http state code %v", resp.StatusCode)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pipe := pipe{}
|
||||
|
||||
if err := json.Unmarshal(body, &pipe); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pipe, nil
|
||||
}
|
||||
|
||||
func (p *pome) loadWithRetry(ctx context.Context, id string) <-chan *pipe {
|
||||
c := make(chan *pipe)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
c <- nil
|
||||
return
|
||||
default:
|
||||
timeout, cancel := context.WithTimeout(context.Background(), time.Millisecond*5000)
|
||||
defer cancel()
|
||||
pipe, err := p.load(timeout, id)
|
||||
|
||||
if err == nil {
|
||||
c <- pipe
|
||||
return
|
||||
}
|
||||
|
||||
time.Sleep(time.Millisecond * 5000)
|
||||
}
|
||||
}
|
||||
}()
|
||||
return c
|
||||
}
|
||||
|
||||
func (p *pome) challenge(conn ssh.ConnMetadata, client ssh.KeyboardInteractiveChallenge) (ssh.AdditionalChallengeContext, error) {
|
||||
|
||||
uid, err := uuid.NewRandom()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id := uid.String()
|
||||
url := p.Config.LoginBaseURL + id
|
||||
|
||||
say := func(msg string) error {
|
||||
_, err = client(conn.User(), msg, nil, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := say(fmt.Sprintf("Open %v in browser to login (timeout %v senconds)", url, p.Config.Timeout)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d := time.Now().Add(time.Duration(p.Config.Timeout) * time.Second)
|
||||
ctx, cancel := context.WithDeadline(context.Background(), d)
|
||||
defer cancel()
|
||||
c := p.loadWithRetry(ctx, id)
|
||||
|
||||
pipe := <-c
|
||||
|
||||
if pipe == nil {
|
||||
_ = say("Login timeout")
|
||||
return nil, fmt.Errorf("timeout")
|
||||
}
|
||||
|
||||
_ = say(fmt.Sprintf("Connecting to %v@%v", pipe.Username, pipe.Address))
|
||||
|
||||
pipe.say = say
|
||||
return pipe, nil
|
||||
}
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
package pome
|
||||
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/tg123/sshpiper/sshpiperd/challenger"
|
||||
"github.com/tg123/sshpiper/sshpiperd/upstream"
|
||||
)
|
||||
|
||||
type plugin struct {
|
||||
pome
|
||||
}
|
||||
|
||||
func (plugin) GetName() string {
|
||||
return "pome"
|
||||
}
|
||||
|
||||
func (plugin) GetOpts() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *plugin) Init(logger *log.Logger) error {
|
||||
p.pome.logger = logger
|
||||
return nil
|
||||
}
|
||||
|
||||
func (plugin) ListPipe() ([]upstream.Pipe, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (plugin) CreatePipe(opt upstream.CreatePipeOption) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (plugin) RemovePipe(name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type challengerPlugin struct {
|
||||
*plugin
|
||||
}
|
||||
|
||||
func (p *challengerPlugin) GetHandler() challenger.Handler {
|
||||
return p.challenge
|
||||
}
|
||||
|
||||
type upstreamPlugin struct {
|
||||
*plugin
|
||||
}
|
||||
|
||||
func (p *upstreamPlugin) GetHandler() upstream.Handler {
|
||||
return p.authWithPipe
|
||||
}
|
||||
|
||||
func (p *challengerPlugin) GetOpts() interface{} {
|
||||
return &p.Config
|
||||
}
|
||||
|
||||
func init() {
|
||||
p := &plugin{}
|
||||
upstream.Register("pome", &upstreamPlugin{p})
|
||||
challenger.Register("pome", &challengerPlugin{p})
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
package pome
|
||||
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type pipe struct {
|
||||
Owner string `json:"owner"`
|
||||
ServerID string `json:"serverId"`
|
||||
Username string `json:"username"`
|
||||
Address string `json:"address"`
|
||||
Auth string `json:"auth"`
|
||||
PrivateKey string `json:"privateKey"`
|
||||
UpPassword string `json:"upPassword"`
|
||||
|
||||
say func(msg string) error
|
||||
}
|
||||
|
||||
func (pipe) ChallengerName() string {
|
||||
return "pome"
|
||||
}
|
||||
|
||||
func (p pipe) Meta() interface{} {
|
||||
return p
|
||||
}
|
||||
|
||||
func (p pipe) ChallengedUsername() string {
|
||||
return p.Username
|
||||
}
|
||||
|
||||
type pome struct {
|
||||
logger *log.Logger
|
||||
|
||||
Config struct {
|
||||
LoginBaseURL string `long:"challenger-pome-loginurl" description:"Send this url/{id} to user for login" env:"SSHPIPERD_CHALLENGER_POME_LOGINURL" ini-name:"challenger-pome-loginurl"`
|
||||
CheckBaseURL string `long:"challenger-pome-checkurl" description:"Call this url/{id} to retrieve login info" env:"SSHPIPERD_CHALLENGER_POME_CHECKURL" ini-name:"challenger-pome-checkurl"`
|
||||
Timeout uint `long:"challenger-pome-timeout" default:"60" description:"Timeout for waiting response from checkurl" env:"SSHPIPERD_CHALLENGER_POME_TIMEOUT" ini-name:"challenger-pome-timeout"`
|
||||
}
|
||||
}
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
package pome
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
"github.com/tg123/sshpiper/sshpiperd/upstream"
|
||||
"github.com/tg123/sshpiper/sshpiperd/utils"
|
||||
)
|
||||
|
||||
func (p *pome) authWithPipe(conn ssh.ConnMetadata, challengeContext ssh.AdditionalChallengeContext) (net.Conn, *ssh.AuthPipe, error) {
|
||||
|
||||
pipe, ok := challengeContext.Meta().(pipe)
|
||||
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("bad pome context")
|
||||
}
|
||||
|
||||
host, port, err := upstream.SplitHostPortForSSH(pipe.Address)
|
||||
if err != nil {
|
||||
return nil, nil, pipe.say("Not add Please check your configure")
|
||||
}
|
||||
|
||||
addr := fmt.Sprintf("%v:%v", utils.FormatIPAddress(host), port)
|
||||
c, err := net.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, nil, pipe.say(fmt.Sprintf("Cannot connect to %v, reason: %v", addr, err))
|
||||
}
|
||||
|
||||
callback := func() (ssh.AuthPipeType, ssh.AuthMethod, error) {
|
||||
|
||||
switch pipe.Auth {
|
||||
case "key":
|
||||
|
||||
private, err := ssh.ParsePrivateKey([]byte(pipe.PrivateKey))
|
||||
if err != nil {
|
||||
return ssh.AuthPipeTypeNone, nil, err
|
||||
}
|
||||
|
||||
return ssh.AuthPipeTypeMap, ssh.PublicKeys(private), nil
|
||||
case "pass":
|
||||
return ssh.AuthPipeTypeMap, ssh.Password(pipe.UpPassword), nil
|
||||
}
|
||||
|
||||
return ssh.AuthPipeTypeNone, nil, fmt.Errorf("unsupport auth type %v", pipe.Auth)
|
||||
}
|
||||
|
||||
return c, &ssh.AuthPipe{
|
||||
User: pipe.Username,
|
||||
|
||||
NoneAuthCallback: func(conn ssh.ConnMetadata) (ssh.AuthPipeType, ssh.AuthMethod, error) {
|
||||
return callback()
|
||||
},
|
||||
|
||||
PasswordCallback: func(conn ssh.ConnMetadata, password []byte) (ssh.AuthPipeType, ssh.AuthMethod, error) {
|
||||
return callback()
|
||||
},
|
||||
|
||||
PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (ssh.AuthPipeType, ssh.AuthMethod, error) {
|
||||
return callback()
|
||||
},
|
||||
|
||||
UpstreamHostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
}, nil
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
package challenger
|
||||
|
||||
import (
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
"github.com/tg123/sshpiper/sshpiperd/registry"
|
||||
)
|
||||
|
||||
// Handler is the callback for additional challenger
|
||||
// use args client ssh.KeyboardInteractiveChallenge to interact with downstream
|
||||
// return bool to indicate whether if the challenge is passed
|
||||
type Handler func(conn ssh.ConnMetadata, client ssh.KeyboardInteractiveChallenge) (ssh.ChallengeContext, error)
|
||||
|
||||
// Provider is a factory for Challenger
|
||||
type Provider interface {
|
||||
registry.Plugin
|
||||
|
||||
GetHandler() Handler
|
||||
}
|
||||
|
||||
var (
|
||||
drivers = registry.NewRegistry()
|
||||
)
|
||||
|
||||
// Register adds an challenger with given name to registry
|
||||
func Register(name string, driver Provider) {
|
||||
drivers.Register(name, driver)
|
||||
}
|
||||
|
||||
// All return all registered challenger
|
||||
func All() []string {
|
||||
return drivers.Drivers()
|
||||
}
|
||||
|
||||
// Get returns an challenger by name, return nil if not found
|
||||
func Get(name string) Provider {
|
||||
if d, ok := drivers.Get(name).(Provider); ok {
|
||||
return d
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue