Lots of work on docuemtation. The docs page now shows the correct
installation dependent URLs. For now using ServerALiveInterval to avoid disconnects.
This commit is contained in:
		
							parent
							
								
									21f50a4005
								
							
						
					
					
						commit
						12ecb72329
					
				| @ -92,7 +92,7 @@ func sshServer(hostKeyFile string) *ssh.Server { | ||||
| 		} | ||||
| 	}) | ||||
| 
 | ||||
| 	log.Println("starting ssh server") | ||||
| 	log.Println("starting ssh server, waiting for debug sessions") | ||||
| 	server := ssh.Server{ | ||||
| 		PasswordHandler: passwordAuth, | ||||
| 		SubsystemHandlers: map[string]ssh.SubsystemHandler{ | ||||
| @ -164,6 +164,8 @@ func main() { | ||||
| 		log.Println("WebSocket connection error:", err) | ||||
| 		return | ||||
| 	} | ||||
| 	conn.SetReadDeadline(time.Time{}) | ||||
| 	conn.SetWriteDeadline(time.Time{}) | ||||
| 	wsConn := websocketutil.NewWebSocketConn(conn) | ||||
| 	defer wsConn.Close() | ||||
| 
 | ||||
|  | ||||
| @ -1,14 +1,18 @@ | ||||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"cidebug/pkg/converge" | ||||
| 	"cidebug/pkg/websocketutil" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"log" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"os" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| ) | ||||
| 
 | ||||
| func parsePublicId(path string) (publicId string, _ error) { | ||||
| @ -25,6 +29,90 @@ func catchAllHandler(w http.ResponseWriter, r *http.Request) { | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // filters to filter html content.
 | ||||
| var filters map[string]string = make(map[string]string) | ||||
| 
 | ||||
| type FileHandlerFilter struct { | ||||
| 	http.Handler | ||||
| } | ||||
| 
 | ||||
| func (handler FileHandlerFilter) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||||
| 	mutex := sync.Mutex{} | ||||
| 	// pedantic
 | ||||
| 	mutex.Lock() | ||||
| 
 | ||||
| 	if r.TLS == nil { | ||||
| 		filters["secure"] = "" | ||||
| 	} else { | ||||
| 		filters["secure"] = "s" | ||||
| 	} | ||||
| 	for _, header := range []string{"X-Forwarded-Proto", "X-Scheme", "X-Forwarded-Scheme"} { | ||||
| 		values := r.Header.Values(header) | ||||
| 		for _, value := range values { | ||||
| 			if strings.ToLower(value) == "https" { | ||||
| 				filters["secure"] = "s" | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	filters["host"] = r.Host | ||||
| 	mutex.Unlock() | ||||
| 
 | ||||
| 	w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") | ||||
| 	w.Header().Set("Pragma", "no-cache") | ||||
| 	w.Header().Set("Expires", "0") | ||||
| 
 | ||||
| 	handler.Handler.ServeHTTP(w, r) | ||||
| } | ||||
| 
 | ||||
| type FilteredFileSystem struct { | ||||
| 	fs http.FileSystem | ||||
| } | ||||
| 
 | ||||
| func (ffs FilteredFileSystem) Open(name string) (http.File, error) { | ||||
| 	log.Println("Name : " + name) | ||||
| 	f, err := ffs.fs.Open(name) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if !strings.HasSuffix(name, ".html") { | ||||
| 		return f, nil | ||||
| 	} | ||||
| 	return NewFilteredFile(f, filters) | ||||
| } | ||||
| 
 | ||||
| type FilteredFile struct { | ||||
| 	http.File | ||||
| 
 | ||||
| 	// Read bytes 0..(index-1) already
 | ||||
| 	index        int | ||||
| 	fullContents *bytes.Buffer | ||||
| } | ||||
| 
 | ||||
| func NewFilteredFile(f http.File, filter map[string]string) (FilteredFile, error) { | ||||
| 	file := FilteredFile{ | ||||
| 		index:        0, | ||||
| 		fullContents: bytes.NewBuffer(make([]byte, 0)), | ||||
| 	} | ||||
| 	file.File = f | ||||
| 
 | ||||
| 	_, err := io.Copy(file.fullContents, file.File) | ||||
| 	if err != nil { | ||||
| 		return FilteredFile{}, err | ||||
| 	} | ||||
| 	contents := file.fullContents.String() | ||||
| 	for key, value := range filter { | ||||
| 		key = "@" + key + "@" | ||||
| 		contents = strings.ReplaceAll(contents, key, value) | ||||
| 	} | ||||
| 	file.fullContents = bytes.NewBufferString(contents) | ||||
| 
 | ||||
| 	return file, nil | ||||
| } | ||||
| 
 | ||||
| func (ff FilteredFile) Read(p []byte) (n int, err error) { | ||||
| 	return ff.fullContents.Read(p) | ||||
| } | ||||
| 
 | ||||
| func main() { | ||||
| 
 | ||||
| 	downloadDir := "downloads" | ||||
| @ -65,7 +153,14 @@ func main() { | ||||
| 
 | ||||
| 	http.HandleFunc("/agent/", registrationService.Handle) | ||||
| 	http.HandleFunc("/client/", clientService.Handle) | ||||
| 	http.Handle("/docs/", http.StripPrefix("/docs/", http.FileServer(http.Dir(downloadDir)))) | ||||
| 
 | ||||
| 	filesystem := http.Dir(downloadDir) | ||||
| 	filteredFilesystem := FilteredFileSystem{ | ||||
| 		fs: filesystem, | ||||
| 	} | ||||
| 	fileHandler := FileHandlerFilter{http.FileServer(filteredFilesystem)} | ||||
| 
 | ||||
| 	http.Handle("/docs/", http.StripPrefix("/docs/", fileHandler)) | ||||
| 	http.HandleFunc("/", catchAllHandler) | ||||
| 
 | ||||
| 	// Start HTTP server
 | ||||
|  | ||||
| @ -7,6 +7,7 @@ import ( | ||||
| 	"log" | ||||
| 	"net" | ||||
| 	"os" | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| func closeConnection(conn net.Conn) { | ||||
| @ -25,6 +26,8 @@ func handleConnection(conn net.Conn, wsURL string) { | ||||
| 		log.Println("WebSocket connection error:", err) | ||||
| 		return | ||||
| 	} | ||||
| 	_wsConn.SetReadDeadline(time.Time{}) | ||||
| 	_wsConn.SetWriteDeadline(time.Time{}) | ||||
| 	wsConn := websocketutil.NewWebSocketConn(_wsConn) | ||||
| 	defer wsConn.Close() | ||||
| 
 | ||||
|  | ||||
| @ -7,6 +7,7 @@ import ( | ||||
| 	"log" | ||||
| 	"net" | ||||
| 	"os" | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| func closeConnection(conn net.Conn) { | ||||
| @ -29,6 +30,8 @@ func main() { | ||||
| 	wsURL := os.Args[1] | ||||
| 
 | ||||
| 	_wsConn, _, err := websocket.DefaultDialer.Dial(wsURL, nil) | ||||
| 	_wsConn.SetReadDeadline(time.Time{}) | ||||
| 	_wsConn.SetWriteDeadline(time.Time{}) | ||||
| 	if err != nil { | ||||
| 		log.Println("WebSocket connection error:", err) | ||||
| 		panic(err) | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| Session is set to expire at %s | ||||
| Session is set to expire at %v | ||||
| 
 | ||||
| The session expires automatically after %d time. | ||||
| If there are no more sessions after logging out, the agent | ||||
| @ -8,6 +8,6 @@ You can extend this time using | ||||
| 
 | ||||
|   touch $agentdir/.hold | ||||
| 
 | ||||
| To prevent the agent from exiting after the last sessioni is gone, | ||||
| To prevent the agent from exiting after the last session is gone, | ||||
| also use the above command in any shell. | ||||
| 
 | ||||
|  | ||||
| @ -144,9 +144,9 @@ func fileExists(filename string) bool { | ||||
| func (state *AgentState) expiryTime(filename string) time.Time { | ||||
| 	stats, err := os.Stat(filename) | ||||
| 	if err != nil { | ||||
| 		return state.startTime | ||||
| 		return state.startTime.Add(state.agentExpriryTime) | ||||
| 	} | ||||
| 	return stats.ModTime() | ||||
| 	return stats.ModTime().Add(state.agentExpriryTime) | ||||
| } | ||||
| 
 | ||||
| // Behavior to implement
 | ||||
| @ -177,6 +177,11 @@ func check() { | ||||
| 			PrintHelpMessage(session.sshSession) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if !fileExists(holdFilename) && len(state.sessions) == 0 { | ||||
| 		log.Printf("All clients disconnected and no '%s' file found, exiting", holdFilename) | ||||
| 		os.Exit(0) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func messageUsers(message string) { | ||||
|  | ||||
| @ -72,6 +72,8 @@ func ConnectWebSocket(conn net.Conn, urlStr string) (net.Conn, error) { | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	wsConn.SetReadDeadline(time.Time{}) | ||||
| 	wsConn.SetWriteDeadline(time.Time{}) | ||||
| 
 | ||||
| 	return NewWebSocketConn(wsConn), nil | ||||
| } | ||||
|  | ||||
| @ -5,6 +5,7 @@ import ( | ||||
| 	"log" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| type WebSocketAddr string | ||||
| @ -28,6 +29,8 @@ func handleWebSocket(w http.ResponseWriter, r *http.Request, | ||||
| 		log.Println("Error upgrading to WebSocket:", err) | ||||
| 		return | ||||
| 	} | ||||
| 	conn.SetReadDeadline(time.Time{}) | ||||
| 	conn.SetWriteDeadline(time.Time{}) | ||||
| 	wsConn := NewWebSocketConn(conn) | ||||
| 	defer wsConn.Close() | ||||
| 
 | ||||
|  | ||||
| @ -14,6 +14,39 @@ | ||||
| 
 | ||||
| <div class="container"> | ||||
| 
 | ||||
|     <h1>About</h1> | ||||
| 
 | ||||
|     <p> | ||||
|     Converge is a utility for troubleshooting builds on continuous integration serves. | ||||
|     It solves a common problem where the cause of job failure is difficult to determine. | ||||
|     This is complicated furhter by the fact that build jobs are usually run on a build | ||||
|     farm where there is no access to the build agents or in more modern envrionments when | ||||
|     jobs are run in ephemeral containers. | ||||
|     </p> | ||||
| 
 | ||||
|     <p> | ||||
|     With Converge it is possible to get remote shell access to such jobs. This works | ||||
|     by configuring the build job to connect to a Converge server using an agent program. | ||||
|     The agent program can be downloaded from within the CI job using curl or wget. | ||||
|     Next, an end-use can connect to the Converge server, a rendez-vous server, that connects | ||||
|     the client and server together. | ||||
|     </p> | ||||
| 
 | ||||
|     <p> | ||||
|     The setup is such that the connection from client (end-user) to server (agent on CI job) | ||||
|     is end-to-end encrypted. The Converge server itself is no more than a bitpipe which pumps | ||||
|     data between client and agent. | ||||
|     </p> | ||||
| 
 | ||||
|     <p> | ||||
|     Both ssh and sftp are supported. Multiple shells are also allowed. | ||||
|     </p> | ||||
| 
 | ||||
|     <p> | ||||
|     There is a timeout mechanism in the agent such that jobs do not hang indefinetely waiting | ||||
|     for a connection. | ||||
|     </p> | ||||
| 
 | ||||
|     <h1>Usage</h1> | ||||
| 
 | ||||
|     <h2>Continous integration jobs</h2> | ||||
| @ -22,12 +55,12 @@ | ||||
|         In a continous integration job, download the agent, chmod it and run it. | ||||
|     </p> | ||||
|     <pre> | ||||
|      curl https://HOSTPORT/docs/agent > agent | ||||
|      ./agent wss://HOST:PORT/agent/ID | ||||
|      curl http@secure@://@host@/docs/agent > agent | ||||
|      chmod 755 agent | ||||
|      ./agent ws@secure@://@host@/agent/ID | ||||
|     </pre> | ||||
|     <p> | ||||
|         Above, HOST:PORT is the hostname:port of the converge server and ID is a unique id | ||||
|         Above, ID is a unique id | ||||
|         for the job. This should not conflict with other ids. | ||||
| 
 | ||||
|         This connects the agent to the converge server. Clients can now connect to converge | ||||
| @ -37,7 +70,7 @@ | ||||
|     <h2>Local clients: using ssh proxy command </h2> | ||||
| 
 | ||||
|     <pre> | ||||
|     curl https://HOST:PORT/docs/wsproxy > wsproxy | ||||
|     curl http@secure@://@host@/docs/wsproxy > wsproxy | ||||
|     chmod 755 wsproxy | ||||
|     </pre> | ||||
| 
 | ||||
| @ -49,8 +82,8 @@ | ||||
|     </p> | ||||
| 
 | ||||
|     <pre> | ||||
|     ssh -oProxyCommand="wsproxy https://HOST:PORT/client/ID" -p 10000 abc@localhost | ||||
|     sftp -oProxyCommand="wsproxy https://HOST:PORT/client/ID" -oPort 10000 abc@localhost | ||||
|     ssh -oServerAliveInterval=10 -oProxyCommand="wsproxy ws@secure@://@host@/client/ID"  abc@localhost | ||||
|     sftp -oServerAliveInterval=10 -oProxyCommand="wsproxy ws@secure@://@host@/client/ID" abc@localhost | ||||
|     </pre> | ||||
| 
 | ||||
|     <p> | ||||
| @ -70,15 +103,9 @@ | ||||
|     </p> | ||||
| 
 | ||||
|     <pre> | ||||
|     # for HTTP hosted server | ||||
|     curl http://HOST:PORT/docs/tcptows > tcptows | ||||
|     curl http@secure@://@host@/docs/tcptows > tcptows | ||||
|     chmod 755 tcptows | ||||
|     ./tcptows 10000 ws://HOST:PORT/client/ID | ||||
| 
 | ||||
|     # for HTTPS hosted server | ||||
|     curl https://HOST:PORT/docs/tcptows > tcptows | ||||
|     chmod 755 tcptows | ||||
|     ./tcptows 10000 wss://HOST:PORT/client/ID | ||||
|     ./tcptows 10000 ws@secure@://@host@/client/ID | ||||
|     </pre> | ||||
| 
 | ||||
|     <p> | ||||
| @ -90,8 +117,8 @@ | ||||
|     </p> | ||||
| 
 | ||||
|     <pre> | ||||
|     ssh -p 10000 abc@localhost | ||||
|     sftp -oPort 10000 abc@localhost | ||||
|     ssh -oServerAliveInterval=10 -p 10000 abc@localhost | ||||
|     sftp -oServerAliveInterval=10 -oPort 10000 abc@localhost | ||||
|     </pre> | ||||
| 
 | ||||
|     <p> | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user