-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #9 from PhilippePitzClairoux/feature/major-code-re…
…factoring Feature/major code refactoring
- Loading branch information
Showing
5 changed files
with
224 additions
and
150 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,118 +1,27 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"flag" | ||
"fmt" | ||
"log" | ||
"net/http" | ||
"os" | ||
"os/exec" | ||
"os/signal" | ||
|
||
"github.com/PhilippePitzClairoux/openconnect-sso/internal" | ||
"github.com/chromedp/chromedp" | ||
"log" | ||
) | ||
|
||
// flags | ||
var server = flag.String("server", "", "Server to connect to via openconnect") | ||
var username = flag.String("username", "", "Username to inject in login form") | ||
var password = flag.String("password", "", "Password to inject in login form") | ||
var extraArgs = flag.String("extra-args", "", "Extra args for openconnect (will not override pre-existing ones)") | ||
|
||
func main() { | ||
flag.Parse() | ||
|
||
// Register kill/interrupt signals | ||
exit := make(chan os.Signal) | ||
signal.Notify(exit, os.Kill, os.Interrupt) | ||
|
||
// Initialize http clients and start authentication process | ||
client := internal.NewHttpClient(*server) | ||
cookieFound := make(chan string) | ||
targetUrl := internal.GetActualUrl(client, *server) | ||
samlAuth := internal.AuthenticationInit(client, targetUrl) | ||
ctx, closeBrowser := internal.CreateBrowserContext() | ||
|
||
// Here we setup a listener to catch the event of a user closing their browser. | ||
chromedp.ListenTarget(ctx, func(ev interface{}) { | ||
internal.CloseBrowserOnRenderProcessGone(ev, exit) | ||
}) | ||
|
||
// generate tasks | ||
tasks := generateDefaultBrowserTasks(samlAuth) | ||
|
||
// close browser at the end - no matter what happens | ||
defer closeBrowser() | ||
|
||
// handle exit signal | ||
go handleExit(exit, closeBrowser) | ||
|
||
log.Println("Starting goroutine that searches for authentication cookie ", samlAuth.Auth.SsoV2TokenCookieName) | ||
go internal.BrowserCookieFinder(ctx, cookieFound, samlAuth.Auth.SsoV2TokenCookieName) | ||
|
||
log.Println("Open browser and navigate to SSO login page : ", samlAuth.Auth.SsoV2Login) | ||
err := chromedp.Run(ctx, tasks) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
// consume cookie and connect to vpn | ||
startVpnOnLoginCookie(cookieFound, client, samlAuth, targetUrl, closeBrowser) | ||
} | ||
|
||
func handleExit(exit chan os.Signal, browser context.CancelFunc) { | ||
sig := <-exit | ||
log.Printf("Got an exit signal (%s)! Cya!", sig.String()) | ||
browser() | ||
os.Exit(0) | ||
} | ||
|
||
func generateDefaultBrowserTasks(samlAuth *internal.AuthenticationInitExpectedResponse) chromedp.Tasks { | ||
var tasks chromedp.Tasks | ||
|
||
// create list of tasks to be executed by browser | ||
tasks = append(tasks, chromedp.Navigate(samlAuth.Auth.SsoV2Login)) | ||
addAutofillTaskOnValue(&tasks, *password, "#passwordInput") | ||
addAutofillTaskOnValue(&tasks, *username, "#userNameInput") | ||
|
||
return tasks | ||
} | ||
|
||
func addAutofillTaskOnValue(actions *chromedp.Tasks, value, selector string) { | ||
if value != "" { | ||
*actions = append( | ||
*actions, | ||
chromedp.WaitVisible(selector, chromedp.ByID), | ||
chromedp.SendKeys(selector, value, chromedp.ByID), | ||
) | ||
if *server == "" { | ||
log.Println("missing mandatory parameter --server") | ||
flag.PrintDefaults() | ||
} | ||
} | ||
|
||
// startVpnOnLoginCookie waits to get a cookie from the authenticationCookies channel before confirming | ||
// the authentication process (to get token/cert) and then starting openconnect | ||
func startVpnOnLoginCookie(authenticationCookies chan string, client *http.Client, auth *internal.AuthenticationInitExpectedResponse, targetUrl string, closeBrowser context.CancelFunc) { | ||
for cookie := range authenticationCookies { | ||
token, cert := internal.AuthenticationConfirmation(client, auth, cookie, targetUrl) | ||
closeBrowser() // close browser | ||
|
||
command := exec.Command("sudo", | ||
"openconnect", | ||
fmt.Sprintf("--useragent=AnyConnect Linux_64 %s", internal.VERSION), | ||
fmt.Sprintf("--version-string=%s", internal.VERSION), | ||
fmt.Sprintf("--cookie=%s", token), | ||
fmt.Sprintf("--servercert=%s", cert), | ||
targetUrl, | ||
) | ||
|
||
command.Stdout = os.Stdout | ||
command.Stderr = os.Stdout | ||
command.Stdin = os.Stdin | ||
|
||
log.Println("Starting openconnect: ", command.String()) | ||
err := command.Run() | ||
if err != nil { | ||
log.Fatal("Could not start command : ", err) | ||
} | ||
openconnect := internal.NewOpenconnectCtx(*server, *username, *password) | ||
err := openconnect.Run() | ||
if err != nil { | ||
log.Fatal("Could not run openconnect-sso : ", err) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
package internal | ||
|
||
import ( | ||
"context" | ||
"github.com/chromedp/cdproto/inspector" | ||
"github.com/chromedp/cdproto/network" | ||
"github.com/chromedp/chromedp" | ||
"os" | ||
"strings" | ||
) | ||
|
||
// opts are chrome options | ||
var opts = append(chromedp.DefaultExecAllocatorOptions[:], | ||
chromedp.Flag("headless", false), // Set headless mode to false | ||
chromedp.Flag("disable-gpu", false), | ||
) | ||
|
||
// createBrowserContext creates new chromedp context and exec allocator | ||
func createBrowserContext() (context.Context, context.CancelFunc) { | ||
ctx, _ := chromedp.NewContext(context.Background()) | ||
allocCtx, _ := chromedp.NewExecAllocator(ctx, opts...) | ||
|
||
return chromedp.NewContext(allocCtx) | ||
} | ||
|
||
// closeBrowserOnRenderProcessGone sends an exit signal when the | ||
// "Render process gone" is found (user manually closes the browser). | ||
func closeBrowserOnRenderProcessGone(ev interface{}, exit chan os.Signal) { | ||
ins, ok := ev.(*inspector.EventDetached) | ||
if ok { | ||
if strings.Contains(ins.Reason.String(), "Render process gone.") { | ||
exit <- os.Kill | ||
} | ||
} | ||
} | ||
|
||
// browserCookieFinder setup's a chromedp listener in order to look | ||
// through the cookies channel for a cookie that matches name | ||
func (oc *OpenconnectCtx) browserCookieFinder(name string) { | ||
chromedp.ListenTarget(oc.browserCtx, func(ev interface{}) { | ||
switch ev := ev.(type) { | ||
case *network.EventRequestWillBeSentExtraInfo: | ||
for _, cookie := range ev.AssociatedCookies { | ||
if cookie.Cookie.Name == name { | ||
oc.cookieFoundChan <- cookie.Cookie.Value | ||
} | ||
} | ||
} | ||
}) | ||
} | ||
|
||
// generateDefaultBrowserTasks adds a task to inject username and/or password if the argument is present. | ||
// also adds the initial Navigate command to open the browser on the right window | ||
func (oc *OpenconnectCtx) generateDefaultBrowserTasks(samlAuth *AuthenticationInitExpectedResponse) chromedp.Tasks { | ||
var tasks chromedp.Tasks | ||
|
||
// create list of tasks to be executed by browser | ||
tasks = append(tasks, chromedp.Navigate(samlAuth.Auth.SsoV2Login)) | ||
addAutofillTaskOnValue(&tasks, oc.password, "#passwordInput") | ||
addAutofillTaskOnValue(&tasks, oc.username, "#userNameInput") | ||
|
||
return tasks | ||
} | ||
|
||
// addAutofillTaskOnValue adds a task if value is not empty | ||
func addAutofillTaskOnValue(actions *chromedp.Tasks, value, selector string) { | ||
if value != "" { | ||
*actions = append( | ||
*actions, | ||
chromedp.WaitVisible(selector, chromedp.ByID), | ||
chromedp.SendKeys(selector, value, chromedp.ByID), | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
package internal | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"github.com/chromedp/chromedp" | ||
"log" | ||
"net/http" | ||
"os" | ||
"os/exec" | ||
"os/signal" | ||
) | ||
|
||
type OpenconnectCtx struct { | ||
process *exec.Cmd | ||
client *http.Client | ||
exit chan os.Signal | ||
cookieFoundChan chan string | ||
exitChan chan os.Signal | ||
targetUrl string | ||
server string | ||
username string | ||
password string | ||
browserCtx context.Context | ||
closeBrowser context.CancelFunc | ||
} | ||
|
||
func NewOpenconnectCtx(server, username, password string) *OpenconnectCtx { | ||
client := NewHttpClient(server) | ||
exit := make(chan os.Signal) | ||
|
||
// register exit signals | ||
signal.Notify(exit, os.Kill, os.Interrupt) | ||
|
||
return &OpenconnectCtx{ | ||
client: client, | ||
cookieFoundChan: make(chan string), | ||
exitChan: exit, | ||
targetUrl: getActualUrl(client, server), | ||
username: username, | ||
password: password, | ||
} | ||
} | ||
|
||
func (oc *OpenconnectCtx) Run() error { | ||
samlAuth, err := oc.AuthenticationInit() | ||
if err != nil { | ||
log.Println("Could not start authentication process...") | ||
return err | ||
} | ||
|
||
tasks, err := oc.startBrowser(samlAuth) | ||
if err != nil { | ||
log.Println("Could not start browser properly...") | ||
return err | ||
} | ||
|
||
// close browser at the end - no matter what happens | ||
defer oc.closeBrowser() | ||
|
||
// handle exit signal | ||
log.Println("Starting goroutine to handle exit signals") | ||
go oc.handleExit() | ||
|
||
log.Println("Starting goroutine to search for cookie", samlAuth.Auth.SsoV2TokenCookieName) | ||
go oc.browserCookieFinder(samlAuth.Auth.SsoV2TokenCookieName) | ||
|
||
log.Println("Open browser and navigate to SSO login page : ", samlAuth.Auth.SsoV2Login) | ||
err = chromedp.Run(oc.browserCtx, tasks) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// consume cookie and connect to vpn | ||
return oc.startVpnOnLoginCookie(samlAuth) | ||
} | ||
|
||
func (oc *OpenconnectCtx) startBrowser(samlAuth *AuthenticationInitExpectedResponse) (chromedp.Tasks, error) { | ||
oc.browserCtx, oc.closeBrowser = createBrowserContext() | ||
tasks := oc.generateDefaultBrowserTasks(samlAuth) | ||
|
||
// setup listener to exit program when browser is closed | ||
chromedp.ListenTarget(oc.browserCtx, func(ev interface{}) { | ||
closeBrowserOnRenderProcessGone(ev, oc.exitChan) | ||
}) | ||
|
||
return tasks, nil | ||
} | ||
|
||
// startVpnOnLoginCookie waits to get a cookie from the authenticationCookies channel before confirming | ||
// the authentication process (to get token/cert) and then starting openconnect | ||
func (oc *OpenconnectCtx) startVpnOnLoginCookie(auth *AuthenticationInitExpectedResponse) error { | ||
for cookie := range oc.cookieFoundChan { | ||
token, cert, err := oc.AuthenticationConfirmation(auth, cookie) | ||
oc.closeBrowser() // close browser | ||
|
||
if err != nil { | ||
return err | ||
} | ||
|
||
oc.process = exec.Command("sudo", | ||
"openconnect", | ||
fmt.Sprintf("--useragent=AnyConnect Linux_64 %s", VERSION), | ||
fmt.Sprintf("--version-string=%s", VERSION), | ||
fmt.Sprintf("--cookie=%s", token), | ||
fmt.Sprintf("--servercert=%s", cert), | ||
oc.targetUrl, | ||
) | ||
|
||
oc.process.Stdout = os.Stdout | ||
oc.process.Stderr = os.Stdout | ||
oc.process.Stdin = os.Stdin | ||
|
||
log.Println("Starting openconnect: ", oc.process.String()) | ||
return oc.process.Run() | ||
} | ||
|
||
return nil | ||
} |
Oops, something went wrong.