kube_client/client/
upgrade.rs

1use http::{self, Response, StatusCode};
2use thiserror::Error;
3use tokio_tungstenite::tungstenite as ws;
4
5use crate::client::Body;
6
7// Binary subprotocol v4. See `Client::connect`.
8pub const WS_PROTOCOL: &str = "v4.channel.k8s.io";
9
10/// Possible errors from upgrading to a WebSocket connection
11#[cfg(feature = "ws")]
12#[cfg_attr(docsrs, doc(cfg(feature = "ws")))]
13#[derive(Debug, Error)]
14pub enum UpgradeConnectionError {
15    /// The server did not respond with [`SWITCHING_PROTOCOLS`] status when upgrading the
16    /// connection.
17    ///
18    /// [`SWITCHING_PROTOCOLS`]: http::status::StatusCode::SWITCHING_PROTOCOLS
19    #[error("failed to switch protocol: {0}")]
20    ProtocolSwitch(http::status::StatusCode),
21
22    /// `Upgrade` header was not set to `websocket` (case insensitive)
23    #[error("upgrade header was not set to websocket")]
24    MissingUpgradeWebSocketHeader,
25
26    /// `Connection` header was not set to `Upgrade` (case insensitive)
27    #[error("connection header was not set to Upgrade")]
28    MissingConnectionUpgradeHeader,
29
30    /// `Sec-WebSocket-Accept` key mismatched.
31    #[error("Sec-WebSocket-Accept key mismatched")]
32    SecWebSocketAcceptKeyMismatch,
33
34    /// `Sec-WebSocket-Protocol` mismatched.
35    #[error("Sec-WebSocket-Protocol mismatched")]
36    SecWebSocketProtocolMismatch,
37
38    /// Failed to get pending HTTP upgrade.
39    #[error("failed to get pending HTTP upgrade: {0}")]
40    GetPendingUpgrade(#[source] hyper::Error),
41}
42
43// Verify upgrade response according to RFC6455.
44// Based on `tungstenite` and added subprotocol verification.
45pub fn verify_response(res: &Response<Body>, key: &str) -> Result<(), UpgradeConnectionError> {
46    if res.status() != StatusCode::SWITCHING_PROTOCOLS {
47        return Err(UpgradeConnectionError::ProtocolSwitch(res.status()));
48    }
49
50    let headers = res.headers();
51    if !headers
52        .get(http::header::UPGRADE)
53        .and_then(|h| h.to_str().ok())
54        .map(|h| h.eq_ignore_ascii_case("websocket"))
55        .unwrap_or(false)
56    {
57        return Err(UpgradeConnectionError::MissingUpgradeWebSocketHeader);
58    }
59
60    if !headers
61        .get(http::header::CONNECTION)
62        .and_then(|h| h.to_str().ok())
63        .map(|h| h.eq_ignore_ascii_case("Upgrade"))
64        .unwrap_or(false)
65    {
66        return Err(UpgradeConnectionError::MissingConnectionUpgradeHeader);
67    }
68
69    let accept_key = ws::handshake::derive_accept_key(key.as_ref());
70    if !headers
71        .get(http::header::SEC_WEBSOCKET_ACCEPT)
72        .map(|h| h == &accept_key)
73        .unwrap_or(false)
74    {
75        return Err(UpgradeConnectionError::SecWebSocketAcceptKeyMismatch);
76    }
77
78    // Make sure that the server returned the correct subprotocol.
79    if !headers
80        .get(http::header::SEC_WEBSOCKET_PROTOCOL)
81        .map(|h| h == WS_PROTOCOL)
82        .unwrap_or(false)
83    {
84        return Err(UpgradeConnectionError::SecWebSocketProtocolMismatch);
85    }
86
87    Ok(())
88}
89
90/// Generate a random key for the `Sec-WebSocket-Key` header.
91/// This must be nonce consisting of a randomly selected 16-byte value in base64.
92pub fn sec_websocket_key() -> String {
93    use base64::Engine;
94    let r: [u8; 16] = rand::random();
95    base64::engine::general_purpose::STANDARD.encode(r)
96}