feat(server): activate projected authentication safely

This commit is contained in:
User
2026-08-22 01:01:36 +02:00
parent 903c0b4de5
commit 3d9a9f0675
32 changed files with 1837 additions and 38 deletions
+143 -2
View File
@@ -22,6 +22,7 @@ import (
"unicode/utf16"
"unicode/utf8"
"github.com/aritmolab/thothii/tools/tht/internal/authprojection"
"github.com/aritmolab/thothii/tools/tht/internal/compose"
"github.com/aritmolab/thothii/tools/tht/internal/config"
"github.com/aritmolab/thothii/tools/tht/internal/output"
@@ -46,6 +47,8 @@ var errCommandRefused = errors.New("authentication command refused")
var writeNewAuthFile = safeio.WriteCanonicalNewFile
var removeAuthFile = safeio.RemoveCanonicalPrivateRegular
var runProjectedAuthMutation = RunProjectedMutation
var publishProjectedAuthCanonical = PublishProjectedCanonical
// Run implements the host-only authentication operator surface. It accepts password bytes only
// from an echo-free terminal or a bounded private file, and never writes them to either stream.
@@ -62,27 +65,165 @@ func RunWithRunner(ctx context.Context, installation config.Installation, args [
directory := installation.AuthenticationDirectory()
switch args[0] {
case "configure":
if err := configure(directory, args[1:], stdin, stderr); err != nil {
if err := runInstallationAuthMutation(ctx, installation, func() error {
return configure(directory, args[1:], stdin, stderr)
}); err != nil {
return authFailure(stderr, authMessage(err))
}
return 0
case "publish":
if err := publishInstallationAuthentication(ctx, installation, args[1:]); err != nil {
return authFailure(stderr, authMessage(err))
}
return 0
case "status":
if installation.HasRuntimeAuthProjection() {
if err := projectedStatus(installation, args[1:], stdout); err != nil {
return authFailure(stderr, authMessage(err))
}
return 0
}
if err := status(directory, args[1:], stdout); err != nil {
return authFailure(stderr, authMessage(err))
}
return 0
case "user":
if err := user(directory, args[1:], stdin, stdout, stderr); err != nil {
if err := runInstallationUserMutation(ctx, installation, args[1:], func() error {
return user(directory, args[1:], stdin, stdout, stderr)
}); err != nil {
return authFailure(stderr, authMessage(err))
}
return 0
case "check":
if err := RequireRuntimeAuthProjectionReady(installation); err != nil {
return authFailure(stderr, authMessage(err))
}
return checkCommand(ctx, installation, args[1:], stdout, stderr, runner)
default:
return authFailure(stderr, "unknown auth subcommand")
}
}
// RuntimeAuthProjectionStatus reads only public runtime-projection metadata and compares it with
// a detached, validated snapshot of the canonical authentication store. It never publishes or
// repairs the projection.
func RuntimeAuthProjectionStatus(installation config.Installation) (ProjectionStatus, error) {
projection := installation.RuntimeAuthProjection()
if projection == nil {
return ProjectionStatus{}, errCommandRefused
}
canonical, err := loadSnapshotBytes(installation.AuthenticationDirectory())
if err != nil {
return ProjectionStatus{}, errCommandRefused
}
published, err := authprojection.Inspect(authprojection.Spec{
RuntimeRoot: projection.Directory,
UID: projection.UID,
GID: projection.GID,
})
if errors.Is(err, authprojection.ErrBlocked) {
return ProjectionStatus{
State: "blocked",
CanonicalRevision: canonical.CanonicalRevision,
Equal: false,
}, nil
}
if err != nil {
return ProjectionStatus{}, errCommandRefused
}
return ProjectionStatus{
State: published.Selector.State,
Generation: published.Snapshot.Generation,
CanonicalRevision: canonical.CanonicalRevision,
Equal: equalProjection(published, canonical),
}, nil
}
// RequireRuntimeAuthProjectionReady is the shared fail-closed pre-admission check. It is a no-op
// for an installation that does not declare a runtime projection.
func RequireRuntimeAuthProjectionReady(installation config.Installation) error {
if !installation.HasRuntimeAuthProjection() {
return nil
}
status, err := RuntimeAuthProjectionStatus(installation)
if err != nil || status.State != "ready" || !status.Equal {
return errCommandRefused
}
return nil
}
func runInstallationAuthMutation(ctx context.Context, installation config.Installation, mutate func() error) error {
projection := installation.RuntimeAuthProjection()
if projection == nil {
return mutate()
}
if err := requireProjectedAuthMutationPrivilege(); err != nil {
return errCommandRefused
}
return runProjectedAuthMutation(ctx, installation.AuthenticationDirectory(), ProjectionSpec{
RuntimeRoot: projection.Directory,
UID: projection.UID,
GID: projection.GID,
}, mutate)
}
func runInstallationUserMutation(ctx context.Context, installation config.Installation, args []string, mutate func() error) error {
if len(args) == 0 || args[0] == "list" || !installation.HasRuntimeAuthProjection() {
return mutate()
}
switch args[0] {
case "add", "set-password", "enable", "disable", "grant", "revoke", "logout-all":
return runInstallationAuthMutation(ctx, installation, mutate)
default:
return mutate()
}
}
func publishInstallationAuthentication(ctx context.Context, installation config.Installation, args []string) error {
if len(args) != 0 || !installation.HasRuntimeAuthProjection() {
return errCommandRefused
}
if err := requireProjectedAuthMutationPrivilege(); err != nil {
return errCommandRefused
}
projection := installation.RuntimeAuthProjection()
status, err := publishProjectedAuthCanonical(ctx, installation.AuthenticationDirectory(), ProjectionSpec{
RuntimeRoot: projection.Directory,
UID: projection.UID,
GID: projection.GID,
})
if err != nil || status.State != "ready" || !status.Equal {
return errCommandRefused
}
return nil
}
func projectedStatus(installation config.Installation, args []string, stdout io.Writer) error {
jsonMode := len(args) == 1 && args[0] == "--json"
if len(args) != 0 && !jsonMode {
return errCommandRefused
}
status, err := RuntimeAuthProjectionStatus(installation)
if err != nil {
return errCommandRefused
}
if jsonMode {
return json.NewEncoder(stdout).Encode(struct {
State string `json:"state"`
Generation string `json:"generation"`
CanonicalRevision string `json:"canonicalRevision"`
Equal bool `json:"equal"`
}{
State: status.State,
Generation: status.Generation,
CanonicalRevision: status.CanonicalRevision,
Equal: status.Equal,
})
}
_, err = fmt.Fprintf(stdout, "State: %s\nGeneration: %s\nCanonical revision: %s\nEqual: %t\n", status.State, status.Generation, status.CanonicalRevision, status.Equal)
return err
}
// AuthDiagnostic is the closed JSON contract emitted by the backend diagnostic command.
type AuthDiagnostic struct {
Level string `json:"level"`