feat: serve DWH authentication over Unix socket
This commit is contained in:
@@ -14,6 +14,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -25,15 +26,15 @@ import (
|
||||
"github.com/aritmolab/thothii/tools/dwh-auth/internal/record"
|
||||
"github.com/aritmolab/thothii/tools/dwh-auth/internal/registry"
|
||||
"github.com/aritmolab/thothii/tools/dwh-auth/internal/securefile"
|
||||
"github.com/aritmolab/thothii/tools/dwh-auth/internal/service"
|
||||
)
|
||||
|
||||
const (
|
||||
exitOK = 0
|
||||
exitUsage = 2
|
||||
exitNotFound = 3
|
||||
exitIntegrity = 4
|
||||
maxReasonBytes = 160
|
||||
commandServeMsg = "serve is not available in this release"
|
||||
exitOK = 0
|
||||
exitUsage = 2
|
||||
exitNotFound = 3
|
||||
exitIntegrity = 4
|
||||
maxReasonBytes = 160
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -41,6 +42,7 @@ var (
|
||||
generateCredential = func() (credential.Material, error) { return credential.Generate(rand.Reader) }
|
||||
addRecord = func(store *registry.Store, value record.Record) error { return store.Add(value) }
|
||||
closeStore = func(store *registry.Store) error { return store.Close() }
|
||||
listenAndServe = service.ListenAndServe
|
||||
)
|
||||
|
||||
// Run executes one dwh-auth invocation and returns its stable process exit
|
||||
@@ -53,7 +55,7 @@ func Run(ctx context.Context, args []string, _ io.Reader, stdout, stderr io.Writ
|
||||
return exitIntegrity
|
||||
}
|
||||
if len(args) > 0 && args[0] == "serve" {
|
||||
return runServe(args[1:], stderr)
|
||||
return runServe(ctx, args[1:], stderr)
|
||||
}
|
||||
root, rest, err := parseRoot(args)
|
||||
if err != nil {
|
||||
@@ -437,17 +439,21 @@ func runCheck(ctx context.Context, root string, args []string, stdout, stderr io
|
||||
return exitOK
|
||||
}
|
||||
|
||||
func runServe(args []string, stderr io.Writer) int {
|
||||
if len(args) != 4 || args[0] != "--registry-root" || args[2] != "--socket" || !filepath.IsAbs(args[1]) || !filepath.IsAbs(args[3]) {
|
||||
func runServe(ctx context.Context, args []string, stderr io.Writer) int {
|
||||
if len(args) != 4 || args[0] != "--registry-root" || args[2] != "--socket" || !canonicalAbsolute(args[1]) || !canonicalAbsolute(args[3]) {
|
||||
writeLine(stderr, "unsafe invocation")
|
||||
return exitUsage
|
||||
}
|
||||
// The Unix-socket service is intentionally introduced by Task 4. Keeping
|
||||
// this exact grammar reserved prevents accidental plaintext or network fallback.
|
||||
writeLine(stderr, commandServeMsg)
|
||||
return exitIntegrity
|
||||
logger := log.New(stderr, "", 0)
|
||||
if err := listenAndServe(ctx, service.Config{RegistryRoot: args[1], Socket: args[3], Logger: logger}); err != nil {
|
||||
writeLine(stderr, "integrity failure")
|
||||
return exitIntegrity
|
||||
}
|
||||
return exitOK
|
||||
}
|
||||
|
||||
func canonicalAbsolute(path string) bool { return filepath.IsAbs(path) && filepath.Clean(path) == path }
|
||||
|
||||
func snapshotContainsKey(store *registry.Store, keyID string) (bool, error) {
|
||||
values, err := store.List()
|
||||
if err != nil {
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/aritmolab/thothii/tools/dwh-auth/internal/credential"
|
||||
"github.com/aritmolab/thothii/tools/dwh-auth/internal/record"
|
||||
"github.com/aritmolab/thothii/tools/dwh-auth/internal/registry"
|
||||
"github.com/aritmolab/thothii/tools/dwh-auth/internal/service"
|
||||
)
|
||||
|
||||
const sentinelSecret = "SENTINEL-DWH-SECRET-must-never-be-emitted"
|
||||
@@ -295,17 +296,29 @@ func TestMetadataAndExpiryAreValidatedBeforeKeyGeneration(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeAcceptsOnlyExactTaskFourReservation(t *testing.T) {
|
||||
func TestServeAcceptsOnlyExactCheckedServiceConfig(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
socket := filepath.Join(t.TempDir(), "verify.sock")
|
||||
oldListen := listenAndServe
|
||||
t.Cleanup(func() { listenAndServe = oldListen })
|
||||
called := false
|
||||
listenAndServe = func(ctx context.Context, config service.Config) error {
|
||||
called = true
|
||||
if ctx == nil || config.RegistryRoot != root || config.Socket != socket || config.Logger == nil {
|
||||
t.Fatalf("serve config = %#v", config)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
stdout, stderr, code := run(t, "serve", "--registry-root", root, "--socket", socket)
|
||||
if code != 4 || stdout != "" || stderr != "serve is not available in this release\n" {
|
||||
t.Fatalf("valid serve reservation = (%d, %q, %q)", code, stdout, stderr)
|
||||
if code != 0 || stdout != "" || stderr != "" || !called {
|
||||
t.Fatalf("valid serve = (%d, %q, %q), called=%v", code, stdout, stderr, called)
|
||||
}
|
||||
invalid := [][]string{
|
||||
{"serve", "--registry-root", "relative", "--socket", socket},
|
||||
{"serve", "--registry-root", root + "/.", "--socket", socket},
|
||||
{"serve", "--registry-root", root},
|
||||
{"serve", "--registry-root", root, "--socket", "relative"},
|
||||
{"serve", "--registry-root", root, "--socket", filepath.Dir(socket) + "/./verify.sock"},
|
||||
{"serve", "--registry-root", root, "--socket", socket, "--socket", socket},
|
||||
{"serve", "--socket", socket, "--registry-root", root},
|
||||
{"serve", "--registry-root", root, "--socket", socket, "--unknown", "x"},
|
||||
@@ -319,6 +332,18 @@ func TestServeAcceptsOnlyExactTaskFourReservation(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeSanitizesServiceStartupFailure(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
socket := filepath.Join(t.TempDir(), "verify.sock")
|
||||
oldListen := listenAndServe
|
||||
t.Cleanup(func() { listenAndServe = oldListen })
|
||||
listenAndServe = func(context.Context, service.Config) error { return errors.New(sentinelSecret) }
|
||||
stdout, stderr, code := run(t, "serve", "--registry-root", root, "--socket", socket)
|
||||
if code != 4 || stdout != "" || stderr != "integrity failure\n" || strings.Contains(stdout+stderr, sentinelSecret) {
|
||||
t.Fatalf("service startup failure = (%d, %q, %q)", code, stdout, stderr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateCleansOnlyWhenFailedPublicationProvesKeyAbsent(t *testing.T) {
|
||||
oldAdd, oldClose := addRecord, closeStore
|
||||
t.Cleanup(func() { addRecord, closeStore = oldAdd, oldClose })
|
||||
|
||||
Reference in New Issue
Block a user