v0.0.8 - Add FIPS npub-aware approval prompt and simplify qube installer to nsigner-only startup flow

This commit is contained in:
Laan Tungir
2026-05-03 19:50:29 -04:00
parent f9d7b94962
commit 252d026991
3 changed files with 225 additions and 140 deletions

View File

@@ -6,34 +6,23 @@ set -euo pipefail
#
# Installs into $HOME:
# - nsigner -> ~/.local/bin/nsigner
# - fips/fipsctl/fipstop -> ~/.local/bin/
# - fips config -> ~/.config/fips/fips.yaml
# - user service -> ~/.config/systemd/user/fips.service
# - startup helper -> ~/start_nsigner.sh
#
# Usage:
# bash install_qube_fips_nsigner.sh
# bash install_qube_fips_nsigner.sh --help
#
# Optional env vars:
# NSIGNER_VERSION=v0.0.6
# FIPS_VERSION=v0.2.0-rel
# NSIGNER_VERSION=v0.0.7
# NSIGNER_GITEA_TOKEN=<token> # if n_signer release assets are private
# NSIGNER_BINARY_URL=<direct url to nsigner_static_x86_64>
NSIGNER_VERSION="${NSIGNER_VERSION:-v0.0.6}"
FIPS_VERSION="${FIPS_VERSION:-v0.2.0-rel}"
NSIGNER_VERSION="${NSIGNER_VERSION:-v0.0.7}"
NSIGNER_RELEASE_PAGE="https://git.laantungir.net/laantungir/n_signer/releases/tag/${NSIGNER_VERSION}"
NSIGNER_API_TAG_URL="https://git.laantungir.net/api/v1/repos/laantungir/n_signer/releases/tags/${NSIGNER_VERSION}"
NSIGNER_GIT_URL="https://git.laantungir.net/laantungir/n_signer.git"
FIPS_RELEASE_PAGE="https://github.com/jmcorgan/fips/releases/tag/${FIPS_VERSION}"
FIPS_ARCHIVE_URL="https://github.com/jmcorgan/fips/archive/refs/tags/${FIPS_VERSION}.tar.gz"
PREFIX_BIN="${HOME}/.local/bin"
PREFIX_CFG="${HOME}/.config"
PREFIX_DATA="${HOME}/.local/share/nsigner-fips-install"
SYSTEMD_USER_DIR="${HOME}/.config/systemd/user"
log() { printf "\033[1;34m[INFO]\033[0m %s\n" "$*"; }
warn() { printf "\033[1;33m[WARN]\033[0m %s\n" "$*"; }
@@ -45,25 +34,19 @@ Usage: bash install_qube_fips_nsigner.sh [options]
User-only install (Qubes AppVM friendly):
- n_signer ${NSIGNER_VERSION}
- FIPS ${FIPS_VERSION}
- user-level systemd service for FIPS
- signer startup helper script
Options:
-h, --help Show this help and exit
Optional env vars:
NSIGNER_VERSION=v0.0.6
FIPS_VERSION=v0.2.0-rel
NSIGNER_VERSION=v0.0.7
NSIGNER_GITEA_TOKEN=<token> # required if n_signer release assets are private
NSIGNER_BINARY_URL=<direct url to nsigner_static_x86_64>
Install paths:
~/.local/bin/nsigner
~/.local/bin/fips
~/.local/bin/fipsctl
~/.local/bin/fipstop
~/.config/fips/fips.yaml
~/.config/systemd/user/fips.service
~/start_nsigner.sh
EOF
}
@@ -74,28 +57,22 @@ require_cmd() {
}
}
install_build_deps() {
install_runtime_deps() {
if command -v apt-get >/dev/null 2>&1; then
log "Installing build/runtime dependencies via apt"
log "Installing runtime dependencies via apt"
sudo apt-get update
sudo apt-get install -y \
ca-certificates curl jq tar gzip git python3 \
build-essential pkg-config libssl-dev zlib1g-dev \
rustc cargo
sudo apt-get install -y ca-certificates curl jq
elif command -v dnf >/dev/null 2>&1; then
log "Installing build/runtime dependencies via dnf"
sudo dnf install -y \
ca-certificates curl jq tar gzip git python3 \
gcc make pkgconf-pkg-config openssl-devel zlib-devel \
rust cargo
log "Installing runtime dependencies via dnf"
sudo dnf install -y ca-certificates curl jq
else
err "Unsupported distro: need apt-get or dnf to install build dependencies"
err "Unsupported distro: need apt-get or dnf to install runtime dependencies"
exit 1
fi
}
prepare_dirs() {
mkdir -p "${PREFIX_BIN}" "${PREFIX_CFG}/fips" "${PREFIX_DATA}" "${SYSTEMD_USER_DIR}"
mkdir -p "${PREFIX_BIN}"
}
download_nsigner_asset_url() {
@@ -135,103 +112,47 @@ install_nsigner() {
log "Installed ${PREFIX_BIN}/nsigner from release binary"
}
install_fips() {
log "Installing FIPS ${FIPS_VERSION}"
log "Release page: ${FIPS_RELEASE_PAGE}"
write_signer_start_script() {
local script_path="${HOME}/start_nsigner.sh"
rm -rf "${PREFIX_DATA}/fips-src"
mkdir -p "${PREFIX_DATA}/fips-src"
cat >"${script_path}" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
curl -fL -o "${PREFIX_DATA}/fips.tar.gz" "${FIPS_ARCHIVE_URL}"
tar -xzf "${PREFIX_DATA}/fips.tar.gz" -C "${PREFIX_DATA}/fips-src" --strip-components=1
export PATH="$HOME/.local/bin:$PATH"
LISTEN_TARGET="${NSIGNER_LISTEN_TARGET:-tcp:[::]:8080}"
(
cd "${PREFIX_DATA}/fips-src"
cargo build --release
echo "=== n_signer startup ==="
echo "listen target: ${LISTEN_TARGET}"
install -m 0755 ./target/release/fips "${PREFIX_BIN}/fips"
install -m 0755 ./target/release/fipsctl "${PREFIX_BIN}/fipsctl"
if [[ -x ./target/release/fipstop ]]; then
install -m 0755 ./target/release/fipstop "${PREFIX_BIN}/fipstop"
fi
)
log "Installed FIPS binaries into ${PREFIX_BIN}"
}
write_fips_user_config() {
local cfg="${PREFIX_CFG}/fips/fips.yaml"
if [[ -f "${cfg}" ]]; then
warn "Keeping existing ${cfg}"
return
fi
cat >"${cfg}" <<'EOF'
# User-local FIPS config for AppVM persistence
# Edit peers to join your mesh.
node:
identity:
persistent: true
# Try userspace tun name; adjust if your environment requires a different setting.
tun:
enabled: true
name: fips0
mtu: 1280
dns:
enabled: true
bind_addr: "127.0.0.1"
port: 5354
transports:
udp:
bind_addr: "0.0.0.0:2121"
tcp:
bind_addr: "0.0.0.0:8443"
peers: []
EOF
log "Wrote ${cfg}"
}
write_fips_user_service() {
local svc="${SYSTEMD_USER_DIR}/fips.service"
cat >"${svc}" <<EOF
[Unit]
Description=FIPS mesh daemon (user)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=${PREFIX_BIN}/fips --config ${PREFIX_CFG}/fips/fips.yaml
Restart=on-failure
RestartSec=2
Environment=RUST_LOG=info
[Install]
WantedBy=default.target
EOF
log "Wrote ${svc}"
}
start_fips_user_service() {
require_cmd systemctl
systemctl --user daemon-reload
systemctl --user enable --now fips
if systemctl --user is-active --quiet fips; then
log "fips user service: active"
# Optional: print current FIPS identity info if fipsctl is available.
if command -v fipsctl >/dev/null 2>&1; then
if fipsctl show status >/dev/null 2>&1; then
STATUS_JSON="$(fipsctl show status)"
elif sudo -n fipsctl show status >/dev/null 2>&1; then
STATUS_JSON="$(sudo -n fipsctl show status)"
else
warn "fips user service is not active"
warn "Check logs with: journalctl --user -u fips -n 200 --no-pager"
STATUS_JSON=""
fi
if [[ -n "${STATUS_JSON}" ]]; then
FIPS_IPV6="$(printf '%s\n' "${STATUS_JSON}" | sed -n 's/.*"ipv6_addr": "\([^"]*\)".*/\1/p')"
FIPS_NPUB="$(printf '%s\n' "${STATUS_JSON}" | sed -n 's/.*"npub": "\([^"]*\)".*/\1/p')"
[[ -n "${FIPS_IPV6}" ]] && echo "fips ipv6: ${FIPS_IPV6}"
[[ -n "${FIPS_NPUB}" ]] && echo "fips npub: ${FIPS_NPUB}"
else
echo "fips status: unavailable (run as user in fips group or with sudo)"
fi
fi
echo
echo "Starting signer..."
echo "On first remote request, approve in prompt with [y] or [a]."
exec "$HOME/.local/bin/nsigner" --listen "${LISTEN_TARGET}"
EOF
chmod 0755 "${script_path}"
log "Wrote ${script_path}"
}
post_checks() {
@@ -239,15 +160,10 @@ post_checks() {
log "Running post-install checks"
require_cmd nsigner
require_cmd fips
require_cmd fipsctl
nsigner --version || true
fips --version || true
log "User binaries installed in: ${PREFIX_BIN}"
log "If needed, add to shell PATH: export PATH=\"${PREFIX_BIN}:\$PATH\""
log "Edit peers in: ${PREFIX_CFG}/fips/fips.yaml"
}
main() {
@@ -256,16 +172,20 @@ main() {
exit 0
fi
install_build_deps
if [[ $# -gt 0 ]]; then
err "Unknown option: $1"
show_help
exit 1
fi
install_runtime_deps
prepare_dirs
install_nsigner
install_fips
write_fips_user_config
write_fips_user_service
start_fips_user_service
write_signer_start_script
post_checks
log "Completed user-only install of n_signer + FIPS"
log "Completed user-only install of n_signer"
log "Start signer with: ~/start_nsigner.sh"
}
main "$@"

View File

@@ -451,8 +451,8 @@ int socket_name_random(char *out, size_t out_len);
/* Version information (auto-updated by build/version tooling) */
#define NSIGNER_VERSION_MAJOR 0
#define NSIGNER_VERSION_MINOR 0
#define NSIGNER_VERSION_PATCH 7
#define NSIGNER_VERSION "v0.0.7"
#define NSIGNER_VERSION_PATCH 8
#define NSIGNER_VERSION "v0.0.8"
/* NSIGNER_HEADERLESS_DECLS_END */

View File

@@ -469,6 +469,157 @@ int transport_recv_framed(int fd, char **out_payload, size_t max_size);
static int g_prompt_always_allow = 0;
static int g_noninteractive_prompt_default = -1;
static int caller_id_extract_ipv6(const char *caller_id, char *out_ipv6, size_t out_sz) {
const char *start;
const char *end;
size_t len;
if (caller_id == NULL || out_ipv6 == NULL || out_sz == 0) {
return -1;
}
if (strncmp(caller_id, "tcp:[", 5) != 0) {
return -1;
}
start = caller_id + 5;
end = strchr(start, ']');
if (end == NULL || end[1] != ':') {
return -1;
}
len = (size_t)(end - start);
if (len == 0 || len >= out_sz) {
return -1;
}
memcpy(out_ipv6, start, len);
out_ipv6[len] = '\0';
return 0;
}
static int read_cmd_output(const char *cmd, char **out_buf) {
FILE *fp;
char chunk[512];
char *buf = NULL;
size_t used = 0;
size_t cap = 0;
if (cmd == NULL || out_buf == NULL) {
return -1;
}
*out_buf = NULL;
fp = popen(cmd, "r");
if (fp == NULL) {
return -1;
}
while (fgets(chunk, sizeof(chunk), fp) != NULL) {
size_t n = strlen(chunk);
if (used + n + 1 > cap) {
size_t new_cap = (cap == 0) ? 2048 : cap * 2;
while (new_cap < used + n + 1) {
new_cap *= 2;
}
{
char *tmp = (char *)realloc(buf, new_cap);
if (tmp == NULL) {
free(buf);
(void)pclose(fp);
return -1;
}
buf = tmp;
cap = new_cap;
}
}
memcpy(buf + used, chunk, n);
used += n;
}
(void)pclose(fp);
if (buf == NULL) {
return -1;
}
buf[used] = '\0';
*out_buf = buf;
return 0;
}
static int lookup_fips_peer_for_ipv6(const char *ipv6,
char *out_npub,
size_t out_npub_sz,
char *out_name,
size_t out_name_sz) {
char *json = NULL;
cJSON *root = NULL;
cJSON *peers = NULL;
int i;
if (ipv6 == NULL || out_npub == NULL || out_npub_sz == 0 || out_name == NULL || out_name_sz == 0) {
return -1;
}
out_npub[0] = '\0';
out_name[0] = '\0';
if (read_cmd_output("fipsctl show peers 2>/dev/null", &json) != 0) {
return -1;
}
root = cJSON_Parse(json);
free(json);
if (root == NULL) {
return -1;
}
peers = cJSON_GetObjectItemCaseSensitive(root, "peers");
if (!cJSON_IsArray(peers)) {
cJSON_Delete(root);
return -1;
}
for (i = 0; i < cJSON_GetArraySize(peers); ++i) {
cJSON *peer = cJSON_GetArrayItem(peers, i);
cJSON *peer_ip;
cJSON *peer_npub;
cJSON *peer_name;
if (!cJSON_IsObject(peer)) {
continue;
}
peer_ip = cJSON_GetObjectItemCaseSensitive(peer, "ipv6_addr");
if (!cJSON_IsString(peer_ip) || peer_ip->valuestring == NULL) {
continue;
}
if (strcmp(peer_ip->valuestring, ipv6) != 0) {
continue;
}
peer_npub = cJSON_GetObjectItemCaseSensitive(peer, "npub");
peer_name = cJSON_GetObjectItemCaseSensitive(peer, "display_name");
if (cJSON_IsString(peer_npub) && peer_npub->valuestring != NULL) {
strncpy(out_npub, peer_npub->valuestring, out_npub_sz - 1);
out_npub[out_npub_sz - 1] = '\0';
}
if (cJSON_IsString(peer_name) && peer_name->valuestring != NULL) {
strncpy(out_name, peer_name->valuestring, out_name_sz - 1);
out_name[out_name_sz - 1] = '\0';
}
cJSON_Delete(root);
return (out_npub[0] != '\0') ? 0 : -1;
}
cJSON_Delete(root);
return -1;
}
void server_set_prompt_always_allow(int enabled) {
g_prompt_always_allow = enabled ? 1 : 0;
}
@@ -501,6 +652,20 @@ static int prompt_for_policy_decision(const caller_identity_t *caller,
printf("\nApproval required\n");
printf("caller: %s\n", (caller != NULL) ? caller->caller_id : "unknown");
if (caller != NULL && caller->kind == NSIGNER_LISTEN_TCP) {
char ipv6[INET6_ADDRSTRLEN];
char npub[128];
char display_name[128];
if (caller_id_extract_ipv6(caller->caller_id, ipv6, sizeof(ipv6)) == 0 &&
lookup_fips_peer_for_ipv6(ipv6, npub, sizeof(npub), display_name, sizeof(display_name)) == 0) {
if (display_name[0] != '\0') {
printf("fips peer: %s (%s)\n", npub, display_name);
} else {
printf("fips peer: %s\n", npub);
}
}
}
printf("method: %s\n", (method != NULL) ? method : "unknown");
printf("role: %s\n", (role_name != NULL) ? role_name : "unknown");
printf("purpose: %s\n", (purpose != NULL) ? purpose : "unknown");