internal/server/certificate_export_permission_test.go
Rama main · réplica de lectura del repositorio original.
// 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)
}
}