V1: Block or allow certain paths to a backend, using Caddy proxy

1. My Caddy version (caddy version):

Version 1

2. My Caddyfile

server.company.com:443 {
	log ./logs/access.log
	#tls self_signed

	proxy / http://localhost:8888 {
		transparent
		websocket
		insecure_skip_verify 
	}
		
	proxy /login 127.0.0.1:4200 {
		transparent
	}
}

3. The problem Iā€™m having:

Okay, Iā€™m trying to get my head around how the Caddyfile works.

Iā€™m trying to setup a reverse proxy, that either blocks certain paths, or preferably whitelists allowed paths.

So that the Caddy proxy can behave differently for:

5. What I already tried:

Tried reading and searching the docs and forum. But have not yet found the entrypoint for what I want to achieve.

Any hints, please?

The list of either allowed path or blocked paths is a bit longer than 2, maybe 3-10, depending on a few things.

Long enough that Iā€™d rather not repeat it more than once. : )

Hi @vbakke,

In Caddy v1 youā€™ll need to rely on rewrite hacks in order to achieve this. Whitelist is slightly more complicated than blacklist, but theyā€™re both doable. See examples with comments:

# Proxy from whitelist
example.com {
  # Prepend with special prefix to proxy from
  rewrite {
    if {path} starts_with /foo
    if {path} starts_with /allowed
    if {path} starts_with /path
    to /proxywhitelist{uri}
  }

  # Stop malicious actors from abusing your proxy prefix
  # to get non-whitelisted requests past the whitelist
  # - this works because only one rewrite goes off, either
  # the proxy prefix or this security measure. Nobody should
  # ever be requesting your proxy prefix directly.
  rewrite /proxywhitelist {
    to /forbidden
  }
  status 403 /forbidden

  # Proxy and strip prefix to make this process transparent
  proxy /proxywhitelist 127.0.0.1 {
    without /proxywhitelist
  }
}
# Proxy except for blacklist
example.com {
  # We use status here to blacklist paths directly with 403
  # responses, but you can also use a bulk rewrite to handle
  # the blacklist with another directive if you need to,
  # just adapt the other example into a blacklist
  status 403 {
    /bar
    /blacklisted
    /denied
  }

  # Proxy everything else
  proxy / 127.0.0.1
}

In v2, this is ludicrously easier with matchers. Itā€™s hard to state just how much better this kind of scenario is is handled in v2.

2 Likes

@Whitestrake For bonus points :taco:, what would this config (approximately) look like in v2?

1 Like

v2 configuration:

# Proxy from whitelist
example.com {
  @whitelisted {
    path /foo* /allowed* /path*
  }

  reverse_proxy @whitelisted 127.0.0.1
}
# Proxy non blacklisted
example.com {
  @notblacklisted {
    not {
      path /bar* /blacklisted* /denied*
    }
  }

  reverse_proxy @notblacklisted 127.0.0.1
}

Alternately to proxying the non-blacklisted paths and leaving blacklisted responses ambiguous (probably 404s), you could remove the not and instead handle it as a blacklist by responding with a 403 (or other response as desired), then reverse proxying everything else.

4 Likes

Nice! Thank you! Thereā€™s no way I would have figured out the V1 all by myself.

V2 will be a definite choice as soon as the beta label is gone. :slight_smile:
(Yes, I know itā€™s production ready, but I just canā€™t bring myself to installing security software on clientsā€™ servers with a ā€˜betaā€™ suffix.)

Greatly appreciated that you provided solutions for both versions.

ā€“
Just a side question about how the wildcard is matching urls.

I have a ā€œfolderā€ prefix in the URL which might be blank, or a folder.

If I wanted to match:

  • /bar
  • /prefix1/bar
  • /prefix2/bar

Would /*/bar suffice? Or would that fail on /bar?
Any differences between V1 and V2 on wildcards?

In v1 there is no wildcard matching (*), per se, although there is regex matching (in v1 and v2).

You can rewrite if {path} starts_with or not_starts_with or contains etc (a full list of conditionals is here: https://caddyserver.com/v1/docs/rewrite#if-conditions).

Even with v2, Iā€™d also be wary of /*/bar. Itā€™s not limited by element - itā€™s a globular match, so it would hit /prefix1/bar, /prefix2/bar, but also /some/really/long/path/bar.

(Note also that as a globular match, /foo* will be satisfied by /foo/bar but also /foobar - take note of that if matching a specific path element is important! /foo/* might be better in some cases.)

You can use a path_regexp matcher in v2 for those ones in order to be safe when dealing with strict path elements.

2 Likes

Okay. Thank you, once again.

In my case, ā€˜barā€™ will be unique and appear only once in any legal url.

So status 404 {path} contains /bar/ might work?
But then, status doesnā€™t have conditions, do they?

Nope, status itself doesnā€™t support those conditionals. Only rewrite and redir do.

Youā€™ll need to rewrite to some path that you then use status on.

rewrite {
  if {path} contains /bar/
  to /notfound
}
status 404 /notfound

(in v2 this is a one-liner, respond */bar/* 404)

2 Likes

Thank you so much! I should have enough to tinker a bit, now.

Examples with context and explanations are just so mush more helpful than just examples.

Appreciate your help! :sunglasses:
Vegard

2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.