mirror of
https://github.com/emqx/neuron.git
synced 2026-10-16 20:30:39 +08:00
rm neuron.key pem
This commit is contained in:
@@ -86,10 +86,6 @@ $ cd neuron && mkdir build && cd build
|
||||
$ 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
|
||||
```bash
|
||||
$ 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
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -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
@@ -1,8 +1,20 @@
|
||||
import jwt # pip install pyjwt
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
with open("neuron.key", "r") as f:
|
||||
private_key = f.read()
|
||||
key_candidates = [
|
||||
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())
|
||||
two_years = 60 * 60 * 24 * 365 * 2
|
||||
|
||||
@@ -52,6 +52,16 @@ void handle_ping(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(
|
||||
aio, neu_json_login_req_t, neu_json_decode_login_req, {
|
||||
neu_json_login_resp_t login_resp = { 0 };
|
||||
|
||||
@@ -39,7 +39,6 @@
|
||||
static bool exit_flag = false;
|
||||
neu_manager_t * g_manager = NULL;
|
||||
zlog_category_t *neuron = NULL;
|
||||
bool disable_jwt = false;
|
||||
bool sub_filter_err = false;
|
||||
int default_log_level = ZLOG_LEVEL_NOTICE;
|
||||
char host_port[32] = { 0 };
|
||||
|
||||
+147
-57
@@ -20,10 +20,16 @@
|
||||
#include <assert.h>
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/pem.h>
|
||||
|
||||
#include <jwt.h>
|
||||
|
||||
@@ -39,50 +45,12 @@ struct public_key_store {
|
||||
int size;
|
||||
};
|
||||
|
||||
bool disable_jwt = false;
|
||||
|
||||
static struct public_key_store key_store;
|
||||
static char neuron_private_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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
int len = 0;
|
||||
@@ -117,31 +86,146 @@ static char *load_key(const char *dir, char *name)
|
||||
|
||||
f = fopen(path, "r");
|
||||
if (f == NULL) {
|
||||
zlog_error(neuron, "Failed to open file: %s, errno: %d", name, errno);
|
||||
return NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
len = fread(content, 1, sizeof(content), f);
|
||||
if (len <= 0) {
|
||||
zlog_error(neuron, "Failed to read file: %s, errno: %d", name, errno);
|
||||
|
||||
fclose(f);
|
||||
return NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
char private_path[256] = { 0 };
|
||||
char public_path[256] = { 0 };
|
||||
|
||||
(void) dir_path;
|
||||
strncpy(neuron_private_key, private_key_content,
|
||||
sizeof(neuron_private_key) - 1);
|
||||
if (dir_path == NULL || *dir_path == '\0') {
|
||||
zlog_error(neuron, "JWT config directory is empty");
|
||||
return;
|
||||
}
|
||||
|
||||
strncpy(neuron_public_key, public_key_content,
|
||||
sizeof(neuron_public_key) - 1);
|
||||
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");
|
||||
|
||||
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)
|
||||
@@ -158,8 +242,6 @@ static void scanf_key(const char *dir_path)
|
||||
}
|
||||
|
||||
while (NULL != (ptr = readdir(dir))) {
|
||||
char *content = NULL;
|
||||
|
||||
if (!strcmp((char *) ptr->d_name, ".") ||
|
||||
!strcmp((char *) ptr->d_name, "..")) {
|
||||
continue;
|
||||
@@ -167,8 +249,9 @@ static void scanf_key(const char *dir_path)
|
||||
|
||||
if (strstr((char *) ptr->d_name, ".pem") != NULL ||
|
||||
strstr((char *) ptr->d_name, ".pub") != NULL) {
|
||||
content = load_key(dir_path, (char *) ptr->d_name);
|
||||
assert(content != NULL);
|
||||
char content[2047] = { 0 };
|
||||
assert(load_key_file(dir_path, (char *) ptr->d_name, content,
|
||||
sizeof(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)
|
||||
{
|
||||
if (disable_jwt) {
|
||||
zlog_notice(neuron,
|
||||
"HTTP auth is disabled, skip JWT RSA key generation");
|
||||
return 0;
|
||||
}
|
||||
|
||||
load_neuron_key(dir_path);
|
||||
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)
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import pytest
|
||||
|
||||
import inspect
|
||||
|
||||
import neuron.api as api
|
||||
import neuron.config as config
|
||||
import neuron.process as process
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
parser.addoption(
|
||||
@@ -10,3 +16,37 @@ def pytest_addoption(parser):
|
||||
@pytest.fixture(autouse=True, scope="session")
|
||||
def get_envx(request):
|
||||
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()
|
||||
|
||||
@@ -215,6 +215,19 @@ class TestLaunch:
|
||||
os.remove(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",
|
||||
when="start neuron",
|
||||
then="log level set right")
|
||||
|
||||
Reference in New Issue
Block a user