a73x

internal/cli/ca_test.go

Ref:   Size: 3.6 KiB   History

package cli

import (
	"context"
	"encoding/json"
	"io"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"golang.org/x/crypto/ssh"
)

// A real (public) ed25519 key line — uploads are parse-validated locally.
const testUserCALine = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPZ8BFXvSU9tCz3sm5uuXG8UXsRWCkEBHYBJk8OjJgeA me@laptop\n"

func TestUploadUserCA(t *testing.T) {
	var gotPath, gotAuth, gotKey string
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		gotPath = r.URL.Path
		gotAuth = r.Header.Get("Authorization")
		body, _ := io.ReadAll(r.Body)
		var m map[string]string
		json.Unmarshal(body, &m)
		gotKey = m["public_key"]
		w.Write([]byte(`{"fingerprint":"SHA256:server-echo"}`))
	}))
	defer srv.Close()

	pub := filepath.Join(t.TempDir(), "ca.pub")
	os.WriteFile(pub, []byte(testUserCALine), 0o644)

	out, err := UploadUserCA(context.Background(), srv.URL, "tok123", "default", pub)
	if err != nil {
		t.Fatal(err)
	}
	if gotPath != "/api/v1/tenants/default/user-cas" || gotAuth != "Bearer tok123" {
		t.Errorf("request: %s %s", gotPath, gotAuth)
	}
	if gotKey != testUserCALine {
		t.Errorf("public_key = %q", gotKey)
	}
	// The summary line carries the LOCALLY computed fingerprint of the
	// uploaded key (same value the server echoes) and the tenant.
	pk, _, _, _, err := ssh.ParseAuthorizedKey([]byte(testUserCALine))
	if err != nil {
		t.Fatal(err)
	}
	if !strings.Contains(out, ssh.FingerprintSHA256(pk)) || !strings.Contains(out, "default") {
		t.Errorf("out = %q", out)
	}
}

// An empty tenant targets the tenant-less endpoint (the token names the tenant)
// and the summary line makes no tenant claim.
func TestUploadUserCATokenNamedTenant(t *testing.T) {
	var gotPath string
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		gotPath = r.URL.Path
		w.Write([]byte(`{"fingerprint":"SHA256:server-echo"}`))
	}))
	defer srv.Close()

	pub := filepath.Join(t.TempDir(), "ca.pub")
	os.WriteFile(pub, []byte(testUserCALine), 0o644)

	out, err := UploadUserCA(context.Background(), srv.URL, "tok123", "", pub)
	if err != nil {
		t.Fatal(err)
	}
	if gotPath != "/api/v1/user-cas" {
		t.Errorf("path = %q, want /api/v1/user-cas", gotPath)
	}
	if strings.Contains(out, "for tenant") {
		t.Errorf("out must not claim a tenant we did not pin: %q", out)
	}
	pk, _, _, _, err := ssh.ParseAuthorizedKey([]byte(testUserCALine))
	if err != nil {
		t.Fatal(err)
	}
	if !strings.Contains(out, ssh.FingerprintSHA256(pk)) {
		t.Errorf("out = %q", out)
	}
}

func TestUploadUserCAErrorSurfacesBody(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		http.Error(w, "tenant not found", http.StatusNotFound)
	}))
	defer srv.Close()
	pub := filepath.Join(t.TempDir(), "ca.pub")
	os.WriteFile(pub, []byte(testUserCALine), 0o644)
	_, err := UploadUserCA(context.Background(), srv.URL, "t", "nope", pub)
	if err == nil || !strings.Contains(err.Error(), "tenant not found") {
		t.Fatalf("want body in error, got %v", err)
	}
}

func TestUploadUserCARejectsGarbageLocally(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		t.Error("garbage key must be rejected before any request leaves the machine")
	}))
	defer srv.Close()
	pub := filepath.Join(t.TempDir(), "ca.pub")
	os.WriteFile(pub, []byte("not a key at all\n"), 0o644)
	_, err := UploadUserCA(context.Background(), srv.URL, "t", "default", pub)
	if err == nil || !strings.Contains(err.Error(), "not an SSH public key") {
		t.Fatalf("want local parse error, got %v", err)
	}
}