Help hiding only one named folder

1. Caddy version (caddy version):

v2.0.0 h1:pQSaIJGFluFvu8KDGDODV8u4/QRED/OPyIR+MWYYse8=

2. How I run Caddy:

caddy.service file (not API)
or
caddy start/stop/reload

a. System environment:

Raspberry Pi (Raspbian Buster Lite)

b. Command:

caddy start --config <path to file>

caddy reload --config <path to file>

c. Service/unit/compose file:

N/A

d. My complete Caddyfile or JSON config:

mydomain.duckdns.org {
	log {
		output file /var/log/caddy/portainer.log
		format console
	}
	@internal {
		remote_ip 192.168.0.0/16
	}
	handle @internal {
		reverse_proxy 127.0.0.1:9000
	}
	respond 403
}
jellyfin.mydomain.duckdns.org {
	log {
		output file /var/log/caddy/jellyfin.log
		format console
	}
	reverse_proxy 127.0.0.1:8096
}
omv.mydomain.duckdns.org {
        log {
		output file /var/log/caddy/omv.log
		format console
	}
	reverse_proxy 127.0.0.1:81
}
nas.mydomain.duckdns.org {
	log {
		output file /var/log/caddy/nas.log
		format console
	}
	root * /srv/dev-disk-by-label-HomeDrive/
	@noAccess {
		not path */RESTRICTED/*
	}
	file_server @noAccess browse {
                 hide RESTRICTED Games "TV SHOWS"
}
	encode gzip
	basicauth {
		username (hashbrown64)
	}
	@iPhone {
		not remote_ip xxx.xxx.xxx.xxx
	}
	respond @iPhone 403
}

3. The problem Iā€™m having:

I am trying to figure out the secret recipe for hiding files properly in Caddy v2. I understand that folders/files can be denied by using a ā€˜named matcherā€™ with ā€˜not pathā€™ and calling that out in the file_server directive which I am using for my ā€˜RESTRICTEDā€™ folder.

Using that method is great but any user can still see the folder (although access is denied) and I would like to have it invisible to all as well as denying permission.

I have tried many variations of using the hide module and it does work very well. My problem is I canā€™t figure out how to make it only hide one instance of a named folder and not all of them.

4. Error messages and/or full log output:

5. What I already tried:

As you see in my caddyfile I have one folder with not path and then I also hid that folder along with another called Games and one called ā€œTV SHOWSā€.

	@noAccess {
		not path */RESTRICTED/*
	}
	file_server @noAccess browse {
                 hide RESTRICTED Games "TV SHOWS"
}

It does exactly what I want it to do except all of my Games folders in every directory are hidden. I only want to hide one Games folder and thatā€™s the one in ā€¦Arcade/Games

I have tried all of these:

hide RESTRICTED /srv/dev-disk-by-label-HomeDrive/Arcade/Games "TV SHOWS"
hide RESTRICTED */srv/dev-disk-by-label-HomeDrive/Arcade/Games "TV SHOWS"
hide RESTRICTED */srv/dev-disk-by-label-HomeDrive/Arcade/Games*
hide RESTRICTED */Arcade/Games "TV SHOWS"
hide RESTRICTED /Arcade/Games "TV SHOWS"
hide RESTRICTED /Arcade/Games* "TV SHOWS"
hide RESTRICTED */Arcade/Games/* "TV SHOWS"

And probably a few other things. I have also separated the hidden folders with a semi-colon which works but still doesnā€™t allow me to hide just one directory. i.e. hide RESTRICTED ; Games ; "TV SHOWS"

Iā€™m sure thereā€™s a way to get the desired results but I just canā€™t crack the code.

Any help would be greatly appreciated.

6. Links to relevant resources:

The logic that does the hiding is here:

It uses the filepath.Match function which you can see documented here:

Iā€™m not sure what the exact answer is for you but hopefully this points you in the right direction.

1 Like

Unfortunately I donā€™t read code very well (at all). I have tried some of the option from the golang site you posted.

Hereā€™s what I know.

hide * hides everything
hide *Games hides all Games folders in all directories
hide */Games hides nothing
hide *Arcade/Games hides nothing
hide */Arcade/Games hides nothing
hide Arcade?Games hides nothing
hide Arcade/Games/ hides nothing
hide */Arcade/Games/* nada
hide */Arcade*Games/* nada
hide *Arcade*Games* nada

I have tried many more variations also with no luck. I donā€™t really need this function right now but was more experimenting with the hide module. If I get it figured out Iā€™ll update the post with the solution.

Thanks.

1 Like

Hmm. Could you try ./Arcade/Games? The dot at the front means ā€œcurrent directoryā€ which I hope is read as relative to the root defined.

Thatā€™s a no-go also. Doesnā€™t hide anything.

Okay, I confirmed that if you have a dir structure like this:

.
ā”œā”€ā”€ Arcade
ā”‚   ā””ā”€ā”€ Games
ā””ā”€ā”€ Caddyfile

And you make a GET /Arcade request, then only Games is passed to the fileHidden() function, which means any rule that involves the full path doesnā€™t work at all.

I think we would need to update the code to pass the whole path plus the filename being tested to fileHidden(). This can be tricky though. Itā€™ll take a bit of thinking to figure out the correct solution. It might also break things for existing users.

Hereā€™s a diff that should do what you need but Iā€™m not sure this is the right change to make:

diff --git a/modules/caddyhttp/fileserver/browselisting.go b/modules/caddyhttp/fileserver/browselisting.go
index 9c7c4a20..d27d70f6 100644
--- a/modules/caddyhttp/fileserver/browselisting.go
+++ b/modules/caddyhttp/fileserver/browselisting.go
@@ -18,6 +18,7 @@ import (
        "net/url"
        "os"
        "path"
+       "path/filepath"
        "sort"
        "strconv"
        "strings"
@@ -38,7 +39,7 @@ func (fsrv *FileServer) directoryListing(files []os.FileInfo, canGoUp bool, urlP
        for _, f := range files {
                name := f.Name()

-               if fileHidden(name, filesToHide) {
+               if fileHidden(filepath.Join(urlPath, name)[1:], filesToHide) {
                        continue
                }

That makes it possible to do hide Arcade/Games to only hide that one Games dir.

1 Like

To clarify, hereā€™s how file hiding works:

  • If the file to hide is only a filename (i.e. has no path separator as defined by the operating system), then only filename are compared (regardless of path).
  • The file to hide and the candidate filepath (or filename, according to above rule) is compared using a glob pattern.
  • If the glob pattern is bad, it just does a naive prefix comparison: if the file to hide prefixes the candidate file, then it is considered hidden.

Hope that helps :slight_smile:

Ok now that I understand what all of that means the question is how do I make those changes? I found the old browselisting.go file on GitHub but I canā€™t find it on my server.

I donā€™t understand how I should be making that change. I installed git and I used the git diff command with the paths you have listed but it couldnā€™t find them so Iā€™m assuming those are personal?

Is this a change you were saying could be made in the future or that I can make now and try it out? If so how do I change those lines?

Thanks

It kind of makes sense to me. I still canā€™t see why it doesnā€™t recognize a filepath. Iā€™ve tried it by using * and ? in place of the ā€˜/ā€™ but that didnā€™t help. Iā€™m guessing it has to do with the way the code is written that @francislavoie posted above. Like I said Itā€™s not something I need right now but was just trying to become more familiar with how Caddy works.

Are we sure this is the case?

At a glance, the fileHidden function is only ever called with filename or a derivative thereof. filename is a sanitised path join of root and suffix. root is the defined web root or . as fallback; suffix is the entire request URL path.

As far as I can tell, fileHidden is given the whole path every time. It does throw out the path and uses a filepath.Base if it canā€™t find any instances of a file separator, though.


@jfirestorm44,

I do note that the filepath.Match glob only catches a single path element. If you need to hide every file inside /Arcade/Games/, e.g. /Arcade/Games/foo and /Arcade/Games/bar, but NOT hide /Arcade/Games/foo/bar OR the index itself, the correct parameter should be /Arcade/Games/*.

If you want to hide /foo/Games/ and /bar/Games/ but not /foo/bar/Games/, the correct parameter should be /*/Games. As youā€™ve noted, using Games hides any directory called Games/ (or specifically you tried *Games, which would also hide /foo/barGames/).

The correct parameter should be /Arcade/Games. Note that this wonā€™t actually hide any files inside of the /Arcade/Games/ directory. If you want to hide the directory and everything in it, you probably just want a responder instead; ā€œinvisibleā€ is just a 404 response.

Yes Iā€™m 100% sure, I compiled Caddy with log statements and the fileHidden call in browselisting.go only passes each filename in the range of files in the current directory.

Oh, of course, youā€™re absolutely right.

Yeah. Your diff looks good, but if we can replicate this structure, strictly speaking:

And then call fileHidden(filename, filesToHide), weā€™d be replicating most accurately the behaviour found in staticfiles.go.

Weā€™ve got our fsrv and repl handy in directoryListing(), could just copy that logic basically straight over.

1 Like

Ok that makes sense. I was under thinking that the way it should work is if Directory is hidden then everything below it automatically becomes hidden also since you canā€™t see the top directory and therefore canā€™t access it.

Unfortunately ā€˜/Arcade/Games/ā€™ or any variation of that doesnā€™t work. I also have it set up as a ā€˜not pathā€™ so thereā€™s no access to that folder when I enable that. I just wanted the addition ability to hide it and all of its contents from snoopy people who ask questions if ever needed. Iā€™m the only one using this network right now as this is just a home project to get my feet wet in the networking realm.

Aye. Sorry to ā€œtalk over your headā€ so to speak in your thread here on the forum, but thatā€™s what @francislavoie and I have been going back-and-forth about; the logic is correct in the static file server itself (so it would never serve a hidden directory index file or a regular file if they were hidden), but seems incorrect for the directory listing functionality (i.e. what you get from browse; it seems to show them regardless unless theyā€™re just a filename, not a full path).

What I said should be correct (but isnā€™t right now), likely will be correct once a PR goes through to fix things up.

3 Likes

This topic was automatically closed after 30 days. New replies are no longer allowed.