summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore15
-rw-r--r--config.go39
-rw-r--r--go.mod10
-rw-r--r--go.sum22
-rw-r--r--handler.go71
-rw-r--r--main.go29
-rw-r--r--path.go60
7 files changed, 246 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a48fe5c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+# Binaries for programs and plugins
+*.exe
+*.exe~
+*.dll
+*.so
+*.dylib
+
+# Test binary, built with `go test -c`
+*.test
+
+# Output of the go coverage tool, specifically when used with LiteIDE
+*.out
+
+# IDE specific files
+.idea/
diff --git a/config.go b/config.go
new file mode 100644
index 0000000..cd315c0
--- /dev/null
+++ b/config.go
@@ -0,0 +1,39 @@
+package main
+
+import "fmt"
+
+// Config represents the global Portgate config.
+type Config struct {
+ // Where Portgate will be running at.
+ portgatePort int
+ portgateHost string
+
+ // Where the requests will be proxied to.
+ targetHost string
+
+ allowedPorts []int
+ forbiddenPorts []int
+}
+
+// GetConfig creates the Portgate config from outside sources such as
+// the environment variables and the portgate.yml file.
+func GetConfig() (Config, error) {
+ // TODO: Read config from environment/file
+ return Config{
+ portgatePort: 8080,
+ targetHost: "localhost",
+
+ allowedPorts: []int{80},
+ forbiddenPorts: []int{},
+ }, nil
+}
+
+// PortgateAddress is the address on which Portgate will run.
+func (c *Config) PortgateAddress() string {
+ return fmt.Sprintf("%s:%d", c.portgateHost, c.portgatePort)
+}
+
+// TargetAddress is the address of the destination server.
+func (c *Config) TargetAddress(port int) string {
+ return fmt.Sprintf("%s:%d", c.targetHost, port)
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..6ba1ccd
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,10 @@
+module portgate
+
+go 1.16
+
+require (
+ github.com/valyala/fasthttp v1.28.0
+
+ github.com/andybalholm/brotli v1.0.3 // indirect
+ github.com/klauspost/compress v1.13.1 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..c912579
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,22 @@
+github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
+github.com/andybalholm/brotli v1.0.3 h1:fpcw+r1N1h0Poc1F/pHbW40cUm/lMEQslZtCkBQ0UnM=
+github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
+github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
+github.com/klauspost/compress v1.13.1 h1:wXr2uRxZTJXHLly6qhJabee5JqIhTRoLBhDOA74hDEQ=
+github.com/klauspost/compress v1.13.1/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
+github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
+github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
+github.com/valyala/fasthttp v1.28.0 h1:ruVmTmZaBR5i67NqnjvvH5gEv0zwHfWtbjoyW98iho4=
+github.com/valyala/fasthttp v1.28.0/go.mod h1:cmWIqlu99AO/RKcp1HWaViTqc57FswJOfYYdPJBl8BA=
+github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
+golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
diff --git a/handler.go b/handler.go
new file mode 100644
index 0000000..edb0ff2
--- /dev/null
+++ b/handler.go
@@ -0,0 +1,71 @@
+package main
+
+import (
+ "github.com/valyala/fasthttp"
+ "net/http"
+)
+
+// RequestHandler keeps data relevant to the request handlers.
+type RequestHandler struct {
+ // Pointer to the global Portgate config, the values of which can change at runtime.
+ config *Config
+ // HTTP Client for requesting resources from the destination host.
+ client fasthttp.Client
+}
+
+// handleRequest handles all types of requests and delegates to more specific handlers.
+func (h *RequestHandler) handleRequest(ctx *fasthttp.RequestCtx) {
+ path := ParsePath(string(ctx.Path()))
+
+ if path.DestinationIdentifier == -1 {
+ // We were not given a port.
+
+ if path.ResourcePath == "/_portgate" {
+ h.handlePortgateRequest(ctx)
+ } else {
+ // TODO: Try to grab actual destination from Referer header.
+ h.handleUnknownRequest(ctx)
+ }
+ } else {
+ // We were given a port, so we have to pass the request through to the destination host.
+
+ h.handlePassthroughRequest(ctx, path)
+ }
+}
+
+// handlePassthroughRequest handles requests which are supposed to be proxied to the destination host.
+// If the user is authorized they are allowed to pass, otherwise they should be redirected to
+// the authentication page. (/_portgate)
+func (h *RequestHandler) handlePassthroughRequest(ctx *fasthttp.RequestCtx, p Path) {
+ // TODO: Check authorization.
+ // TODO: Check whether port is allowed to be accessed.
+
+ // We reuse the request given to us by the user with minor changes to route it to the
+ // destination host.
+ ctx.Request.SetRequestURI(p.MakeUrl(h.config.targetHost))
+ ctx.Request.Header.Set("Host", h.config.TargetAddress(p.DestinationIdentifier))
+
+ // We pipe the response given to us by the destination host back to the user.
+ // Since it's possible that we get a redirect, we take this into account,
+ // but only allow upto 10 redirects.
+ err := h.client.DoRedirects(&ctx.Request, &ctx.Response, 10)
+ if err != nil {
+ ctx.SetStatusCode(http.StatusInternalServerError)
+ _, _ = ctx.WriteString("An error occurred.")
+ }
+}
+
+// handlePortgateRequest handles all Portgate specific request for either showing Portgate
+// specific pages or handling creation of authorization tokens.
+func (h *RequestHandler) handlePortgateRequest(ctx *fasthttp.RequestCtx) {
+ // TODO: Implement authentication, authorization
+ _, _ = ctx.WriteString("Portgate request.")
+}
+
+// handleUnknownRequest handles any request which could not be processed due to missing
+// information.
+func (h *RequestHandler) handleUnknownRequest(ctx *fasthttp.RequestCtx) {
+ // TODO: Show error page
+ ctx.SetStatusCode(http.StatusNotFound)
+ _, _ = ctx.WriteString("Unknown request.")
+}
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..9870dee
--- /dev/null
+++ b/main.go
@@ -0,0 +1,29 @@
+package main
+
+import (
+ "github.com/valyala/fasthttp"
+ "log"
+)
+
+func main() {
+ log.Print("Starting Portgate...")
+
+ // Get global Portgate config.
+ config, err := GetConfig()
+ if err != nil {
+ log.Fatal("Failed to get Portgate config.")
+ }
+
+ // Create handler for requests
+ handler := RequestHandler{
+ config: &config,
+ client: fasthttp.Client{},
+ }
+
+ // Start to listen to the outside world.
+ log.Print("Listening for requests on port 8080.")
+ err = fasthttp.ListenAndServe(config.PortgateAddress(), handler.handleRequest)
+ if err != nil {
+ log.Fatalf("Portgate server could not be started: %s", err)
+ }
+}
diff --git a/path.go b/path.go
new file mode 100644
index 0000000..bb7154a
--- /dev/null
+++ b/path.go
@@ -0,0 +1,60 @@
+package main
+
+import (
+ "fmt"
+ "path"
+ "strconv"
+ "strings"
+)
+
+// Path represents a routing destination the user gave.
+type Path struct {
+ // Identifier to which port of the destination host the path points to and to which the
+ // user's request will be proxied to.
+ // If there was no identifier given it's -1.
+ DestinationIdentifier int
+ // The path without the port identifier.
+ // This is the path which will be requested from the destination.
+ ResourcePath string
+}
+
+// ParsePath creates a Path from the requested URL.
+func ParsePath(p string) Path {
+ p = path.Clean("/" + p)
+
+ // Get first path part, which is the destinationIdentifier
+
+ destinationIdentifierEnd := strings.Index(p[1:], "/") + 1
+
+ // If there is no '/' at in the path, apart from the root, the first part is the entire path
+ if destinationIdentifierEnd == 0 {
+ destinationIdentifierEnd = len(p)
+ }
+
+ destinationIdentifier := p[1:destinationIdentifierEnd]
+
+ // We have to to check that destinationIdentifier is a port
+ port, err := strconv.Atoi(destinationIdentifier)
+ if err == nil {
+ // We got an identifier and can split the path
+
+ resourcePath := path.Clean("/" + p[destinationIdentifierEnd:])
+ return Path{
+ DestinationIdentifier: port,
+ ResourcePath: resourcePath,
+ }
+ } else {
+ // We got some other path without an identifier
+
+ return Path{
+ DestinationIdentifier: -1,
+ ResourcePath: p,
+ }
+ }
+}
+
+// MakeUrl creates the URL on the destination host that the user wants to access.
+func (p *Path) MakeUrl(targetHost string) string {
+ // TODO: Figure out what to do with TLS
+ return fmt.Sprintf("http://%s:%d%s", targetHost, p.DestinationIdentifier, p.ResourcePath)
+}