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

By default block service proxy to external IP addresses. #57265

Merged
merged 1 commit into from
Jan 24, 2018

Conversation

brendandburns
Copy link
Contributor

@brendandburns brendandburns commented Dec 16, 2017

What this PR does / why we need it:
Currently, the Service Proxy on the APIServer allows unrestricted access to any IP address that the APIServer machine can reach. This is likely undesirable in many cases.

Update the service proxy so that it filters Endpoints to only those that have a TargetRef that matches a known Pod.

Fixes #58761

Release note:

Access to externally managed IP addresses via the kube-apiserver service proxy subresource is no longer allowed by default. This can be re-enabled via the `ServiceProxyAllowExternalIPs` feature gate, but will be disallowed completely in 1.11

@k8s-ci-robot k8s-ci-robot added do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. release-note Denotes a PR that will be considered when it comes time to generate release notes. size/L Denotes a PR that changes 100-499 lines, ignoring generated files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. labels Dec 16, 2017
@brendandburns brendandburns force-pushed the svc-proxy branch 2 times, most recently from d58ab68 to 20bee87 Compare January 9, 2018 17:59
@@ -81,9 +82,13 @@ func NewStorage(registry Registry, endpoints endpoint.Registry, serviceIPs ipall
serviceNodePorts: serviceNodePorts,
proxyTransport: proxyTransport,
}
redirectOnly := true
if utilfeature.DefaultFeatureGate.Enabled(features.ServiceProxyAllowExternalIPs) {
redirectOnly = false
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure we can assume everything talking to this endpoint will follow redirects

opts := &metainternalversion.ListOptions{
LabelSelector: labels.Set(svc.Spec.Selector).AsSelector(),
}
obj, err = r.pods.List(r.ctx, opts)
Copy link
Member

Choose a reason for hiding this comment

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

this doesn't take readiness/availability into account, right?

}
ix := rand.Intn(len(podList.Items))
pod := podList.Items[ix]
path := fmt.Sprintf("/apis/v1/namespaces/%s/pods/%s/proxy%s", pod.Namespace, pod.Name, r.path)
Copy link
Member

Choose a reason for hiding this comment

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

this drops port and protocol info (see logic in service REST#ResourceLocation):

svcScheme, svcName, portStr, valid := utilnet.SplitSchemeNamePort(id)

@liggitt
Copy link
Member

liggitt commented Jan 9, 2018

rather than switching to a redirect, it seems less disruptive to require the selected endpoint address to actually refer to a real pod in the same namespace with a matching IP

// Pick a random address.
ip := ss.Addresses[rand.Intn(len(ss.Addresses))].IP
port := int(ss.Ports[i].Port)
return &url.URL{
Scheme: svcScheme,
Host: net.JoinHostPort(ip, strconv.Itoa(port)),
}, rs.proxyTransport, nil

At that point, require the selected Address to have a targetRef, look up the pod, make sure it exists and has a matching IP. Something like:

// Pick a random address.
addr := ss.Addresses[rand.Intn(len(ss.Addresses))]

if noExternalAddresses {
	ns := genericapirequest.NamespaceFrom(ctx)
	if addr.TargetRef == nil {
		return nil, nil, errors.NewServiceUnavailable("...")
	}
	if addr.TargetRef.Kind != "Pod" {
		return nil, nil, errors.NewServiceUnavailable("...")
	}
	if addr.TargetRef.Namespace == "" || addr.TargetRef.Namespace != ns {
		return nil, nil, errors.NewServiceUnavailable("...")
	}
	pod, err := rs.pods.GetPod(ctx, addr.TargetRef.Name)
	if err != nil {
		return nil, nil, errors.NewServiceUnavailable("...")
	}
	if pod.Status.PodIP != addr.IP {
		return nil, nil, errors.NewServiceUnavailable("...")
	}
}

ip := addr.IP
port := int(ss.Ports[i].Port)
return &url.URL{
	Scheme: svcScheme,
	Host:   net.JoinHostPort(ip, strconv.Itoa(port)),
}, rs.proxyTransport, nil

@brendandburns
Copy link
Contributor Author

brendandburns commented Jan 9, 2018

@liggitt thanks for the feedback, I'll address the readiness and port/scheme comments.

Regarding redirect vs. validation, my concern is that as it is currently constructed, the proxy is enabling the end user to cloak their requests with the APIServer's IP address. That makes me fairly uncomfortable from an audit/network ACL perspective.

The only valid way to access the /proxy endpoint is via web sockets/spdy right shouldn't every web sockets/spdy client be able to handle a redirect?

Also, in a world where masters are network partitioned from workers, except for master -> kubelet communication, requiring masters to be able to directly communicate with Service/Pod IPs opens another potentially vulnerable network path.

@liggitt
Copy link
Member

liggitt commented Jan 9, 2018

The only valid way to access the /proxy endpoint is via web sockets/spdy right shouldn't every web sockets/spdy client be able to handle a redirect?

No, any http request is valid, and many http libraries don't follow redirects automatically.

@liggitt
Copy link
Member

liggitt commented Jan 9, 2018

the proxy is enabling the end user to cloak their requests with the APIServer's IP address. That makes me fairly uncomfortable from an audit/network ACL perspective.

if that is the issue, then this PR doesn't really help... it's just redirecting to the pods/proxy API endpoint, which still goes through the API server and cloaks the request with the APIServer's IP address, right?

@brendandburns
Copy link
Contributor Author

The difference is that the pod proxy path tunnels things to the kubelet rather than communicating with the IP address directly. My concern isn't around losing the client IP address (that is going to happen either way as you suggest) but my concern is that the request is coming from the APIServer's IP address which is much more likely to have special permissions in network configuration rules.

Also, since the path to the kubelet is an HTTP call, there's a much better audit trail than making a random IP request from the APIServer's network namespace.

Basically, as currently written this code enables an arbitrary pod to appear to be traffic from the API server, that doesn't make me super comfortable.

@brendandburns
Copy link
Contributor Author

also wrt to clients following redirects, sure, that's true of course, but if your client isn't handling redirects, you're probably already in a world of hurt (e.g. OAuth relies on redirects), so it doesn't seem that bad to me to say "if you want proxy, you need to handle redirects" Proxy isn't core functionality imho, so adding some limitations doesn't seem that bad.

@liggitt
Copy link
Member

liggitt commented Jan 10, 2018

The difference is that the pod proxy path tunnels things to the kubelet rather than communicating with the IP address directly.

Actually, the service proxy subresource communicates with the IP the same way the pod proxy subresource does (see the transport it is given to use, which is the same transport the pod proxy uses). Requiring the resolved IP to be a pod IP seems sufficient to me, and gets you identical protection, without changing behavior to redirect.

@brendandburns
Copy link
Contributor Author

I'll take a look at the transport, if it is indeed tunnelling to the kubelet, then I agree that validation seems reasonable to me.

@brendandburns
Copy link
Contributor Author

Hrm, as far as I read they both just use the default networking. I don't really like either of those... I swear the pod proxy tunneled to kubelet sigh I agree it's no worse that what would happen with a redirect to the Pod, so I'll just do the Pod IP validation...

But I think we should also change the default so that we don't drive from the APIServer's IP address...

@brendandburns
Copy link
Contributor Author

brendandburns commented Jan 10, 2018

@liggitt re-factored as you suggested to just filter based on Pod TargetRef.

Please take another look.

@brendandburns brendandburns removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Jan 10, 2018
@brendandburns brendandburns changed the title [WIP] By default block service proxy to external IP addresses. By default block service proxy to external IP addresses. Jan 10, 2018
@@ -419,6 +422,9 @@ func (rs *REST) ResourceLocation(ctx genericapirequest.Context, id string) (*url
if err != nil {
return nil, nil, err
}
if !utilfeature.DefaultFeatureGate.Enabled(features.ServiceProxyAllowExternalIPs) {
Copy link
Member

Choose a reason for hiding this comment

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

seems like we'd want to do the check on a particular address inside the loop, after we've verified the subset even has the port we want, since it involves a remote call

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do you think it really matters? How many times is there an endpoint that doesn't match the port for the service?

(and is Proxy really a performance-sensitive call?)

Copy link
Member

Choose a reason for hiding this comment

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

if you have a service backed by 400 pods (c.f.#58050 ), this would make it do 400 pod lookups as a pre-filter before selecting a single one and using it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's not the point, because you're going to pay that cost no matter what. My point was: how many services do you not pay it either way?

Copy link
Member

Choose a reason for hiding this comment

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

If you find a suitable endpoint address first, then verify the pod as a second step, in the normal case, you only look up one pod, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, I see your approach, I can restructure the code in that way, its more complicated then I was thinking...

@brendandburns
Copy link
Contributor Author

/retest

1 similar comment
@brendandburns
Copy link
Contributor Author

/retest

@brendandburns brendandburns force-pushed the svc-proxy branch 2 times, most recently from 7d96855 to 02f335b Compare January 18, 2018 06:05
@brendandburns
Copy link
Contributor Author

/retest

@liggitt
Copy link
Member

liggitt commented Jan 23, 2018

Name is fine with me, are there others who need to approve?

Not sure. Is the intent to deprecate and default off this "feature" eventually (I'd be in favor of that), leaving the feature gate as an escape hatch for people who still wanted this behavior during the deprecation period? If so, I wonder if we'd want the feature gate to sound more perjorative.

Just noticed the release-note isn't accurate... it's not defaulted off yet.

@brendandburns
Copy link
Contributor Author

Yes, my plan is to eventually remove the "feature" since it is a bug imho, but I want to leave an escape hatch for at least one release for people whom it breaks.

I believe that the release note is correct.

The feature is "allow external ips" the default for features is false afaik, and thus the note:

By default disable access to external IP addresses from the apiserver service proxy.

Appears to be correct?

@liggitt
Copy link
Member

liggitt commented Jan 23, 2018

Yes, my plan is to eventually remove the "feature" since it is a bug imho

I'd agree

The feature is "allow external ips" the default for features is false afaik, and thus the note:

By default disable access to external IP addresses from the apiserver service proxy.

Appears to be correct?

Ah, you're right, though I think you need to add it to the defaultKubernetesFeatureGates so it can actually be set. Not sure what the designation is... sort of need a "DEPRECATED" state.

Service proxy uses redirects to Pods instead of direct access.
@brendandburns
Copy link
Contributor Author

@liggitt added to defaultKubernetesFeatureGates default false. Added a new section for 'backwards compatability' features.

ptal.

Thanks

@liggitt
Copy link
Member

liggitt commented Jan 24, 2018

thanks

/lgtm

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Jan 24, 2018
@brendandburns
Copy link
Contributor Author

/retest

@brendandburns brendandburns added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Jan 24, 2018
@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: brendandburns, liggitt

Associated issue: #58761

The full list of commands accepted by this bot can be found here.

Needs approval from an approver in each of these OWNERS Files:

You can indicate your approval by writing /approve in a comment
You can cancel your approval by writing /approve cancel in a comment

@brendandburns brendandburns added this to the v1.10 milestone Jan 24, 2018
@k8s-github-robot
Copy link

/test all [submit-queue is verifying that this PR is safe to merge]

@k8s-github-robot
Copy link

Automatic merge from submit-queue. If you want to cherry-pick this change to another branch, please follow the instructions here.

@k8s-github-robot k8s-github-robot merged commit a5c4630 into kubernetes:master Jan 24, 2018
k8s-github-robot pushed a commit that referenced this pull request Jan 27, 2018
Automatic merge from submit-queue. If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>.

Categorize deprecated feature gate more accurately

related to #58761

follow up from #57265 to clarify the status of the feature gate

```release-note
NONE
```
k8s-publishing-bot added a commit to kubernetes/apiserver that referenced this pull request Jan 27, 2018
Automatic merge from submit-queue. If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>.

Categorize deprecated feature gate more accurately

related to #58761

follow up from kubernetes/kubernetes#57265 to clarify the status of the feature gate

```release-note
NONE
```

Kubernetes-commit: e8225f5618d7bf9251115b9a8be689175bbed52f
k8s-github-robot pushed a commit that referenced this pull request Jan 30, 2018
…#58878-upstream-release-1.8

Automatic merge from submit-queue.

Automated cherry pick of #57265: By default block service proxy to external IP addresses.

#58878: Add deprecated stage of feature gates

Cherry pick of #57265 #58878 on release-1.8.

#57265: By default block service proxy to external IP addresses.
#58878: Add deprecated stage of feature gates

```release-note
Access to externally managed IP addresses via the kube-apiserver service proxy subresource is no longer allowed by default. This can be re-enabled via the `ServiceProxyAllowExternalIPs` feature gate, but will be disallowed completely in 1.11
```
k8s-github-robot pushed a commit that referenced this pull request Feb 1, 2018
…#58878-upstream-release-1.9

Automatic merge from submit-queue.

Automated cherry pick of #57265: By default block service proxy to external IP addresses.

#58878: Add deprecated stage of feature gates

Cherry pick of #57265 #58878 on release-1.9.

#57265: By default block service proxy to external IP addresses.
#58878: Add deprecated stage of feature gates

```release-note
Access to externally managed IP addresses via the kube-apiserver service proxy subresource is no longer allowed by default. This can be re-enabled via the `ServiceProxyAllowExternalIPs` feature gate, but will be disallowed completely in 1.11
```
k8s-github-robot pushed a commit that referenced this pull request Feb 3, 2018
…#58878-upstream-release-1.7

Automatic merge from submit-queue.

Automated cherry pick of #57265: By default block service proxy to external IP addresses.

#58878: Add deprecated stage of feature gates

Cherry pick of #57265 #58878 on release-1.7.

#57265: By default block service proxy to external IP addresses.
#58878: Add deprecated stage of feature gates

```release-note
Access to externally managed IP addresses via the kube-apiserver service proxy subresource is no longer allowed by default. This can be re-enabled via the `ServiceProxyAllowExternalIPs` feature gate, but will be disallowed completely in 1.11
```
@mingfang
Copy link

I don't agree this is a bug and in fact it's a feature I depend on.
After you deprecate ServiceProxyAllowExternalIPs in 1.11, then what is the alternative replacement for this feature?

@ChikkannaSuhas
Copy link

ChikkannaSuhas commented Jun 4, 2018

1). Does this feature block an External Service(or IP), that I am trying to access such as my database that is outside the kubernetes cluster.

2). Also, if we are trying to protect the access of services, present in another namespace. Cannot the same result be achieved by NetworkPolicy objects or even RBAC/ABAC for that matter, instead of introducing the above feature in this PR ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. lgtm "Looks good to me", indicates that a PR is ready to be merged. release-note Denotes a PR that will be considered when it comes time to generate release notes. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants