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

*: mitigate DNS rebinding attacks in insecure etcd server #9372

Merged
merged 9 commits into from
Mar 1, 2018

Conversation

gyuho
Copy link
Contributor

@gyuho gyuho commented Feb 28, 2018

Fix #9353.

Reference

This works

make docker-dns-test-insecure-run -f ./hack/scripts-dev/Makefile

Will add more documentation once reviewed.

/cc @mitake @zelivans @ericchiang

@ericchiang
Copy link
Contributor

ericchiang commented Feb 28, 2018

For better or for worse I think defaulting to only loopback address is going to break a lot of people.

Since we can't really guarantee anything when auth is disabled, does it make sense to only enable this when --host-whitelist is provided? This seems like a useful feature even when auth is enabled anyway.

@ericchiang
Copy link
Contributor

ericchiang commented Feb 28, 2018

And just to clarify, CVE-2018-5702 isn't about etcd, so it probably shouldn't be the issue title unless you like giving people heart attacks :)

@gyuho gyuho changed the title CVE-2018-5702: mitigate DNS rebinding attacks in insecure server *: mitigate DNS rebinding attacks in insecure server Feb 28, 2018
@gyuho gyuho changed the title *: mitigate DNS rebinding attacks in insecure server *: mitigate DNS rebinding attacks in insecure etcd server Feb 28, 2018
@gyuho
Copy link
Contributor Author

gyuho commented Feb 28, 2018

Also, as @ericchiang pointed out, this might break a lot of etcd clusters with no auth.
Will make host-whiltelist optional (default value to * to allow any hostnames).

@codecov-io
Copy link

codecov-io commented Feb 28, 2018

Codecov Report

❗ No coverage uploaded for pull request base (master@f0eb772). Click here to learn what that means.
The diff coverage is 50%.

Impacted file tree graph

@@            Coverage Diff            @@
##             master    #9372   +/-   ##
=========================================
  Coverage          ?   72.46%           
=========================================
  Files             ?      362           
  Lines             ?    30779           
  Branches          ?        0           
=========================================
  Hits              ?    22303           
  Misses            ?     6851           
  Partials          ?     1625
Impacted Files Coverage Δ
etcdserver/config.go 79.8% <ø> (ø)
embed/config.go 60.71% <100%> (ø)
embed/serve.go 64.23% <31.57%> (ø)
embed/etcd.go 67.32% <40%> (ø)
etcdserver/server.go 78.69% <60%> (ø)
pkg/httputil/httputil.go 80% <71.42%> (ø)
etcdmain/config.go 81.11% <71.42%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update f0eb772...9f0027d. Read the comment docs.

embed/serve.go Outdated
// https://github.com/transmission/transmission/pull/468
func errCVE20185702(host string) string {
return fmt.Sprintf(`
etcd received your request, but the hostname was unrecognized.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"but the Host header was unrecognized"

embed/serve.go Outdated
// connection is not secure, or auth is not enabled
if req.TLS == nil || !m.s.AuthStore().IsAuthEnabled() {
// allow if host is in whitelist
ok = m.s.IsHostWhitelisted(host)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We still want to enforce a Host whitelist even if auth is enabled, right? I'd just recommend

if !m.s.IsHostWhitelisted("*") && !m.s.IsHostWhitelisted(host) {
    // error
}

embed/serve.go Outdated
host := netutil.GetHost(req.Host)

// allow if loopback address or "*"
ok := netutil.IsLoopback(host) || m.s.IsHostWhitelisted("*")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd recommend dropping the Loopback check. If you've gone through the trouble of specifying a Host header whitelist, then you don't care about loopback (plus non-browser clients can arbitrarily set whatever Host header they want).

Copy link
Contributor Author

@gyuho gyuho Feb 28, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right. I was first replicating the logic in transmission/transmission#468, but this can be simplified as you suggested. Will rework on this.

// and only returns the host in the address.
func GetHost(addr string) (host string) {
addr = strings.TrimSpace(addr)
u, _ := url.Parse(addr)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think http.Request.Host can be a URL. It can only be a host name or "host:port"


// GetHost removes all schemes and ports from address
// and only returns the host in the address.
func GetHost(addr string) (host string) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: GetHost should probably take an http.Request so it's clearer what string it's acting on.

@@ -251,6 +251,9 @@ type EtcdServer struct {

leadTimeMu sync.RWMutex
leadElectedTime time.Time

hostWhitelistMu sync.RWMutex
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: don't need the mutex because you're only reading from the map?

embed/serve.go Outdated

// enforce client origin (CVE-2018-5702)
// https://bugs.chromium.org/p/project-zero/issues/detail?id=1447#c2
if req != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can the request be nil? Should it be rejected if there's a Host header whitelist?

@gyuho
Copy link
Contributor Author

gyuho commented Feb 28, 2018

@ericchiang All addressed. PTAL. Thanks!


`etcdserver.ServerConfig` has added a new field `HostWhitelist` and its default value is empty. If empty and etcd server does not enable TLS, all HTTP client requests will be rejected. This is to prevent [DNS Rebinding](https://en.wikipedia.org/wiki/DNS_rebinding) [attacks](https://bugs.chromium.org/p/project-zero/issues/detail?id=1447). Set it to `"*"` to allow all hostnames.

Before and after (e.g. [k8s.io/kubernetes/test/e2e_node/services/etcd.go](https://github.com/kubernetes/kubernetes/blob/release-1.10/test/e2e_node/services/etcd.go#L78-L81))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jpbetz FYI, this will be breaking change for Kubernetes testing with etcd v3.4.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we avoid this by having an empty HostWhiteList mean "allow all" instead of []string{"*"}.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ericchiang Good idea. Then, we won't have to worry about breaking changes. Will change.

Copy link
Contributor

@ericchiang ericchiang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just nits, other than that lgtm.

Thanks for getting this done!

embed/serve.go Outdated
@@ -265,9 +268,37 @@ func (m *httpWrapper) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if req != nil && req.URL != nil && strings.HasPrefix(req.URL.Path, "/v3beta/") {
req.URL.Path = strings.Replace(req.URL.Path, "/v3beta/", "/v3/", 1)
}

// client connection is insecure, then enforce client origin policy
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: outdated comment

host, _, err := net.SplitHostPort(req.Host)
if host == "" &&
err != nil &&
strings.Contains(err.Error(), "missing port in address") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

strings.Contains? hmmm

Seems fine since there's pretty thorough testing, but this definitely isn't best practice.

return ""
}
host, _, err := net.SplitHostPort(req.Host)
if host == "" &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

err != nil should probably indicate that any host value returned is invalid, right? Why not just

h, _, err := net.SplitHostPort(req.Host)
if err != nil {
    return req.Host
}
return h

Signed-off-by: Gyuho Lee <gyuhox@gmail.com>
Signed-off-by: Gyuho Lee <gyuhox@gmail.com>
Signed-off-by: Gyuho Lee <gyuhox@gmail.com>
Signed-off-by: Gyuho Lee <gyuhox@gmail.com>
Signed-off-by: Gyuho Lee <gyuhox@gmail.com>
Signed-off-by: Gyuho Lee <gyuhox@gmail.com>
Signed-off-by: Gyuho Lee <gyuhox@gmail.com>
Signed-off-by: Gyuho Lee <gyuhox@gmail.com>
@gyuho
Copy link
Contributor Author

gyuho commented Mar 1, 2018

@ericchiang All addressed. Now Host whitelist is empty by default, to allow all hostnames.

Merging in greens. Thanks for review!

Signed-off-by: Gyuho Lee <gyuhox@gmail.com>
@gyuho gyuho merged commit a7e5790 into etcd-io:master Mar 1, 2018
@gyuho gyuho deleted the origin branch March 1, 2018 04:38
@ericchiang
Copy link
Contributor

Awesome thanks!

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

Successfully merging this pull request may close these issues.

3 participants