diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index d6624b67..171bcf6c 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -2,6 +2,12 @@ All changes to this project will be documented in this file. +## [11.0.5] - 2020-12-17 + +(ws) Implement simple header based websocket authorization technique to reject +client which do not supply a certain header ("Authorization") with a special +value (see doc). + ## [11.0.4] - 2020-11-16 (ixwebsocket) Handle EINTR return code in ix::poll and IXSelectInterrupt diff --git a/docs/ws.md b/docs/ws.md index 7793414c..75d11115 100644 --- a/docs/ws.md +++ b/docs/ws.md @@ -195,6 +195,63 @@ Server: Python/3.7 websockets/8.0.2 Upgrade: websocket ``` +It is possible to pass custom HTTP header when doing the connection handshake, +the remote server might process them to implement a simple authorization +scheme. + +``` +src$ ws connect -H Authorization:supersecret ws://localhost:8008 +Type Ctrl-D to exit prompt... +[2020-12-17 22:35:08.732] [info] Authorization: supersecret +Connecting to url: ws://localhost:8008 +> [2020-12-17 22:35:08.736] [info] ws_connect: connected +[2020-12-17 22:35:08.736] [info] Uri: / +[2020-12-17 22:35:08.736] [info] Headers: +[2020-12-17 22:35:08.736] [info] Connection: Upgrade +[2020-12-17 22:35:08.736] [info] Sec-WebSocket-Accept: 2yaTFcdwn8KL6IzSMj2u6Le7KTg= +[2020-12-17 22:35:08.736] [info] Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=15; client_max_window_bits=15 +[2020-12-17 22:35:08.736] [info] Server: ixwebsocket/11.0.4 macos ssl/SecureTransport zlib 1.2.11 +[2020-12-17 22:35:08.736] [info] Upgrade: websocket +[2020-12-17 22:35:08.736] [info] Received 25 bytes +ws_connect: received message: Authorization suceeded! +[2020-12-17 22:35:08.736] [info] Received pong ixwebsocket::heartbeat::30s::0 +hello +> [2020-12-17 22:35:25.157] [info] Received 7 bytes +ws_connect: received message: hello +``` + +If the wrong header is passed in, the server would close the connection with a custom close code (>4000, and <4999). + +``` +[2020-12-17 22:39:37.044] [info] Upgrade: websocket +ws_connect: connection closed: code 4001 reason Permission denied +``` + +## echo server + +The ws echo server will respond what the client just sent him. If we use the +simple --http_authorization_header we can enforce that client need to pass a +special value in the Authorization header to connect. + +``` +$ ws echo_server --http_authorization_header supersecret +[2020-12-17 22:35:06.192] [info] Listening on 127.0.0.1:8008 +[2020-12-17 22:35:08.735] [info] New connection +[2020-12-17 22:35:08.735] [info] remote ip: 127.0.0.1 +[2020-12-17 22:35:08.735] [info] id: 0 +[2020-12-17 22:35:08.735] [info] Uri: / +[2020-12-17 22:35:08.735] [info] Headers: +[2020-12-17 22:35:08.735] [info] Authorization: supersecret +[2020-12-17 22:35:08.735] [info] Connection: Upgrade +[2020-12-17 22:35:08.735] [info] Host: localhost:8008 +[2020-12-17 22:35:08.735] [info] Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=15; client_max_window_bits=15 +[2020-12-17 22:35:08.735] [info] Sec-WebSocket-Key: eFF2Gf25dC7eC15Ab1135G== +[2020-12-17 22:35:08.735] [info] Sec-WebSocket-Version: 13 +[2020-12-17 22:35:08.735] [info] Upgrade: websocket +[2020-12-17 22:35:08.735] [info] User-Agent: ixwebsocket/11.0.4 macos ssl/SecureTransport zlib 1.2.11 +[2020-12-17 22:35:25.157] [info] Received 7 bytes +``` + ## Websocket proxy ``` diff --git a/ixwebsocket/IXSocketServer.h b/ixwebsocket/IXSocketServer.h index f06f81ca..a049e24c 100644 --- a/ixwebsocket/IXSocketServer.h +++ b/ixwebsocket/IXSocketServer.h @@ -7,9 +7,9 @@ #pragma once #include "IXConnectionState.h" +#include "IXNetSystem.h" #include "IXSelectInterrupt.h" #include "IXSocketTLSOptions.h" -#include "IXNetSystem.h" #include #include #include diff --git a/ixwebsocket/IXWebSocketVersion.h b/ixwebsocket/IXWebSocketVersion.h index 632540ee..30bff6e1 100644 --- a/ixwebsocket/IXWebSocketVersion.h +++ b/ixwebsocket/IXWebSocketVersion.h @@ -6,4 +6,4 @@ #pragma once -#define IX_WEBSOCKET_VERSION "11.0.4" +#define IX_WEBSOCKET_VERSION "11.0.5" diff --git a/ws/ws.cpp b/ws/ws.cpp index 2bf0bca2..053c791a 100644 --- a/ws/ws.cpp +++ b/ws/ws.cpp @@ -1367,7 +1367,8 @@ namespace ix const ix::SocketTLSOptions& tlsOptions, bool ipv6, bool disablePerMessageDeflate, - bool disablePong) + bool disablePong, + const std::string& httpHeaderAuthorization) { spdlog::info("Listening on {}:{}", hostname, port); @@ -1393,9 +1394,9 @@ namespace ix } server.setOnClientMessageCallback( - [greetings](std::shared_ptr connectionState, - WebSocket& webSocket, - const WebSocketMessagePtr& msg) { + [greetings, httpHeaderAuthorization](std::shared_ptr connectionState, + WebSocket& webSocket, + const WebSocketMessagePtr& msg) { auto remoteIp = connectionState->getRemoteIp(); if (msg->type == ix::WebSocketMessageType::Open) { @@ -1409,6 +1410,19 @@ namespace ix spdlog::info("{}: {}", it.first, it.second); } + if (!httpHeaderAuthorization.empty()) + { + auto authorization = msg->openInfo.headers["Authorization"]; + if (authorization != httpHeaderAuthorization) + { + webSocket.close(4001, "Permission denied"); + } + else + { + webSocket.sendText("Authorization suceeded!"); + } + } + if (greetings) { webSocket.sendText("Welcome !"); @@ -3039,6 +3053,7 @@ int main(int argc, char** argv) std::string publisherRolesecret; std::string sendMsg("hello world"); std::string filename; + std::string httpHeaderAuthorization; ix::SocketTLSOptions tlsOptions; ix::CobraConfig cobraConfig; ix::CobraBotConfig cobraBotConfig; @@ -3185,6 +3200,7 @@ int main(int argc, char** argv) echoServerApp->fallthrough(); echoServerApp->add_option("--port", port, "Port"); echoServerApp->add_option("--host", hostname, "Hostname"); + echoServerApp->add_option("--http_authorization_header", httpHeaderAuthorization, "Hostname"); echoServerApp->add_flag("-q", quiet, "Quiet / only display warnings and errors"); echoServerApp->add_flag("-g", greetings, "Greet"); echoServerApp->add_flag("-6", ipv6, "IpV6"); @@ -3507,8 +3523,14 @@ int main(int argc, char** argv) } else if (app.got_subcommand("echo_server")) { - ret = ix::ws_echo_server_main( - port, greetings, hostname, tlsOptions, ipv6, disablePerMessageDeflate, disablePong); + ret = ix::ws_echo_server_main(port, + greetings, + hostname, + tlsOptions, + ipv6, + disablePerMessageDeflate, + disablePong, + httpHeaderAuthorization); } else if (app.got_subcommand("push_server")) {