mirror of
https://github.com/pomerium/pomerium.git
synced 2025-05-09 23:27:43 +02:00
add mock IdP device auth flow
This commit is contained in:
parent
fe6bb6aea5
commit
039a87dac9
2 changed files with 59 additions and 54 deletions
|
@ -1,11 +1,7 @@
|
||||||
package ssh
|
package ssh
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
@ -36,6 +32,7 @@ func TestSSH(t *testing.T) {
|
||||||
// pomerium + upstream setup
|
// pomerium + upstream setup
|
||||||
env := testenv.New(t)
|
env := testenv.New(t)
|
||||||
|
|
||||||
|
env.Add(scenarios.NewIDP([]*scenarios.User{{Email: "test@example.com"}}, scenarios.WithEnableDeviceAuth(true)))
|
||||||
env.Add(scenarios.SSH(scenarios.SSHConfig{}))
|
env.Add(scenarios.SSH(scenarios.SSHConfig{}))
|
||||||
env.Add(&ki)
|
env.Add(&ki)
|
||||||
|
|
||||||
|
@ -44,13 +41,12 @@ func TestSSH(t *testing.T) {
|
||||||
upstreams.WithAuthorizedKey(clientKey.PublicKey(), "demo"))
|
upstreams.WithAuthorizedKey(clientKey.PublicKey(), "demo"))
|
||||||
r := up.Route().
|
r := up.Route().
|
||||||
From(env.SubdomainURLWithScheme("ssh", "ssh")).
|
From(env.SubdomainURLWithScheme("ssh", "ssh")).
|
||||||
Policy(func(p *config.Policy) { p.AllowPublicUnauthenticatedAccess = true })
|
Policy(func(p *config.Policy) { p.AllowAnyAuthenticatedUser = true })
|
||||||
env.AddUpstream(up)
|
env.AddUpstream(up)
|
||||||
env.Start()
|
env.Start()
|
||||||
snippets.WaitStartupComplete(env)
|
snippets.WaitStartupComplete(env)
|
||||||
|
|
||||||
// test scenario -- first verify that the upstream is working at all
|
// verify that a connection can be established
|
||||||
//client, err := up.DirectDial(r, clientConfig)
|
|
||||||
client, err := up.Dial(r, clientConfig)
|
client, err := up.Dial(r, clientConfig)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
|
@ -69,46 +65,3 @@ func newSSHKey(t *testing.T) ssh.Signer {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return signer
|
return signer
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHelloWorld(t *testing.T) {
|
|
||||||
t.Skip("debugging...")
|
|
||||||
|
|
||||||
key, err := os.ReadFile("/Users/kjenkins/scratch/sshd/demo_key")
|
|
||||||
require.NoError(t, err)
|
|
||||||
signer, err := ssh.ParsePrivateKey(key)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
config := &ssh.ClientConfig{
|
|
||||||
User: "demo",
|
|
||||||
Auth: []ssh.AuthMethod{
|
|
||||||
ssh.PublicKeys(signer),
|
|
||||||
},
|
|
||||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
|
||||||
}
|
|
||||||
|
|
||||||
conn, err := ssh.Dial("tcp", "localhost:2222", config)
|
|
||||||
require.NoError(t, err, "unable to connect")
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
//conn.ServerVersion()
|
|
||||||
|
|
||||||
sess, err := conn.NewSession()
|
|
||||||
require.NoError(t, err, "unable to start session")
|
|
||||||
defer sess.Close()
|
|
||||||
|
|
||||||
var output bytes.Buffer
|
|
||||||
sess.Stdout = &output
|
|
||||||
sess.Stdin = strings.NewReader("whoami\n")
|
|
||||||
|
|
||||||
err = sess.Shell()
|
|
||||||
|
|
||||||
fmt.Println("Shell() returned ", err)
|
|
||||||
|
|
||||||
err = sess.Wait()
|
|
||||||
|
|
||||||
fmt.Println("Wait() returned ", err)
|
|
||||||
|
|
||||||
fmt.Println(" --> output:\n\n", output.String())
|
|
||||||
|
|
||||||
//sess.SendRequest()
|
|
||||||
}
|
|
||||||
|
|
|
@ -20,6 +20,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
|
||||||
"github.com/go-jose/go-jose/v3"
|
"github.com/go-jose/go-jose/v3"
|
||||||
"github.com/go-jose/go-jose/v3/jwt"
|
"github.com/go-jose/go-jose/v3/jwt"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
@ -46,6 +48,7 @@ type IDP struct {
|
||||||
|
|
||||||
type IDPOptions struct {
|
type IDPOptions struct {
|
||||||
enableTLS bool
|
enableTLS bool
|
||||||
|
enableDeviceAuth bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type IDPOption func(*IDPOptions)
|
type IDPOption func(*IDPOptions)
|
||||||
|
@ -62,6 +65,12 @@ func WithEnableTLS(enableTLS bool) IDPOption {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WithEnableDeviceAuth(enableDeviceAuth bool) IDPOption {
|
||||||
|
return func(o *IDPOptions) {
|
||||||
|
o.enableDeviceAuth = enableDeviceAuth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Attach implements testenv.Modifier.
|
// Attach implements testenv.Modifier.
|
||||||
func (idp *IDP) Attach(ctx context.Context) {
|
func (idp *IDP) Attach(ctx context.Context) {
|
||||||
env := testenv.EnvFromContext(ctx)
|
env := testenv.EnvFromContext(ctx)
|
||||||
|
@ -120,7 +129,7 @@ func (idp *IDP) Attach(ctx context.Context) {
|
||||||
router.Handle("/.well-known/openid-configuration", func(w http.ResponseWriter, r *http.Request) {
|
router.Handle("/.well-known/openid-configuration", func(w http.ResponseWriter, r *http.Request) {
|
||||||
log.Ctx(ctx).Debug().Str("method", r.Method).Str("uri", r.RequestURI).Send()
|
log.Ctx(ctx).Debug().Str("method", r.Method).Str("uri", r.RequestURI).Send()
|
||||||
rootURL, _ := url.Parse(idp.url.Value())
|
rootURL, _ := url.Parse(idp.url.Value())
|
||||||
_ = json.NewEncoder(w).Encode(map[string]interface{}{
|
config := map[string]interface{}{
|
||||||
"issuer": rootURL.String(),
|
"issuer": rootURL.String(),
|
||||||
"authorization_endpoint": rootURL.ResolveReference(&url.URL{Path: "/oidc/auth"}).String(),
|
"authorization_endpoint": rootURL.ResolveReference(&url.URL{Path: "/oidc/auth"}).String(),
|
||||||
"token_endpoint": rootURL.ResolveReference(&url.URL{Path: "/oidc/token"}).String(),
|
"token_endpoint": rootURL.ResolveReference(&url.URL{Path: "/oidc/token"}).String(),
|
||||||
|
@ -129,11 +138,19 @@ func (idp *IDP) Attach(ctx context.Context) {
|
||||||
"id_token_signing_alg_values_supported": []string{
|
"id_token_signing_alg_values_supported": []string{
|
||||||
"ES256",
|
"ES256",
|
||||||
},
|
},
|
||||||
})
|
}
|
||||||
|
if idp.enableDeviceAuth {
|
||||||
|
config["device_authorization_endpoint"] =
|
||||||
|
rootURL.ResolveReference(&url.URL{Path: "/oidc/device/code"}).String()
|
||||||
|
}
|
||||||
|
serveJSON(w, config)
|
||||||
})
|
})
|
||||||
router.Handle("/oidc/auth", idp.HandleAuth)
|
router.Handle("/oidc/auth", idp.HandleAuth)
|
||||||
router.Handle("/oidc/token", idp.HandleToken)
|
router.Handle("/oidc/token", idp.HandleToken)
|
||||||
router.Handle("/oidc/userinfo", idp.HandleUserInfo)
|
router.Handle("/oidc/userinfo", idp.HandleUserInfo)
|
||||||
|
if idp.enableDeviceAuth {
|
||||||
|
router.Handle("/oidc/device/code", idp.HandleDeviceCode)
|
||||||
|
}
|
||||||
|
|
||||||
env.AddUpstream(router)
|
env.AddUpstream(router)
|
||||||
}
|
}
|
||||||
|
@ -258,14 +275,25 @@ func (idp *IDP) HandleAuth(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
// HandleToken handles the token flow for OIDC.
|
// HandleToken handles the token flow for OIDC.
|
||||||
func (idp *IDP) HandleToken(w http.ResponseWriter, r *http.Request) {
|
func (idp *IDP) HandleToken(w http.ResponseWriter, r *http.Request) {
|
||||||
rawCode := r.FormValue("code")
|
if idp.enableDeviceAuth && r.FormValue("device_code") != "" {
|
||||||
|
idp.serveToken(w, r, &State{
|
||||||
|
ClientID: r.FormValue("client_id"),
|
||||||
|
Email: "fake.user@example.com",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rawCode := r.FormValue("code")
|
||||||
state, err := DecodeState(rawCode)
|
state, err := DecodeState(rawCode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
idp.serveToken(w, r, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *IDP) serveToken(w http.ResponseWriter, r *http.Request, state *State) {
|
||||||
serveJSON(w, map[string]interface{}{
|
serveJSON(w, map[string]interface{}{
|
||||||
"access_token": state.Encode(),
|
"access_token": state.Encode(),
|
||||||
"refresh_token": state.Encode(),
|
"refresh_token": state.Encode(),
|
||||||
|
@ -300,6 +328,30 @@ func (idp *IDP) HandleUserInfo(w http.ResponseWriter, r *http.Request) {
|
||||||
serveJSON(w, state.GetUserInfo(idp.userLookup))
|
serveJSON(w, state.GetUserInfo(idp.userLookup))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HandleDeviceCode initiates a device auth code flow.
|
||||||
|
//
|
||||||
|
// This is the bare minimum to simulate the device auth code flow. There is no client_id
|
||||||
|
// verification or any actual login.
|
||||||
|
func (idp *IDP) HandleDeviceCode(w http.ResponseWriter, r *http.Request) {
|
||||||
|
deviceCode := "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS"
|
||||||
|
userCode := "ABCD-EFGH"
|
||||||
|
|
||||||
|
rootURL, _ := url.Parse(idp.url.Value())
|
||||||
|
u := rootURL.ResolveReference(&url.URL{Path: "/oidc/device"}) // note: not actually implemented
|
||||||
|
verificationURI := u.String()
|
||||||
|
u.RawQuery = "user_code=" + userCode
|
||||||
|
verificationURIComplete := u.String()
|
||||||
|
|
||||||
|
serveJSON(w, &oauth2.DeviceAuthResponse{
|
||||||
|
DeviceCode: deviceCode,
|
||||||
|
UserCode: userCode,
|
||||||
|
VerificationURI: verificationURI,
|
||||||
|
VerificationURIComplete: verificationURIComplete,
|
||||||
|
Expiry: time.Now().Add(5 * time.Minute),
|
||||||
|
Interval: 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
type RootURLKey struct{}
|
type RootURLKey struct{}
|
||||||
|
|
||||||
var rootURLKey RootURLKey
|
var rootURLKey RootURLKey
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue