diff --git a/Vagrantfile b/Vagrantfile index 678efd39c44c2238c9f729c4af26846c34bd562d..42171daf6e3883ed829fb12633f344f3d24ae480 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -3,9 +3,16 @@ Vagrant.configure("2") do |config| config.vm.box = "debian/jessie64" - config.vm.synced_folder ".", "/test", type: 'virtualbox' + config.vm.synced_folder ".", "/cryptops-api", type: 'virtualbox' + # If cryptops-client directory exists, mount it as well so you can test easier + if File.directory?(File.expand_path('../cryptops-client')) + config.vm.synced_folder "../cryptops-client", "/cryptops-client", type: 'virtualbox' + end config.vm.provision "shell", inline: <<-SHELL apt-get update apt-get install -y libmicrohttpd-dev libjansson-dev libcurl4-gnutls-dev libgnutls28-dev libgcrypt20-dev libcryptsetup-dev + if ! grep -q 'export LD_LIBRARY_PATH="/cryptops-api/libraries"' /home/vagrant/.bashrc ; then + echo export LD_LIBRARY_PATH="/cryptops-api/libraries">> /home/vagrant/.bashrc + fi SHELL end diff --git a/src/api/ssh_keys_delete.c b/src/api/ssh_keys_delete.c new file mode 100644 index 0000000000000000000000000000000000000000..9c763b8cf1ff52a941a5194eb1bfbf05892c0235 --- /dev/null +++ b/src/api/ssh_keys_delete.c @@ -0,0 +1,45 @@ +/** + * Callback function that deletes an SSH key from the list of keys authorised + * for access to the initrd. The line will be left empty, because that keeps + * the ids of SSH keys intact for ssh_keys_get + * + * @param[in] request incoming HTTP request + * @param[out] response HTTP response to the request + * @param[in] user_data extra data to pass between main thread and callbacks + * @return internal status code + */ +int callback_ssh_keys_delete(const struct _u_request * request, + struct _u_response * response, void * user_data) +{ + // Read ssh key id from request URI. + const char * id_string = u_map_get(request->map_url, "id"); + if (id_string == NULL) + { + return send_simple_response(response, 400, "error", + "missing url parameter `id`"); + } + + int id; + int r = parse_int(id_string, &id); + if (r != 0) + { + printf("invalid url parameter `id`: %s\n", id_string); + return send_simple_response(response, 400, "error", + "invalid url parameter `id`"); + } + + // Replace the key at ID with "" + r = replace_ssh_key(id, ""); + if (r < 0) + { + if (r == -1) + return send_simple_response(response, 500, "error", + "error opening authorized_keys"); + if (r == -2) + return send_simple_response(response, 500, "error", + "error opening authorized_keys tmp file"); + return send_simple_response(response, 500, "error", + "Unknown error while processing ssh keys"); + } + return send_simple_response(response, 200, "status", "ok"); +} diff --git a/src/api/ssh_keys_get.c b/src/api/ssh_keys_get.c index 255442174be3a3f72fa99ed6eb59b766a4865c42..3818706297ada1c2e81f56eb61d211c16390ef40 100644 --- a/src/api/ssh_keys_get.c +++ b/src/api/ssh_keys_get.c @@ -23,11 +23,15 @@ json_t * readAuthorizedKeysToJson() ssize_t read; while ((read = getline(&line, &line_length, authorized_keys)) != -1) { - asprintf(&field, "%d", index); - // Remove trailing newline. - line[strcspn(line, "\n")] = 0; - json_object_set(keys, field, json_string(line)); - ++index; + // Ignore empty lines, they do have IDs though. + if (strlen(line) > 1) + { + asprintf(&field, "%d", index); + // Remove trailing newline. + line[strcspn(line, "\n")] = 0; + json_object_set(keys, field, json_string(line)); + } + index++; } // Close file and clean up. diff --git a/src/api/ssh_keys_post.c b/src/api/ssh_keys_post.c index 08fd28f2628463ca8530e238beb4f3f434974e24..30633c144cb5c25504cb9d9e9201429e9493e90d 100644 --- a/src/api/ssh_keys_post.c +++ b/src/api/ssh_keys_post.c @@ -41,13 +41,12 @@ int callback_ssh_keys_post(const struct _u_request * request, return send_simple_response(response, 400, "error", "missing ssh-key"); } + // add cryptops-client command to ssh-key char * ssh_key_with_command; - add_ssh_command(&ssh_key_with_command, ssh_key); - asprintf(&ssh_key_with_command, "%s\n", ssh_key_with_command); - // Write SSH key to file + asprintf(&ssh_key_with_command, "%s\n", ssh_key_with_command); fprintf(authorized_keys, ssh_key_with_command); fclose(authorized_keys); diff --git a/src/api/ssh_keys_put.c b/src/api/ssh_keys_put.c new file mode 100644 index 0000000000000000000000000000000000000000..9bf53bde27041c323d0aa26f1fb1182968bd092c --- /dev/null +++ b/src/api/ssh_keys_put.c @@ -0,0 +1,60 @@ +/** + * Callback function that updates an SSH key in the list of keys authorised + * for access to the initrd. Needs an ID provided in the URL and a new SSH key + * provided in the request body. + * + * @param[in] request incoming HTTP request + * @param[out] response HTTP response to the request + * @param[in] user_data extra data to pass between main thread and callbacks + * @return internal status code + */ +int callback_ssh_keys_put(const struct _u_request * request, + struct _u_response * response, void * user_data) +{ + // Read ssh key id from request URI. + const char * id_string = u_map_get(request->map_url, "id"); + if (id_string == NULL) + { + return send_simple_response(response, 400, "error", + "missing url parameter `id`"); + } + + int id; + int r = parse_int(id_string, &id); + if (r != 0) + { + printf("invalid url parameter `id`: %s\n", id_string); + return send_simple_response(response, 400, "error", + "invalid url parameter `id`"); + } + + // Read in json request body. + json_t * json_input = ulfius_get_json_body_request(request, NULL); + + // Read SSH key from request. + const char * ssh_key; + ssh_key = json_string_value(json_object_get(json_input, "ssh-key")); + if (ssh_key == NULL) + { + return send_simple_response(response, 400, "error", "missing ssh-key"); + } + + // add cryptops-client command to ssh-key + char * ssh_key_with_command; + add_ssh_command(&ssh_key_with_command, ssh_key); + + r = replace_ssh_key(id, ssh_key_with_command); + + if (r < 0) + { + if (r == -1) + return send_simple_response(response, 500, "error", + "error opening authorized_keys"); + if (r == -2) + return send_simple_response(response, 500, "error", + "error opening authorized_keys tmp file"); + return send_simple_response(response, 500, "error", + "Unknown error while processing ssh keys"); + } + return send_simple_response(response, 200, "status", "ok"); +} diff --git a/src/auxiliary.c b/src/auxiliary.c index db1f7af3102e10c382acff61258b17dc3a222ad8..85cc449bed997b11fcebffaaa7c091d847d99562 100644 --- a/src/auxiliary.c +++ b/src/auxiliary.c @@ -220,6 +220,92 @@ int parse_int(const char * input, int * result) return 0; } +/** + * Write an ssh_key to a specific rule in the authorized_keys file. + * + * @param[in] id line number/ssh_key id + * @param[in] ssh_key ssh key contents. If this string is empty, this is seen + * as deleting the key. Newline characters are ignored + */ +int replace_ssh_key(int id, const char * ssh_key) +{ + // Open file. + FILE * authorized_keys_in = fopen(AUTHORIZED_KEYS_PATH, "r"); + // Check if that succeeded. + if (authorized_keys_in == NULL) + { + printf("Could not open authorized_keys file for reading\n"); + return -1; + } + + // Make tmp outfile for authorized keys + char * authorized_keys_out_name; + asprintf(&authorized_keys_out_name, "%s%s", AUTHORIZED_KEYS_PATH, ".tmp"); + + // Open file. + FILE * authorized_keys_out = fopen(authorized_keys_out_name, "w"); + // Check if that succeeded. + if (authorized_keys_out == NULL) + { + printf("Could not open authorized_keys tmp file for writing\n"); + return -2; + } + + char ch; + int line_number = 1; + + // Loop through all the lines in the input file + do + { + if (line_number != id) + { + // Copy 1 line + ch = getc(authorized_keys_in); + while (ch != EOF && ch != '\n') + { + // Copy all lines that don't have id as line number + putc(ch, authorized_keys_out); + ch = getc(authorized_keys_in); + } + } + else + { + // Insert an ssh key instead of a line + ch = *ssh_key++; + while (ch != '\0') + { + // New line in ssh key is not allowed, because that will screw + // up the indices + if (ch != '\n') + putc(ch, authorized_keys_out); + ch = *ssh_key++; + } + + // ignore replaced line + do + { + ch = getc(authorized_keys_in); + } while (ch != EOF && ch != '\n'); + } + + if (ch != EOF) + { + // Insert newline + putc('\n', authorized_keys_out); + // Increment line number + line_number++; + } + } while (ch != EOF); + + fclose(authorized_keys_in); + fclose(authorized_keys_out); + + // Remove old authorized_keys file and replace it with the new one + remove(AUTHORIZED_KEYS_PATH); + rename(authorized_keys_out_name, AUTHORIZED_KEYS_PATH); + return 0; +} + /** * Add the SSH_COMMAND string in front of ssh_key unless it's already there * because people have seen it being used in ssh_keys_list. diff --git a/src/cryptops-api.c b/src/cryptops-api.c index ffbc915b82a7fb604da6d7c1c47c87a2187ce497..87dc0d6953fa5636df908c5564c78e0978f70e83 100644 --- a/src/cryptops-api.c +++ b/src/cryptops-api.c @@ -12,7 +12,9 @@ #include <api/encryption_unlock_post.c> #include <api/encryption_keys_put.c> #include <api/ssh_keys_get.c> +#include <api/ssh_keys_put.c> #include <api/ssh_keys_post.c> +#include <api/ssh_keys_delete.c> int main(int argc, char ** argv) { @@ -58,9 +60,15 @@ int main(int argc, char ** argv) ulfius_add_endpoint_by_val(&instance, "GET" , PREFIX, "/ssh/keys", 0, &callback_ssh_keys_get, NULL); + ulfius_add_endpoint_by_val(&instance, "PUT" , PREFIX, + "/ssh/keys/:id", + 0, &callback_ssh_keys_put, NULL); ulfius_add_endpoint_by_val(&instance, "POST" , PREFIX, "/ssh/keys", 0, &callback_ssh_keys_post, NULL); + ulfius_add_endpoint_by_val(&instance, "DELETE" , PREFIX, + "/ssh/keys/:id", + 0, &callback_ssh_keys_delete, NULL); // Add default endpoint. ulfius_set_default_endpoint(&instance, &callback_default, NULL);