Add ability to disable auth and force specific creds for the web UI

This commit is contained in:
David Dworken 2024-02-19 11:14:32 -08:00
parent 57022d522f
commit 3baba33d70
No known key found for this signature in database
3 changed files with 35 additions and 13 deletions

View File

@ -2971,14 +2971,14 @@ func TestWebUi(t *testing.T) {
tester.RunInteractiveShell(t, `echo foobar`) tester.RunInteractiveShell(t, `echo foobar`)
// Start the server // Start the server
require.NoError(t, tester.RunInteractiveShellBackground(t, `hishtory start-web-ui`)) require.NoError(t, tester.RunInteractiveShellBackground(t, `hishtory start-web-ui --force-creds hishtory:my_password`))
time.Sleep(time.Second) time.Sleep(time.Second)
defer tester.RunInteractiveShell(t, `killall hishtory`) defer tester.RunInteractiveShell(t, `killall hishtory`)
// And check that the server seems to be returning valid data // And check that the server seems to be returning valid data
req, err := http.NewRequest("GET", "http://localhost:8000?q=foobar", nil) req, err := http.NewRequest("GET", "http://localhost:8000?q=foobar", nil)
require.NoError(t, err) require.NoError(t, err)
req.SetBasicAuth("hishtory", hctx.GetConf(hctx.MakeContext()).UserSecret) req.SetBasicAuth("hishtory", "my_password")
resp, err := http.DefaultClient.Do(req) resp, err := http.DefaultClient.Do(req)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 200, resp.StatusCode) require.Equal(t, 200, resp.StatusCode)

View File

@ -1,7 +1,9 @@
package cmd package cmd
import ( import (
"fmt"
"os" "os"
"strings"
"github.com/ddworken/hishtory/client/hctx" "github.com/ddworken/hishtory/client/hctx"
"github.com/ddworken/hishtory/client/lib" "github.com/ddworken/hishtory/client/lib"
@ -9,15 +11,34 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var disableAuth *bool
var forceCreds *string
var webUiCmd = &cobra.Command{ var webUiCmd = &cobra.Command{
Use: "start-web-ui", Use: "start-web-ui",
Short: "Serve a basic web UI for interacting with your shell history", Short: "Serve a basic web UI for interacting with your shell history",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
lib.CheckFatalError(webui.StartWebUiServer(hctx.MakeContext())) overridenUsername := ""
overridenPassword := ""
if *forceCreds != "" {
if strings.Contains(*forceCreds, ":") {
splitCreds := strings.SplitN(*forceCreds, ":", 2)
overridenUsername = splitCreds[0]
overridenPassword = splitCreds[1]
} else {
lib.CheckFatalError(fmt.Errorf("--force-creds=%#v doesn't contain a colon to delimit username and password", *forceCreds))
}
}
if *disableAuth && *forceCreds != "" {
lib.CheckFatalError(fmt.Errorf("cannot specify both --disable-auth and --force-creds"))
}
lib.CheckFatalError(webui.StartWebUiServer(hctx.MakeContext(), *disableAuth, overridenUsername, overridenPassword))
os.Exit(1) os.Exit(1)
}, },
} }
func init() { func init() {
rootCmd.AddCommand(webUiCmd) rootCmd.AddCommand(webUiCmd)
disableAuth = webUiCmd.Flags().Bool("disable-auth", false, "Disable authentication for the Web UI (Warning: This means your entire shell history will be accessible from the local web server)")
forceCreds = webUiCmd.Flags().String("force-creds", "", "Specify the credentials to use for basic auth in the form `user:password`")
} }

View File

@ -8,7 +8,6 @@ import (
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
"os"
"html/template" "html/template"
@ -127,19 +126,21 @@ func secureStringEquals(s1, s2 string) bool {
return subtle.ConstantTimeCompare([]byte(s1), []byte(s2)) == 1 return subtle.ConstantTimeCompare([]byte(s1), []byte(s2)) == 1
} }
func StartWebUiServer(ctx context.Context) error { func StartWebUiServer(ctx context.Context, disableAuth bool, overridenUsername, overridenPassword string) error {
username := "hishtory" username := "hishtory"
// Note that uuid.NewRandom() uses crypto/rand and returns a UUID with 122 bits of security // Note that uuid.NewRandom() uses crypto/rand and returns a UUID with 122 bits of security
password := uuid.Must(uuid.NewRandom()).String() password := uuid.Must(uuid.NewRandom()).String()
if os.Getenv("HISHTORY_TEST") != "" { if overridenUsername != "" && overridenPassword != "" {
// For testing, we also support having the password be the secret key. This is still mostly secure, but username = overridenUsername
// it has the risk of the secret key being exposed over HTTP. It also means that the password doesn't password = overridenPassword
// rotate with each server instance. This is why we don't prefer this normally, but as a test-only method
// this is still plenty secure.
password = hctx.GetConf(ctx).UserSecret
} }
http.Handle("/", withBasicAuth(username, password)(http.HandlerFunc(webuiHandler))) wba := withBasicAuth(username, password)
http.Handle("/htmx/results-table", withBasicAuth(username, password)(http.HandlerFunc(htmx_resultsTable))) if disableAuth {
// No-op wrapper that doesn't enforce auth
wba = func(h http.Handler) http.Handler { return h }
}
http.Handle("/", wba(http.HandlerFunc(webuiHandler)))
http.Handle("/htmx/results-table", wba(http.HandlerFunc(htmx_resultsTable)))
server := http.Server{ server := http.Server{
BaseContext: func(l net.Listener) context.Context { return ctx }, BaseContext: func(l net.Listener) context.Context { return ctx },