Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@ For more information on how to generate these files, please consult the official
[Docker documentation](https://docs.docker.com/engine/security/protect-access/).

The files can be uploaded to the device using HTTP.
The dockerd service will restart, or try to start, after each HTTP POST request.
The request will be rejected if the file being uploaded has the incorrect header or footer for that file type.
The dockerd service will restart, or try to start, after each successful HTTP POST request.

```sh
curl --anyauth -u "root:$DEVICE_PASSWORD" -F [email protected] -X POST \
Expand Down Expand Up @@ -209,6 +210,21 @@ has a significantly higher inference time when using a small and slow SD card.
To get more informed about specifications, check the
[SD Card Standards](https://www.sdcard.org/developers/sd-standard-overview/).

## Additional configuration

For even more control over the dockerd daemon,
a configuration file can be uploaded to the device using HTTP.

```sh
curl --anyauth -u "root:$DEVICE_PASSWORD" -F [email protected] -X POST \
http://$DEVICE_IP/local/dockerdwrapper/daemon.json
```

The complete specification of this file can be found in the Docker reference, in section
[Daemon configuration file](https://docs.docker.com/reference/cli/dockerd/#daemon-configuration-file).

The dockerd service will automatically restart after a new configuration file has been uploaded.

## Using the Docker ACAP

The Docker ACAP does not contain the docker client binary. This means that all
Expand Down
2 changes: 2 additions & 0 deletions app/app_paths.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@

#define APP_DIRECTORY "/usr/local/packages/" APP_NAME
#define APP_LOCALDATA APP_DIRECTORY "/localdata"

#define DAEMON_JSON "daemon.json"
2 changes: 1 addition & 1 deletion app/dockerdwrapper.c
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,7 @@ static bool start_dockerd(const struct settings* settings, struct app_state* app
args_len - args_offset,
"%s %s",
"dockerd",
"--config-file " APP_LOCALDATA "/daemon.json");
"--config-file " APP_LOCALDATA "/" DAEMON_JSON);

g_strlcpy(msg, "Starting dockerd", msg_len);

Expand Down
5 changes: 5 additions & 0 deletions app/html/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,10 @@ <h2>Remove TLS certificates and keys</h2>
curl --anyauth -u $DEVICE_USER:$DEVICE_PASSWORD -X DELETE http://$DEVICE_IP/local/dockerdwrapper/server-cert.pem<br>
curl --anyauth -u $DEVICE_USER:$DEVICE_PASSWORD -X DELETE http://$DEVICE_IP/local/dockerdwrapper/server-key.pem<br>
</code>
<h2>Check and upload dockerd configuration</h2>
<code>
curl --anyauth -u $DEVICE_USER:$DEVICE_PASSWORD http://$DEVICE_IP/local/dockerdwrapper/daemon.json<br>
curl --anyauth -u $DEVICE_USER:$DEVICE_PASSWORD -F [email protected] -X POST http://$DEVICE_IP/local/dockerdwrapper/daemon.json.pem<br>
</code>
</body>
</html>
35 changes: 34 additions & 1 deletion app/http_request.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
#include "app_paths.h"
#include "fcgi_write_file_from_stream.h"
#include "log.h"
#include "tls.h"
#include <gio/gio.h>
#include <sys/stat.h>

#define HTTP_200_OK "200 OK"
#define HTTP_204_NO_CONTENT "204 No Content"
#define HTTP_400_BAD_REQUEST "400 Bad Request"
#define HTTP_403_FORBIDDEN "403 Forbidden"
#define HTTP_404_NOT_FOUND "404 Not Found"
#define HTTP_405_METHOD_NOT_ALLOWED "405 Method Not Allowed"
#define HTTP_422_UNPROCESSABLE_CONTENT "422 Unprocessable Content"
Expand Down Expand Up @@ -51,6 +53,12 @@ static bool remove_from_localdata(const char* filename) {
return success;
}

// Certificate files have more restrictions on them than daemon.json. This function is only designed
// for filenames exposed in the httpConfig part of the manifest.
static bool cert_filename(const char* filename) {
return strcmp(filename, DAEMON_JSON) != 0;
}

static void
response(FCGX_Request* request, const char* status, const char* content_type, const char* body) {
FCGX_FPrintF(request->out,
Expand Down Expand Up @@ -81,7 +89,11 @@ post_request(FCGX_Request* request, const char* filename, restart_dockerd_t rest
response_msg(request, HTTP_422_UNPROCESSABLE_CONTENT, "Upload to temporary file failed.");
return;
}
if (!copy_to_localdata(temp_file, filename))
if (cert_filename(filename) && !tls_file_has_correct_format(filename, temp_file)) {
g_autofree char* msg =
g_strdup_printf("File is not a valid %s.", tls_file_description(filename));
response_msg(request, HTTP_400_BAD_REQUEST, msg);
} else if (!copy_to_localdata(temp_file, filename))
response_msg(request, HTTP_500_INTERNAL_SERVER_ERROR, "Failed to copy file to localdata");
else {
response_204_no_content(request);
Expand All @@ -92,6 +104,25 @@ post_request(FCGX_Request* request, const char* filename, restart_dockerd_t rest
log_error("Failed to remove %s: %s", temp_file, strerror(errno));
}

static void get_request(FCGX_Request* request, const char* filename) {
if (strcmp(filename, DAEMON_JSON) != 0) {
response_msg(request, HTTP_403_FORBIDDEN, "Resource is write-only");
return;
}

g_autofree char* contents = NULL;
gsize length;
GError* error = NULL;
const char* full_path = APP_LOCALDATA "/" DAEMON_JSON;
if (!g_file_get_contents(full_path, &contents, &length, &error)) {
log_error("Failed to read %s: %s.", full_path, error->message);
response_msg(request, HTTP_404_NOT_FOUND, "Could not read file");
return;
}

response(request, HTTP_200_OK, "application/json", contents);
}

static void delete_request(FCGX_Request* request, const char* filename) {
if (!exists_in_localdata(filename))
response_msg(request, HTTP_404_NOT_FOUND, "File not found in localdata");
Expand Down Expand Up @@ -129,6 +160,8 @@ void http_request_callback(void* request_void_ptr, void* restart_dockerd_void_pt

if (strcmp(method, "POST") == 0)
post_request(request, filename, restart_dockerd_void_ptr);
else if (strcmp(method, "GET") == 0)
get_request(request, filename);
else if (strcmp(method, "DELETE") == 0)
delete_request(request, filename);
else
Expand Down
5 changes: 5 additions & 0 deletions app/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@
],
"settingPage": "index.html",
"httpConfig": [
{
"access": "admin",
"name": "daemon.json",
"type": "fastCgi"
},
{
"access": "admin",
"name": "ca.pem",
Expand Down
68 changes: 68 additions & 0 deletions app/tls.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "app_paths.h"
#include "log.h"
#include <glib.h>
#include <stdio.h>
#include <unistd.h>

#define TLS_CERT_PATH APP_LOCALDATA
Expand All @@ -18,6 +19,17 @@ static struct cert tls_certs[] = {{"--tlscacert", "ca.pem", "CA certificate"},

#define NUM_TLS_CERTS (sizeof(tls_certs) / sizeof(tls_certs[0]))

#define BEGIN(x) "-----BEGIN " x "-----\n"
#define END(x) "-----END " x "-----\n"
#define CERTIFICATE "CERTIFICATE"
#define PRIVATE_KEY "PRIVATE KEY"
#define RSA_PRIVATE_KEY "RSA PRIVATE KEY"

// Filename is assumed to be one of those listed in tls_certs[].
static bool is_key_file(const char* filename) {
return strstr(filename, "key");
}

static bool cert_file_exists(const struct cert* tls_cert) {
g_autofree char* full_path = g_strdup_printf("%s/%s", TLS_CERT_PATH, tls_cert->filename);
return access(full_path, F_OK) == 0;
Expand All @@ -39,6 +51,13 @@ void tls_log_missing_cert_warnings(void) {
tls_certs[i].filename);
}

const char* tls_file_description(const char* filename) {
for (size_t i = 0; i < NUM_TLS_CERTS; ++i)
if (strcmp(filename, tls_certs[i].filename) == 0)
return tls_certs[i].description;
return NULL;
}

const char* tls_args_for_dockerd(void) {
static char args[512]; // Too small buffer will cause truncated options, nothing more.
const char* end = args + sizeof(args);
Expand All @@ -53,3 +72,52 @@ const char* tls_args_for_dockerd(void) {
tls_certs[i].filename);
return args;
}

static bool read_bytes_from(FILE* fp, int whence, char* buffer, int num_bytes) {
const long offset = whence == SEEK_SET ? 0 : -num_bytes;
if (fseek(fp, offset, whence) != 0) {
log_error("Could not reposition stream to %s%ld: %s",
whence == SEEK_SET ? "SEEK_SET+" : "SEEK_END",
offset,
strerror(errno));
return false;
}
if (fread(buffer, num_bytes, 1, fp) != 1) {
log_error("Could not read %d bytes: %s", num_bytes, strerror(errno));
return false;
}
return true;
}

static bool is_file_section_equal_to(FILE* fp, int whence, const char* section) {
char buffer[128];
int to_read = strlen(section);
if (!read_bytes_from(fp, whence, buffer, to_read))
return false;
buffer[to_read] = '\0';
return strncmp(buffer, section, to_read) == 0;
}

static bool has_header_and_footer(FILE* fp, const char* header, const char* footer) {
return is_file_section_equal_to(fp, SEEK_SET, header) &&
is_file_section_equal_to(fp, SEEK_END, footer);
}

bool tls_file_has_correct_format(const char* filename, const char* path_to_file) {
FILE* fp = fopen(path_to_file, "r");
if (!fp) {
log_error("Could not read %s", path_to_file);
return false;
}

bool correct = is_key_file(filename)
? (has_header_and_footer(fp, BEGIN(PRIVATE_KEY), END(PRIVATE_KEY)) ||
has_header_and_footer(fp, BEGIN(RSA_PRIVATE_KEY), END(RSA_PRIVATE_KEY)))
: has_header_and_footer(fp, BEGIN(CERTIFICATE), END(CERTIFICATE));
if (!correct)
log_error("%s does not contain the headers and footers for a %s.",
path_to_file,
tls_file_description(filename));
fclose(fp);
return correct;
}
2 changes: 2 additions & 0 deletions app/tls.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@

bool tls_missing_certs(void);
void tls_log_missing_cert_warnings(void);
const char* tls_file_description(const char* filename);
const char* tls_args_for_dockerd(void);
bool tls_file_has_correct_format(const char* filename, const char* path_to_file);