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

KEP: hardened exec requests #1899

Merged
merged 5 commits into from
Sep 30, 2020
Merged

Conversation

tallclair
Copy link
Member

Harden exec requests against SSRF vulnerabilities. See kubernetes/kubernetes#92914 for motivation.

For #1898.

/sig node api-machinery
/area security

/cc @liggitt @cjcullen @mtaufen

@k8s-ci-robot k8s-ci-robot added sig/node Categorizes an issue or PR as relevant to SIG Node. sig/api-machinery Categorizes an issue or PR as relevant to SIG API Machinery. labels Jul 15, 2020
@k8s-ci-robot
Copy link
Contributor

@tallclair: The label(s) area/security cannot be applied, because the repository doesn't have them

In response to this:

Harden exec requests against SSRF vulnerabilities. See kubernetes/kubernetes#92914 for motivation.

For #1898.

/sig node api-machinery
/area security

/cc @liggitt @cjcullen @mtaufen

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

@k8s-ci-robot k8s-ci-robot added the cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. label Jul 15, 2020
@tallclair tallclair added this to the v1.20 milestone Jul 15, 2020
@k8s-ci-robot k8s-ci-robot added size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. kind/kep Categorizes KEP tracking issues and PRs modifying the KEP directory sig/architecture Categorizes an issue or PR as relevant to SIG Architecture. labels Jul 15, 2020
Comment on lines +141 to +142
The second path format includes the pod's UID, and is never called by the kube-apiserver. We should
remove these endpoints as they're unused, to reduce code complexity.
Copy link
Contributor

Choose a reason for hiding this comment

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

Aside: if you wanted to preserve the UID checking behavior, it might be feasible to make up a conditional header If-UID: and fail requests where that's set and not the same as the running Pod's UID.

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't think it's used anywhere. It would be easy enough to add this approach later if a use case arose.

endpoints](#2-require-a--request-to-the-streaming-endpoints) and [4. Require the request options to
be provided in the request body](#4-require-the-request-options-to-be-provided-in-the-request-body).

#### 3. Require the websocket protocol for GET requests.
Copy link
Contributor

Choose a reason for hiding this comment

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

https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#websockets-and-spdy says:

Clients should use the SPDY protocols if their clients have native support, or WebSockets as a fallback.

In the future, an HTTP/2 implementation will be exposed that deprecates SPDY.

Is that still accurate?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not clear on why some clients (browsers?) aren't able to use POST + WebSocket - it'd be nice to have that clarified.

Copy link
Member

Choose a reason for hiding this comment

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

correct. browsers don't allow controlling the http method when opening a websocket

Copy link
Contributor

@sftim sftim Jul 23, 2020

Choose a reason for hiding this comment

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

I think I get it. If I'm right, the challenge here is that the client sends a single request to the API server that is both an upgrade request and request for a command execution. But I'm not sure I've understood, even after re-reading several times.

I haven't looked at the API here because I don't think there's documentation on how this works. The upgrade to WebSocket includes the exec request in its query string. So the options could include:

  1. Retain current behavior because it's not vulnerable to this attack (?)
  2. POST requesting an exec, API server returns a WebSocket URI, client send a (GET) Upgrade: to open that WebSocket, continue conversation over WebSocket
  3. Client requests a WebSocket, upgrade happens, and then within the WebSocket stream request an exec

I couldn't be sure I've understood what this section is calling for. With that in mind, would you be willing @tallclair to reword this to clarify?

Copy link
Member Author

Choose a reason for hiding this comment

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

  1. It is vulnerable to attack, but by locking it down to only websocket requests, it makes it harder to exploit (especially in environments where websockets aren't used).
  2. This is similar to the approach we previously took for the apiserver <-> CRI communications. I like the idea, but it's also a breaking change. We could skip the caching layer that we used in the CRI implementation by just signing the request parameters (i.e. POST request to get the signed query params, follow up with the GET including the signature)
  3. I'm not familiar enough with the websocket protocol to know how complicated this would be. It's certainly feasible, but as it's also a breaking change I'm not sure how it compares with Role-based access control (RBAC) #2

I'll update the KEP with these alternatives.

Copy link
Member Author

Choose a reason for hiding this comment

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

Updated with these options.

Copy link
Member

Choose a reason for hiding this comment

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

What does a browser do if it is redirected from an https://... endpoint to a wss://... endpoint? Does it open a websocket request?

Copy link
Contributor

Choose a reason for hiding this comment

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

GET (and other HTTP verbs) don't have any meaning for WebSockets, so I think the browser will error out.
(untested)

Copy link
Member Author

Choose a reason for hiding this comment

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

I haven't tested it, but the redirected request would be missing the required headers for the opening handshake, so I assume it wouldn't work. https://tools.ietf.org/html/rfc6455#section-1.3

Copy link
Member Author

Choose a reason for hiding this comment

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

Thinking about this more, I have a strong preference for "alternative 1" (hash of request as a subprotocol). Alternative 2 (exec token request) would require managing a signing key and option 3 (make request over the websocket connection) would be a much larger change. Since headers can't be modified on a redirect, I think the hash approach provides sufficient protection with minimal changes on the client & server side. We just need a well-defined serialized input for the hash function. Then the question is whether we want to tie this to the HardenedExecRequests feature gate.

@knight42
Copy link
Member

@tallclair I just noticed this KEP when I am drafting #1908. Would it help if we make kubectl not follow redirects by default?

@tallclair
Copy link
Member Author

Yes, I think these 2 approaches are complementary, and we should pursue both.

@tallclair
Copy link
Member Author

/assign @liggitt

@derekwaynecarr
Copy link
Member

/assign

I won’t be able to read in depth until next week.

@tallclair tallclair mentioned this pull request Sep 17, 2020
13 tasks
Copy link
Member

@liggitt liggitt left a comment

Choose a reason for hiding this comment

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

a few initial questions

keps/sig-node/1898-hardened-exec/README.md Outdated Show resolved Hide resolved
keps/sig-node/1898-hardened-exec/README.md Outdated Show resolved Hide resolved
keps/sig-node/1898-hardened-exec/README.md Show resolved Hide resolved

- If only body options are provided, use those.
- If only query options are provided, use those.
- If both body and query options are provided, require that they be identical.
Copy link
Member

Choose a reason for hiding this comment

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

does this match current behavior?

Copy link
Member Author

Choose a reason for hiding this comment

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

The current behavior completely ignores the request body. Only the query parameters are used.

endpoints](#2-require-a--request-to-the-streaming-endpoints) and [4. Require the request options to
be provided in the request body](#4-require-the-request-options-to-be-provided-in-the-request-body).

#### 3. Require the websocket protocol for GET requests.
Copy link
Member

Choose a reason for hiding this comment

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

What does a browser do if it is redirected from an https://... endpoint to a wss://... endpoint? Does it open a websocket request?

@mlbiam
Copy link

mlbiam commented Sep 28, 2020

A few thoughts:

  1. Agree that there shouldn't be any redirects followed.
  2. I really like the idea of using a POST with any API call to initiate the request, even if it's breaking. The current method of using a combination of request parameters and headers is really painful to implement. I spent days looking through the code and test cases to try to decipher it.

i can setup a quick test to answer @liggitt question about what browsers will do on a redirect from a GET to wss://. My guess is it will just follow it.

@tallclair
Copy link
Member Author

Thanks @mlbiam !

i can setup a quick test to answer @liggitt question about what browsers will do on a redirect from a GET to wss://. My guess is it will just follow it.

If you get a chance to test this, that would be great! I think a redirect from a https:// URL to a wss:// URL will be rejected because it doesn't have the correct headers, but I'm guessing a redirect from wss:// to wss:// will work.

@mlbiam
Copy link

mlbiam commented Sep 28, 2020

@tallclair

I think a redirect from a https:// URL to a wss:// URL will be rejected because it doesn't have the correct headers

That brings up a really good question. Does it matter if a redirect works? A redirect from a GET to wss:// would only ever happen from a js library inside of a browser, right? just a simple 302 from the url bar wouldn't do much to your point.

@tallclair
Copy link
Member Author

If the redirect works, it's more important that we get redirect protection on websocket requests. If the redirect doesn't work, we might be able to treat them as their own thing.

Copy link
Member

@derekwaynecarr derekwaynecarr left a comment

Choose a reason for hiding this comment

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

I think the open questions are addressable after the alpha phase, and this has sufficient detail to move forward.

/approve
/lgtm

### Non-Goals

- Protecting proxy requests
- Maintaining backwards compatibility of the Kubelet API, which is only intended for consumption by
Copy link
Member

Choose a reason for hiding this comment

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

I agree with this goal, my fear is that it may not have been honored ;-)


The changes to the Kubelet API are:

1. Remove the `/run` endpoint, which is unused in core Kubernetes.
Copy link
Member

Choose a reason for hiding this comment

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

for the history buffs, i think this links back to 2016 when we moved away from it.
kubernetes/kubernetes#24921

The changes to the Kubelet API are:

1. Remove the `/run` endpoint, which is unused in core Kubernetes.
2. Require a `POST` request for the `/exec`, `/attach` and `/portForward` endpoints.
Copy link
Member

Choose a reason for hiding this comment

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

I agree that POST is also semantically better because a GET should not have changed system state.

request. This endpoint is not used by any Kubernetes components, and should be removed to reduce the
attack surface.

**Risk:** Although this endpoint is not exposed in the Kubernetes API, it is still reachable via a
Copy link
Member

Choose a reason for hiding this comment

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

I am inclined to just remove it. We gave no version guarantee on the endpoint.

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Sep 30, 2020
@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: derekwaynecarr, tallclair

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

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@k8s-ci-robot k8s-ci-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Sep 30, 2020
@k8s-ci-robot k8s-ci-robot merged commit 40f5ac0 into kubernetes:master Sep 30, 2020
@liggitt
Copy link
Member

liggitt commented Oct 1, 2020

2. I really like the idea of using a POST with any API call to initiate the request, even if it's breaking

If that comment refers to the kubelet API, we can do that assuming the kube-apiserver is the only client.

If that comment refers to the kube-apiserver API, then requiring a POST breaks existing browser websocket clients with no recourse.

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. kind/kep Categorizes KEP tracking issues and PRs modifying the KEP directory lgtm "Looks good to me", indicates that a PR is ready to be merged. sig/api-machinery Categorizes an issue or PR as relevant to SIG API Machinery. sig/architecture Categorizes an issue or PR as relevant to SIG Architecture. sig/node Categorizes an issue or PR as relevant to SIG Node. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants