introduce az device code challenger

This commit is contained in:
Boshi Lian 2019-01-09 03:16:02 -08:00
parent bbca18f418
commit 374f474c10
9 changed files with 132 additions and 10 deletions

View 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{})
}

View 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
}

View file

@ -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() {

View file

@ -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 {

View file

@ -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
}
}

View file

@ -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"

View file

@ -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)

View file

@ -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

View file

@ -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) {