add in-protocol authentication mechanism

pull/105/head
Falk Werner 1 year ago
parent 9423d75021
commit 5db3b28b5a

@ -1,12 +1,21 @@
# Authentication
Webfuse supports token-based authentication using HTTP headers. To activate authentication, two command line option needs to be given:
Webfuse supports two authentications mechanisms:
- token-based authentication using HTTP headers
- in-protocol authentication
To activate authentication, two command line option needs to be given:
- `--wf-authenticator PATH`
allows to specify an executable used for authentication
- `--wf-auth-header HEADER`
- `--wf-auth-header HEADER` _(optional)_
allows to specify the HTTP header used for authentication
When `--wf-auth-header` is not specifiend or the header is not contained
in the HTTP request, the in-protocol solutions is used: Before any other
operation, the credentials are queried via `getcreds`request.
## Authenticator
An authenticator is an executable or script used for token-based

@ -313,6 +313,7 @@ _Note that the following numbers are in `hexadecimal` notation._
| rmdir | 0x14 | 0x94 |
| statfs | 0x15 | 0x95 |
| utimens | 0x16 | 0x96 |
| getcreds | 0x17 | 0x97 |
## Methods
@ -807,6 +808,29 @@ _Note that handle might be invalid (-1), even if the file is open._
| type | u8 | message type (0x96) |
| result | result | operation status |
### getcreds
Query credentials. When authentication is active and the in-protocol
authentication mechanism is used, this is the first request a
webfuse service sends to a provider.
#### Request
| Field | Data Type | Description |
| ------ | --------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x17) |
_Note that handle might be invalid (-1), even if the file is open._
#### Response
| Field | Data Type | Description |
| ------ | --------- | ----------- |
| id | u32 | message id |
| type | u8 | message type (0x97) |
| creds | str | credentials |
## Examples
### Get file attributes

@ -5,6 +5,7 @@ import os
import stat
import websockets
import errno
import getpass
INVALID_FD = 0xffffffffffffffff
@ -269,10 +270,12 @@ class FilesystemProvider:
0x14: FilesystemProvider.rmdir,
0x15: FilesystemProvider.statfs,
0x16: FilesystemProvider.utimens,
0x17: FilesystemProvider.getcreds,
}
async def run(self):
async with websockets.connect(self.url, extra_headers=[("X-Auth-Token", "user:bob;token=foo")]) as connection:
extra_headers = [("X-Auth-Token", "user:bob;token=foo")]
async with websockets.connect(self.url, extra_headers=extra_headers) as connection:
while True:
request = await connection.recv()
reader = MessageReader(request)
@ -565,7 +568,11 @@ class FilesystemProvider:
writer.write_u64(buffer.f_files)
writer.write_u64(buffer.f_ffree)
writer.write_u64(buffer.f_namemax)
def getcreds(self, reader, writer):
credentials = getpass.getpass(prompt="credentials: ")
writer.write_str(credentials)
if __name__ == '__main__':
provider = FilesystemProvider('.', 'ws://localhost:8081')

@ -418,6 +418,11 @@ public:
return (result == 0) ? 0 : -errno;
}
std::string get_credentials() override
{
return getpass("credentials: ");
}
private:
std::string get_full_path(std::string const & path)

@ -419,4 +419,11 @@ int filesystem::statfs(std::string const & path, struct statvfs * statistics)
}
}
// get credentials is handled internally
std::string filesystem::get_credentials()
{
throw std::runtime_error("not implemented");
}
}

@ -47,6 +47,7 @@ public:
int statfs(std::string const & path, struct statvfs * statistics) override;
std::string get_credentials() override;
private:
ws_server &proxy;

@ -131,5 +131,11 @@ int empty_filesystem::statfs(std::string const & path, struct statvfs * statisti
return -ENOSYS;
}
std::string empty_filesystem::get_credentials()
{
return "";
}
}

@ -39,6 +39,8 @@ public:
int rmdir(std::string const & path) override;
int statfs(std::string const & path, struct statvfs * statistics) override;
std::string get_credentials() override;
};
}

@ -48,6 +48,8 @@ public:
virtual int rmdir(std::string const & path) = 0;
virtual int statfs(std::string const & path, struct statvfs * statistics) = 0;
virtual std::string get_credentials() = 0;
};
}

@ -122,6 +122,9 @@ public:
case request_type::statfs:
fs_statfs(reader, writer);
break;
case request_type::getcreds:
fs_get_credentials(reader, writer);
break;
default:
std::cout << "unknown request: " << ((int) req_type) << std::endl;
break;
@ -371,6 +374,13 @@ private:
}
}
void fs_get_credentials(messagereader & reader, messagewriter & writer)
{
std::string credentials = fs_.get_credentials();
writer.write_str(credentials);
}
filesystem_i & fs_;
ws_client client;
};

@ -29,6 +29,7 @@ request_type get_request_type(uint8_t value)
case static_cast<uint8_t>(request_type::rmdir): return request_type::rmdir;
case static_cast<uint8_t>(request_type::statfs): return request_type::statfs;
case static_cast<uint8_t>(request_type::utimens): return request_type::utimens;
case static_cast<uint8_t>(request_type::getcreds): return request_type::getcreds;
default:
return request_type::unknown;
}

@ -30,7 +30,8 @@ enum class request_type: uint8_t
readdir = 0x13,
rmdir = 0x14,
statfs = 0x15,
utimens = 0x16
utimens = 0x16,
getcreds =0x17
};
request_type get_request_type(uint8_t value);

@ -29,6 +29,7 @@ response_type get_response_type(request_type value)
case request_type::rmdir: return response_type::rmdir;
case request_type::statfs: return response_type::statfs;
case request_type::utimens: return response_type::utimens;
case request_type::getcreds: return response_type::getcreds;
default:
return response_type::unknown;
}

@ -31,7 +31,8 @@ enum class response_type: uint8_t
readdir = 0x93,
rmdir = 0x94,
statfs = 0x95,
utimens = 0x96
utimens = 0x96,
getcreds = 0x97
};
response_type get_response_type(request_type value);

@ -54,7 +54,7 @@ static int ws_server_callback(struct lws *wsi, enum lws_callback_reasons reason,
handler->on_receive(wsi, in, len);
break;
case LWS_CALLBACK_SERVER_WRITEABLE:
handler->on_writable();
result = handler->on_writable();
break;
default:
break;

@ -51,6 +51,7 @@ namespace webfuse
server_handler::server_handler(std::string const & auth_app, std::string const & auth_hdr)
: connection(nullptr)
, id(0)
, shutdown_requested(false)
, is_authenticated(false)
, authenticator(auth_app)
, auth_header(auth_hdr)
@ -69,9 +70,26 @@ int server_handler::on_established(lws * wsi)
if (nullptr == connection)
{
connection = wsi;
id = 0;
if ((!is_authenticated) && (!authenticator.empty()))
{
{
messagewriter writer(request_type::getcreds);
std::lock_guard<std::mutex> lock(mut);
uint32_t id = next_id();
writer.set_id(id);
requests.emplace(std::move(writer));
}
lws_callback_on_writable(wsi);
}
return 0;
}
// already connected: refuse
return -1;
}
@ -86,22 +104,29 @@ void server_handler::on_receive(lws * wsi, void* in, int len)
{
webfuse::messagereader reader(current_message);
uint32_t id = reader.read_u32();
reader.read_u8(); // read message type: ToDo: use it
auto const message_type = reader.read_u8(); // read message type: ToDo: use it
std::lock_guard<std::mutex> lock(mut);
auto it = pending_responses.find(id);
if (it != pending_responses.end())
if (static_cast<response_type>(message_type) == response_type::getcreds)
{
it->second.set_value(std::move(reader));
pending_responses.erase(it);
finish_authentication(wsi, std::move(reader));
}
else
{
// ToDo: log request not found
std::cout << "warning: request not found: id=" << id << std::endl;
for(auto const & entry: pending_responses)
std::lock_guard<std::mutex> lock(mut);
auto it = pending_responses.find(id);
if (it != pending_responses.end())
{
it->second.set_value(std::move(reader));
pending_responses.erase(it);
}
else
{
std::cout << "\t" << entry.first << std::endl;
// ToDo: log request not found
std::cout << "warning: request not found: id=" << id << std::endl;
for(auto const & entry: pending_responses)
{
std::cout << "\t" << entry.first << std::endl;
}
}
}
}
@ -113,8 +138,10 @@ void server_handler::on_receive(lws * wsi, void* in, int len)
}
}
void server_handler::on_writable()
int server_handler::on_writable()
{
if (shutdown_requested) { return -1; }
webfuse::messagewriter writer(webfuse::request_type::unknown);
bool has_msg = false;
bool has_more = false;
@ -143,6 +170,8 @@ void server_handler::on_writable()
{
lws_callback_on_writable(connection);
}
return 0;
}
void server_handler::on_closed(lws * wsi)
@ -258,5 +287,22 @@ uint32_t server_handler::next_id()
return id;
}
void server_handler::finish_authentication(lws * wsi, messagereader reader)
{
auto const credentials = reader.read_str();
webfuse::authenticator auth(authenticator);
auto const result = auth.authenticate(credentials);
if (result)
{
is_authenticated = true;
}
else
{
shutdown_requested = true;
lws_callback_on_writable(wsi);
}
}
}

@ -25,7 +25,7 @@ public:
int on_established(lws* wsi);
void on_receive(lws * wsi, void* in, int len);
void on_writable();
int on_writable();
void on_closed(lws * wsi);
std::future<messagereader> perform(messagewriter writer);
@ -35,9 +35,11 @@ private:
int authenticate_via_header(lws * wsi);
std::string get_auth_token(lws * wsi) const;
uint32_t next_id();
void finish_authentication(lws * wsi, messagereader reader);
struct lws * connection;
uint32_t id;
bool shutdown_requested;
std::atomic<bool> is_authenticated;
std::string authenticator;

@ -40,6 +40,9 @@ public:
MOCK_METHOD(int, rmdir, (std::string const & path));
MOCK_METHOD(int, statfs, (std::string const & path, struct statvfs * statistics));
MOCK_METHOD(std::string, get_credentials, ());
};
}

Loading…
Cancel
Save