Skip to content

Commit

Permalink
rejection http-response, ca certs
Browse files Browse the repository at this point in the history
  • Loading branch information
superstes committed Oct 1, 2023
1 parent f21c825 commit 51d67a4
Show file tree
Hide file tree
Showing 12 changed files with 329 additions and 40 deletions.
9 changes: 5 additions & 4 deletions config_example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@ service:
tproxy: false
- mode: 'proxyproto'
port: 4129
# - mode: 'http' # not yet implemented
# port: 4130
# - mode: 'https' # not yet implemented
# port: 4131
- mode: 'http'
port: 4130
- mode: 'https' # not yet implemented
port: 4131
# - mode: 'socks5' # not yet implemented

Check warning on line 18 in config_example.yml

View workflow job for this annotation

GitHub Actions / build (3.1)

18:5 [comments-indentation] comment not indented like content
# port: 4132

certs:
caPublic: '/tmp/calamary.ca.crt'
serverPublic: '/tmp/calamary.crt'
serverPrivate: '/tmp/calamary.key'
interceptPublic: '/tmp/calamary.subca.crt'
Expand Down
199 changes: 199 additions & 0 deletions docs/source/info/modes.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
.. _modes:

.. include:: ../_inc/head.rst

.. include:: ../_inc/in_progress.rst

#####
Modes
#####

Transparent
###########

Behaviour
=========

DNAT - TCP (plaintext)
----------------------

**Server**

.. code-block:: bash
2023-10-01 23:43:01 | INFO | 192.168.11.104 => 135.181.170.219:80 | Accept
**Client**

.. code-block:: bash
curl http://superstes.eu -v
* Trying 135.181.170.219:80...
* Connected to superstes.eu (135.181.170.219) port 80 (#0)
> GET / HTTP/1.1
> Host: superstes.eu
...
<
<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
<hr><center>nginx</center>
</body>
</html>
* Connection #0 to host superstes.eu left intact
DNAT - TLS
----------
**Server**
.. code-block:: bash
2023-10-01 23:43:09 | INFO | 192.168.11.104 => 135.181.170.219:443 | Accept
**Client**
.. code-block:: bash
host@calamary$ curl https://superstes.eu -v
* Trying 135.181.170.219:443...
* Connected to superstes.eu (135.181.170.219) port 443 (#0)
...
< HTTP/2 302
< server: nginx
...
<
<html>
<head><title>302 Found</title></head>
<body>
<center><h1>302 Found</h1></center>
<hr><center>nginx</center>
</body>
</html>
* Connection #0 to host superstes.eu left intact
HTTP Proxy
##########
Behaviour
=========
HTTP
----
**Server**
.. code-block:: bash
2023-10-01 23:40:34 | INFO | 127.0.0.1 => 135.181.170.219:80 | Accept
**Client**
.. code-block:: bash
host@calamary$ http_proxy=http://localhost:4130 curl http://superstes.eu -v
* Uses proxy env variable http_proxy == 'http://localhost:4130'
* Trying 127.0.0.1:4130...
* Connected to (nil) (127.0.0.1) port 4130 (#0)
> GET http://superstes.eu/ HTTP/1.1
> Host: superstes.eu
> User-Agent: curl/7.81.0
> Accept: */*
> Proxy-Connection: Keep-Alive
>
...
<
<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
<hr><center>nginx</center>
</body>
</html>
* Connection #0 to host (nil) left intact
HTTPS
-----
**Server**
.. code-block:: bash
2023-10-01 23:40:43 | INFO | 127.0.0.1 => 135.181.170.219:443 | Accept
**Client**
.. code-block:: bash
host@calamary$ https_proxy=http://localhost:4130 curl https://superstes.eu -v
* Uses proxy env variable https_proxy == 'http://localhost:4130'
* Trying 127.0.0.1:4130...
* Connected to (nil) (127.0.0.1) port 4130 (#0)
* allocate connect buffer!
* Establish HTTP proxy tunnel to superstes.eu:443
> CONNECT superstes.eu:443 HTTP/1.1
> Host: superstes.eu:443
> User-Agent: curl/7.81.0
> Proxy-Connection: Keep-Alive
>
< HTTP/1.1 200 OK
< Content-Length: 0
* Ignoring Content-Length in CONNECT 200 response
<
* Proxy replied 200 to CONNECT request
* CONNECT phase completed!
...
> GET / HTTP/2
> Host: superstes.eu
> user-agent: curl/7.81.0
> accept: */*
>
...
< HTTP/2 302
< server: nginx
...
<
* TLSv1.2 (IN), TLS header, Supplemental data (23):
<html>
<head><title>302 Found</title></head>
<body>
<center><h1>302 Found</h1></center>
<hr><center>nginx</center>
</body>
</html>
* Connection #0 to host (nil) left intact
HTTPS Proxy
###########
Behaviour
=========
tbd
Proxy Protocol
##############
Behaviour
=========
tbd
SOCKS5
######
Behaviour
=========
tbd
2 changes: 2 additions & 0 deletions docs/source/info/rules.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ Multiple matches can be defined in a single rule.
The value of matches is **case-insensitive** by default.

NOTE: The HTTP host-header domain is not compared if :code:`dns` is used - as it can be modified easily.

You can define **multiple values** for each match.

Matches can also be **negated** by using the :code:`!` prefix:
Expand Down
5 changes: 5 additions & 0 deletions lib/cnf/cnf_file/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ func validateListener(lncnf cnf.ServiceListener, fail bool) bool {
}

func validateCerts(certs cnf.ServiceCertificates, fail bool) bool {
if certs.CAPublic != "" {
if !validateCert(certs.CAPublic, true, fail) {
return false
}
}
if certs.ServerPublic != "" || certs.ServerPrivate != "" {
if !validateCert(certs.ServerPublic, true, fail) {
return false
Expand Down
1 change: 1 addition & 0 deletions lib/cnf/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ type ServiceMetrics struct {
}

type ServiceCertificates struct {
CAPublic string `yaml:"caPublic"`
ServerPublic string `yaml:"serverPublic"`
ServerPrivate string `yaml:"serverPrivate"`
InterceptPublic string `yaml:"interceptPublic"`
Expand Down
5 changes: 0 additions & 5 deletions lib/main/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,6 @@ func (svc *service) shutdown(cancel context.CancelFunc) {
}
log.Info("service", "Stopped")
os.Exit(0)
/*
ctx := context.Background()
doneHTTP := httpserver.Shutdown(ctx)
<-doneHTTP
*/
}

func (svc *service) serve(srv rcv.Server) (err error) {
Expand Down
43 changes: 36 additions & 7 deletions lib/proc/fwd/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,25 @@ func ForwardHttp(srvCnf cnf.ServiceListener, l4Proto meta.Proto, conn net.Conn)
}

// todo: full parsing may not be needed if 'connect'
pktProxy, connProxyIo := parseConn(srvCnf, l4Proto, conn)
pktProxy, connProxyIo, err := parseConn(srvCnf, l4Proto, conn)
if err != nil {
return
}

if pktProxy.L5.Proto != meta.ProtoL5Http {
parse.LogConnError("forward", pktProxy, "Got non HTTP-Request on HTTP server")
return
}

reqProxy := readRequest(pktProxy, connProxyIo)

if reqProxy == nil {
return

} else if reqProxy.Method == http.MethodConnect {
forwardConnect(srvCnf, l4Proto, conn, pktProxy, connProxyIo, reqProxy)

} else {
// plaintext HTTP is unsafe by design and should not be allowed
// plaintext HTTP is unsecure by design and should not be allowed
// todo: implement generic https-redirection response

forwardPlain(srvCnf, l4Proto, conn, pktProxy, connProxyIo, reqProxy)
Expand All @@ -67,7 +71,11 @@ func forwardPlain(
pkt.L3.DestIP = dest
parse.LogConnDebug("forward", pkt, "Updated destination IP")

filterConn(pkt, conn, connIo)
if !filterConn(pkt, conn, connIo) {
proxyResp := responseReject()
proxyResp.Write(conn)
return
}
send.ForwardHttp(pkt, conn, connIo, req)
}

Expand All @@ -87,7 +95,11 @@ func forwardConnect(
return
}

pkt, connIo := parseConn(srvCnf, l4Proto, conn)
pkt, connIo, err := parseConn(srvCnf, l4Proto, conn)
if err != nil {
return
}

host, port := parse.SplitHttpHost(reqProxy.Host, pkt.L5.Encrypted)
pkt.L5Http = &parse.ParsedHttp{
Host: host,
Expand All @@ -108,9 +120,18 @@ func forwardConnect(
pkt.L3.DestIP = dest
parse.LogConnDebug("forward", pkt, "Updated destination IP")

filterConn(pkt, conn, connIo)
send.Forward(pkt, conn, connIo)
if !filterConn(pkt, conn, connIo) {
if pkt.L5.Encrypted == meta.OptBoolTrue {
// http response for https will show a cryptic response ('wrong version number' vs 'eof while reading')
return

} else {
proxyResp := responseReject()
proxyResp.Write(conn)
return
}
}
send.Forward(pkt, conn, connIo)
}

func resolveTargetHostname(pkt parse.ParsedPacket) net.IP {
Expand Down Expand Up @@ -150,6 +171,14 @@ func responseFailed() http.Response {
}
}

func responseReject() http.Response {
return http.Response{
StatusCode: http.StatusForbidden,
ProtoMajor: 1,
ProtoMinor: 1,
}
}

func readRequest(pkt parse.ParsedPacket, connIo io.ReadWriter) *http.Request {
reqRaw := bufio.NewReader(connIo)
req, err := http.ReadRequest(reqRaw)
Expand Down
23 changes: 16 additions & 7 deletions lib/proc/fwd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,30 +23,39 @@ func Forward(srvCnf cnf.ServiceListener, l4Proto meta.Proto, conn net.Conn) {
defer metrics.CurrentConn.Dec()
}

pkt, connIo := parseConn(srvCnf, l4Proto, conn)
filterConn(pkt, conn, connIo)
pkt, connIo, err := parseConn(srvCnf, l4Proto, conn)
if err != nil {
return
}
if !filterConn(pkt, conn, connIo) {
return
}
send.Forward(pkt, conn, connIo)
}

func parseConn(srvCnf cnf.ServiceListener, l4Proto meta.Proto, conn net.Conn) (pkt parse.ParsedPacket, connIo io.ReadWriter) {
func parseConn(srvCnf cnf.ServiceListener, l4Proto meta.Proto, conn net.Conn) (pkt parse.ParsedPacket, connIo io.ReadWriter, err error) {
connIo = conn
connIoBuf := new(bytes.Buffer)
connIoTee := io.TeeReader(connIo, connIoBuf)

pkt = parse.Parse(srvCnf, l4Proto, conn, connIoTee)
pkt, err = parse.Parse(srvCnf, l4Proto, conn, connIoTee)

if err != nil {
return
}

// write read bytes back to stream so we can forward them
connIo = u.NewReadWriter(io.MultiReader(bytes.NewReader(connIoBuf.Bytes()), connIo), connIo)

return
}

func filterConn(pkt parse.ParsedPacket, conn net.Conn, connIo io.ReadWriter) {
func filterConn(pkt parse.ParsedPacket, conn net.Conn, connIo io.ReadWriter) bool {
if !filter.Filter(pkt) {
parse.LogConnInfo("forward", pkt, "Denied")
conn.Close()
return
return false
}

parse.LogConnInfo("forward", pkt, "Accept")
return true
}
Loading

0 comments on commit 51d67a4

Please sign in to comment.