How to stop remote access

1. Caddy version (caddy version): v2

2. How I run Caddy:

I used the installation method from the website. I use caddy run or caddy start.

a. System environment:

Raspberry Pi 4 4GB RAM (Raspbian Buster Lite)
OpenMediaVault 5
Caddy v2
Dockers: Poratiner and jellyfin
Used as home NAS

b. Command:

Not sure what command I need to use

paste command here

c. Service/unit/compose file:

Docker and again not sure what command to use

paste full file contents here

d. My complete Caddyfile or JSON config:

mydomain1.duckdns.org {
reverse_proxy 127.0.0.1:9000
}

mydomain2.duckdns.org {
reverse_proxy 127.0.0.1:8096
}

Could this be written under one domain?

3. The problem I’m having:

I would like to use Caddy to secure my access to Portaineer even while on my own network. The way I have the file set up right now also allows access when not on my network which I don’t want. (Primary problem right now)

Honestly I would like to secure my entire network including access to my router which currently I access not secured. I there a global option for accessing anything on my NAS to be over https? (secondary problem)

4. Error messages and/or full log output:

No errors

5. What I already tried:

Looking in Portainer for an option to disable access remotely.

6. Links to relevant resources:

Thank you in advance!

1 Like

Hi @jfirestorm44,

First - a note on terminology. Generally when speaking in the context of web technologies, when you say “remote”, it means “any other host than the server itself”.

So your router is “remote” from your server. Internal / external network is the terminology you’re probably looking for.


So, you’re trying to deny external access. Are you trying to allow some sites to be externally available while others are only internally available?

If so, you can - within each site that should only be internal - check to make sure the remote IP address is coming from a private IP range. This is a quick and easy way to check it’s being accessed internally.

There’s a matcher that does this - remote_ip: Request matchers (Caddyfile) — Caddy Documentation

What you want to do is set up a matcher with the CIDR ranges for your LAN, and then handle or route those requests properly. Leave unmatched requests either unrouted or respond explicitly with some kind of denial, like a 403 Forbidden.

example.com {
  @internal {
    remote_ip 192.168.0.0/16
  }
  handle @internal {
    reverse_proxy 127.0.0.1:9000
  }
  respond 403
}
2 Likes

I appreciate you hitting me with some knowledge. I have a long ways to go when it comes to learning this networking stuff.

I did see that in the docs but unfortunately I can’t seem to figure out how to actually put them to use in the Caddyfile. I find a lot of threads on things I’m looking for but they all seem to be v1 of Caddy and I’m just not at that level of knowing how to make it fit into v2.

I just did a quick search for the handle directive to read more about it. Can you point me to where I can read more about @internal? I can’t seem to find anything on that one.

Additionally I’ve seen other threads that you’ve helped on for Caddy v1 in regards to getting multiple ports to work under one domain name.

I’ve tried:

mydomain.duckdns.org {
reverse_proxy /app1 127.0.0.1:9000
reverse_proxy /app2 127.0.0.1:8096
}

I’ve also done it with /app1/ and /app1/*. Only the first app works though. The second will not load. Is this going to require a sub-subdomain? How exactly do I do that if so?

Thank you again for the assist.

Sorry if this is turning into a coding lesson 101. I did some more digging on the @internal and found the @ symbol having to do with decorators in python. If that is the same as we are talking about here then I’ll read more on it.

Also the code provided does return a 403 error when accessing via mydomain.duckdns.org but shouldn’t I be allowed to access it within my home network? Once I turn off the wifi and try to access it externally then I should get a 403 error correct?

Nope, that’s totally different. @ is how we define named matchers in the Caddyfile: Request matchers (Caddyfile) — Caddy Documentation

It’s basically just a cute way of referring to a class of requests that match some qualifications.

1 Like

Ok I think I get it. It could as easily say @tacos and do the same function. So with going by what @Whitestrake provided He essentially said @internal=remote_ip 192.168.0.0/16. Then below that he had to use ‘handle’ IOT call out the @internal and then providede @internal a directive, in this case reverse_proxy.

Now the part that got me more excited than figuring any of that out (provided I understand it right) the last part which I remembered from the tutorial. Instead of respond 403 I changed it to “You are not authorized to be here!” and it worked.

Now how does using that over ‘respond 403’ change anything in the realm of security?

Also how do I make it allow me to access it on my internal network over https but give a 403 when external? Right now I get 403 while on my laptop connected to wifi.

I also figured out the sub-subdomain stuff while searching for answers. Here is the current file

mysubdomain.duckdns.org {
@internal {
remote_ip 192.168.0.0/16
}
handle @internal {
reverse_proxy 127.0.0.1:9000
}
respond “You are not authorized to be here!”
}
jellyfin.mysubdomain.duckdns.org {
reverse_proxy 127.0.0.1:8096
}

So now I can access jellyfin under the same DNS as Portainer but like I said above Portainer won’t let me access it even internally unless I use the local address 192.168.

No change, really! Might be strictly technically confusing, though, because by omitting a status, the respond module defaults to Status 200. So your body says “go away!” but your server is telling the client “good request, here’s what you asked for (Status 200)!”

So, technically the response that closest matches what you’ve written in English would be Status 401 (Unauthorized). But again, technically speaking that’s not the ideal status to give a client, because it implies that with some form of authentication (such as an Authorization header) the client could be authorized to access the resource!

So - looping back around, because there’s no way for an external client to become authorized, the best status to send is 403 - Forbidden. (And writing that in English might not go awry - e.g. “Access to this resource is forbidden from external networks!” or something.)

Anyway, to set both a body AND a status, just specify the status last, e.g.: respond "Go away!" 403

What IP address does your laptop get when it’s on wifi?

1 Like

Looking at my router everything is 192.168.1.x. I tried from my phone also and get the 403 error also.

Throw log in your Caddyfile, run Caddy, make a request from your laptop, and then post the log line you get?

FYI, some quick testing:

➜ cat Caddyfile
http://:8080
log
@internal {
  remote_ip 192.168.0.0/16
}
handle @internal {
  respond "Internal!" 200
}
respond "External!" 403

~/Projects/test
➜ curl --ipv4 192.168.0.101:8080
Internal!⏎

~/Projects/test
➜ curl --ipv4 localhost:8080
External!⏎

And the corresponding log outputs:

2020/05/11 23:56:54.484 INFO http.log.access handled request {"request": {"method": "GET", "uri": "/", "proto": "HTTP/1.1", "remote_addr": "192.168.0.101:56345", "host": "192.168.0.101:8080", "headers": {"User-Agent": ["curl/7.64.1"], "Accept": ["*/*"]}}, "common_log": "192.168.0.101 - - [12/May/2020:09:56:54 +1000] \"GET / HTTP/1.1\" 200 9", "duration": 0.000013904, "size": 9, "status": 200, "resp_headers": {"Server": ["Caddy"], "Content-Type": []}}

2020/05/11 23:57:13.981 ERROR http.log.access handled request {"request": {"method": "GET", "uri": "/", "proto": "HTTP/1.1", "remote_addr": "127.0.0.1:56348", "host": "localhost:8080", "headers": {"User-Agent": ["curl/7.64.1"], "Accept": ["*/*"]}}, "common_log": "127.0.0.1 - - [12/May/2020:09:57:13 +1000] \"GET / HTTP/1.1\" 403 9", "duration": 0.000022953, "size": 9, "status": 403, "resp_headers": {"Server": ["Caddy"], "Content-Type": []}}

So at least I’m quite confident the syntax is correct, something else must be going on.

1 Like

Where do I pull the log from?

When you use caddy run, it should run in the foreground of your terminal and you should be able to view the logs as they appear.

Not saure I did this right but here’s what I have.

pi@raspberrypi:/usr/local $ sudo caddy run --config /srv/dev-disk-by-label-HomeD rive/“config files”/Caddyfile2
2020/05/12 00:25:45.112 INFO using provided configuration {“config_file”: “/srv/dev-disk-by-label-HomeDrive/config files/Caddyfile2”, “config_adapter”: “” }
2020/05/12 00:25:45.124 INFO admin admin endpoint started {“address”: “tcp /localhost:2019”, “enforce_origin”: false, “origins”: [“localhost:2019”, “[::1]: 2019”, “127.0.0.1:2019”]}
2020/05/11 20:25:45 [INFO][cache:0x3ffa080] Started certificate maintenance rout ine
2020/05/12 00:25:45.129 INFO http server is listening only on the HTTPS po rt but has no TLS connection policies; adding one to enable TLS {“server_name”: “srv0”, “https_port”: 443}
2020/05/12 00:25:45.132 INFO http enabling automatic HTTP->HTTPS redirects {“server_name”: “srv0”}
2020/05/12 00:25:45.142 INFO tls cleaned up storage units
2020/05/12 00:25:45.142 INFO http enabling automatic TLS certificate manag ement {“domains”: [“jellyfin.redacted.duckdns.org”, “redacted.duckdns.org”]}
2020/05/12 00:25:45.150 INFO autosaved config {“file”: “/root/.config/ caddy/autosave.json”}
2020/05/12 00:25:45.150 INFO serving initial configuration
pi@raspberrypi:/usr/local $ sudo caddy run --config /srv/dev-disk-by-label-HomeD rive/“config files”/Caddyfile2
2020/05/12 00:25:45.112 INFO using provided configuration {“config_file”: “/srv/dev-disk-by-label-HomeDrive/config files/Caddyfile2”, “config_adapter”: “” }
2020/05/12 00:25:45.124 INFO admin admin endpoint started {“address”: “tcp /localhost:2019”, “enforce_origin”: false, “origins”: [“localhost:2019”, “[::1]: 2019”, “127.0.0.1:2019”]}
2020/05/11 20:25:45 [INFO][cache:0x3ffa080] Started certificate maintenance rout ine
2020/05/12 00:25:45.129 INFO http server is listening only on the HTTPS po rt but has no TLS connection policies; adding one to enable TLS {“server_name”: “srv0”, “https_port”: 443}
2020/05/12 00:25:45.132 INFO http enabling automatic HTTP->HTTPS redirects {“server_name”: “srv0”}
2020/05/12 00:25:45.142 INFO tls cleaned up storage units

Here’s where I put the log in:

mydomain.duckdns.org {
log
@internal {
remote_ip 192.168.0.0/16
}
handle @internal {
reverse_proxy 127.0.0.1:9000
}
respond “You are not authorized to be here!”
}
jellyfin.mydomain.duckdns.org {
reverse_proxy 127.0.0.1:8096
}

Yep, that’s all the logs from the startup process.

Now make the request to Caddy from your laptop and a new line should appear. That’s what we’re looking for.

2020/05/12 00:33:12.847 INFO http.log.access handled request {“request”: {“method”: “GET”, “uri”: “/”, “proto”: “HTTP/2.0”, “remote_addr”: “72.218.54.194:53269”, “host”: “redacted.duckdns.org”, “headers”: {“Upgrade-Insecure-Requests”: [“1”], “User-Agent”: [“Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36”], “Sec-Fetch-User”: ["?1"], “Sec-Fetch-Dest”: [“document”], “Accept-Language”: [“en-US,en;q=0.9”], “Cookie”: [“portainer.datatable_text_filter_home_endpoints=; portainer.datatable_settings_containers=%7B%22open%22%3Afalse%2C%22repeater%22%3A%7B%22autoRefresh%22%3Afalse%2C%22refreshRate%22%3A%2230%22%7D%2C%22truncateContainerName%22%3Atrue%2C%22containerNameTruncateSize%22%3A32%2C%22showQuickActionStats%22%3Atrue%2C%22showQuickActionLogs%22%3Atrue%2C%22showQuickActionExec%22%3Atrue%2C%22showQuickActionInspect%22%3Atrue%2C%22showQuickActionAttach%22%3Afalse%7D; portainer.datatable_settings_container-networks=%7B%22open%22%3Afalse%2C%22repeater%22%3A%7B%22autoRefresh%22%3Afalse%2C%22refreshRate%22%3A%2230%22%7D%7D; portainer.datatable_settings_volumes=%7B%22open%22%3Afalse%2C%22repeater%22%3A%7B%22autoRefresh%22%3Afalse%2C%22refreshRate%22%3A%2230%22%7D%7D; portainer.datatable_settings_stacks=%7B%22open%22%3Afalse%2C%22repeater%22%3A%7B%22autoRefresh%22%3Afalse%2C%22refreshRate%22%3A%2230%22%7D%7D; portainer.datatable_settings_registries=%7B%22open%22%3Afalse%2C%22repeater%22%3A%7B%22autoRefresh%22%3Afalse%2C%22refreshRate%22%3A%2230%22%7D%7D; portainer.datatable_settings_users=%7B%22open%22%3Afalse%2C%22repeater%22%3A%7B%22autoRefresh%22%3Afalse%2C%22refreshRate%22%3A%2230%22%7D%7D; portainer.LOGIN_STATE_UUID=a2719de1-f247-462e-8dac-f2423ffd1f33; portainer.datatable_settings_images=%7B%22open%22%3Afalse%2C%22repeater%22%3A%7B%22autoRefresh%22%3Afalse%2C%22refreshRate%22%3A%2230%22%7D%7D; portainer.datatable_text_filter_templates=; portainer.datatable_settings_stack-containers=%7B%22open%22%3Afalse%2C%22repeater%22%3A%7B%22autoRefresh%22%3Afalse%2C%22refreshRate%22%3A%2230%22%7D%2C%22truncateContainerName%22%3Atrue%2C%22containerNameTruncateSize%22%3A32%2C%22showQuickActionStats%22%3Atrue%2C%22showQuickActionLogs%22%3Atrue%2C%22showQuickActionExec%22%3Atrue%2C%22showQuickActionInspect%22%3Atrue%2C%22showQuickActionAttach%22%3Afalse%7D; portainer.datatable_settings_networks=%7B%22open%22%3Afalse%2C%22repeater%22%3A%7B%22autoRefresh%22%3Afalse%2C%22refreshRate%22%3A%2230%22%7D%7D”], “Cache-Control”: [“max-age=0”], “Accept”: [“text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.9”], “Sec-Fetch-Site”: [“none”], “Sec-Fetch-Mode”: [“navigate”], “Accept-Encoding”: [“gzip, deflate, br”]}, “tls”: {“resumed”: false, “version”: 772, “ciphersuite”: 4867, “proto”: “h2”, “proto_mutual”: true, “server_name”: “redacted.duckdns.org”}}, “common_log”: “72.218.54.194 - - [11/May/2020:20:33:12 -0400] “GET / HTTP/2.0” 200 34”, “duration”: 0.000083535, “size”: 34, “status”: 200, “resp_headers”: {“Server”: [“Caddy”], “Content-Type”: []}}
2020/05/12 00:33:12.944 INFO http.log.access handled request {“request”: {“method”: “GET”, “uri”: “/favicon.ico”, “proto”: “HTTP/2.0”, “remote_addr”: “72.218.54.194:53269”, “host”: “redacted.duckdns.org”, “headers”: {“Cookie”: [“portainer.datatable_text_filter_home_endpoints=; portainer.datatable_settings_containers=%7B%22open%22%3Afalse%2C%22repeater%22%3A%7B%22autoRefresh%22%3Afalse%2C%22refreshRate%22%3A%2230%22%7D%2C%22truncateContainerName%22%3Atrue%2C%22containerNameTruncateSize%22%3A32%2C%22showQuickActionStats%22%3Atrue%2C%22showQuickActionLogs%22%3Atrue%2C%22showQuickActionExec%22%3Atrue%2C%22showQuickActionInspect%22%3Atrue%2C%22showQuickActionAttach%22%3Afalse%7D; portainer.datatable_settings_container-networks=%7B%22open%22%3Afalse%2C%22repeater%22%3A%7B%22autoRefresh%22%3Afalse%2C%22refreshRate%22%3A%2230%22%7D%7D; portainer.datatable_settings_volumes=%7B%22open%22%3Afalse%2C%22repeater%22%3A%7B%22autoRefresh%22%3Afalse%2C%22refreshRate%22%3A%2230%22%7D%7D; portainer.datatable_settings_stacks=%7B%22open%22%3Afalse%2C%22repeater%22%3A%7B%22autoRefresh%22%3Afalse%2C%22refreshRate%22%3A%2230%22%7D%7D; portainer.datatable_settings_registries=%7B%22open%22%3Afalse%2C%22repeater%22%3A%7B%22autoRefresh%22%3Afalse%2C%22refreshRate%22%3A%2230%22%7D%7D; portainer.datatable_settings_users=%7B%22open%22%3Afalse%2C%22repeater%22%3A%7B%22autoRefresh%22%3Afalse%2C%22refreshRate%22%3A%2230%22%7D%7D; portainer.LOGIN_STATE_UUID=a2719de1-f247-462e-8dac-f2423ffd1f33; portainer.datatable_settings_images=%7B%22open%22%3Afalse%2C%22repeater%22%3A%7B%22autoRefresh%22%3Afalse%2C%22refreshRate%22%3A%2230%22%7D%7D; portainer.datatable_text_filter_templates=; portainer.datatable_settings_stack-containers=%7B%22open%22%3Afalse%2C%22repeater%22%3A%7B%22autoRefresh%22%3Afalse%2C%22refreshRate%22%3A%2230%22%7D%2C%22truncateContainerName%22%3Atrue%2C%22containerNameTruncateSize%22%3A32%2C%22showQuickActionStats%22%3Atrue%2C%22showQuickActionLogs%22%3Atrue%2C%22showQuickActionExec%22%3Atrue%2C%22showQuickActionInspect%22%3Atrue%2C%22showQuickActionAttach%22%3Afalse%7D; portainer.datatable_settings_networks=%7B%22open%22%3Afalse%2C%22repeater%22%3A%7B%22autoRefresh%22%3Afalse%2C%22refreshRate%22%3A%2230%22%7D%7D”], “Pragma”: [“no-cache”], “User-Agent”: [“Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36”], “Accept”: [“image/webp,image/apng,image/,/*;q=0.8”], “Referer”: [“https://redacted.duckdns.org/”], “Accept-Encoding”: [“gzip, deflate, br”], “Accept-Language”: [“en-US,en;q=0.9”], “Cache-Control”: [“no-cache”], “Sec-Fetch-Site”: [“same-origin”], “Sec-Fetch-Mode”: [“no-cors”], “Sec-Fetch-Dest”: [“empty”]}, “tls”: {“resumed”: false, “version”: 772, “ciphersuite”: 4867, “proto”: “h2”, “proto_mutual”: true, “server_name”: “redacted.duckdns.org”}}, “common_log”: “72.218.54.194 - - [11/May/2020:20:33:12 -0400] “GET /favicon.ico HTTP/2.0” 200 34”, “duration”: 0.000058629, “size”: 34, “status”: 200, “resp_headers”: {“Server”: [“Caddy”], “Content-Type”: []}}

“remote_addr”: “72.218.54.194:53269”

For some reason, your laptop’s requests are most definitely not coming through the LAN.

That’s an external IP address. (Also, seriously consider putting 403 on the end of your respond directive so that it’s easy to pick out that it is, in fact, a rejection and not OK i.e. “status”: 200)

Well that’s weird. It’s connected to my wifi and doesn’t have any kind of it own network LTE etc.

My phone is also connected to the wifi and doing the same thing.

Ok I’ll throw the 403 on it

Here’s the ipconfig:
Windows IP Configuration

Wireless LAN adapter Wi-Fi:

Connection-specific DNS Suffix . :
Link-local IPv6 Address . . . . . : fe80::fd85:3311:3cb5:b106%3
IPv4 Address. . . . . . . . . . . : 192.168.1.18
Subnet Mask . . . . . . . . . . . : 255.255.255.0
Default Gateway . . . . . . . . . : 192.168.1.1

Ethernet adapter Bluetooth Network Connection:

Media State . . . . . . . . . . . : Media disconnected
Connection-specific DNS Suffix . :

I think you’ve got hairpin NAT, and your request to DuckDNS is going out to your router and looping back in from your own external IP address.

You will need split DNS to ensure that your laptop resolves your DuckDNS request to your server’s internal address and goes straight to it, instead of going out to the internet.

i.e. A DNS request for duckdns.example must resolve to the LAN IP of your Pi.

Otherwise, there’s pretty much zero way to tell for sure whether a request is truly coming from the internet or whether it came from LAN and just looped out to the internet and back.

1 Like

Ok I’ll have to look into that. This would be done through my router correct?

Most likely, yes.

If it can’t be done there, things get a bit more complicated.