From 75e1bd77bac4e84b42937b9b16e120eb46bc66ec Mon Sep 17 00:00:00 2001
From: Erik Brakkee 
Date: Sat, 3 Aug 2024 23:10:57 +0200
Subject: [PATCH] Alternative contextpath is now supported. This will simplify
 hosting in cases where you have no control over DNS but only over one domain.
---
 cmd/converge/converge.go         | 30 +++++++++++++++++++-----------
 cmd/converge/convergeaccess.go   | 15 ++++++++++++++-
 cmd/templaterender/render.go     |  2 +-
 pkg/models/convergeaccess.go     |  2 +-
 pkg/server/templates/usage.templ | 20 ++++++++++----------
 5 files changed, 45 insertions(+), 24 deletions(-)
diff --git a/cmd/converge/converge.go b/cmd/converge/converge.go
index a057524..531f4c2 100644
--- a/cmd/converge/converge.go
+++ b/cmd/converge/converge.go
@@ -27,9 +27,10 @@ func parsePublicId(path string) (publicId string, _ error) {
 	return matches[1], nil
 }
 
-func catchAllHandler(w http.ResponseWriter, r *http.Request) {
-	http.Redirect(w, r, "/docs", http.StatusFound)
-	return
+func catchAllHandler(contextPath string) func(w http.ResponseWriter, r *http.Request) {
+	return func(w http.ResponseWriter, r *http.Request) {
+		http.Redirect(w, r, contextPath+"/docs", http.StatusFound)
+	}
 }
 
 func printHelp(msg string) {
@@ -48,8 +49,13 @@ func printHelp(msg string) {
 		"an embedded SSH server to provide interactive access to the end-user. This works\n" +
 		"both on linux and on windows.\n" +
 		"\n" +
-		"-s : directory where static content of converge is placed\n" +
-		"-d : directory where downloads of converge are placed\n"
+		"Options\n" +
+		"-s :  directory where static content of converge is placed\n" +
+		"-d : directory where downloads of converge are placed\n" +
+		"-c : by default all content is served at /. Use this option to specify\n" +
+		"                  a different context path. For instance to host converge at a base\n" +
+		"                  URL of https://example.com/converge, specify /converg (and without\n" +
+		"                  trailing slash. "
 	fmt.Fprintln(os.Stderr, helpText)
 	os.Exit(1)
 }
@@ -58,6 +64,7 @@ func main() {
 
 	downloaddir := "."
 	staticdir := "../static"
+	contextpath := ""
 
 	args := os.Args[1:]
 	for len(args) > 0 && strings.HasPrefix(args[0], "-") {
@@ -74,6 +81,12 @@ func main() {
 			}
 			staticdir = args[1]
 			args = args[1:]
+		case "-c":
+			if len(args) <= 1 {
+				printHelp("The -c option expects an argument")
+			}
+			contextpath = args[1]
+			args = args[1:]
 		default:
 			printHelp("Unknown option " + args[0])
 		}
@@ -162,7 +175,6 @@ func main() {
 
 	// TODO remove, simulate contextpath
 
-	contextpath := ""
 	http.HandleFunc(contextpath+"/agent/", registrationService.Handle)
 	http.HandleFunc(contextpath+"/client/", clientService.Handle)
 	http.HandleFunc(contextpath+"/ws/sessions", sessionService.Handle)
@@ -176,11 +188,7 @@ func main() {
 
 	// TODO remove for testing contextpath
 
-	catchAllHandler2 := func(w http.ResponseWriter, r *http.Request) {
-		http.Redirect(w, r, contextpath+"/docs", http.StatusFound)
-		return
-	}
-	http.HandleFunc(contextpath+"/", catchAllHandler2)
+	http.HandleFunc(contextpath+"/", catchAllHandler(contextpath))
 
 	// create usage generator
 	http.HandleFunc(contextpath+"/usage", generateCLIExammple)
diff --git a/cmd/converge/convergeaccess.go b/cmd/converge/convergeaccess.go
index f69407f..5cb1160 100644
--- a/cmd/converge/convergeaccess.go
+++ b/cmd/converge/convergeaccess.go
@@ -3,11 +3,23 @@ package main
 import (
 	"converge/pkg/models"
 	"converge/pkg/server/converge"
+	"log"
 	"net/http"
+	"regexp"
 	"strings"
 )
 
 func getConvergeAccess(r *http.Request, sshRemoteUser string) models.ConvergeAccess {
+
+	pattern := regexp.MustCompile("^(.*)/usage$")
+	matches := pattern.FindStringSubmatch(r.URL.Path)
+	contextPath := ""
+	if len(matches) != 2 {
+		log.Printf("Cannot determine context path for %s, assumming it is empty", r.URL.Path)
+	} else {
+		contextPath = matches[1]
+	}
+
 	secure := ""
 	if r.TLS == nil {
 		secure = ""
@@ -27,9 +39,10 @@ func getConvergeAccess(r *http.Request, sshRemoteUser string) models.ConvergeAcc
 	if err != nil {
 		panic(err)
 	}
+	baseUrl := strings.ReplaceAll(r.Host+contextPath, "//", "/")
 	return models.ConvergeAccess{
 		Secure:   secure,
-		HostPort: r.Host,
+		BaseUrl:  baseUrl,
 		Location: location,
 		Username: sshRemoteUser,
 	}
diff --git a/cmd/templaterender/render.go b/cmd/templaterender/render.go
index 3ea7024..55b0bf9 100644
--- a/cmd/templaterender/render.go
+++ b/cmd/templaterender/render.go
@@ -39,7 +39,7 @@ func main() {
 
 	access := models.ConvergeAccess{
 		Secure:   "s",
-		HostPort: "example.com",
+		BaseUrl:  "example.com",
 		Location: netherlands,
 		Username: "converge",
 	}
diff --git a/pkg/models/convergeaccess.go b/pkg/models/convergeaccess.go
index 007c321..3c5e2d1 100644
--- a/pkg/models/convergeaccess.go
+++ b/pkg/models/convergeaccess.go
@@ -5,7 +5,7 @@ import "time"
 type ConvergeAccess struct {
 	// 's" when secure, "" otherwise
 	Secure   string
-	HostPort string
+	BaseUrl  string
 	Location *time.Location
 	Username string
 }
diff --git a/pkg/server/templates/usage.templ b/pkg/server/templates/usage.templ
index 5a5e65d..51e9f4d 100644
--- a/pkg/server/templates/usage.templ
+++ b/pkg/server/templates/usage.templ
@@ -13,23 +13,23 @@ templ AgentUsage(access models.ConvergeAccess, usageInputs UsageInputs) {
 
     if usageInputs.RemoteShells[BASH] {
         {addSshKeys(BASH, usageInputs.SshKeys)}
-        curl --fail-with-body http{access.Secure}://{access.HostPort}/downloads/agent > agent{`
+        curl --fail-with-body http{access.Secure}://{access.BaseUrl}/downloads/agent > agent{`
         chmod 755 agent
-        `}./agent --id {usageInputs.Id} ws{access.Secure}://{access.HostPort}{`
+        `}./agent --id {usageInputs.Id} ws{access.Secure}://{access.BaseUrl}{`
         rm -f agent
         `}
     }
     if usageInputs.RemoteShells[CMD]  {
         {addSshKeys(CMD, usageInputs.SshKeys)}
-        curl --fail-with-body http{access.Secure}://{access.HostPort}/downloads/agent.exe > agent.exe{`
-        `}agent --id {usageInputs.Id} ws{access.Secure}://{access.HostPort}{`
+        curl --fail-with-body http{access.Secure}://{access.BaseUrl}/downloads/agent.exe > agent.exe{`
+        `}agent --id {usageInputs.Id} ws{access.Secure}://{access.BaseUrl}{`
         del agent.exe
         `}
     }
     if usageInputs.RemoteShells[POWERSHELL]  {
         {addSshKeys(POWERSHELL, usageInputs.SshKeys)}
-        curl --fail-with-body http{access.Secure}://{access.HostPort}/downloads/agent.exe > agent.exe{`
-        `}agent --id {usageInputs.Id} ws{access.Secure}://{access.HostPort}{`
+        curl --fail-with-body http{access.Secure}://{access.BaseUrl}/downloads/agent.exe > agent.exe{`
+        `}agent --id {usageInputs.Id} ws{access.Secure}://{access.BaseUrl}{`
         del agent.exe
         `}
     }
@@ -55,8 +55,8 @@ templ AgentUsage(access models.ConvergeAccess, usageInputs UsageInputs) {
     
 
      {`
-          `}ssh -oServerAliveInterval=10 -oProxyCommand="wsproxy ws{access.Secure}://{access.HostPort}/client/{usageInputs.Id}"  { access.Username }{"@localhost"}   {`
-          `}sftp -oServerAliveInterval=10 -oProxyCommand="wsproxy ws{access.Secure}://{access.HostPort}/client/{usageInputs.Id}" { access.Username }{"@localhost"}   {`
+          `}ssh -oServerAliveInterval=10 -oProxyCommand="wsproxy ws{access.Secure}://{access.BaseUrl}/client/{usageInputs.Id}"  { access.Username }{"@localhost"}   {`
+          `}sftp -oServerAliveInterval=10 -oProxyCommand="wsproxy ws{access.Secure}://{access.BaseUrl}/client/{usageInputs.Id}" { access.Username }{"@localhost"}   {`
           `}
 
     This requires the wsproxy utility which is available in the
@@ -79,8 +79,8 @@ templ AgentUsage(access models.ConvergeAccess, usageInputs UsageInputs) {
        using:
     
     {`
-         `}tcptows ws{access.Secure}://{access.HostPort}/client/{usageInputs.Id}   {`
-         `}tcptows ws{access.Secure}://{access.HostPort}/client/{usageInputs.Id}   {`
+         `}tcptows ws{access.Secure}://{access.BaseUrl}/client/{usageInputs.Id}   {`
+         `}tcptows ws{access.Secure}://{access.BaseUrl}/client/{usageInputs.Id}   {`
          `}
 
     Working with the agent