Skip to content
Snippets Groups Projects
Commit c1e00511 authored by Kevin Lyda's avatar Kevin Lyda :speech_balloon:
Browse files

Add auto_token client option

Add an auto_token client option to change the flow such that the
token will be sent back to the client directly by the browser.
There are a number of "TODO" messages in this commit which will
hopefully be addressed in code review. I'm not sure if they're
needed.
parent 8a9cd6cd
Branches
No related tags found
No related merge requests found
......@@ -172,3 +172,79 @@ func Sign(pub ssh.PublicKey, token string, conf *Config) (*ssh.Certificate, erro
}
return cert, nil
}
// Listener type contains information for the client listener.
type Listener struct {
Srv *http.Server
TargetURL string
Token chan string
}
// StartHTTPServer starts an http server in the background.
func StartHTTPServer() *Listener {
listening := make(chan bool)
listener := &Listener{
Srv: &http.Server{},
Token: make(chan string),
}
timeout := 5 * time.Second // TODO: Configurable?
portStart := 8050 // TODO: Configurable?
portCheck := []byte("OK") // TODO: Random?
authCallbackURL := "/auth/callback" // TODO: Random?
portCheckURL := "/port/check" // TODO: Random?
http.HandleFunc(authCallbackURL,
func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Write([]byte("<html><head><title>Authorized</title></head><body>Authorized. You can now close this window.</body></html>"))
defer r.Body.Close()
listener.Token <- r.FormValue("token")
})
http.HandleFunc(portCheckURL,
func(w http.ResponseWriter, r *http.Request) {
listening <- true
w.Write(portCheck)
})
// Create the http server.
go func() {
for port := portStart; port < 65535; port++ {
listener.Srv.Addr = fmt.Sprintf("localhost:%d", port)
if err := listener.Srv.ListenAndServe(); err != nil {
if strings.Contains(err.Error(), "Server closed") {
return // Shutdown was called.
} else if !strings.Contains(err.Error(), "address already in use") {
fmt.Printf("Httpserver: ListenAndServe() error: %s", err)
return // Some other error.
}
}
}
}()
// Make sure http server is up.
go func() {
for i := 0 * time.Second; i < timeout; i += time.Second {
time.Sleep(1)
resp, err := http.Get(
fmt.Sprintf("http://%s%s", listener.Srv.Addr, portCheckURL))
if err != nil {
continue
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if bytes.Equal(body, portCheck) {
return
}
}
}()
select {
case <-listening:
listener.TargetURL =
fmt.Sprintf("http://%s%s", listener.Srv.Addr, authCallbackURL)
return listener
case <-time.After(timeout):
return nil
}
}
......@@ -16,6 +16,7 @@ type Config struct {
Validity string `mapstructure:"validity"`
ValidateTLSCertificate bool `mapstructure:"validate_tls_certificate"`
PublicFilePrefix string `mapstructure:"key_file_prefix"`
AutoToken bool `mapstructure:"auto_token"`
}
func setDefaults() {
......@@ -25,6 +26,7 @@ func setDefaults() {
viper.BindPFlag("validity", pflag.Lookup("validity"))
viper.BindPFlag("key_file_prefix", pflag.Lookup("key_file_prefix"))
viper.SetDefault("validateTLSCertificate", true)
viper.SetDefault("auto_token", false)
}
// ReadConfig reads the client configuration from a file into a Config struct.
......
......@@ -7,6 +7,7 @@ import (
"fmt"
"log"
"net"
"net/url"
"os"
"os/user"
"path"
......@@ -49,11 +50,25 @@ func main() {
if err != nil {
log.Fatalln("Error generating key pair: ", err)
}
fmt.Printf("Your browser has been opened to visit %s\n", c.CA)
if err := browser.OpenURL(c.CA); err != nil {
authURL := c.CA
var listener *client.Listener
if c.AutoToken {
listener = client.StartHTTPServer()
if listener != nil {
authURL = fmt.Sprintf("%s?auto_token=%s", c.CA, url.PathEscape(listener.TargetURL))
}
}
fmt.Printf("Your browser has been opened to visit %s\n", authURL)
if err := browser.OpenURL(authURL); err != nil {
fmt.Println("Error launching web browser. Go to the link in your web browser")
}
var token string
if listener != nil {
// TODO: Timeout?
token = <-listener.Token
}
if token == "" {
fmt.Print("Enter token: ")
scanner := bufio.NewScanner(os.Stdin)
var buffer bytes.Buffer
......@@ -64,7 +79,8 @@ func main() {
if err != nil {
log.Fatalln(err)
}
token := string(tokenBytes)
token = string(tokenBytes)
}
cert, err := client.Sign(pub, token, c)
if err != nil {
......
......@@ -115,9 +115,12 @@ func (a *app) auth(w http.ResponseWriter, r *http.Request) {
}
func (a *app) index(w http.ResponseWriter, r *http.Request) {
log.Printf("Entering index handler.")
tok := a.getAuthToken(r)
log.Printf("Token found: %v\n", tok)
autoTokenURL := a.getSessionVariable(r, "auto_token")
if autoTokenURL != "" {
http.Redirect(w, r, fmt.Sprintf("%s?token=%s",
autoTokenURL, tok.AccessToken), http.StatusSeeOther)
} else {
page := struct {
Token string
}{tok.AccessToken}
......@@ -125,6 +128,7 @@ func (a *app) index(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.New("token.html").Parse(templates.Token))
tmpl.Execute(w, page)
}
}
func (a *app) revoked(w http.ResponseWriter, r *http.Request) {
revoked, err := a.certstore.GetRevoked()
......
......@@ -253,6 +253,7 @@ func (a *app) authed(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t := a.getAuthToken(r)
if !t.Valid() || !a.authprovider.Valid(t) {
a.setSessionVariable(w, r, "auto_token", r.FormValue("auto_token"))
a.setSessionVariable(w, r, "origin_url", r.URL.EscapedPath())
http.Redirect(w, r, "/auth/login", http.StatusSeeOther)
return
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment