introduce az device code challenger
This commit is contained in:
parent
bbca18f418
commit
374f474c10
9 changed files with 132 additions and 10 deletions
21
sshpiperd/challenger/azdevicecode/azdevicecode.go
Normal file
21
sshpiperd/challenger/azdevicecode/azdevicecode.go
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
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{})
|
||||
}
|
||||
100
sshpiperd/challenger/azdevicecode/handler.go
Normal file
100
sshpiperd/challenger/azdevicecode/handler.go
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
package azdevicecode
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/services/graphrbac/1.6/graphrbac"
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
"github.com/Azure/go-autorest/autorest/adal"
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
)
|
||||
|
||||
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" choice:"AzureChinaCloud" choice:"AzureGermanCloud" choice:"AzurePublicCloud" choice:"AzureUSGovernmentCloud"`
|
||||
Resource string `long:"challenger-azdevicecode-resource" default:"https://graph.windows.net/" description:"Resource URI to access, default is Graph API" env:"SSHPIPERD_CHALLENGER_AZDEVICECODE_RESOURCE" ini-name:"challenger-azdevicecode-resource"`
|
||||
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
|
||||
oauthConfig adal.OAuthConfig
|
||||
}
|
||||
|
||||
func (c *authClient) Init(logger *log.Logger) error {
|
||||
c.logger = logger
|
||||
|
||||
env, err := azure.EnvironmentFromName(c.Config.Env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
oauthConfig, err := adal.NewOAuthConfig(env.ActiveDirectoryEndpoint, c.Config.TenantId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.oauthConfig = *oauthConfig
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type aadUser struct {
|
||||
user graphrbac.User
|
||||
}
|
||||
|
||||
func (*aadUser) ChallengerName() string {
|
||||
return "azdevicecode"
|
||||
}
|
||||
|
||||
func (a *aadUser) Meta() interface{} {
|
||||
return a.user
|
||||
}
|
||||
|
||||
func (a *aadUser) ChallengedUsername() string {
|
||||
return *a.user.UserPrincipalName
|
||||
}
|
||||
|
||||
func (c *authClient) challenge(conn ssh.ConnMetadata, client ssh.KeyboardInteractiveChallenge) (ssh.AdditionalChallengeContext, error) {
|
||||
oauthClient := &http.Client{}
|
||||
deviceCode, err := adal.InitiateDeviceAuth(oauthClient, c.oauthConfig, c.Config.ClientId, c.Config.Resource)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = client(conn.User(), *deviceCode.Message, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
token, err := adal.WaitForUserCompletion(oauthClient, deviceCode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// skip read user info from graph api
|
||||
if c.Config.NoReadGraph {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
spt, err := adal.NewServicePrincipalTokenFromManualToken(c.oauthConfig, c.Config.ClientId, c.Config.Resource, *token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signedInUserClient := graphrbac.NewSignedInUserClient(c.Config.TenantId)
|
||||
signedInUserClient.Authorizer = autorest.NewBearerAuthorizer(spt)
|
||||
|
||||
result, err := signedInUserClient.Get(context.TODO())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &aadUser{user: result}, nil
|
||||
}
|
||||
|
|
@ -18,7 +18,7 @@ const (
|
|||
SSHPIPER_PAM_SERVICE_FILE = "/etc/pam.d/sshpiperd"
|
||||
)
|
||||
|
||||
func pamChallenger(conn ssh.ConnMetadata, client ssh.KeyboardInteractiveChallenge) (bool, error) {
|
||||
func pamChallenger(conn ssh.ConnMetadata, client ssh.KeyboardInteractiveChallenge) (ssh.AdditionalChallengeContext, error) {
|
||||
|
||||
user := conn.User()
|
||||
|
||||
|
|
@ -52,15 +52,15 @@ func pamChallenger(conn ssh.ConnMetadata, client ssh.KeyboardInteractiveChalleng
|
|||
})
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = t.Authenticate(0)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import (
|
|||
// 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) (bool, error)
|
||||
type Handler func(conn ssh.ConnMetadata, client ssh.KeyboardInteractiveChallenge) (ssh.AdditionalChallengeContext, error)
|
||||
|
||||
// Provider is a factory for Challenger
|
||||
type Provider interface {
|
||||
|
|
|
|||
|
|
@ -9,11 +9,11 @@ import (
|
|||
)
|
||||
|
||||
func makeWelcomeChallenger(text string) challenger.Handler {
|
||||
return func(conn ssh.ConnMetadata, client ssh.KeyboardInteractiveChallenge) (bool, error) {
|
||||
return func(conn ssh.ConnMetadata, client ssh.KeyboardInteractiveChallenge) (ssh.AdditionalChallengeContext, error) {
|
||||
|
||||
client(conn.User(), text, nil, nil)
|
||||
|
||||
return true, nil
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
_ "github.com/tg123/sshpiper/sshpiperd/upstream/database"
|
||||
_ "github.com/tg123/sshpiper/sshpiperd/upstream/workingdir"
|
||||
|
||||
_ "github.com/tg123/sshpiper/sshpiperd/challenger/azdevicecode"
|
||||
_ "github.com/tg123/sshpiper/sshpiperd/challenger/pam"
|
||||
_ "github.com/tg123/sshpiper/sshpiperd/challenger/welcometext"
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import (
|
|||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
func (p *plugin) findUpstream(conn ssh.ConnMetadata) (net.Conn, *ssh.AuthPipe, error) {
|
||||
func (p *plugin) findUpstream(conn ssh.ConnMetadata, challengeContext ssh.AdditionalChallengeContext) (net.Conn, *ssh.AuthPipe, error) {
|
||||
|
||||
user := conn.User()
|
||||
d, err := lookupDownstreamWithFallback(p.db, user)
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import (
|
|||
// the returned auth pipe is to map/convert downstream auth method to another auth for
|
||||
// connecting to upstream.
|
||||
// e.g. map downstream public key to another upstream private key
|
||||
type Handler func(conn ssh.ConnMetadata) (net.Conn, *ssh.AuthPipe, error)
|
||||
type Handler func(conn ssh.ConnMetadata, challengeContext ssh.AdditionalChallengeContext) (net.Conn, *ssh.AuthPipe, error)
|
||||
|
||||
type CreatePipeOption struct {
|
||||
Username string
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ func parseUpstreamFile(data string) (host string, port int, user string, err err
|
|||
return
|
||||
}
|
||||
|
||||
func findUpstreamFromUserfile(conn ssh.ConnMetadata) (net.Conn, *ssh.AuthPipe, error) {
|
||||
func findUpstreamFromUserfile(conn ssh.ConnMetadata, challengeContext ssh.AdditionalChallengeContext) (net.Conn, *ssh.AuthPipe, error) {
|
||||
user := conn.User()
|
||||
|
||||
if !checkUsername(user) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue