Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Caddy 2 #74

Merged
merged 58 commits into from
Feb 12, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
e92fe97
Begin refactor for Caddy 2
mholt Apr 20, 2020
03a7df4
Fix misuse of sync.Pool (#76)
darhwa Jun 20, 2020
8d6f47b
Support v2 Caddyfile (#79)
klzgrad Oct 2, 2020
7b5b582
General cleanup
mholt Dec 4, 2020
ebb2fce
revert TestConnectAuthWrongProbeResist
sergeyfrolov Dec 5, 2020
0c30e89
TestConnectAuthWrongProbeResist: add debug print
sergeyfrolov Dec 5, 2020
87f2f2f
Fix Probe Resist tests' output
sergeyfrolov Dec 5, 2020
9bab45d
Merge branch 'master' into caddy2
mholt Dec 6, 2020
9fe1292
Run gofmt
mholt Dec 6, 2020
509e7ec
Finish updating tests for now
mholt Feb 8, 2021
84f6867
Minor cleanups and tidying
mholt Feb 24, 2021
75511e6
Remove dialLocal
sergeyfrolov Feb 28, 2021
2a9402b
Fix TestWhitelistAllowing
sergeyfrolov Feb 28, 2021
1e6dbb1
Remove localDialAddr
sergeyfrolov Feb 28, 2021
6d9747a
Fix tls setup code in tests
mholt Mar 1, 2021
7b288bc
Support HTTP/3 (#80)
klzgrad Jun 7, 2021
a7059fa
Merge branch 'master' into caddy2
gaby Mar 22, 2023
44bd8c7
Fix multiauth
Mygod Apr 26, 2023
51a9bb5
Remove useless fields
Mygod Apr 26, 2023
035199e
Add support for printing user id in log
Mygod Apr 28, 2023
0ea925f
Start to update README for Caddy 2
mholt May 9, 2023
685d1d0
Update dependencies
mholt May 9, 2023
8b5d75a
Buffer http response instead of writing directly (#106)
Mygod May 29, 2023
df7dc95
Update dependencies, add support for Github Actions. Formatting and l…
gaby Jun 25, 2023
5b23d1a
Remove oldstable from matrix and platforms until tests are stable.
gaby Jun 25, 2023
da96a33
Fix some of the gosec/golangci-lint issues
gaby Jun 25, 2023
0d92f63
Downgrade qtls-go to v0.2.2
gaby Jun 25, 2023
3a10a41
Add more workflows, fix warnings from several linters
gaby Jun 27, 2023
b6d4eb8
More linter fixes
gaby Jun 27, 2023
2e45608
Remove go vet
gaby Jun 27, 2023
a29f011
Merge branch 'caddy2' into v2multiauth
Mygod Jul 19, 2023
4e71e85
Log invalid user as well
Mygod Jul 20, 2023
6a4bb51
Refine error user
Mygod Jul 21, 2023
d7ab126
Update Caddy to v2.7.1
gaby Aug 3, 2023
71e3f6e
fix acl_test.go test cases, restore the acl error logic to be the sam…
johnzhanghua Sep 25, 2023
5b6051e
fix probe_resist_test.go test cases, the repose header['Alt-Svc'] is …
johnzhanghua Sep 25, 2023
65d0590
Merge pull request #109 from johnzhanghua/caddy2_fix_tests
gaby Oct 29, 2023
2ae345a
Cleanup go.sum file
gaby Oct 29, 2023
acd1337
Update golang.org/x/net
gaby Oct 29, 2023
fd47e41
Increase golangci-lint timeout to 5min
gaby Oct 29, 2023
9de7188
Fix arg to golangci-lint
gaby Oct 29, 2023
397bfdf
Merge remote-tracking branch 'origin/caddy2' into v2multiauth
Mygod Oct 29, 2023
cd93f0e
Fix upstream creds in test
Mygod Nov 2, 2023
611a439
Fix encoded len
Mygod Nov 2, 2023
9976c57
Oops
Mygod Nov 2, 2023
226c444
Remove duplicate code
Mygod Nov 5, 2023
5b0898c
Remove useless field
Mygod Nov 5, 2023
31f01c9
Merge pull request #99 from Mygod/v2multiauth
gaby Nov 5, 2023
2f2742b
Bump github-actions
gaby Jan 9, 2024
9c191f5
migrate from http.Flusher to http.NewResponseController
WeidiDeng Jan 17, 2024
90e3711
migrate from http.Hijacker to http.NewResponseController
WeidiDeng Jan 19, 2024
5ab96ca
Add platforms to CI
gaby Jan 27, 2024
3befe51
Remove macos from platforms
gaby Jan 27, 2024
558230f
Bump dependencies. Add go1.22
gaby Feb 12, 2024
32f2633
Update workflows to use go-stable. Remove staticcheck workflow
gaby Feb 12, 2024
284b217
Bump quic-go to v0.41.0
gaby Feb 12, 2024
46e92f5
Remove go1.20 from workflows
gaby Feb 12, 2024
bf7eb31
Use golangci-lint setup from caddyserver/server. Fix all issues
gaby Feb 12, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 20 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,48 +14,53 @@ For a complete list of features and their usage, see Caddyfile syntax:

## Caddyfile Syntax (Server Configuration)

The simplest way to enable the forward proxy without authentication just include the `forwardproxy` directive in your Caddyfile. However, this allows anyone to use your server as a proxy, which might not be desirable.
The simplest way to enable the forward proxy without authentication just include the `forward_proxy` directive in your Caddyfile. However, this allows anyone to use your server as a proxy, which might not be desirable.

Open a block for more control; here's an example of all properties in use (note that the syntax is subject to change):
The `forward_proxy` directive has no default order and must be used within a `route` directive to explicitly specify its order of evaluation. In the Caddyfile the addresses must start with `:443` for the `forward_proxy` to work for proxy requests of all origins.

Here's an example of all properties in use (note that the syntax is subject to change):

```
forwardproxy {
basicauth user1 0NtCL2JPJBgPPMmlPcJ
basicauth user2 密码
:443, example.com
route {
forward_proxy {
basic_auth user1 0NtCL2JPJBgPPMmlPcJ
basic_auth user2 密码
ports 80 443
hide_ip
hide_via
probe_resistance secret-link-kWWL9Q.com # alternatively you can use a real domain, such as caddyserver.com
serve_pac /secret-proxy.pac
response_timeout 30
dial_timeout 30
upstream https://user:[email protected]
acl {
allow *.caddyserver.com
deny 192.168.1.1/32 192.168.0.0/16 *.prohibitedsite.com *.localhost
allow ::1/128 8.8.8.8 github.com *.github.io
allowfile /path/to/whitelist.txt
denyfile /path/to/blacklist.txt
allow_file /path/to/whitelist.txt
deny_file /path/to/blacklist.txt
allow all
deny all # unreachable rule, remaining requests are matched by `allow all` above
}
}
file_server
}
```

(The square brackets `[ ]` indicate values you should replace; do not actually include the brackets.)

##### Security

- **basicauth [user] [password]**
Sets basic HTTP auth credentials. This property may be repeated multiple times. Note that this is different from Caddy's built-in `basicauth` directive. BE SURE TO CHECK THE NAME OF THE SITE THAT IS REQUESTING CREDENTIALS BEFORE YOU ENTER THEM.
- **basic_auth [user] [password]**
Sets basic HTTP auth credentials. This property may be repeated multiple times. Note that this is different from Caddy's built-in `basic_auth` directive. BE SURE TO CHECK THE NAME OF THE SITE THAT IS REQUESTING CREDENTIALS BEFORE YOU ENTER THEM.
_Default: no authentication required._

- **probe_resistance [secretlink.tld]**
Attempts to hide the fact that the site is a forward proxy.
Proxy will no longer respond with "407 Proxy Authentication Required" if credentials are incorrect or absent,
and will attempt to mimic a generic Caddy web server as if the forward proxy is not enabled.
Probing resistance works (and makes sense) only if basicauth is set up.
To use your proxy with probe resistance, supply your basicauth credentials to your client configuration.
Probing resistance works (and makes sense) only if `basic_auth` is set up.
To use your proxy with probe resistance, supply your `basic_auth` credentials to your client configuration.
If your proxy client(browser, operating system, browser extension, etc)
allows you to preconfigure credentials, and sends credentials preemptively, you do not need secret link.
If your proxy client does not preemptively send credentials, you will have to visit your secret link in your browser to trigger the authentication.
Expand Down Expand Up @@ -91,9 +96,9 @@ The hostname in each forwardproxy request will be resolved to an IP address,
and caddy will check the IP address and hostname against the directives in order until a directive matches the request.
acl_directive may be:
- **allow [ip or subnet or hostname] [ip or subnet or hostname]...**
- **allowfile /path/to/whitelist.txt**
- **allow_file /path/to/whitelist.txt**
- **deny [ip or subnet or hostname] [ip or subnet or hostname]...**
- **denyfile /path/to/blacklist.txt**
- **deny_file /path/to/blacklist.txt**

If you don't want unmatched requests to be subject to the default policy, you could finish
your acl rules with one of the following to specify action on unmatched requests:
Expand All @@ -105,7 +110,7 @@ acl_directive may be:
Note that hostname rules, matched early in the chain, will override later IP rules,
so it is advised to put IP rules first, unless domains are highly trusted and should override the
IP rules. Also note that domain-based blacklists are easily circumventable by directly specifying the IP.
For `allowfile`/`denyfile` directives, syntax is the same, and each entry must be separated by newline.
For `allow_file`/`deny_file` directives, syntax is the same, and each entry must be separated by newline.
This policy applies to all requests except requests to the proxy's own domain and port.
Whitelisting/blacklisting of ports on per-host/IP basis is not supported.
_Default policy:_
Expand All @@ -117,10 +122,6 @@ _Default deny rules intend to prohibit access to localhost and local networks an

##### Timeouts

- **response_timeout [integer]**
Sets timeout (in seconds) to get full response for HTTP requests made by proxy on behalf of users (does not affect `CONNECT`-method requests).
_Default: no timeout._

- **dial_timeout [integer]**
Sets timeout (in seconds) for establishing TCP connection to target website. Affects all requests.
_Default: 20 seconds._
Expand Down
29 changes: 28 additions & 1 deletion acl.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ import (
"strings"
)

type ACLRule struct {
mholt marked this conversation as resolved.
Show resolved Hide resolved
Subjects []string `json:"subjects,omitempty"`
Allow bool `json:"allow,omitempty"`
}

type aclDecision uint8

const (
Expand Down Expand Up @@ -65,7 +70,7 @@ func (a *aclAllRule) tryMatch(ip net.IP, domain string) aclDecision {
return aclDecisionDeny
}

func newAclRule(ruleSubject string, allow bool) (aclRule, error) {
func newACLRule(ruleSubject string, allow bool) (aclRule, error) {
if ruleSubject == "all" {
return &aclAllRule{allow: allow}, nil
}
Expand Down Expand Up @@ -94,3 +99,25 @@ func newAclRule(ruleSubject string, allow bool) (aclRule, error) {
}
return &aclDomainRule{domain: ruleSubject, subdomainsAllowed: subdomainsAllowed, allow: allow}, nil
}

// isValidDomainLite shamelessly rejects non-LDH names. returns nil if domains seems valid
func isValidDomainLite(domain string) error {
for i := 0; i < len(domain); i++ {
c := domain[i]
if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '_' || '0' <= c && c <= '9' ||
c == '-' || c == '.' {
continue
}
return errors.New("character " + string(c) + " is not allowed")
}
sections := strings.Split(domain, ".")
for _, s := range sections {
if len(s) == 0 {
return errors.New("empty section between dots in domain name or trailing dot")
}
if len(s) > 63 {
return errors.New("domain name section is too long")
}
}
return nil
}
69 changes: 34 additions & 35 deletions acl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,14 @@ import (
test port blocking working
test blacklist allowed
test blacklist refused with correct status

*/

func TestWhitelistAllowing(t *testing.T) {
useTls := true
for _, httpProxyVer := range testHttpProxyVersions {
const useTLS, dialLocal = true, true
for _, httpProxyVer := range testHTTPProxyVersions {
for _, resource := range testResources {
response, err := getViaProxy(caddyTestTarget.addr, resource, caddyForwardProxyWhiteListing.addr, httpProxyVer,
"", useTls)
"", useTLS, dialLocal)
if err != nil {
t.Fatal(err)
} else if err = responseExpected(response, caddyTestTarget.contents[resource]); err != nil {
Expand All @@ -28,11 +27,11 @@ func TestWhitelistAllowing(t *testing.T) {
}

func TestWhitelistBlocking(t *testing.T) {
useTls := true
for _, httpProxyVer := range testHttpProxyVersions {
const useTLS, dialLocal = true, false
for _, httpProxyVer := range testHTTPProxyVersions {
for _, resource := range testResources {
response, err := getViaProxy(caddyHTTPTestTarget.addr, resource, caddyForwardProxyWhiteListing.addr, httpProxyVer,
"", useTls)
"", useTLS, dialLocal)
if err != nil {
t.Fatal(err)
} else if response.StatusCode != http.StatusForbidden {
Expand All @@ -41,10 +40,10 @@ func TestWhitelistBlocking(t *testing.T) {
}
}

for _, httpProxyVer := range testHttpProxyVersions {
for _, httpProxyVer := range testHTTPProxyVersions {
for _, resource := range testResources {
response, err := getViaProxy("google.com:6451", resource, caddyForwardProxyWhiteListing.addr, httpProxyVer,
"", useTls)
"", useTLS, dialLocal)
if err != nil {
t.Fatal(err)
} else if response.StatusCode != http.StatusForbidden {
Expand All @@ -55,11 +54,11 @@ func TestWhitelistBlocking(t *testing.T) {
}

func TestLocalhostDefaultForbidden(t *testing.T) {
useTls := true
for _, httpProxyVer := range testHttpProxyVersions {
const useTLS, dialLocal = true, false
for _, httpProxyVer := range testHTTPProxyVersions {
for _, resource := range testResources {
response, err := getViaProxy("localhost:6451", resource, caddyForwardProxyNoBlacklistOverride.addr, httpProxyVer,
"", useTls)
"", useTLS, dialLocal)
if err != nil {
t.Fatal(err)
} else if response.StatusCode != http.StatusForbidden {
Expand All @@ -68,10 +67,10 @@ func TestLocalhostDefaultForbidden(t *testing.T) {
}
}

for _, httpProxyVer := range testHttpProxyVersions {
for _, httpProxyVer := range testHTTPProxyVersions {
for _, resource := range testResources {
response, err := getViaProxy("127.0.0.1:808", resource, caddyForwardProxyNoBlacklistOverride.addr, httpProxyVer,
"", useTls)
"", useTLS, dialLocal)
if err != nil {
t.Fatal(err)
} else if response.StatusCode != http.StatusForbidden {
Expand All @@ -80,10 +79,10 @@ func TestLocalhostDefaultForbidden(t *testing.T) {
}
}

for _, httpProxyVer := range testHttpProxyVersions {
for _, httpProxyVer := range testHTTPProxyVersions {
for _, resource := range testResources {
response, err := getViaProxy("[::1]:8080", resource, caddyForwardProxyNoBlacklistOverride.addr, httpProxyVer,
"", useTls)
"", useTLS, dialLocal)
if err != nil {
t.Fatal(err)
} else if response.StatusCode != http.StatusForbidden {
Expand All @@ -94,11 +93,11 @@ func TestLocalhostDefaultForbidden(t *testing.T) {
}

func TestLocalNetworksDefaultForbidden(t *testing.T) {
useTls := true
for _, httpProxyVer := range testHttpProxyVersions {
const useTLS, dialLocal = true, false
for _, httpProxyVer := range testHTTPProxyVersions {
for _, resource := range testResources {
response, err := getViaProxy("10.0.0.0:80", resource, caddyForwardProxyNoBlacklistOverride.addr, httpProxyVer,
"", useTls)
"", useTLS, dialLocal)
if err != nil {
t.Fatal(err)
} else if response.StatusCode != http.StatusForbidden {
Expand All @@ -107,10 +106,10 @@ func TestLocalNetworksDefaultForbidden(t *testing.T) {
}
}

for _, httpProxyVer := range testHttpProxyVersions {
for _, httpProxyVer := range testHTTPProxyVersions {
for _, resource := range testResources {
response, err := getViaProxy("127.222.34.1:443", resource, caddyForwardProxyNoBlacklistOverride.addr, httpProxyVer,
"", useTls)
"", useTLS, dialLocal)
if err != nil {
t.Fatal(err)
} else if response.StatusCode != http.StatusForbidden {
Expand All @@ -119,10 +118,10 @@ func TestLocalNetworksDefaultForbidden(t *testing.T) {
}
}

for _, httpProxyVer := range testHttpProxyVersions {
for _, httpProxyVer := range testHTTPProxyVersions {
for _, resource := range testResources {
response, err := getViaProxy("172.16.0.1:8080", resource, caddyForwardProxyNoBlacklistOverride.addr, httpProxyVer,
"", useTls)
"", useTLS, dialLocal)
if err != nil {
t.Fatal(err)
} else if response.StatusCode != http.StatusForbidden {
Expand All @@ -131,10 +130,10 @@ func TestLocalNetworksDefaultForbidden(t *testing.T) {
}
}

for _, httpProxyVer := range testHttpProxyVersions {
for _, httpProxyVer := range testHTTPProxyVersions {
for _, resource := range testResources {
response, err := getViaProxy("192.168.192.168:888", resource, caddyForwardProxyNoBlacklistOverride.addr, httpProxyVer,
"", useTls)
"", useTLS, dialLocal)
if err != nil {
t.Fatal(err)
} else if response.StatusCode != http.StatusForbidden {
Expand All @@ -145,11 +144,11 @@ func TestLocalNetworksDefaultForbidden(t *testing.T) {
}

func TestBlacklistBlocking(t *testing.T) {
useTls := true
for _, httpProxyVer := range testHttpProxyVersions {
const useTLS, dialLocal = true, false
for _, httpProxyVer := range testHTTPProxyVersions {
for _, resource := range testResources {
response, err := getViaProxy(blacklistedDomain, resource, caddyForwardProxyBlackListing.addr, httpProxyVer,
"", useTls)
"", useTLS, dialLocal)
if err != nil {
t.Fatal(err)
} else if response.StatusCode != http.StatusForbidden {
Expand All @@ -158,10 +157,10 @@ func TestBlacklistBlocking(t *testing.T) {
}
}

for _, httpProxyVer := range testHttpProxyVersions {
for _, httpProxyVer := range testHTTPProxyVersions {
for _, resource := range testResources {
response, err := getViaProxy(blacklistedIPv4, resource, caddyForwardProxyBlackListing.addr, httpProxyVer,
"", useTls)
"", useTLS, dialLocal)
if err != nil {
t.Fatal(err)
} else if response.StatusCode != http.StatusForbidden {
Expand All @@ -170,10 +169,10 @@ func TestBlacklistBlocking(t *testing.T) {
}
}

for _, httpProxyVer := range testHttpProxyVersions {
for _, httpProxyVer := range testHTTPProxyVersions {
for _, resource := range testResources {
response, err := getViaProxy("["+blacklistedIPv6+"]:80", resource, caddyForwardProxyBlackListing.addr, httpProxyVer,
"", useTls)
"", useTLS, dialLocal)
if err != nil {
t.Fatal(err)
} else if response.StatusCode != http.StatusForbidden {
Expand All @@ -184,11 +183,11 @@ func TestBlacklistBlocking(t *testing.T) {
}

func TestBlacklistAllowing(t *testing.T) {
useTls := true
for _, httpProxyVer := range testHttpProxyVersions {
const useTLS, dialLocal = true, true
for _, httpProxyVer := range testHTTPProxyVersions {
for _, resource := range testResources {
response, err := getViaProxy(caddyTestTarget.addr, resource, caddyForwardProxyBlackListing.addr, httpProxyVer,
"", useTls)
"", useTLS, dialLocal)
if err != nil {
t.Fatal(err)
} else if err = responseExpected(response, caddyTestTarget.contents[resource]); err != nil {
Expand Down
12 changes: 0 additions & 12 deletions cmd/caddy/caddy.go

This file was deleted.

Loading