Skip to content

Commit 54ea229

Browse files
feat: https server
1 parent f3195ad commit 54ea229

5 files changed

Lines changed: 43 additions & 47 deletions

File tree

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ coverage
55
*.local.toml
66
*.local.yaml
77
*.local.json
8-
TODO.md
8+
TODO.md
9+
cert

api.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package main
22

33
import (
44
"bytes"
5-
"cmp"
65
"context"
76
"crypto/subtle"
87
"encoding/json"
@@ -485,10 +484,10 @@ func toEnvKey(s string) (key string) {
485484
}
486485

487486
func paramsToEnv(r *http.Request, pathParams []string) []string {
488-
params := r.URL.Query()
489-
env := make([]string, 0, len(params)+len(pathParams))
487+
queryParams := r.URL.Query()
488+
env := make([]string, 0, len(queryParams)+len(pathParams))
490489

491-
for k, v := range params {
490+
for k, v := range queryParams {
492491
env = append(env, toEnvKey(k)+"="+strings.Join(v, " "))
493492
}
494493

@@ -516,7 +515,7 @@ func newAPIRoutes(ctx context.Context, cancelableJobs *safeMap[string, func()])
516515
mux := http.NewServeMux()
517516

518517
for _, e := range config.Endpoints {
519-
h, token := newExecHandler(ctx, e, cancelableJobs), cmp.Or(e.Token, config.Token)
518+
h, token := newExecHandler(ctx, e, cancelableJobs), config.Server.Token
520519
pattern := fmt.Sprintf(
521520
"%s %s",
522521
strings.ToUpper(e.method),

config.go

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,18 @@ import (
1313
"github.com/pelletier/go-toml/v2"
1414
)
1515

16+
type Server struct {
17+
LogLevel string `json:"log_level,omitempty" toml:"log_level,commented"`
18+
DBPath string `json:"database_path,omitempty" toml:"database_path,commented"`
19+
Token string `json:"token,omitempty" toml:"token,commented"`
20+
ListenAddr string `json:"listen_addr,omitempty" toml:"listen_addr,commented"`
21+
CertFile string `json:"cert_file,omitempty" toml:"cert_file,commented"`
22+
KeyFile string `json:"key_file,omitempty" toml:"key_file,commented"`
23+
}
24+
1625
type Config struct {
17-
LogLevel string `json:"log_level,omitempty" toml:"log_level,commented"`
18-
DBPath string `json:"database_path,omitempty" toml:"database_path,commented"`
19-
Token string `json:"token,omitempty" toml:"token,commented"`
20-
ListenAddr string `json:"listen_addr,omitempty" toml:"listen_addr,commented"`
21-
Endpoints []Endpoint `json:"endpoints,omitempty" toml:"endpoints,commented"`
26+
Server Server `json:"server,omitempty" toml:"server,commented"`
27+
Endpoints []Endpoint `json:"endpoints,omitempty" toml:"endpoints,commented"`
2228

2329
configPath string
2430
sha string
@@ -27,15 +33,19 @@ type Config struct {
2733
func (c *Config) validate() error {
2834
uid := os.Getuid()
2935

30-
if c.ListenAddr == "" {
36+
if c.Server.ListenAddr == "" {
3137
return errors.New("listen_addr must not be empty")
3238
}
3339

34-
if _, _, err := net.SplitHostPort(c.ListenAddr); err != nil {
40+
if c.Server.Token == "" {
41+
return errors.New("server token must not be empty")
42+
}
43+
44+
if _, _, err := net.SplitHostPort(c.Server.ListenAddr); err != nil {
3545
return fmt.Errorf("listen_addr must be host:port or :port: %v", err)
3646
}
3747

38-
_, err := parseLogLevel(c.LogLevel)
48+
_, err := parseLogLevel(c.Server.LogLevel)
3949
if err != nil {
4050
return fmt.Errorf("invalid log level: %v", err)
4151
}
@@ -55,8 +65,6 @@ func (c *Config) validate() error {
5565
"path", e.Path,
5666
"index", i,
5767
)
58-
} else if c.Token == "" && e.Token == "" {
59-
return fmt.Errorf("token missing for path: %q: set global token or endpoint[%d].token", e.Path, i)
6068
}
6169

6270
if _, dup := seen[e.Path]; dup {
@@ -74,7 +82,7 @@ func (c *Config) setDefaults() error {
7482
return errors.New("cannot set defaults on nil config")
7583
}
7684

77-
c.ListenAddr = cmp.Or(c.ListenAddr, defaultListenAddr)
85+
c.Server.ListenAddr = cmp.Or(c.Server.ListenAddr, defaultListenAddr)
7886

7987
return nil
8088
}
@@ -85,14 +93,10 @@ func (c *Config) redact() *Config {
8593
}
8694

8795
redacted := *c
88-
89-
if redacted.Token != "" {
90-
redacted.Token = redact
91-
}
92-
9396
redacted.Endpoints = append([]Endpoint(nil), redacted.Endpoints...)
94-
for i, e := range redacted.Endpoints {
95-
redacted.Endpoints[i] = e.redact()
97+
98+
if redacted.Server.Token != "" {
99+
redacted.Server.Token = redact
96100
}
97101

98102
return &redacted

endpoint.go

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ type resolvedEndpoint struct {
3030
type Endpoint struct {
3131
Summary string `json:"summary,omitempty" toml:"summary,commented"`
3232
Path string `json:"path,omitempty" toml:"path,commented"`
33-
Token string `json:"token,omitempty" toml:"token,commented"`
3433
Method string `json:"method,omitempty" toml:"method,commented"`
3534
Cmd []string `json:"cmd,omitempty" toml:"cmd,commented"`
3635
EnvAllowlist []string `json:"env_allowlist,omitempty" toml:"env_allowlist,commented"`
@@ -102,17 +101,6 @@ func (e *Endpoint) resolve() {
102101
}
103102
}
104103

105-
func (e *Endpoint) redact() Endpoint {
106-
if e.Token == "" {
107-
return *e
108-
}
109-
110-
redacted := *e
111-
redacted.Token = redact
112-
113-
return redacted
114-
}
115-
116104
type ExecResult struct {
117105
Stdout string `json:"stdout,omitempty"`
118106
Stderr string `json:"stderr,omitempty"`

main.go

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package main
33
import (
44
"context"
55
"crypto/sha256"
6+
"crypto/tls"
67
"encoding/hex"
78
"errors"
89
"flag"
@@ -26,7 +27,7 @@ const (
2627
defaultConfigName = ".execd.toml"
2728
defaultCacheDir = ".execd.d"
2829
defaultDBFilename = "execd.sqlite"
29-
defaultListenAddr = ":8081"
30+
defaultListenAddr = ":8443"
3031
redact = "*****"
3132
)
3233

@@ -87,14 +88,14 @@ func mustInitialize() {
8788

8889
logger.Info("config sha256", "sha", c.sha)
8990

90-
l, _ := parseLogLevel(c.LogLevel) // already validated during config parsing
91+
l, _ := parseLogLevel(c.Server.LogLevel) // already validated during config parsing
9192

9293
logger = slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: l}))
9394
logger.Info("resolved config", "path", configPath, "config", c.redact())
9495

9596
config = c
9697

97-
reqs, err := newExecDB(c.DBPath)
98+
reqs, err := newExecDB(c.Server.DBPath)
9899
if err != nil {
99100
logger.Error("exec db:", "err", err)
100101
os.Exit(1)
@@ -143,29 +144,32 @@ func main() {
143144
root.Handle("/hx/", http.StripPrefix("/hx", newHXRoutes(rr)))
144145
root.Handle("/ui/", http.StripPrefix("/ui", newUIRoutes(rr)))
145146

146-
root.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
147-
http.Redirect(w, r, "/ui/", http.StatusFound)
148-
}))
149-
150147
srv := &http.Server{
151-
Addr: config.ListenAddr,
148+
Addr: config.Server.ListenAddr,
152149
Handler: root,
153150
ReadHeaderTimeout: 10 * time.Second,
151+
TLSConfig: &tls.Config{
152+
MinVersion: tls.VersionTLS13,
153+
},
154154
}
155155

156156
lc := net.ListenConfig{}
157157

158-
l, err := lc.Listen(ctx, "tcp", config.ListenAddr)
158+
l, err := lc.Listen(ctx, "tcp", config.Server.ListenAddr)
159159
if err != nil {
160-
logger.Error("listen failed", "addr", config.ListenAddr, "err", err)
160+
logger.Error("listen failed", "addr", config.Server.ListenAddr, "err", err)
161161
os.Exit(1)
162162
}
163163

164164
logger.Info("server listening", "addr", l.Addr().String())
165165

166166
errCh := make(chan error, 1)
167167
go func(ch chan error) {
168-
ch <- srv.Serve(l)
168+
ch <- srv.ServeTLS(
169+
l,
170+
config.Server.CertFile,
171+
config.Server.KeyFile,
172+
)
169173

170174
close(ch)
171175
}(errCh)

0 commit comments

Comments
 (0)