rm neuron.key pem

This commit is contained in:
fengzero
2026-06-17 02:53:56 +00:00
parent 45f9b5edff
commit 8205586987
8 changed files with 266 additions and 64 deletions
+8 -4
View File
@@ -86,10 +86,6 @@ $ cd neuron && mkdir build && cd build
$ cmake .. && make $ cmake .. && make
``` ```
> **Security Notice**
> To simplify open-source testing, Neuron includes a built-in public/private key pair for HTTP JWT by default.
> **Before compiling source code for production or external delivery, replace the built-in key pair with your own secure keys.**
3. Download and Unzip Dashboard 3. Download and Unzip Dashboard
```bash ```bash
$ wget https://github.com/emqx/neuron-dashboard/releases/download/2.6.3/neuron-dashboard.zip $ wget https://github.com/emqx/neuron-dashboard/releases/download/2.6.3/neuron-dashboard.zip
@@ -103,6 +99,14 @@ $ unzip neuron-dashboard.zip
$ ./neuron --log $ ./neuron --log
``` ```
On the first startup, Neuron will automatically generate `neuron.key` and `neuron.key.pub` in the config directory if they do not exist yet.
If you want to pre-generate the key pair manually, you can still run:
```bash
$ bash ./generate_rsa_keypair.sh ./build/config
```
5. Open a browser and navigate to `http://localhost:7000` to access the Neuron web interface. 5. Open a browser and navigate to `http://localhost:7000` to access the Neuron web interface.
## Configuration ## Configuration
+34
View File
@@ -0,0 +1,34 @@
#!/bin/bash
set -euo pipefail
output_dir="${1:-config}"
private_key_file="${output_dir}/neuron.key"
public_key_file="${output_dir}/neuron.key.pub"
if ! command -v openssl >/dev/null 2>&1; then
echo "openssl is required but was not found in PATH."
exit 1
fi
mkdir -p "${output_dir}"
if [[ -f "${private_key_file}" || -f "${public_key_file}" ]]; then
read -r -p "Existing key files found in ${output_dir}. Overwrite them? [y/N] " answer
case "${answer}" in
[yY]|[yY][eE][sS]) ;;
*)
echo "Aborted."
exit 0
;;
esac
fi
umask 077
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out "${private_key_file}"
openssl pkey -in "${private_key_file}" -pubout -out "${public_key_file}"
echo "Generated RSA key pair:"
echo " ${private_key_file}"
echo " ${public_key_file}"
echo "Run neuron with --config_dir ${output_dir} to use the generated keys."
+14 -2
View File
@@ -1,8 +1,20 @@
import jwt # pip install pyjwt import jwt # pip install pyjwt
import time import time
from pathlib import Path
with open("neuron.key", "r") as f: key_candidates = [
private_key = f.read() Path("config") / "neuron.key",
Path("neuron.key"),
]
private_key = None
for key_path in key_candidates:
if key_path.is_file():
private_key = key_path.read_text()
break
if private_key is None:
raise FileNotFoundError("Cannot find neuron.key in ./config or the current directory")
current_time = int(time.time()) current_time = int(time.time())
two_years = 60 * 60 * 24 * 365 * 2 two_years = 60 * 60 * 24 * 365 * 2
+10
View File
@@ -52,6 +52,16 @@ void handle_ping(nng_aio *aio)
void handle_login(nng_aio *aio) void handle_login(nng_aio *aio)
{ {
if (disable_jwt) {
neu_json_login_resp_t login_resp = { .token = "" };
char * result = NULL;
neu_json_encode_by_fn(&login_resp, neu_json_encode_login_resp, &result);
neu_http_ok(aio, result);
free(result);
return;
}
NEU_PROCESS_HTTP_REQUEST( NEU_PROCESS_HTTP_REQUEST(
aio, neu_json_login_req_t, neu_json_decode_login_req, { aio, neu_json_login_req_t, neu_json_decode_login_req, {
neu_json_login_resp_t login_resp = { 0 }; neu_json_login_resp_t login_resp = { 0 };
-1
View File
@@ -39,7 +39,6 @@
static bool exit_flag = false; static bool exit_flag = false;
neu_manager_t * g_manager = NULL; neu_manager_t * g_manager = NULL;
zlog_category_t *neuron = NULL; zlog_category_t *neuron = NULL;
bool disable_jwt = false;
bool sub_filter_err = false; bool sub_filter_err = false;
int default_log_level = ZLOG_LEVEL_NOTICE; int default_log_level = ZLOG_LEVEL_NOTICE;
char host_port[32] = { 0 }; char host_port[32] = { 0 };
+147 -57
View File
@@ -20,10 +20,16 @@
#include <assert.h> #include <assert.h>
#include <dirent.h> #include <dirent.h>
#include <errno.h> #include <errno.h>
#include <stdbool.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <sys/stat.h>
#include <sys/time.h> #include <sys/time.h>
#include <unistd.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <jwt.h> #include <jwt.h>
@@ -39,50 +45,12 @@ struct public_key_store {
int size; int size;
}; };
bool disable_jwt = false;
static struct public_key_store key_store; static struct public_key_store key_store;
static char neuron_private_key[2048] = { 0 }; static char neuron_private_key[2048] = { 0 };
static char neuron_public_key[2048] = { 0 }; static char neuron_public_key[2048] = { 0 };
static const char *private_key_content =
"-----BEGIN RSA PRIVATE KEY-----\n"
"MIIEpAIBAAKCAQEAzrO7FUz4GGKl4nP5lwKMh5uageJAAHnLZpVxTR1wXA9E870s\n"
"U03gHnPWsh2bXCCx85ymYaiu/5VlptgnU0rQB1I3xGRG8eHmHPrhHNWDRKpY6oY8\n"
"AnTPqXvfa/Z4u+njJzDSqi4qM8Gwfqlgpjlg8DUF+sheLx8hL+x9XFzovTxafrmy\n"
"JRRAVQTkhB7esKKqRF8BMjdOdYZxJEv9jxa0BwXcjprPDlj5TV6k3wJqbq4UtnEE\n"
"VbjEQKqFVkfY4FuJ5NSPHpGEix94PGQGRsiWkGX0vx5udHJ0SlXM6qpMLny18FhW\n"
"bwKNX3urc3mImn/j3zcJ5x1d+FlXAXs1NBSeTQIDAQABAoIBAQDJOT8PZXbAhohn\n"
"A/AeimS0P08S0mbsD6VroGBEajxP4q2FeswD7PQZsTt4+kmcTlfuiLmQqN50AcSL\n"
"wDHIbDRIbEnN7rECGKAj5jfwEgtQdWVKKpOQ8JaYr/a466BtjyuLo4PyGC8NY6mm\n"
"JM3qBEHSlkvT7+uAhBWSye7gU7JfRPoCeFRpaMMC3Ad0DaIt/Pdt9CddNwj56+9j\n"
"gl//blNnBImjvA7/kh9gL69SYVrQoG9vm/UgFbdIwd+w2hhhpx6kuHjRJ21xIV/y\n"
"Yh9k4QhPr7zIkhQ8yn11ZhyZkMC8XAMy7NKCmAFIPNJ8jwvgBkunPEk2aYBeDUaq\n"
"a30VwlIhAoGBAOnm8DW5DROcfSj4Q8zsvgmvhGM0zzF5zmA+4E+tEbf4t6sDUjSs\n"
"ADo2EaiJZz75vLsD7UZHKzFEV/wnuT7SiNCbrJafx8Tp2UgijTkW8gEhAWREJDn5\n"
"FS375RKFdEcpmVHWgsuz4z/0L3BGxmyYWQxx5pD2NL49ZpTIHAHm/XETAoGBAOI6\n"
"8Y08TmM2bnXbL/MggYMJlqOyv27ueVYz9of3FYFAMIe7ebSgrlSCOwsoDZYmegBM\n"
"OrowR3AAnoP4fjGNbIPjNYbiv1qLOdtnvPxYotEz+ZaUBSn9V/gUEVFlgrKxgTKr\n"
"sqi/QMyyJplBTUkUuqYRDuv6TZ3OWKiDUoXTdv8fAoGBAIbbyf+PlESMY3Vtvtm2\n"
"XdODyRbR6ewiyKShW/9UT/T8iBknrwwDZ5YoeoHrxwV+RBynpPRyMCsVto0B8kKQ\n"
"bKWqPBYURb/4/Hgkw7v4yMtx1jWTPDfYryd2JptJKsOk7mtK/Nqp+wpypa9cfyc5\n"
"p44PVdqauco96Jk7zzohjlrVAoGAfjQDrJaH5DDpTjYIeckYdtFSh9+fi3LdnYk/\n"
"bnoYNRJqAE5Fhs5ccih0Z7TgX3L3fFMKL/Pe5kxyIYzuWRZcAvctVSIJPamNjShB\n"
"9UQ9EBe+lJHej54VBP+s6YuHbcg4GtxNvnVy4L5Bah1T0AEQXrQFbv8jbXU/YEJi\n"
"NuXQ7GECgYA0UNvuZxVSDn8fBxARcX3ub5aok5owdLL3SOCPMpNrmOBQj8iJGT+5\n"
"nv7fH1TWVh9yUdRwhd81s5H/PO3/myLmgq0TcPvewtEEwY+++QpsYZwHTDXgxbbw\n"
"++lmJali4fzmd/72JqocGABYhZFt0jbcQ1QEcqUYcD380+cNVZPO+w==\n"
"-----END RSA PRIVATE KEY-----\n";
const char *public_key_content =
"-----BEGIN PUBLIC KEY-----\n"
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzrO7FUz4GGKl4nP5lwKM\n"
"h5uageJAAHnLZpVxTR1wXA9E870sU03gHnPWsh2bXCCx85ymYaiu/5VlptgnU0rQ\n"
"B1I3xGRG8eHmHPrhHNWDRKpY6oY8AnTPqXvfa/Z4u+njJzDSqi4qM8Gwfqlgpjlg\n"
"8DUF+sheLx8hL+x9XFzovTxafrmyJRRAVQTkhB7esKKqRF8BMjdOdYZxJEv9jxa0\n"
"BwXcjprPDlj5TV6k3wJqbq4UtnEEVbjEQKqFVkfY4FuJ5NSPHpGEix94PGQGRsiW\n"
"kGX0vx5udHJ0SlXM6qpMLny18FhWbwKNX3urc3mImn/j3zcJ5x1d+FlXAXs1NBSe\n"
"TQIDAQAB\n"
"-----END PUBLIC KEY-----\n";
static int find_key(const char *name) static int find_key(const char *name)
{ {
for (int i = 0; i < key_store.size; i++) { for (int i = 0; i < key_store.size; i++) {
@@ -105,7 +73,8 @@ static void add_key(char *name, char *value)
sizeof(key_store.key[key_store.size - 1].key) - 1); sizeof(key_store.key[key_store.size - 1].key) - 1);
} }
static char *load_key(const char *dir, char *name) static bool load_key_file(const char *dir, const char *name, char *dst,
size_t dst_len)
{ {
FILE * f = NULL; FILE * f = NULL;
int len = 0; int len = 0;
@@ -117,31 +86,146 @@ static char *load_key(const char *dir, char *name)
f = fopen(path, "r"); f = fopen(path, "r");
if (f == NULL) { if (f == NULL) {
zlog_error(neuron, "Failed to open file: %s, errno: %d", name, errno); return false;
return NULL;
} }
len = fread(content, 1, sizeof(content), f); len = fread(content, 1, sizeof(content), f);
if (len <= 0) { if (len <= 0) {
zlog_error(neuron, "Failed to read file: %s, errno: %d", name, errno);
fclose(f); fclose(f);
return NULL; return false;
} }
fclose(f); fclose(f);
return content;
strncpy(dst, content, dst_len - 1);
dst[dst_len - 1] = '\0';
return true;
}
static bool write_private_key(FILE *file, EVP_PKEY *pkey)
{
if (fchmod(fileno(file), S_IRUSR | S_IWUSR) != 0) {
return false;
}
return PEM_write_PrivateKey(file, pkey, NULL, NULL, 0, NULL, NULL) == 1;
}
static bool write_public_key(FILE *file, EVP_PKEY *pkey)
{
if (fchmod(fileno(file), S_IRUSR | S_IWUSR) != 0) {
return false;
}
return PEM_write_PUBKEY(file, pkey) == 1;
}
static bool generate_key_pair(const char *dir_path)
{
EVP_PKEY_CTX *ctx = NULL;
EVP_PKEY * pkey = NULL;
FILE * private_file = NULL;
FILE * public_file = NULL;
char private_path[256] = { 0 };
char public_path[256] = { 0 };
bool ok = false;
snprintf(private_path, sizeof(private_path), "%s/%s", dir_path,
"neuron.key");
snprintf(public_path, sizeof(public_path), "%s/%s", dir_path,
"neuron.key.pub");
ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
if (ctx == NULL) {
return false;
}
if (EVP_PKEY_keygen_init(ctx) <= 0 ||
EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 2048) <= 0 ||
EVP_PKEY_keygen(ctx, &pkey) <= 0) {
goto done;
}
private_file = fopen(private_path, "w");
if (private_file == NULL) {
goto done;
}
public_file = fopen(public_path, "w");
if (public_file == NULL) {
goto done;
}
ok = write_private_key(private_file, pkey) &&
write_public_key(public_file, pkey);
done:
if (private_file != NULL) {
fclose(private_file);
}
if (public_file != NULL) {
fclose(public_file);
}
EVP_PKEY_free(pkey);
EVP_PKEY_CTX_free(ctx);
if (!ok) {
unlink(private_path);
unlink(public_path);
}
return ok;
} }
static void load_neuron_key(const char *dir_path) static void load_neuron_key(const char *dir_path)
{ {
char private_path[256] = { 0 };
char public_path[256] = { 0 };
(void) dir_path; if (dir_path == NULL || *dir_path == '\0') {
strncpy(neuron_private_key, private_key_content, zlog_error(neuron, "JWT config directory is empty");
sizeof(neuron_private_key) - 1); return;
}
strncpy(neuron_public_key, public_key_content, snprintf(private_path, sizeof(private_path), "%s/%s", dir_path,
sizeof(neuron_public_key) - 1); "neuron.key");
snprintf(public_path, sizeof(public_path), "%s/%s", dir_path,
"neuron.key.pub");
if (!load_key_file(dir_path, "neuron.key", neuron_private_key,
sizeof(neuron_private_key)) ||
!load_key_file(dir_path, "neuron.key.pub", neuron_public_key,
sizeof(neuron_public_key))) {
zlog_warn(neuron, "Missing JWT RSA key pair in %s, generating new keys",
dir_path);
if (!generate_key_pair(dir_path)) {
zlog_error(neuron, "Failed to generate JWT RSA key pair in %s",
dir_path);
neuron_private_key[0] = '\0';
neuron_public_key[0] = '\0';
return;
}
zlog_notice(neuron,
"Generated JWT RSA key pair in config directory: %s",
dir_path);
if (!load_key_file(dir_path, "neuron.key", neuron_private_key,
sizeof(neuron_private_key)) ||
!load_key_file(dir_path, "neuron.key.pub", neuron_public_key,
sizeof(neuron_public_key))) {
zlog_error(neuron,
"Failed to load generated JWT RSA key pair from %s",
dir_path);
neuron_private_key[0] = '\0';
neuron_public_key[0] = '\0';
return;
}
} else {
zlog_notice(neuron,
"Load external RSA key pair from config directory: %s",
dir_path);
}
} }
static void scanf_key(const char *dir_path) static void scanf_key(const char *dir_path)
@@ -158,8 +242,6 @@ static void scanf_key(const char *dir_path)
} }
while (NULL != (ptr = readdir(dir))) { while (NULL != (ptr = readdir(dir))) {
char *content = NULL;
if (!strcmp((char *) ptr->d_name, ".") || if (!strcmp((char *) ptr->d_name, ".") ||
!strcmp((char *) ptr->d_name, "..")) { !strcmp((char *) ptr->d_name, "..")) {
continue; continue;
@@ -167,8 +249,9 @@ static void scanf_key(const char *dir_path)
if (strstr((char *) ptr->d_name, ".pem") != NULL || if (strstr((char *) ptr->d_name, ".pem") != NULL ||
strstr((char *) ptr->d_name, ".pub") != NULL) { strstr((char *) ptr->d_name, ".pub") != NULL) {
content = load_key(dir_path, (char *) ptr->d_name); char content[2047] = { 0 };
assert(content != NULL); assert(load_key_file(dir_path, (char *) ptr->d_name, content,
sizeof(content)));
add_key((char *) ptr->d_name, content); add_key((char *) ptr->d_name, content);
} }
} }
@@ -178,9 +261,16 @@ static void scanf_key(const char *dir_path)
int neu_jwt_init(const char *dir_path) int neu_jwt_init(const char *dir_path)
{ {
if (disable_jwt) {
zlog_notice(neuron,
"HTTP auth is disabled, skip JWT RSA key generation");
return 0;
}
load_neuron_key(dir_path); load_neuron_key(dir_path);
scanf_key("certs"); scanf_key("certs");
return 0; return neuron_private_key[0] == '\0' || neuron_public_key[0] == '\0' ? -1
: 0;
} }
int neu_jwt_new(char **token, const char *user) int neu_jwt_new(char **token, const char *user)
+40
View File
@@ -1,5 +1,11 @@
import pytest import pytest
import inspect
import neuron.api as api
import neuron.config as config
import neuron.process as process
def pytest_addoption(parser): def pytest_addoption(parser):
parser.addoption( parser.addoption(
@@ -10,3 +16,37 @@ def pytest_addoption(parser):
@pytest.fixture(autouse=True, scope="session") @pytest.fixture(autouse=True, scope="session")
def get_envx(request): def get_envx(request):
return request.config.getoption("--env") return request.config.getoption("--env")
def _refresh_default_jwt(token):
old_jwt = config.default_jwt
new_jwt = f"Bearer {token}"
config.default_jwt = new_jwt
for _, func in inspect.getmembers(api, inspect.isfunction):
defaults = func.__defaults__
if defaults:
func.__defaults__ = tuple(
new_jwt if value == old_jwt else value for value in defaults
)
kwdefaults = func.__kwdefaults__
if kwdefaults:
func.__kwdefaults__ = {
key: new_jwt if value == old_jwt else value
for key, value in kwdefaults.items()
}
@pytest.fixture(autouse=True, scope="session")
def init_default_jwt():
process.remove_persistence()
neuron = process.NeuronProcess()
neuron.start()
response = api.login()
assert 200 == response.status_code
_refresh_default_jwt(response.json()["token"])
neuron.stop()
+13
View File
@@ -215,6 +215,19 @@ class TestLaunch:
os.remove(dst_file) os.remove(dst_file)
os.rename(dst_backup, dst_file) os.rename(dst_backup, dst_file)
@description(given="disable_auth enabled", when="call login", then="login should still succeed")
def test_login_with_disable_auth(self):
process.remove_persistence()
p = process.NeuronProcess()
p.start_disable_auth()
response = api.login()
assert 200 == response.status_code
assert "" == response.json()["token"]
p.stop()
@description(given="encironment variables 1, command line 0", @description(given="encironment variables 1, command line 0",
when="start neuron", when="start neuron",
then="log level set right") then="log level set right")