-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Upload and delete certificates using HTTP requests (#109)
--------- Co-authored-by: Mattias Axelsson <[email protected]>
- Loading branch information
1 parent
9c18239
commit 6eb12d6
Showing
11 changed files
with
484 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -100,7 +100,7 @@ Enable TLS: | |
|
||
```sh | ||
curl -s --anyauth -u "root:$DEVICE_PASSWORD" \ | ||
"http://$DEVICE_IP/axis-cgi/param.cgi?action=update&root.dockerdwrapper.UseTLS=yes" | ||
"http://$DEVICE_IP/axis-cgi/param.cgi?action=update&root.dockerdwrapperwithcompose.UseTLS=yes" | ||
``` | ||
|
||
Enable TCP Socket: | ||
|
@@ -116,34 +116,43 @@ Running the ACAP without TLS requires no further setup. | |
|
||
### TLS Setup | ||
|
||
TLS requires a few keys and certificates to work, which are listed in the | ||
subsections below. For more information on how to generate these files, please | ||
consult the official [Docker documentation](https://docs.docker.com/engine/security/protect-access/). | ||
Most of these keys and certificates need to be moved to the Axis device. There are multiple ways to | ||
achieve this, for example by using `scp` to copy the files from a remote machine onto the device. | ||
This can be done by running the following command on the remote machine: | ||
TLS requires the following keys and certificates on the device: | ||
|
||
```sh | ||
scp ca.pem server-cert.pem server-key.pem root@<device ip>:/usr/local/packages/dockerdwrapperwithcompose/localdata/ | ||
``` | ||
* Certificate Authority certificate `ca.pem` | ||
* Server certificate `server-cert.pem` | ||
* Private server key `server-key.pem` | ||
|
||
#### The Certificate Authority (CA) certificate | ||
For more information on how to generate these files, please consult the official | ||
[Docker documentation](https://docs.docker.com/engine/security/protect-access/). | ||
|
||
This certificate needs to be present in the dockerdwrapperwithcompose package folder on the | ||
Axis device and be named `ca.pem`. The full path of the file should be | ||
`/usr/local/packages/dockerdwrapperwithcompose/localdata/ca.pem`. | ||
The files can be uploaded to the device using HTTP. | ||
|
||
#### The server certificate | ||
```sh | ||
curl --anyauth -u "root:$DEVICE_PASSWORD" -F [email protected] -X POST \ | ||
http://$DEVICE_IP/local/dockerdwrapperwithcompose/ca.pem | ||
curl --anyauth -u "root:$DEVICE_PASSWORD" -F [email protected] -X POST \ | ||
http://$DEVICE_IP/local/dockerdwrapperwithcompose/server-cert.pem | ||
curl --anyauth -u "root:$DEVICE_PASSWORD" -F [email protected] -X POST \ | ||
http://$DEVICE_IP/local/dockerdwrapperwithcompose/server-key.pem | ||
``` | ||
|
||
This certificate needs to be present in the dockerdwrapperwithcompose package folder on the | ||
Axis device and be named `server-cert.pem`. The full path of the file should be | ||
`/usr/local/packages/dockerdwrapperwithcompose/localdata/server-cert.pem`. | ||
If desired, they can be deleted from the device using: | ||
|
||
#### The private server key | ||
```sh | ||
curl --anyauth -u "root:$DEVICE_PASSWORD" -X DELETE \ | ||
http://$DEVICE_IP/local/dockerdwrapperwithcompose/ca.pem | ||
curl --anyauth -u "root:$DEVICE_PASSWORD" -X DELETE \ | ||
http://$DEVICE_IP/local/dockerdwrapperwithcompose/server-cert.pem | ||
curl --anyauth -u "root:$DEVICE_PASSWORD" -X DELETE \ | ||
http://$DEVICE_IP/local/dockerdwrapperwithcompose/server-key.pem | ||
``` | ||
|
||
They can also be copied to the `/usr/local/packages/dockerdwrapperwithcompose/localdata` | ||
directory of the device using `scp`. | ||
|
||
This key needs to be present in the dockerdwrapperwithcompose package folder on the Axis device | ||
and be named `server-key.pem`. The full path of the file should be | ||
`/usr/local/packages/dockerdwrapperwithcompose/localdata/server-key.pem`. | ||
```sh | ||
scp ca.pem server-cert.pem server-key.pem root@<device ip>:/usr/local/packages/dockerdwrapperwithcompose/localdata/ | ||
``` | ||
|
||
#### Client key and certificate | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
#include "fcgi_server.h" | ||
#include "log.h" | ||
#include <fcgi_config.h> | ||
#include <fcgi_stdio.h> | ||
#include <glib.h> | ||
#include <sys/socket.h> | ||
#include <sys/stat.h> | ||
#include <sysexits.h> | ||
#include <unistd.h> | ||
|
||
#define FCGI_SOCKET_NAME "FCGI_SOCKET_NAME" | ||
|
||
static const char* g_socket_path = NULL; | ||
static int g_socket = -1; | ||
static GThread* g_thread = NULL; | ||
|
||
static void* handle_fcgi(void* request_callback) { | ||
GThreadPool* workers = g_thread_pool_new((GFunc)request_callback, NULL, -1, FALSE, NULL); | ||
while (workers) { | ||
FCGX_Request* request = g_malloc0(sizeof(FCGX_Request)); | ||
FCGX_InitRequest(request, g_socket, FCGI_FAIL_ACCEPT_ON_INTR); | ||
if (FCGX_Accept_r(request) < 0) { | ||
log_info("FCGX_Accept_r: %s", strerror(errno)); | ||
g_free(request); | ||
break; | ||
} | ||
g_thread_pool_push(workers, request, NULL); | ||
} | ||
log_info("Stopping FCGI server"); | ||
g_thread_pool_free(workers, true, false); | ||
return NULL; | ||
} | ||
|
||
int fcgi_start(fcgi_request_callback request_callback) { | ||
log_debug("Starting FCGI server"); | ||
|
||
g_socket_path = getenv(FCGI_SOCKET_NAME); | ||
if (!g_socket_path) { | ||
log_error("Failed to get environment variable FCGI_SOCKET_NAME"); | ||
return EX_SOFTWARE; | ||
} | ||
|
||
if (FCGX_Init() != 0) { | ||
log_error("FCGX_Init failed: %s", strerror(errno)); | ||
return EX_SOFTWARE; | ||
} | ||
|
||
if ((g_socket = FCGX_OpenSocket(g_socket_path, 5)) < 0) { | ||
log_error("FCGX_OpenSocket failed: %s", strerror(errno)); | ||
return EX_SOFTWARE; | ||
} | ||
chmod(g_socket_path, S_IRWXU | S_IRWXG | S_IRWXO); | ||
|
||
/* Create a thread for request handling */ | ||
if ((g_thread = g_thread_new("fcgi_server", &handle_fcgi, request_callback)) == NULL) { | ||
log_error("Failed to launch FCGI server thread"); | ||
return EX_SOFTWARE; | ||
} | ||
|
||
log_debug("Launched FCGI server thread."); | ||
return EX_OK; | ||
} | ||
|
||
void fcgi_stop(void) { | ||
log_debug("Stopping FCGI server."); | ||
FCGX_ShutdownPending(); | ||
|
||
if (g_socket != -1) { | ||
log_debug("Closing and removing FCGI socket."); | ||
if (shutdown(g_socket, SHUT_RD) != 0) { | ||
log_warning("Could not shutdown socket, err: %s", strerror(errno)); | ||
} | ||
if (unlink(g_socket_path) != 0) { | ||
log_warning("Could not unlink socket, err: %s", strerror(errno)); | ||
} | ||
} | ||
log_debug("Joining FCGI server thread."); | ||
g_thread_join(g_thread); | ||
|
||
g_socket_path = NULL; | ||
g_socket = -1; | ||
g_thread = NULL; | ||
log_debug("FCGI server has stopped."); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
#pragma once | ||
|
||
typedef void (*fcgi_request_callback)(void* request_void_ptr, void* userdata); | ||
|
||
int fcgi_start(fcgi_request_callback request_callback); | ||
void fcgi_stop(void); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
#include "fcgi_write_file_from_stream.h" | ||
#include "fcgi_server.h" | ||
#include "log.h" | ||
#include <unistd.h> | ||
|
||
static int request_content_length(const FCGX_Request* request) { | ||
const char* content_length_str = FCGX_GetParam("CONTENT_LENGTH", request->envp); | ||
if (!content_length_str) | ||
return 0; | ||
return strtol(content_length_str, NULL, 10); | ||
} | ||
|
||
char* fcgi_write_file_from_stream(FCGX_Request request) { | ||
char* temp_file = NULL; | ||
const int content_length = request_content_length(&request); | ||
const char* content_type = FCGX_GetParam("CONTENT_TYPE", request.envp); | ||
|
||
log_debug("Content-Type: %s", content_type); | ||
|
||
const char* MULTIPART_FORM_DATA = "multipart/form-data"; | ||
if (strncmp(content_type, MULTIPART_FORM_DATA, sizeof(MULTIPART_FORM_DATA) - 1) != 0) { | ||
log_error("Content type \"%s\" is not supported. Use \"%s\" instead.", | ||
content_type, | ||
MULTIPART_FORM_DATA); | ||
return NULL; | ||
} | ||
|
||
const char* BOUNDARY_KEY = "boundary="; | ||
const char* boundary_text = strstr(content_type, BOUNDARY_KEY); | ||
if (!boundary_text) { | ||
log_error("No multipart boundary found in content-type \"%s\".", content_type); | ||
return NULL; | ||
} | ||
boundary_text += strlen(BOUNDARY_KEY); | ||
const int boundary_len = strlen(boundary_text); | ||
|
||
temp_file = g_strdup_printf("/tmp/fcgi_upload.XXXXXX"); | ||
int file_des = mkstemp(temp_file); | ||
if (file_des == -1) { | ||
log_error("Failed to create %s, err %s.", temp_file, strerror(errno)); | ||
return NULL; | ||
} | ||
log_debug("Opened %s for writing.", temp_file); | ||
|
||
bool remove_temp_file = true; // Clear this to return the filename to the caller. | ||
|
||
const int bufferLen = 2048; | ||
char buffer[bufferLen + 1 /* Allow for NULL termination */]; | ||
|
||
const char* data_start = "\r\n\r\n"; | ||
const char* data_end = "\r\n--"; | ||
|
||
int total_bytes_processed = 0; | ||
bool pre_boundary_found = false; | ||
bool post_boundary_found = false; | ||
|
||
int loop_counter = 0; // First iteration is special. | ||
char* p_payload = buffer; | ||
char* p_payload_end = NULL; | ||
|
||
while (total_bytes_processed < content_length) { | ||
loop_counter++; | ||
const int available_len = bufferLen - (p_payload - buffer); | ||
|
||
const int bytes_read = FCGX_GetStr(p_payload, available_len, request.in); | ||
log_debug("FCGX_GetStr: bytes_read %d, p_payload %p(%d), available_len %d(%d)", | ||
bytes_read, | ||
p_payload, | ||
(int)(p_payload - buffer), | ||
available_len, | ||
available_len - bufferLen); | ||
if (bytes_read < 0) { | ||
log_error("Failed to read from FCGI stream: %s", strerror(errno)); | ||
break; | ||
} | ||
|
||
/* Look for pre boundary */ | ||
if (!pre_boundary_found) { | ||
buffer[bytes_read] = 0; /* NULL terminate */ | ||
p_payload = strstr(buffer + boundary_len + 1, data_start); | ||
if (p_payload == NULL) { | ||
log_error("Failed to find boundary in uploaded data."); | ||
} | ||
pre_boundary_found = true; | ||
p_payload += strlen(data_start); | ||
} else { | ||
log_debug("Pre boundary already found"); | ||
p_payload = buffer; | ||
} | ||
|
||
/* Look for post boundary */ | ||
if (!post_boundary_found) { | ||
char* pchar; | ||
for (pchar = (loop_counter == 1) ? p_payload : buffer; | ||
pchar < buffer + bytes_read - ((loop_counter == 1) ? boundary_len : 0); | ||
pchar++) { | ||
if (memcmp(pchar, boundary_text, boundary_len) == 0) { | ||
log_debug("Post boundary found for %.*s", boundary_len, pchar); | ||
pchar -= strlen(data_end); | ||
if (memcmp(pchar, data_end, strlen(data_end)) != 0) { | ||
log_error("Post boundary data end not found"); | ||
log_debug("Found %02x%02x%02x%02x at post boundary", | ||
pchar[0], | ||
pchar[1], | ||
pchar[2], | ||
pchar[3]); | ||
goto end; | ||
} | ||
post_boundary_found = true; | ||
break; | ||
} | ||
} | ||
p_payload_end = pchar; | ||
} | ||
|
||
int to_write = p_payload_end - p_payload; | ||
int written = 0; | ||
while (to_write > 0) { | ||
if ((written = write(file_des, p_payload + written, to_write)) < 0) { | ||
log_error("Failed to write %d bytes to %s: %s", | ||
to_write, | ||
temp_file, | ||
strerror(errno)); | ||
goto end; | ||
} | ||
total_bytes_processed += written; | ||
to_write -= written; | ||
} | ||
log_debug("write: p_payload %p, %d bytes", p_payload, (int)(p_payload_end - p_payload)); | ||
log_debug("loop %d, bytes_read %d, done %d", | ||
loop_counter, | ||
bytes_read, | ||
total_bytes_processed); | ||
|
||
if (post_boundary_found) { | ||
total_bytes_processed = content_length; | ||
remove_temp_file = false; // File has been successfully received. | ||
} else { | ||
if (!pre_boundary_found) { | ||
log_error("No pre boundary found"); | ||
goto end; | ||
} | ||
if (bytes_read != available_len) { | ||
log_error("No post boundary found"); | ||
goto end; | ||
} | ||
|
||
/* Post boundary may have been partial at payload end. Ensure possible rematch */ | ||
p_payload = buffer + boundary_len; | ||
memcpy(buffer, p_payload_end, boundary_len); | ||
} | ||
} | ||
|
||
end: | ||
if (file_des != -1) { | ||
log_debug("Closing %s after writing %ld bytes.", temp_file, lseek(file_des, 0, SEEK_CUR)); | ||
if (close(file_des) == -1) | ||
log_warning("Failed to close %s: %s", temp_file, strerror(errno)); | ||
} | ||
if (remove_temp_file && temp_file) { | ||
if (unlink(temp_file) != 0) | ||
log_error("Failed to remove %s: %s", temp_file, strerror(errno)); | ||
g_free(temp_file); | ||
temp_file = NULL; | ||
} | ||
return temp_file; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
#pragma once | ||
#include <fcgiapp.h> | ||
|
||
// Given a request with multipart/form-data, store incoming data in a file on /tmp. On success, | ||
// return the filename and let the caller do all cleanup. On failure, log the error, clean up the | ||
// file and return NULL. | ||
char* fcgi_write_file_from_stream(FCGX_Request request); |
Oops, something went wrong.