Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added TLS support #8

Merged
merged 3 commits into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions cmd/serve/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ type command struct {
fileserver Fileserver

cacheControl *string
tlsCertPath *string
tlsKeyPath *string
}

func Command() *command {
Expand All @@ -55,7 +57,8 @@ func (c *command) Setup() error {
c.masterPath = path.Clean(relPath)

if c.masterPath != "" {
c.fileserver = wsinject.NewFileServer(*c.port, *c.wsPath, *c.forceReload)
expectTLS := *c.tlsCertPath != "" && *c.tlsKeyPath != ""
c.fileserver = wsinject.NewFileServer(*c.port, *c.wsPath, *c.forceReload, expectTLS)
mirrorPath, err := c.fileserver.Setup(c.masterPath)
if err != nil {
return fmt.Errorf("failed to setup websocket injected mirror filesystem: %v", err)
Expand Down Expand Up @@ -84,8 +87,16 @@ func (c *command) Run(ctx context.Context) error {
serverErrChan := make(chan error, 1)
fsErrChan := make(chan error, 1)
go func() {
ancli.PrintfOK("now serving directory: '%v' on port: '%v', mirror dir is: '%v'", c.masterPath, *c.port, c.mirrorPath)
err := s.ListenAndServe()
serveTLS := *c.tlsCertPath != "" && *c.tlsKeyPath != ""

ancli.PrintfOK("now serving directory: '%v' on port: '%v', mirror dir is: '%v', tls: %v, certPath: '%v', keyPath: '%v'",
c.masterPath, *c.port, c.mirrorPath, serveTLS, *c.tlsCertPath, *c.tlsKeyPath)
var err error
if serveTLS {
err = s.ListenAndServeTLS(*c.tlsCertPath, *c.tlsKeyPath)
} else {
err = s.ListenAndServe()
}
if !errors.Is(err, http.ErrServerClosed) {
serverErrChan <- err
}
Expand Down Expand Up @@ -127,6 +138,8 @@ func (c *command) Flagset() *flag.FlagSet {
c.wsPath = fs.String("wsPort", "/delta-streamer-ws", "the path which the delta streamer websocket should be hosted on")
c.forceReload = fs.Bool("forceReload", false, "set to true if you wish to reload all attached browser pages on any file change")
c.cacheControl = fs.String("cacheControl", "no-cache", "set to configure the cache-control header")
c.tlsCertPath = fs.String("tlsCertPath", "", "set to a path to a cert, requires tlsKeyPath to be set")
c.tlsKeyPath = fs.String("tlsKeyPath", "", "set to a path to a key, requires tlsCertPath to be set")
c.flagset = fs
return fs
}
60 changes: 60 additions & 0 deletions cmd/serve/serve_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package serve

import (
"context"
"crypto/tls"
"fmt"
"net/http"
"net/http/httptest"
Expand Down Expand Up @@ -166,4 +167,63 @@ func TestRun(t *testing.T) {
got := resp.Header.Get("Cache-Control")
testboil.FailTestIfDiff(t, got, want)
})

t.Run("it should serve with tls if cert and key is specified", func(t *testing.T) {
cmd := setup()
ctx, ctxCancel := context.WithCancel(context.Background())
t.Cleanup(ctxCancel)
testCert := testboil.CreateTestFile(t, "cert.pem")
testCert.Write([]byte(`-----BEGIN CERTIFICATE-----
MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw
DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow
EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d
7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B
5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr
BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1
NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l
Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc
6MF9+Yw1Yy0t
-----END CERTIFICATE-----`))
testKey := testboil.CreateTestFile(t, "key.pem")
testKey.Write([]byte(`-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49
AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q
EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==
-----END EC PRIVATE KEY-----`))
port := 13337
cmd.port = &port
certPath := testCert.Name()
cmd.tlsCertPath = &certPath
keyPath := testKey.Name()
cmd.tlsKeyPath = &keyPath

ready := make(chan struct{})
go func() {
close(ready)
err := cmd.Run(ctx)
if err != nil {
t.Errorf("Run returned error: %v", err)
}
}()
<-ready
time.Sleep(time.Millisecond)

// Cert above expired in 2018
transport := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
}

client := &http.Client{
Transport: transport,
}
resp, err := client.Get(fmt.Sprintf("https://localhost:%v", port))
if err != nil {
t.Fatal(err)
}
if resp.StatusCode != http.StatusOK {
t.Fatalf("expected status code: %v", resp.StatusCode)
}
})
}
2 changes: 1 addition & 1 deletion internal/wsinject/delta_streamer.ws.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ function startWebsocket() {
}

// Establish a connection with the WebSocket server
const socket = new WebSocket('ws://localhost:%v%v');
const socket = new WebSocket('ws%v://localhost:%v%v');

// Event handler for when the WebSocket connection is established
socket.addEventListener('open', function (event) {
Expand Down
10 changes: 8 additions & 2 deletions internal/wsinject/wsinject.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type Fileserver struct {
masterPath string
mirrorPath string
forceReload bool
expectTLS bool
wsPort int
wsPath string
watcher *fsnotify.Watcher
Expand All @@ -37,7 +38,7 @@ var ErrNoHeaderTagFound = errors.New("no header tag found")
const deltaStreamer = `<!-- This script has been injected by wd-41 and allows hot reloads -->
<script type="module" src="delta-streamer.js"></script>`

func NewFileServer(wsPort int, wsPath string, forceReload bool) *Fileserver {
func NewFileServer(wsPort int, wsPath string, forceReload, expectTLS bool) *Fileserver {
mirrorDir, err := os.MkdirTemp("", "wd-41_*")
if err != nil {
panic(err)
Expand All @@ -47,6 +48,7 @@ func NewFileServer(wsPort int, wsPath string, forceReload bool) *Fileserver {
mirrorPath: mirrorDir,
wsPort: wsPort,
wsPath: wsPath,
expectTLS: expectTLS,
forceReload: forceReload,
pageReloadChan: make(chan string),
wsDispatcher: sync.Map{},
Expand Down Expand Up @@ -97,9 +99,13 @@ func (fs *Fileserver) mirrorMaker(p string, info os.DirEntry, err error) error {
}

func (fs *Fileserver) writeDeltaStreamerScript() error {
tlsS := ""
if fs.expectTLS {
tlsS = "s"
}
err := os.WriteFile(
path.Join(fs.mirrorPath, "delta-streamer.js"),
[]byte(fmt.Sprintf(deltaStreamerSourceCode, fs.wsPort, fs.wsPath, fs.forceReload)),
[]byte(fmt.Sprintf(deltaStreamerSourceCode, tlsS, fs.wsPort, fs.wsPath, fs.forceReload)),
0o755)
if err != nil {
return fmt.Errorf("failed to write delta-streamer.js: %w", err)
Expand Down
4 changes: 2 additions & 2 deletions internal/wsinject/wsinject_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func Test_Setup(t *testing.T) {
}
nestedFile := path.Join(nestedDir, "nested.html")
os.WriteFile(nestedFile, []byte(mockHtml), 0o777)
fs := NewFileServer(8080, "/delta-streamer-ws.js", false)
fs := NewFileServer(8080, "/delta-streamer-ws.js", false, false)
_, err = fs.Setup(tmpDir)
if err != nil {
t.Fatalf("failed to setup: %v", err)
Expand Down Expand Up @@ -144,7 +144,7 @@ func Test_Start(t *testing.T) {
if err != nil {
t.Fatalf("failed to create temp dir: %v", err)
}
return NewFileServer(8080, "/delta-streamer-ws.js", false), testFileSystem{
return NewFileServer(8080, "/delta-streamer-ws.js", false, false), testFileSystem{
root: tmpDir,
nestedDir: nestedDir,
}
Expand Down
Loading