Ctrl K
ComunidadGuías…

internal/server/certificate_export_permission_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// SPDX-License-Identifier: Apache-2.0

package server

import (
	"io/fs"
	"os"
	"strings"
	"testing"
)

// v2.61.2 (discussion #127): Caddy's data directory is root-only (0700) and the
// CaddyUI container runs as uid 10001. Listing it failed with "permission
// denied", which was swallowed and reported as "Caddy has not stored a
// certificate for … yet (looked in /caddy-data)" — sending people looking for a
// certificate that was sitting right there.

func denyStorage(t *testing.T, dirDenied, fileDenied bool) {
	t.Helper()
	origDir, origFile := storageReadDir, storageReadFile
	t.Cleanup(func() { storageReadDir, storageReadFile = origDir, origFile })
	perm := &fs.PathError{Op: "open", Path: "x", Err: fs.ErrPermission}
	if dirDenied {
		storageReadDir = func(name string) ([]os.DirEntry, error) {
			if strings.HasSuffix(name, "/caddy/certificates") || strings.HasSuffix(name, "/certificates") {
				return nil, perm
			}
			return origDir(name)
		}
	}
	if fileDenied {
		storageReadFile = func(path string, roots []string) ([]byte, error) { return nil, perm }
	}
}

func TestUnreadableCaddyStorageIsReportedAsPermissionDeniedNotAsMissing(t *testing.T) {
	dir := t.TempDir()
	if err := os.MkdirAll(dir+"/caddy/certificates/acme-v02.api.letsencrypt.org-directory", 0o755); err != nil {
		t.Fatal(err)
	}
	for name, deny := range map[string][2]bool{
		"the certificates directory cannot be listed": {true, false},
		"a certificate file cannot be read":           {false, true},
	} {
		t.Run(name, func(t *testing.T) {
			denyStorage(t, deny[0], deny[1])
			// Put a directory where the file would be so the file branch is reached.
			_ = os.MkdirAll(dir+"/caddy/certificates/acme-v02.api.letsencrypt.org-directory/wildcard_.domain.com", 0o755)
			_, err := findStorageCertificate(dir, []string{"*.domain.com"}, []string{dir})
			if err == nil {
				t.Fatal("expected an error")
			}
			msg := err.Error()
			if strings.Contains(msg, "has not stored a certificate") {
				t.Errorf("a permission problem was reported as a missing certificate: %s", msg)
			}
			for _, want := range []string{"not allowed to read", "permission denied", `user: "0:0"`, "read-only"} {
				if !strings.Contains(msg, want) {
					t.Errorf("the message lacks %q: %s", want, msg)
				}
			}
		})
	}
}

// A genuinely missing certificate still says so — the message is only replaced
// when permission was actually the problem.
func TestMissingCertificateStillReportedAsMissing(t *testing.T) {
	dir := t.TempDir()
	_ = os.MkdirAll(dir+"/caddy/certificates/acme-v02.api.letsencrypt.org-directory", 0o755)
	_, err := findStorageCertificate(dir, []string{"*.domain.com"}, []string{dir})
	if err == nil || !strings.Contains(err.Error(), "has not stored a certificate for *.domain.com") {
		t.Errorf("a truly missing certificate got: %v", err)
	}
	if _, err := findStorageCertificate(t.TempDir()+"/nope", []string{"*.domain.com"}, nil); err == nil || !strings.Contains(err.Error(), "no Caddy certificate storage found") {
		t.Errorf("a missing data directory got: %v", err)
	}
}

func TestExportWriteErrorExplainsPermissionAndPassesOthersThrough(t *testing.T) {
	perm := &fs.PathError{Op: "mkdir", Path: "/exports/mail", Err: fs.ErrPermission}
	msg := exportWriteError(perm, "/exports/mail").Error()
	for _, want := range []string{"not allowed to write to /exports/mail", "uid", "run the caddyui container as root"} {
		if !strings.Contains(msg, want) {
			t.Errorf("write-permission message lacks %q: %s", want, msg)
		}
	}
	other := &fs.PathError{Op: "mkdir", Path: "/x", Err: fs.ErrNotExist}
	if got := exportWriteError(other, "/x"); got != error(other) {
		t.Errorf("an unrelated error was altered: %v", got)
	}
}