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

AutoTLS with non-80 HTTP Port? #1577

Closed
AlbinoGeek opened this issue Aug 11, 2020 · 19 comments
Closed

AutoTLS with non-80 HTTP Port? #1577

AlbinoGeek opened this issue Aug 11, 2020 · 19 comments

Comments

@AlbinoGeek
Copy link

AlbinoGeek commented Aug 11, 2020

Hello there!

This is a purely documentation / example question:

  • How can I start an AutoTLS server, where the HTTP port is 8080 and not 80?

Why?

Because there is NAT in front of iris that routes internal:8080 to external:80

Current Code

if err := app.Run(iris.AutoTLS(":443", "my.domain.here", "[email protected]")); err != nil {
  log.Error("failed to set-up HTTP server", "error", err)
}

Looking through the code some I the two ways to create a Runner that conflict here:

iris/iris.go

Lines 658 to 667 in da029d6

func AutoTLS(
addr string,
domain string, email string,
hostConfigs ...host.Configurator) Runner {
return func(app *Application) error {
return app.NewHost(&http.Server{Addr: addr}).
Configure(hostConfigs...).
ListenAndServeAutoTLS(domain, email, "letscache")
}
}

iris/iris.go

Lines 584 to 590 in da029d6

func Addr(addr string, hostConfigs ...host.Configurator) Runner {
return func(app *Application) error {
return app.NewHost(&http.Server{Addr: addr}).
Configure(hostConfigs...).
ListenAndServe()
}
}

They are simply too abstracted for me to know what I'd be changing without a thorough deep-dive into iris code.

@kataras
Copy link
Owner

kataras commented Aug 11, 2020

Hello @AlbinoGeek,

First of all, just for your knowedge the app.Run/Listen logs the incoming error already.

Now let's deep into your question, you can do it through app.Run(iris.AutoTLS(":8080"....)) but the problem you have is the secondary :http server which redirects to the :https one. There is a Host option called iris.NoTLSRedirect which disables that default behavior e.g. app.Run(iris.AutoTLS(":8080", "...", "...", iris.TLSNoRedirect)).

After that, you have two options to handle the http to https redirection:

  1. can register your own server to do redirections, Iris contains a helper for that example:
import "github.com/kataras/iris/v12/core/host" // import that package.

// to start a new server listening at :8080 and redirects 
// to the secure address, then: 
target, _ := url.Parse("https://127.0.0.1:8080") 
go host.NewRedirection("127.0.0.1:8080", target, iris.StatusMovedPermanently).ListenAndServe()

app := iris.New()
// ...
app.Run(iris.AutoTLS(....), TLSNoRedirect)

Although, you may need the Socket Sharding feature here.

  1. Using a middleware OR using a handler that wraps the router, in your main server.

2.1 middleware:

func redirectToHTTPS(ctx iris.Context) {
  if !ctx.Request().TLS != nil  /* or ctx.Request().ProtoMajor != 2 */  {
    ctx.Redirect(strings.Replace(ctx.FullRequestURI(),"http","https",1), iris.StatusMovedPermanently)
    return
  }

  ctx.Next()
}

app.Use(redirectToHTTPS)

2.2 wrapper:

app.WrapRouter(func(w http.ResponseWriter, r *http.Request, router http.HandlerFunc){
   // checks here, if it's not https then redirect and return otherwise run the router with router(w,r )
})

You can always modify hosts through app.ConfigureHost(func(su *iris.Supervisor){ /* .... */ )).

Please tell me if that helped you.

@AlbinoGeek
Copy link
Author

AlbinoGeek commented Aug 11, 2020

@kataras In these examples it is unclear which is the HTTP port and which is the HTTPS port, as you only use one port within each of the examples. It is my understanding that they cannot be on the same port, but perhaps this is old knowledge. [ and yes, I am using SocketSharding, I just removed it for simplicity of reproducing the issue without adding complexity. ]

@kataras
Copy link
Owner

kataras commented Aug 11, 2020

@AlbinoGeek yes sorry, it was a copy-paste from an iris example, just change the port you are using for the HTTP server, here is the one:

target, _ := url.Parse("https://127.0.0.1:8080") // PORT FOR HTTPS
go host.NewRedirection("127.0.0.1:OTHERPORT_FOR_HTTP", target, iris.StatusMovedPermanently).ListenAndServe()

If you don't need HTTP to HTTPS redirection because other software does that then you can skip this part and just pass the iris.TLSNoRedirect. I don't see the confusion here

@AlbinoGeek
Copy link
Author

@kataras I cannot use iris.TLSNoRedirect in the context of app.Run because:

./main.go:191:22: cannot use iris.TLSNoRedirect (type func(*host.Supervisor)) as type iris.Configurator in argument to app.Run

@kataras
Copy link
Owner

kataras commented Aug 11, 2020

@AlbinoGeek That's why I am asking the Iris version (in the .github issues templates), you don't use the master one, and that's ok. You couldn't override the default behavior on AutoTLS (you can/could on TLS thoguh) before, that's why you don't have the option for iris.TLSNoRedirect

Please always try replicate your issue using the master branch because an "x" bug may be fixed months ago:

$ cd your_project
$ go get -u github.com/kataras/iris/v12@master
$ go run .

EDIT: That error came because he registered the iris.TLSNoRedirect on the Run method instead of the AutoTLS function itself, see the below comment.

@AlbinoGeek
Copy link
Author

AlbinoGeek commented Aug 11, 2020

It is worth noting that AutoTLS internally binds to port 80, causing this issue:

// NAT in front of me points 8443 -> 443 and 8080 -> 80
target, _ := url.Parse("https://127.0.0.1:8443")
go host.NewRedirection("127.0.0.1:8080", target, iris.StatusMovedPermanently).ListenAndServe()

app.Run(iris.AutoTLS(":8443", "my.domain.here", "[email protected]"), iris.WithSocketSharding)

results in: (when not running with the permissions to bind on :80, because this code shouldn't require that)

listen tcp :80: bind: permission denied

All of this was tested with what was master, as of:
github.com/kataras/iris/[email protected]

After updating to master again, the results are still reproducable.

@kataras
Copy link
Owner

kataras commented Aug 11, 2020

OK that's a good start, let me see what's wrong here, will get back to you soon!

@AlbinoGeek
Copy link
Author

AlbinoGeek commented Aug 11, 2020

And for sanity sake, to exclude the NAT from the issue, the following code works:

var (
  plainHost    = pflag.String("host", "0.0.0.0", "HTTP Hostname")
  plainPort    = pflag.Int("port", 8080, "HTTP Port")
)

func main() {
  pflag.Parse()
  
  app := iris.Default()
  app.Use(requestid.New())
  app.Use(iris.Compression)
  
  app.Get("/health", func(c iris.Context) {
    c.JSON(map[string]interface{}{
      "time":      time.Now().Unix(),
    })
  })
  
  app.Logger().SetLevel("debug")
  app.Run(iris.Addr(fmt.Sprintf("%s:%d", *plainHost, *plainPort)))
}

Means the site can be reached at http://127.0.0.1:8080/health and http://external:80/health successfully.

@kataras
Copy link
Owner

kataras commented Aug 11, 2020

@AlbinoGeek, did you try using letsencrypt with raw net/http and you succeed with your current configuration? Because the iris.AutoTLS just uses the acme/autocert package to provide letsencrypt.

About the comment of autobinding to :80, that's a letsencrypt thing and it's normal, it needs it: (that was wrong, it needs it for redirection but we omit that by using a custom one, or not (iris.TLSNoRedirect), see comment below)

The only code you should check:

autoTLSManager := &autocert.Manager{
Prompt: autocert.AcceptTOS,
HostPolicy: hostPolicy,
Email: email,
Cache: cache,
}
return su.runTLS(autoTLSManager.GetCertificate, autoTLSManager.HTTPHandler(nil /* nil for redirect */))

And autocert.Manager: https://github.com/golang/crypto/blob/123391ffb6de907695e1066dc40c1ff09322aeb6/acme/autocert/autocert.go#L368-L383

@AlbinoGeek
Copy link
Author

Totally fair, this makes it an upstream bug (for acme/autocert) -- because my particular setup requires a non-80 HTTP port, due to NAT. [And hey, the exhaustion of IPv4 addresses seems like reason enough to support this.]

@kataras
Copy link
Owner

kataras commented Aug 12, 2020

@AlbinoGeek sorry for confusion but I gave you the ]correct code in the first place, you messup with the parentheses... np, it's normal. All that happens because iris.TLSNoRedirect is not an iris.Configurator but a host.Configurator one and it SHOULD be passed to the AutoTLS function, not the Run method.

The correct: app.Run(iris.AutoTLS(":8443", "example.com", "[email protected]", iris.TLSNoRedirect)) please test.

Example file:

// Note: to disable automatic "http://" to "https://" redirections pass the `iris.TLSNoRedirect`
// host configurator to TLS or AutoTLS functions, e.g:
//
// app.Run(iris.AutoTLS(":443", "example.com", "[email protected]", iris.TLSNoRedirect))

@AlbinoGeek
Copy link
Author

AlbinoGeek commented Aug 12, 2020

The following code:

target, _ := url.Parse("https://my.domain/")
go host.NewRedirection("0.0.0.0:8080", target, iris.StatusMovedPermanently).ListenAndServe()
app.Run(iris.AutoTLS("0.0.0.0:8443", "my.domain", "[email protected]", iris.TLSNoRedirect), iris.WithSocketSharding)

Results in:

...
Now listening on: http://localhost:8443
Application started. Press CTRL+C to shut down.

[HTTP Server] http: TLS handshake error from 192.168.0.1:57048: acme/autocert: unable to satisfy "https://acme-v02.api.letsencrypt.org/acme/authz-v3/REDACTED" for domain "my.domain": no viable challenge type found
[HTTP Server] http: TLS handshake error from 34.209.232.166:53742: acme/autocert: missing certificate
[HTTP Server] http: TLS handshake error from 192.168.0.1:57050: acme/autocert: missing certificate
[HTTP Server] http: TLS handshake error from 64.78.149.164:44484: acme/autocert: missing certificate
[HTTP Server] http: TLS handshake error from 192.168.0.1:57054: acme/autocert: missing certificate
[HTTP Server] http: TLS handshake error from 52.28.236.88:11904: acme/autocert: missing certificate
[HTTP Server] http: TLS handshake error from 192.168.0.1:57060: acme/autocert: missing certificate
[HTTP Server] http: TLS handshake error from 192.168.0.1:57056: acme/autocert: missing certificate
[HTTP Server] http: TLS handshake error from 192.168.0.1:57058: acme/autocert: missing certificate

That's a partial fix at least, the redirect "worked", but ACME failed.

Also worth noting, I can't create a bug upstream because golang/crypto doesn't have issues.


By the way, this above code didn't bind on port :80 -- nvm, saw your message

@kataras
Copy link
Owner

kataras commented Aug 12, 2020

@AlbinoGeek That's a letsencrypt issue and wouldn't work on raw net/http too. It tells you that for domain "my.domain": no viable challenge type found.

By the way, this above code didn't bind on port :80

Yes, it didn't bind to :80 because you passed the iris.TLSNoRedirect correctly this time :D

which leads me to believe that's not strictly an upstream issue, if changing the using code makes it not do so.

Yes it's not upstream thing, I've edited my comment like 10 seconds later, see above.

@kataras
Copy link
Owner

kataras commented Aug 12, 2020

OK now we solve that this is not an Iris problem and I can rest in peace, let's see how we can help you on that, do you prefer continueing that through a private room on our chat?

@AlbinoGeek
Copy link
Author

Effectively that "solves" the original post ask, but still no viable challenge type found does not make a usable LetsEncrypt implementation. What causes this / what do I do about it?

I know LetsEncrypt supports my domain/computer because nginx and certbot works, but AutoTLS does not.

And yes, I'll sign on to the chat.

@kataras
Copy link
Owner

kataras commented Aug 12, 2020

Yes I had some issues with letsencrypt too, had to pass a real domain that directly binds to my remote computer, if you have a free domain that we can bind to one of my remote machines, we can run it together and see that it's working without erorrs.

@AlbinoGeek
Copy link
Author

I'm on chat. I've been trying to do this using real domains, just don't want them showing up here (SEO and such, an unreleased product, etc.) And yes, I have some other domains we can test with as well.

@kataras
Copy link
Owner

kataras commented Aug 12, 2020

That's fine, I know that I was being there. That's why I asked for a private room :) I am in.

@AlbinoGeek
Copy link
Author

AlbinoGeek commented Aug 12, 2020

For those that might find this issue later, here is the current solution:

// Use your externally accessible HTTPS port here
target, _ := url.Parse("https://my.domain/")

// Use your internally accessible HTTP port here
go host.NewRedirection("0.0.0.0:8080", target, iris.StatusMovedPermanently).ListenAndServe()

// Use your internally accessible HTTPS port here
app.Run(iris.AutoTLS("0.0.0.0:8443", "my.domain", "[email protected]", iris.TLSNoRedirect))

// Note however, because of golang/crypto's implementation of certmanager,
// it is NOT currently possible to change the external port 80.

I will PR this into an example shortly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants