Skip to content
This repository has been archived by the owner on Apr 3, 2024. It is now read-only.

Commit

Permalink
Allow web UI to be configured when using mTLS in API (#138)
Browse files Browse the repository at this point in the history
  • Loading branch information
tomcz authored Sep 24, 2022
1 parent 9a56995 commit 06b1f4a
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 16 deletions.
17 changes: 16 additions & 1 deletion cmd/temporalite/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const (
uiPortFlag = "ui-port"
headlessFlag = "headless"
ipFlag = "ip"
uiIPFlag = "ui-ip"
logFormatFlag = "log-format"
logLevelFlag = "log-level"
namespaceFlag = "namespace"
Expand Down Expand Up @@ -123,6 +124,11 @@ func buildCLI() *cli.App {
EnvVars: nil,
Value: "127.0.0.1",
},
&cli.StringFlag{
Name: uiIPFlag,
Usage: `IPv4 address to bind the web UI to instead of localhost`,
DefaultText: "same as --ip (eg. 127.0.0.1)",
},
&cli.StringFlag{
Name: logFormatFlag,
Usage: `customize the log formatting (allowed: ["json" "pretty"])`,
Expand Down Expand Up @@ -194,12 +200,17 @@ func buildCLI() *cli.App {
serverPort = c.Int(portFlag)
metricsPort = c.Int(metricsPortFlag)
uiPort = serverPort + 1000
uiIP = ip
)

if c.IsSet(uiPortFlag) {
uiPort = c.Int(uiPortFlag)
}

if c.IsSet(uiIPFlag) {
uiIP = c.String(uiIPFlag)
}

pragmas, err := getPragmaMap(c.StringSlice(pragmaFlag))
if err != nil {
return err
Expand Down Expand Up @@ -232,7 +243,11 @@ func buildCLI() *cli.App {
temporalite.WithBaseConfig(baseConfig),
}
if !c.Bool(headlessFlag) {
opt := newUIOption(fmt.Sprintf(":%d", c.Int(portFlag)), ip, uiPort)
frontendAddr := fmt.Sprintf("%s:%d", ip, serverPort)
opt, err := newUIOption(frontendAddr, uiIP, uiPort, c.String(configFlag))
if err != nil {
return err
}
if opt != nil {
opts = append(opts, opt)
}
Expand Down
43 changes: 43 additions & 0 deletions cmd/temporalite/mtls_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,12 @@ import (
"context"
"crypto/tls"
"crypto/x509"
"io"
"net/http"
"os"
"path/filepath"
"runtime"
"runtime/debug"
"strconv"
"testing"
"text/template"
Expand Down Expand Up @@ -67,6 +70,17 @@ func TestMTLSConfig(t *testing.T) {
} else if err = os.WriteFile(filepath.Join(confDir, "temporalite.yaml"), buf.Bytes(), 0644); err != nil {
t.Fatal(err)
}
buf.Reset()
tmpl, err = template.New("temporalite-ui.yaml.template").
Funcs(template.FuncMap{"qualified": func(s string) string { return strconv.Quote(filepath.Join(mtlsDir, s)) }}).
ParseFiles(filepath.Join(mtlsDir, "temporalite-ui.yaml.template"))
if err != nil {
t.Fatal(err)
} else if err = tmpl.Execute(&buf, nil); err != nil {
t.Fatal(err)
} else if err = os.WriteFile(filepath.Join(confDir, "temporalite-ui.yaml"), buf.Bytes(), 0644); err != nil {
t.Fatal(err)
}

// Run ephemerally using temp config
args := []string{
Expand Down Expand Up @@ -130,4 +144,33 @@ func TestMTLSConfig(t *testing.T) {
} else if resp.NamespaceInfo.State != enums.NAMESPACE_STATE_REGISTERED {
t.Fatalf("Bad state: %v", resp.NamespaceInfo.State)
}

if !isUIPresent() {
t.Log("headless build detected, not testing temporal-ui mTLS")
return
}

// Pretend to be a browser to invoke the UI API
res, err := http.Get("http://localhost:11233/api/v1/namespaces?")
if err != nil {
t.Fatal(err)
}
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
if err != nil {
t.Fatal(err)
}
if res.StatusCode != http.StatusOK {
t.Fatalf("Unexpected response %s, with body %s", res.Status, string(body))
}
}

func isUIPresent() bool {
info, _ := debug.ReadBuildInfo()
for _, dep := range info.Deps {
if dep.Path == uiServerModule {
return true
}
}
return false
}
5 changes: 5 additions & 0 deletions cmd/temporalite/testdata/temporalite-ui.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
tls:
caFile: dist/rootCA.pem
certFile: dist/client.pem
keyFile: dist/client-key.pem
serverName: local.dev
38 changes: 27 additions & 11 deletions cmd/temporalite/ui.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,45 @@

package main

// This file should be the only one to import ui-server packages.
// This is to avoid embedding the UI's static assets in the binary when the `headless` build tag is enabled.
import (
// This file should be the only one to import ui-server packages.
// This is to avoid embedding the UI's static assets in the binary when the `headless` build tag is enabled.
"strings"

provider "github.com/temporalio/ui-server/v2/plugins/fs_config_provider"
uiserver "github.com/temporalio/ui-server/v2/server"
uiconfig "github.com/temporalio/ui-server/v2/server/config"
uiserveroptions "github.com/temporalio/ui-server/v2/server/server_options"

"github.com/temporalio/temporalite"
)

func newUIOption(frontendAddr string, uiIP string, uiPort int) temporalite.ServerOption {
return temporalite.WithUI(uiserver.NewServer(uiserveroptions.WithConfigProvider(newUIConfig(
func newUIOption(frontendAddr string, uiIP string, uiPort int, configDir string) (temporalite.ServerOption, error) {
cfg, err := newUIConfig(
frontendAddr,
uiIP,
uiPort,
))))
configDir,
)
if err != nil {
return nil, err
}
return temporalite.WithUI(uiserver.NewServer(uiserveroptions.WithConfigProvider(cfg))), nil
}

func newUIConfig(frontendAddr string, uiIP string, uiPort int) *uiconfig.Config {
return &uiconfig.Config{
TemporalGRPCAddress: frontendAddr,
Host: uiIP,
Port: uiPort,
EnableUI: true,
func newUIConfig(frontendAddr string, uiIP string, uiPort int, configDir string) (*uiconfig.Config, error) {
cfg := &uiconfig.Config{
Host: uiIP,
Port: uiPort,
}
if configDir != "" {
if err := provider.Load(configDir, cfg, "temporalite-ui"); err != nil {
if !strings.HasPrefix(err.Error(), "no config files found") {
return nil, err
}
}
}
cfg.TemporalGRPCAddress = frontendAddr
cfg.EnableUI = true
return cfg, nil
}
4 changes: 2 additions & 2 deletions cmd/temporalite/ui_disabled.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ package main

import "github.com/temporalio/temporalite"

func newUIOption(frontendAddr string, uiIP string, uiPort int) temporalite.ServerOption {
return nil
func newUIOption(frontendAddr string, uiIP string, uiPort int, configDir string) (temporalite.ServerOption, error) {
return nil, nil
}
33 changes: 31 additions & 2 deletions cmd/temporalite/ui_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,37 @@ func TestHasUIServerDependency(t *testing.T) {
}

func TestNewUIConfig(t *testing.T) {
cfg := newUIConfig("localhost:7233", "localhost", 8233)
if err := cfg.Validate(); err != nil {
cfg, err := newUIConfig("localhost:7233", "localhost", 8233, "")
if err != nil {
t.Errorf("cannot create config: %s", err)
return
}
if err = cfg.Validate(); err != nil {
t.Errorf("config not valid: %s", err)
}
}

func TestNewUIConfigWithMissingConfigFile(t *testing.T) {
cfg, err := newUIConfig("localhost:7233", "localhost", 8233, "wibble")
if err != nil {
t.Errorf("cannot create config: %s", err)
return
}
if err = cfg.Validate(); err != nil {
t.Errorf("config not valid: %s", err)
}
}

func TestNewUIConfigWithPresentConfigFile(t *testing.T) {
cfg, err := newUIConfig("localhost:7233", "localhost", 8233, "testdata")
if err != nil {
t.Errorf("cannot create config: %s", err)
return
}
if err = cfg.Validate(); err != nil {
t.Errorf("config not valid: %s", err)
}
if cfg.TLS.ServerName != "local.dev" {
t.Errorf("did not load expected config file")
}
}
4 changes: 4 additions & 0 deletions internal/examples/mtls/temporalite-ui.yaml.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
tls:
caFile: {{"server-ca-cert.pem" | qualified}}
certFile: {{"client-cert.pem" | qualified}}
keyFile: {{"client-key.pem" | qualified}}

0 comments on commit 06b1f4a

Please sign in to comment.