kataras / iris

The fastest HTTP/2 Go Web Framework. New, modern and easy to learn. Fast development with Code you control. Unbeatable cost-performance ratio :rocket:
https://www.iris-go.com
BSD 3-Clause "New" or "Revised" License
25.27k stars 2.47k forks source link

[BUG] AutoTLS hosts on wrong addresses #1578

Closed AlbinoGeek closed 4 years ago

AlbinoGeek commented 4 years ago

Describe the bug Using the example AutoTLS code results in http://localhost:443 output Using AutoTLS code for 0.0.0.0:443 results in http://0.0.0.0 output

To Reproduce Steps to reproduce the behavior:

  1. Use the example AutoTLS code
  2. Modify the code to listen on 0.0.0.0:443

Expected behavior AutoTLS hosting on https://0.0.0.0 in both cases

Screenshots

  1. image
  2. image

Desktop (please complete the following information):

iris.Version

AlbinoGeek commented 4 years ago

I can't get this working whatsoever. I get the following error trying to access the server locally:

[HTTP Server] http: TLS handshake error from 127.0.0.1:54234: acme/autocert: missing server name
[HTTP Server] http: TLS handshake error from [::1]:54704: acme/autocert: server name component count invalid

And accessing the server remotely gives me Address Unreachable v.s. using standard app.Listen

kataras commented 4 years ago

Hello @AlbinoGeek please check my reply on your previous issue as I am reading this new one

kataras commented 4 years ago

I think that it a log "issue", indeed the server is running on http:...:443 when accessing the https://...:80, the errors you got in your console maybe result of localhost testing instead of a domain or no? AutoTLS works on a linux (ubuntu) and windows systems here, can you give me more information please?

AlbinoGeek commented 4 years ago

This may require #1577 to be solved first, as this is using AutoTLS which I cannot get working in any configuration at the moment. I even tried (For the sake of this ticket) changing the NAT to allow 80->80 and 443->443 specifically, then running iris as root :fearful:

As per more information:

$ uname -a
Linux doom.broughton.lan 5.7.11-200.fc32.x86_64 #1 SMP Wed Jul 29 17:15:52 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux

$ go version
go version go1.14.6 linux/amd64

$ cat go.sum | grep iris | cut -d' ' -f1,2
github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod
github.com/iris-contrib/httpexpect/v2 v2.0.5
github.com/iris-contrib/httpexpect/v2 v2.0.5/go.mod
github.com/iris-contrib/jade v1.1.4
github.com/iris-contrib/jade v1.1.4/go.mod
github.com/iris-contrib/pongo2 v0.0.1
github.com/iris-contrib/pongo2 v0.0.1/go.mod
github.com/iris-contrib/schema v0.0.2
github.com/iris-contrib/schema v0.0.2/go.mod
github.com/kataras/iris/v12 v12.1.9-0.20200809192844-da029d6f3722
github.com/kataras/iris/v12 v12.1.9-0.20200809192844-da029d6f3722/go.mod

It is fairly common in my experience for an HTTP server behind this sort of proxy/NAT to still function (just without the SSL portion) on testing domains such as localhost. It is worth noting that I have put real domains in the AutoTLS code as follows (redacted however):

app.Run(iris.AutoTLS(":443", "my.domain", "me@my.domain"))
kataras commented 4 years ago

This binds to :80 because of the automatic HTTP -> HTTPS server, the iris.TLSNoRedirect is missing from the iris.AutoTLS function. For users reading this issue, please follow this.

@AlbinoGeek I think you can close this issue now?

AlbinoGeek commented 4 years ago

I can close #1577 , but not this issue. This issue remains as a bug in the logging that shows http://localhost:443 even when its actually listening on 0.0.0.0:443 .

AlbinoGeek commented 4 years ago

Alright, after speaking with @kataras a bit, here's what we've come to:

For case 1) The server is correctly listening on 0.0.0.0:443 even though it displays localhost:443 in the text, this is to give the user a click-able link in their console, as opposed to the former which is not a real IP address. Totally understandable.

For case 2) The same logic as case 1 should apply, but is not currently.


In regards to no viable challenge type found found in #1577 , this is being fixed now.

kataras commented 4 years ago

Now when using AutoTLS with known domain, something like Now listening on: https://example.com will be displayed instead:

Thanks @AlbinoGeek, your issue and contribution was one of the best we've ever had!

AlbinoGeek commented 4 years ago

This aaaaaaaaaaalmost works, just one more change (messaged you on git)

https://github.com/kataras/iris/blob/0edf0affb0bd438ec389396a04bea54b33b54aa3/core/host/supervisor.go#L410-L412

This is the only remaining blocker on my NAT situation.

kataras commented 4 years ago

@AlbinoGeek These lines are commented out (and the host:port part fixed), give it a shot

AlbinoGeek commented 4 years ago

Commits up to and including ff5e43f have fixed this!

However, due to the way golang/crypto works, external port 80 cannot be changed, this is not iris fault. Every other port can be changed successfully now, to work behind a NAT or proxy for example.


For anyone who finds this in the future (I'm going to PR an example), the following code works:

Solution

This allows a system where NAT or Port Forwarding creates the following rules to host iris. As a very positive side-effect, AutoTLS effectively doesn't require root when running as configured:

Firewall Rules

Debian 9 or older, Fedora 30 or older, RedHat/CentOS 7 or older, Ubuntu 16 or older,

# Forward internal 8443 to external 443
sudo iptables -t nat -A PREROUTING -p tcp --dport 8443 -j REDIRECT --to-ports 443
sudo iptables -t nat -A     OUTPUT -p tcp --dport 8443 -j REDIRECT --to-ports 443
# Forward internal 8080 to external 80
sudo iptables -t nat -A PREROUTING -p tcp --dport 8080 -j REDIRECT --to-ports 80
sudo iptables -t nat -A     OUTPUT -p tcp --dport 8080 -j REDIRECT --to-ports 80
## You may also need to run this, if it's not working:
## echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward
## and once it's all working, you need to save the firewall rules, or it's lost on reboot:
## iptables-save

Debian 10 or newer, Fedora 32 or newer, RedHat/CentOS 8 or newer, Ubuntu 18 or newer

# Enable Port Forwarding
sudo firewall-cmd --zone=external --add-masquerade
# Forward internal 8443 to external 443
sudo firewall-cmd --zone=external --add-forward-port=port=443:proto=tcp:toport=8443
# Forward internal 8080 to external 80
sudo firewall-cmd --zone=external --add-forward-port=port=80:proto=tcp:toport=8080
## For added security, add ":toaddr=127.0.0.1" to the end of each of the above lines:
## sudo firewall-cmd --zone=external --add-forward-port=port=443:proto=tcp:toport=8443:toaddr=127.0.0.1
## sudo firewall-cmd --zone=external --add-forward-port=port=80:proto=tcp:toport=8080:toaddr=127.0.0.1
## Then, change `internalHost` to "127.0.0.1" below
## And again, once it's working, save the rules so they're not lost on reboot:
## sudo firewall-cmd --runtime-to-permanent

Working Code

package main

import (
    "fmt"
    "net/http"
    "time"

    "github.com/kataras/iris/v12"
    "github.com/spf13/pflag"
)

var (
    adminEmail         = pflag.String("admin-email", "you@example.com", "Administrator email sent to LetsEncrypt")
    domain             = pflag.String("domain", "example.com", "Canonical domain for URL generation")
    externalHost       = pflag.String("external-host", "0.0.0.0", "HTTP(S) Hostname used externally")
    internalHost       = pflag.String("internal-host", "0.0.0.0", "HTTP Hostname used internally")
    internalPortPlain  = pflag.Int("internal-port", 8080, "HTTP Port used internally")
    internalPortSecure = pflag.Int("internal-port-secure", 8443, "HTTPS Port used internally")
)

func main() {
    pflag.Parse()
    app = iris.New()

    app.Get("/", func(ctx iris.Context) {
        ctx.JSON(map[string]interface{}{
            "time":      time.Now().Unix(),
        })
    })

    tlsAddr := fmt.Sprintf("%s:%d", *internalHost, *internalPortSecure)
    domains := fmt.Sprintf("%s www.%s your.%s", *domain, *domain, *domain)
    app.Run(iris.AutoTLS(tlsAddr, domains, *adminEmail, iris.AutoTLSNoRedirect(fallbackServer)))
}

// Without this, the ACME HTTP-01 challenge would fail
func fallbackServer(acme func(http.Handler) http.Handler) *http.Server {
    srv := &http.Server{
        Addr: fmt.Sprintf("%s:%d", *externalHost, *internalPortPlain),
        Handler: acme(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            http.Redirect(w, r, fmt.Sprintf("https://%s/", *domain), iris.StatusTemporaryRedirect)
        })),
    }
    go srv.ListenAndServe()
    return srv
}

Test it's working

# Test Internal plaintext ip/port
$ curl http://localhost:8080
<a href="https://example.com/">Temporary Redirect</a>.
# Test external plaintext ip/port
$ curl http://example.com
<a href="https://example.com/">Temporary Redirect</a>.
# Test external secure ip/port
$ curl https://example.com
{
  "time": 1597304304
}
kataras commented 4 years ago

Good job @AlbinoGeek! If we can dockerize this example and put it on https://github.com/kataras/iris/tree/master/_examples/http-server as nat-letsencrypt will be great, waiting for your PR!