2018-12-10 13:15:26 +10:00
|
|
|
// Copyright 2015 Matthew Holt
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
package certmagic
|
|
|
|
|
|
|
|
import (
|
|
|
|
"net/http"
|
2024-04-13 07:41:19 +10:00
|
|
|
"net/url"
|
2018-12-10 13:15:26 +10:00
|
|
|
"strings"
|
|
|
|
|
2024-04-09 06:05:43 +10:00
|
|
|
"github.com/mholt/acmez/v2/acme"
|
2020-07-30 11:38:12 +10:00
|
|
|
"go.uber.org/zap"
|
2018-12-10 13:15:26 +10:00
|
|
|
)
|
|
|
|
|
|
|
|
// HTTPChallengeHandler wraps h in a handler that can solve the ACME
|
|
|
|
// HTTP challenge. cfg is required, and it must have a certificate
|
|
|
|
// cache backed by a functional storage facility, since that is where
|
|
|
|
// the challenge state is stored between initiation and solution.
|
|
|
|
//
|
Rewrite HTTP and TLS-ALPN solvers; always use our own solvers
This solves several issues related to solving for multiple names
concurrently. The basic idea is that now we always use our own solvers,
which is actually much simpler. We just wrap them in a distributedSolver
which writes the keyAuth material to storage. Our solvers then proceed
to solve the challenges: either by allowing whatever is currently
listening on the challenge port to solve it, or by starting their own
servers. Our solvers keep track of how many challenges each solver is
answering, and the "last one out turns off the lights" so to speak.
Also, where we used to try dialing a port then listening if it was
available, now we just try listening, and if it fails, we make sure it
is in use by dialing it. I've added locking around this as well to
ensure that races for the socket, along with the counters, do not happen.
Overall, this is a much improved solver implementation that can handle
more use cases at a larger scale than before.
Also, a minor data race was revealed in user.go, which only happens in
some rare edge cases as far as I can tell, but I marked them with a TODO
so we can get around to fixing them later.
2020-02-06 10:23:13 +10:00
|
|
|
// If a request is not an ACME HTTP challenge, h will be invoked.
|
2022-03-25 03:34:31 +10:00
|
|
|
func (am *ACMEIssuer) HTTPChallengeHandler(h http.Handler) http.Handler {
|
2018-12-10 13:15:26 +10:00
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
Major refactor to improve performance, correctness, and extensibility
Breaking changes; thank goodness we're not 1.0 yet 😅 - read on!
This change completely separates ACME-specific code from the rest of the
certificate management process, allowing pluggable sources for certs
that aren't ACME.
Notably, most of Config was spliced into ACMEManager. Similarly, there's
now Default and DefaultACME.
Storage structure had to be reconfigured. Certificates are no longer in
the acme/ subfolder since they can be obtained by ways other than ACME!
Certificates moved to a new certificates/ subfolder. The subfolders in
that folder use the path of the ACME endpoint instead of just the host,
so that also changed. Be aware that unless you move your certs over,
CertMagic will not find them and will attempt to get new ones. That is
usually fine for most users, but for extremely large deployments, you
will want to move them over first.
Old certs path:
acme/acme-staging-v02.api.letsencrypt.org/...
New certs path:
certificates/acme-staging-v02.api.letsencrypt.org-directory/...
That's all for significant storage changes!
But this refactor also vastly improves performance, especially at scale,
and makes CertMagic way more resilient to errors. Retries are done on
the staging endpoint by default, so they won't count against your rate
limit. If your hardware can handle it, I'm now pretty confident that you
can give CertMagic a million domain names and it will gracefully manage
them, as fast as it can within internal and external rate limits, even
in the presence of errors. Errors will of course slow some things down,
but you should be good to go if you're monitoring logs and can fix any
misconfigurations or other external errors!
Several other mostly-minor enhancements fix bugs, especially at scale.
For example, duplicated renewal tasks (that continuously fail) will not
pile up on each other: only one will operate, under exponential backoff.
Closes #50 and fixes #55
2020-02-22 07:32:57 +10:00
|
|
|
if am.HandleHTTPChallenge(w, r) {
|
2018-12-10 13:15:26 +10:00
|
|
|
return
|
|
|
|
}
|
|
|
|
h.ServeHTTP(w, r)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
Major refactor to improve performance, correctness, and extensibility
Breaking changes; thank goodness we're not 1.0 yet 😅 - read on!
This change completely separates ACME-specific code from the rest of the
certificate management process, allowing pluggable sources for certs
that aren't ACME.
Notably, most of Config was spliced into ACMEManager. Similarly, there's
now Default and DefaultACME.
Storage structure had to be reconfigured. Certificates are no longer in
the acme/ subfolder since they can be obtained by ways other than ACME!
Certificates moved to a new certificates/ subfolder. The subfolders in
that folder use the path of the ACME endpoint instead of just the host,
so that also changed. Be aware that unless you move your certs over,
CertMagic will not find them and will attempt to get new ones. That is
usually fine for most users, but for extremely large deployments, you
will want to move them over first.
Old certs path:
acme/acme-staging-v02.api.letsencrypt.org/...
New certs path:
certificates/acme-staging-v02.api.letsencrypt.org-directory/...
That's all for significant storage changes!
But this refactor also vastly improves performance, especially at scale,
and makes CertMagic way more resilient to errors. Retries are done on
the staging endpoint by default, so they won't count against your rate
limit. If your hardware can handle it, I'm now pretty confident that you
can give CertMagic a million domain names and it will gracefully manage
them, as fast as it can within internal and external rate limits, even
in the presence of errors. Errors will of course slow some things down,
but you should be good to go if you're monitoring logs and can fix any
misconfigurations or other external errors!
Several other mostly-minor enhancements fix bugs, especially at scale.
For example, duplicated renewal tasks (that continuously fail) will not
pile up on each other: only one will operate, under exponential backoff.
Closes #50 and fixes #55
2020-02-22 07:32:57 +10:00
|
|
|
// HandleHTTPChallenge uses am to solve challenge requests from an ACME
|
2018-12-10 13:15:26 +10:00
|
|
|
// server that were initiated by this instance or any other instance in
|
Major refactor to improve performance, correctness, and extensibility
Breaking changes; thank goodness we're not 1.0 yet 😅 - read on!
This change completely separates ACME-specific code from the rest of the
certificate management process, allowing pluggable sources for certs
that aren't ACME.
Notably, most of Config was spliced into ACMEManager. Similarly, there's
now Default and DefaultACME.
Storage structure had to be reconfigured. Certificates are no longer in
the acme/ subfolder since they can be obtained by ways other than ACME!
Certificates moved to a new certificates/ subfolder. The subfolders in
that folder use the path of the ACME endpoint instead of just the host,
so that also changed. Be aware that unless you move your certs over,
CertMagic will not find them and will attempt to get new ones. That is
usually fine for most users, but for extremely large deployments, you
will want to move them over first.
Old certs path:
acme/acme-staging-v02.api.letsencrypt.org/...
New certs path:
certificates/acme-staging-v02.api.letsencrypt.org-directory/...
That's all for significant storage changes!
But this refactor also vastly improves performance, especially at scale,
and makes CertMagic way more resilient to errors. Retries are done on
the staging endpoint by default, so they won't count against your rate
limit. If your hardware can handle it, I'm now pretty confident that you
can give CertMagic a million domain names and it will gracefully manage
them, as fast as it can within internal and external rate limits, even
in the presence of errors. Errors will of course slow some things down,
but you should be good to go if you're monitoring logs and can fix any
misconfigurations or other external errors!
Several other mostly-minor enhancements fix bugs, especially at scale.
For example, duplicated renewal tasks (that continuously fail) will not
pile up on each other: only one will operate, under exponential backoff.
Closes #50 and fixes #55
2020-02-22 07:32:57 +10:00
|
|
|
// this cluster (being, any instances using the same storage am does).
|
2018-12-10 13:15:26 +10:00
|
|
|
//
|
|
|
|
// If the HTTP challenge is disabled, this function is a no-op.
|
|
|
|
//
|
Major refactor to improve performance, correctness, and extensibility
Breaking changes; thank goodness we're not 1.0 yet 😅 - read on!
This change completely separates ACME-specific code from the rest of the
certificate management process, allowing pluggable sources for certs
that aren't ACME.
Notably, most of Config was spliced into ACMEManager. Similarly, there's
now Default and DefaultACME.
Storage structure had to be reconfigured. Certificates are no longer in
the acme/ subfolder since they can be obtained by ways other than ACME!
Certificates moved to a new certificates/ subfolder. The subfolders in
that folder use the path of the ACME endpoint instead of just the host,
so that also changed. Be aware that unless you move your certs over,
CertMagic will not find them and will attempt to get new ones. That is
usually fine for most users, but for extremely large deployments, you
will want to move them over first.
Old certs path:
acme/acme-staging-v02.api.letsencrypt.org/...
New certs path:
certificates/acme-staging-v02.api.letsencrypt.org-directory/...
That's all for significant storage changes!
But this refactor also vastly improves performance, especially at scale,
and makes CertMagic way more resilient to errors. Retries are done on
the staging endpoint by default, so they won't count against your rate
limit. If your hardware can handle it, I'm now pretty confident that you
can give CertMagic a million domain names and it will gracefully manage
them, as fast as it can within internal and external rate limits, even
in the presence of errors. Errors will of course slow some things down,
but you should be good to go if you're monitoring logs and can fix any
misconfigurations or other external errors!
Several other mostly-minor enhancements fix bugs, especially at scale.
For example, duplicated renewal tasks (that continuously fail) will not
pile up on each other: only one will operate, under exponential backoff.
Closes #50 and fixes #55
2020-02-22 07:32:57 +10:00
|
|
|
// If am is nil or if am does not have a certificate cache backed by
|
2018-12-10 13:15:26 +10:00
|
|
|
// usable storage, solving the HTTP challenge will fail.
|
|
|
|
//
|
|
|
|
// It returns true if it handled the request; if so, the response has
|
|
|
|
// already been written. If false is returned, this call was a no-op and
|
|
|
|
// the request has not been handled.
|
2022-03-25 03:34:31 +10:00
|
|
|
func (am *ACMEIssuer) HandleHTTPChallenge(w http.ResponseWriter, r *http.Request) bool {
|
Major refactor to improve performance, correctness, and extensibility
Breaking changes; thank goodness we're not 1.0 yet 😅 - read on!
This change completely separates ACME-specific code from the rest of the
certificate management process, allowing pluggable sources for certs
that aren't ACME.
Notably, most of Config was spliced into ACMEManager. Similarly, there's
now Default and DefaultACME.
Storage structure had to be reconfigured. Certificates are no longer in
the acme/ subfolder since they can be obtained by ways other than ACME!
Certificates moved to a new certificates/ subfolder. The subfolders in
that folder use the path of the ACME endpoint instead of just the host,
so that also changed. Be aware that unless you move your certs over,
CertMagic will not find them and will attempt to get new ones. That is
usually fine for most users, but for extremely large deployments, you
will want to move them over first.
Old certs path:
acme/acme-staging-v02.api.letsencrypt.org/...
New certs path:
certificates/acme-staging-v02.api.letsencrypt.org-directory/...
That's all for significant storage changes!
But this refactor also vastly improves performance, especially at scale,
and makes CertMagic way more resilient to errors. Retries are done on
the staging endpoint by default, so they won't count against your rate
limit. If your hardware can handle it, I'm now pretty confident that you
can give CertMagic a million domain names and it will gracefully manage
them, as fast as it can within internal and external rate limits, even
in the presence of errors. Errors will of course slow some things down,
but you should be good to go if you're monitoring logs and can fix any
misconfigurations or other external errors!
Several other mostly-minor enhancements fix bugs, especially at scale.
For example, duplicated renewal tasks (that continuously fail) will not
pile up on each other: only one will operate, under exponential backoff.
Closes #50 and fixes #55
2020-02-22 07:32:57 +10:00
|
|
|
if am == nil {
|
2018-12-10 13:15:26 +10:00
|
|
|
return false
|
|
|
|
}
|
Major refactor to improve performance, correctness, and extensibility
Breaking changes; thank goodness we're not 1.0 yet 😅 - read on!
This change completely separates ACME-specific code from the rest of the
certificate management process, allowing pluggable sources for certs
that aren't ACME.
Notably, most of Config was spliced into ACMEManager. Similarly, there's
now Default and DefaultACME.
Storage structure had to be reconfigured. Certificates are no longer in
the acme/ subfolder since they can be obtained by ways other than ACME!
Certificates moved to a new certificates/ subfolder. The subfolders in
that folder use the path of the ACME endpoint instead of just the host,
so that also changed. Be aware that unless you move your certs over,
CertMagic will not find them and will attempt to get new ones. That is
usually fine for most users, but for extremely large deployments, you
will want to move them over first.
Old certs path:
acme/acme-staging-v02.api.letsencrypt.org/...
New certs path:
certificates/acme-staging-v02.api.letsencrypt.org-directory/...
That's all for significant storage changes!
But this refactor also vastly improves performance, especially at scale,
and makes CertMagic way more resilient to errors. Retries are done on
the staging endpoint by default, so they won't count against your rate
limit. If your hardware can handle it, I'm now pretty confident that you
can give CertMagic a million domain names and it will gracefully manage
them, as fast as it can within internal and external rate limits, even
in the presence of errors. Errors will of course slow some things down,
but you should be good to go if you're monitoring logs and can fix any
misconfigurations or other external errors!
Several other mostly-minor enhancements fix bugs, especially at scale.
For example, duplicated renewal tasks (that continuously fail) will not
pile up on each other: only one will operate, under exponential backoff.
Closes #50 and fixes #55
2020-02-22 07:32:57 +10:00
|
|
|
if am.DisableHTTPChallenge {
|
2018-12-10 13:15:26 +10:00
|
|
|
return false
|
|
|
|
}
|
2019-04-30 00:35:53 +10:00
|
|
|
if !LooksLikeHTTPChallenge(r) {
|
2018-12-10 13:15:26 +10:00
|
|
|
return false
|
|
|
|
}
|
Major refactor to improve performance, correctness, and extensibility
Breaking changes; thank goodness we're not 1.0 yet 😅 - read on!
This change completely separates ACME-specific code from the rest of the
certificate management process, allowing pluggable sources for certs
that aren't ACME.
Notably, most of Config was spliced into ACMEManager. Similarly, there's
now Default and DefaultACME.
Storage structure had to be reconfigured. Certificates are no longer in
the acme/ subfolder since they can be obtained by ways other than ACME!
Certificates moved to a new certificates/ subfolder. The subfolders in
that folder use the path of the ACME endpoint instead of just the host,
so that also changed. Be aware that unless you move your certs over,
CertMagic will not find them and will attempt to get new ones. That is
usually fine for most users, but for extremely large deployments, you
will want to move them over first.
Old certs path:
acme/acme-staging-v02.api.letsencrypt.org/...
New certs path:
certificates/acme-staging-v02.api.letsencrypt.org-directory/...
That's all for significant storage changes!
But this refactor also vastly improves performance, especially at scale,
and makes CertMagic way more resilient to errors. Retries are done on
the staging endpoint by default, so they won't count against your rate
limit. If your hardware can handle it, I'm now pretty confident that you
can give CertMagic a million domain names and it will gracefully manage
them, as fast as it can within internal and external rate limits, even
in the presence of errors. Errors will of course slow some things down,
but you should be good to go if you're monitoring logs and can fix any
misconfigurations or other external errors!
Several other mostly-minor enhancements fix bugs, especially at scale.
For example, duplicated renewal tasks (that continuously fail) will not
pile up on each other: only one will operate, under exponential backoff.
Closes #50 and fixes #55
2020-02-22 07:32:57 +10:00
|
|
|
return am.distributedHTTPChallengeSolver(w, r)
|
2018-12-10 13:15:26 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
// distributedHTTPChallengeSolver checks to see if this challenge
|
|
|
|
// request was initiated by this or another instance which uses the
|
Major refactor to improve performance, correctness, and extensibility
Breaking changes; thank goodness we're not 1.0 yet 😅 - read on!
This change completely separates ACME-specific code from the rest of the
certificate management process, allowing pluggable sources for certs
that aren't ACME.
Notably, most of Config was spliced into ACMEManager. Similarly, there's
now Default and DefaultACME.
Storage structure had to be reconfigured. Certificates are no longer in
the acme/ subfolder since they can be obtained by ways other than ACME!
Certificates moved to a new certificates/ subfolder. The subfolders in
that folder use the path of the ACME endpoint instead of just the host,
so that also changed. Be aware that unless you move your certs over,
CertMagic will not find them and will attempt to get new ones. That is
usually fine for most users, but for extremely large deployments, you
will want to move them over first.
Old certs path:
acme/acme-staging-v02.api.letsencrypt.org/...
New certs path:
certificates/acme-staging-v02.api.letsencrypt.org-directory/...
That's all for significant storage changes!
But this refactor also vastly improves performance, especially at scale,
and makes CertMagic way more resilient to errors. Retries are done on
the staging endpoint by default, so they won't count against your rate
limit. If your hardware can handle it, I'm now pretty confident that you
can give CertMagic a million domain names and it will gracefully manage
them, as fast as it can within internal and external rate limits, even
in the presence of errors. Errors will of course slow some things down,
but you should be good to go if you're monitoring logs and can fix any
misconfigurations or other external errors!
Several other mostly-minor enhancements fix bugs, especially at scale.
For example, duplicated renewal tasks (that continuously fail) will not
pile up on each other: only one will operate, under exponential backoff.
Closes #50 and fixes #55
2020-02-22 07:32:57 +10:00
|
|
|
// same storage as am does, and attempts to complete the challenge for
|
2018-12-10 13:15:26 +10:00
|
|
|
// it. It returns true if the request was handled; false otherwise.
|
2022-03-25 03:34:31 +10:00
|
|
|
func (am *ACMEIssuer) distributedHTTPChallengeSolver(w http.ResponseWriter, r *http.Request) bool {
|
Major refactor to improve performance, correctness, and extensibility
Breaking changes; thank goodness we're not 1.0 yet 😅 - read on!
This change completely separates ACME-specific code from the rest of the
certificate management process, allowing pluggable sources for certs
that aren't ACME.
Notably, most of Config was spliced into ACMEManager. Similarly, there's
now Default and DefaultACME.
Storage structure had to be reconfigured. Certificates are no longer in
the acme/ subfolder since they can be obtained by ways other than ACME!
Certificates moved to a new certificates/ subfolder. The subfolders in
that folder use the path of the ACME endpoint instead of just the host,
so that also changed. Be aware that unless you move your certs over,
CertMagic will not find them and will attempt to get new ones. That is
usually fine for most users, but for extremely large deployments, you
will want to move them over first.
Old certs path:
acme/acme-staging-v02.api.letsencrypt.org/...
New certs path:
certificates/acme-staging-v02.api.letsencrypt.org-directory/...
That's all for significant storage changes!
But this refactor also vastly improves performance, especially at scale,
and makes CertMagic way more resilient to errors. Retries are done on
the staging endpoint by default, so they won't count against your rate
limit. If your hardware can handle it, I'm now pretty confident that you
can give CertMagic a million domain names and it will gracefully manage
them, as fast as it can within internal and external rate limits, even
in the presence of errors. Errors will of course slow some things down,
but you should be good to go if you're monitoring logs and can fix any
misconfigurations or other external errors!
Several other mostly-minor enhancements fix bugs, especially at scale.
For example, duplicated renewal tasks (that continuously fail) will not
pile up on each other: only one will operate, under exponential backoff.
Closes #50 and fixes #55
2020-02-22 07:32:57 +10:00
|
|
|
if am == nil {
|
2018-12-10 13:15:26 +10:00
|
|
|
return false
|
|
|
|
}
|
Rewrite HTTP and TLS-ALPN solvers; always use our own solvers
This solves several issues related to solving for multiple names
concurrently. The basic idea is that now we always use our own solvers,
which is actually much simpler. We just wrap them in a distributedSolver
which writes the keyAuth material to storage. Our solvers then proceed
to solve the challenges: either by allowing whatever is currently
listening on the challenge port to solve it, or by starting their own
servers. Our solvers keep track of how many challenges each solver is
answering, and the "last one out turns off the lights" so to speak.
Also, where we used to try dialing a port then listening if it was
available, now we just try listening, and if it fails, we make sure it
is in use by dialing it. I've added locking around this as well to
ensure that races for the socket, along with the counters, do not happen.
Overall, this is a much improved solver implementation that can handle
more use cases at a larger scale than before.
Also, a minor data race was revealed in user.go, which only happens in
some rare edge cases as far as I can tell, but I marked them with a TODO
so we can get around to fixing them later.
2020-02-06 10:23:13 +10:00
|
|
|
host := hostOnly(r.Host)
|
2022-03-08 05:26:52 +10:00
|
|
|
chalInfo, distributed, err := am.config.getChallengeInfo(r.Context(), host)
|
2018-12-10 13:15:26 +10:00
|
|
|
if err != nil {
|
2024-05-25 03:50:51 +10:00
|
|
|
am.Logger.Warn("looking up info for HTTP challenge",
|
2022-09-27 02:19:28 +10:00
|
|
|
zap.String("host", host),
|
2023-01-05 15:17:18 +10:00
|
|
|
zap.String("remote_addr", r.RemoteAddr),
|
|
|
|
zap.String("user_agent", r.Header.Get("User-Agent")),
|
2022-09-27 02:19:28 +10:00
|
|
|
zap.Error(err))
|
2018-12-10 13:15:26 +10:00
|
|
|
return false
|
|
|
|
}
|
2021-01-24 07:28:02 +10:00
|
|
|
return solveHTTPChallenge(am.Logger, w, r, chalInfo.Challenge, distributed)
|
2018-12-10 13:15:26 +10:00
|
|
|
}
|
|
|
|
|
2021-01-24 07:28:02 +10:00
|
|
|
// solveHTTPChallenge solves the HTTP challenge using the given challenge information.
|
|
|
|
// If the challenge is being solved in a distributed fahsion, set distributed to true for logging purposes.
|
|
|
|
// It returns true the properties of the request check out in relation to the HTTP challenge.
|
|
|
|
// Most of this code borrowed from xenolf's built-in HTTP-01 challenge solver in March 2018.
|
|
|
|
func solveHTTPChallenge(logger *zap.Logger, w http.ResponseWriter, r *http.Request, challenge acme.Challenge, distributed bool) bool {
|
2020-07-28 08:50:41 +10:00
|
|
|
challengeReqPath := challenge.HTTP01ResourcePath()
|
2018-12-10 13:15:26 +10:00
|
|
|
if r.URL.Path == challengeReqPath &&
|
2020-07-28 08:50:41 +10:00
|
|
|
strings.EqualFold(hostOnly(r.Host), challenge.Identifier.Value) && // mitigate DNS rebinding attacks
|
2024-04-13 07:41:19 +10:00
|
|
|
r.Method == http.MethodGet {
|
2018-12-10 13:15:26 +10:00
|
|
|
w.Header().Add("Content-Type", "text/plain")
|
2020-07-28 08:50:41 +10:00
|
|
|
w.Write([]byte(challenge.KeyAuthorization))
|
2018-12-10 13:15:26 +10:00
|
|
|
r.Close = true
|
2022-09-27 02:19:28 +10:00
|
|
|
logger.Info("served key authentication",
|
|
|
|
zap.String("identifier", challenge.Identifier.Value),
|
|
|
|
zap.String("challenge", "http-01"),
|
|
|
|
zap.String("remote", r.RemoteAddr),
|
|
|
|
zap.Bool("distributed", distributed))
|
2018-12-10 13:15:26 +10:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2021-01-24 07:28:02 +10:00
|
|
|
// SolveHTTPChallenge solves the HTTP challenge. It should be used only on HTTP requests that are
|
|
|
|
// from ACME servers trying to validate an identifier (i.e. LooksLikeHTTPChallenge() == true). It
|
|
|
|
// returns true if the request criteria check out and it answered with key authentication, in which
|
|
|
|
// case no further handling of the request is necessary.
|
|
|
|
func SolveHTTPChallenge(logger *zap.Logger, w http.ResponseWriter, r *http.Request, challenge acme.Challenge) bool {
|
|
|
|
return solveHTTPChallenge(logger, w, r, challenge, false)
|
|
|
|
}
|
|
|
|
|
2019-04-30 00:35:53 +10:00
|
|
|
// LooksLikeHTTPChallenge returns true if r looks like an ACME
|
|
|
|
// HTTP challenge request from an ACME server.
|
|
|
|
func LooksLikeHTTPChallenge(r *http.Request) bool {
|
2024-04-13 07:41:19 +10:00
|
|
|
return r.Method == http.MethodGet &&
|
|
|
|
strings.HasPrefix(r.URL.Path, acmeHTTPChallengeBasePath)
|
2019-04-30 00:35:53 +10:00
|
|
|
}
|
|
|
|
|
2024-04-13 07:41:19 +10:00
|
|
|
// LooksLikeZeroSSLHTTPValidation returns true if the request appears to be
|
|
|
|
// domain validation from a ZeroSSL/Sectigo CA. NOTE: This API is
|
|
|
|
// non-standard and is subject to change.
|
|
|
|
func LooksLikeZeroSSLHTTPValidation(r *http.Request) bool {
|
|
|
|
return r.Method == http.MethodGet &&
|
|
|
|
strings.HasPrefix(r.URL.Path, zerosslHTTPValidationBasePath)
|
|
|
|
}
|
|
|
|
|
|
|
|
// HTTPValidationHandler wraps the ZeroSSL HTTP validation handler such that
|
|
|
|
// it can pass verification checks from ZeroSSL's API.
|
|
|
|
//
|
|
|
|
// If a request is not a ZeroSSL HTTP validation request, h will be invoked.
|
|
|
|
func (iss *ZeroSSLIssuer) HTTPValidationHandler(h http.Handler) http.Handler {
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
if iss.HandleZeroSSLHTTPValidation(w, r) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
h.ServeHTTP(w, r)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// HandleZeroSSLHTTPValidation is to ZeroSSL API HTTP validation requests like HandleHTTPChallenge
|
|
|
|
// is to ACME HTTP challenge requests.
|
|
|
|
func (iss *ZeroSSLIssuer) HandleZeroSSLHTTPValidation(w http.ResponseWriter, r *http.Request) bool {
|
|
|
|
if iss == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if !LooksLikeZeroSSLHTTPValidation(r) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return iss.distributedHTTPValidationAnswer(w, r)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (iss *ZeroSSLIssuer) distributedHTTPValidationAnswer(w http.ResponseWriter, r *http.Request) bool {
|
|
|
|
if iss == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
logger := iss.Logger
|
|
|
|
if logger == nil {
|
|
|
|
logger = zap.NewNop()
|
|
|
|
}
|
|
|
|
host := hostOnly(r.Host)
|
|
|
|
valInfo, distributed, err := iss.getDistributedValidationInfo(r.Context(), host)
|
|
|
|
if err != nil {
|
2024-05-25 03:50:51 +10:00
|
|
|
logger.Warn("looking up info for HTTP validation",
|
2024-04-13 07:41:19 +10:00
|
|
|
zap.String("host", host),
|
|
|
|
zap.String("remote_addr", r.RemoteAddr),
|
|
|
|
zap.String("user_agent", r.Header.Get("User-Agent")),
|
|
|
|
zap.Error(err))
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return answerHTTPValidation(logger, w, r, valInfo, distributed)
|
|
|
|
}
|
|
|
|
|
|
|
|
func answerHTTPValidation(logger *zap.Logger, rw http.ResponseWriter, req *http.Request, valInfo acme.Challenge, distributed bool) bool {
|
|
|
|
// ensure URL matches
|
|
|
|
validationURL, err := url.Parse(valInfo.URL)
|
|
|
|
if err != nil {
|
|
|
|
logger.Error("got invalid URL from CA",
|
|
|
|
zap.String("file_validation_url", valInfo.URL),
|
|
|
|
zap.Error(err))
|
|
|
|
rw.WriteHeader(http.StatusInternalServerError)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if req.URL.Path != validationURL.Path {
|
|
|
|
rw.WriteHeader(http.StatusNotFound)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
rw.Header().Add("Content-Type", "text/plain")
|
|
|
|
req.Close = true
|
|
|
|
|
|
|
|
rw.Write([]byte(valInfo.Token))
|
|
|
|
|
|
|
|
logger.Info("served HTTP validation credential",
|
|
|
|
zap.String("validation_path", valInfo.URL),
|
|
|
|
zap.String("challenge", "http-01"),
|
|
|
|
zap.String("remote", req.RemoteAddr),
|
|
|
|
zap.Bool("distributed", distributed))
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
|
|
|
acmeHTTPChallengeBasePath = "/.well-known/acme-challenge"
|
|
|
|
zerosslHTTPValidationBasePath = "/.well-known/pki-validation/"
|
|
|
|
)
|