Move allowed_nodes to managed file
This commit is contained in:
parent
5951a364f7
commit
430c98c5ef
2 changed files with 170 additions and 52 deletions
|
|
@ -1,6 +1,8 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
|
@ -24,6 +26,7 @@ type Config struct {
|
||||||
MetricsAddress string `yaml:"metrics_address"`
|
MetricsAddress string `yaml:"metrics_address"`
|
||||||
LogLevel string `yaml:"log_level"`
|
LogLevel string `yaml:"log_level"`
|
||||||
AllowedNodes []string `yaml:"allowed_nodes"`
|
AllowedNodes []string `yaml:"allowed_nodes"`
|
||||||
|
AllowedNodesFile string `yaml:"allowed_nodes_file"`
|
||||||
StrictNodeValidation bool `yaml:"strict_node_validation"`
|
StrictNodeValidation bool `yaml:"strict_node_validation"`
|
||||||
ReadTimeout time.Duration `yaml:"read_timeout"`
|
ReadTimeout time.Duration `yaml:"read_timeout"`
|
||||||
WriteTimeout time.Duration `yaml:"write_timeout"`
|
WriteTimeout time.Duration `yaml:"write_timeout"`
|
||||||
|
|
@ -55,6 +58,8 @@ type ControlPlaneConfig struct {
|
||||||
InsecureSkipVerify bool `yaml:"insecure_skip_verify"`
|
InsecureSkipVerify bool `yaml:"insecure_skip_verify"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const defaultAllowedNodesFile = "/etc/pulse-sensor-proxy/allowed_nodes.yaml"
|
||||||
|
|
||||||
// PeerConfig represents a peer entry with capabilities.
|
// PeerConfig represents a peer entry with capabilities.
|
||||||
type PeerConfig struct {
|
type PeerConfig struct {
|
||||||
UID uint32 `yaml:"uid"`
|
UID uint32 `yaml:"uid"`
|
||||||
|
|
@ -204,6 +209,29 @@ func loadConfig(configPath string) (*Config, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cfg.AllowedNodesFile == "" {
|
||||||
|
if _, err := os.Stat(defaultAllowedNodesFile); err == nil {
|
||||||
|
cfg.AllowedNodesFile = defaultAllowedNodesFile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.AllowedNodesFile != "" {
|
||||||
|
if fileNodes, err := loadAllowedNodesFile(cfg.AllowedNodesFile); err != nil {
|
||||||
|
log.Warn().
|
||||||
|
Err(err).
|
||||||
|
Str("allowed_nodes_file", cfg.AllowedNodesFile).
|
||||||
|
Msg("Failed to load allowed nodes file")
|
||||||
|
} else if len(fileNodes) > 0 {
|
||||||
|
cfg.AllowedNodes = append(cfg.AllowedNodes, fileNodes...)
|
||||||
|
log.Info().
|
||||||
|
Str("allowed_nodes_file", cfg.AllowedNodesFile).
|
||||||
|
Int("allowed_node_count", len(fileNodes)).
|
||||||
|
Msg("Loaded allowed nodes from file")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.AllowedNodes = normalizeNodes(cfg.AllowedNodes)
|
||||||
|
|
||||||
// Strict node validation override
|
// Strict node validation override
|
||||||
if envStrict := os.Getenv("PULSE_SENSOR_PROXY_STRICT_NODE_VALIDATION"); envStrict != "" {
|
if envStrict := os.Getenv("PULSE_SENSOR_PROXY_STRICT_NODE_VALIDATION"); envStrict != "" {
|
||||||
parsed, err := parseBool(envStrict)
|
parsed, err := parseBool(envStrict)
|
||||||
|
|
@ -386,6 +414,64 @@ func splitAndTrim(raw string) []string {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func normalizeNodes(nodes []string) []string {
|
||||||
|
seen := make(map[string]struct{})
|
||||||
|
var result []string
|
||||||
|
for _, node := range nodes {
|
||||||
|
trimmed := strings.TrimSpace(node)
|
||||||
|
if trimmed == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
key := strings.ToLower(trimmed)
|
||||||
|
if _, exists := seen[key]; exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[key] = struct{}{}
|
||||||
|
result = append(result, trimmed)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadAllowedNodesFile(path string) ([]string, error) {
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type wrapper struct {
|
||||||
|
AllowedNodes []string `yaml:"allowed_nodes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var w wrapper
|
||||||
|
if err := yaml.Unmarshal(data, &w); err == nil && len(w.AllowedNodes) > 0 {
|
||||||
|
return normalizeNodes(w.AllowedNodes), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var list []string
|
||||||
|
if err := yaml.Unmarshal(data, &list); err == nil && len(list) > 0 {
|
||||||
|
return normalizeNodes(list), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(bytes.NewReader(data))
|
||||||
|
var plain []string
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := strings.TrimSpace(scanner.Text())
|
||||||
|
if line == "" || strings.HasPrefix(line, "#") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(line, "-") {
|
||||||
|
line = strings.TrimSpace(strings.TrimPrefix(line, "-"))
|
||||||
|
}
|
||||||
|
if line != "" {
|
||||||
|
plain = append(plain, line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return normalizeNodes(plain), err
|
||||||
|
}
|
||||||
|
return normalizeNodes(plain), nil
|
||||||
|
}
|
||||||
|
|
||||||
// detectHostCIDRs detects local host IP addresses as /32 (IPv4) or /128 (IPv6) CIDRs
|
// detectHostCIDRs detects local host IP addresses as /32 (IPv4) or /128 (IPv6) CIDRs
|
||||||
func detectHostCIDRs() []string {
|
func detectHostCIDRs() []string {
|
||||||
var cidrs []string
|
var cidrs []string
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,9 @@
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
CONFIG_FILE="/etc/pulse-sensor-proxy/config.yaml"
|
||||||
|
ALLOWED_NODES_FILE="/etc/pulse-sensor-proxy/allowed_nodes.yaml"
|
||||||
|
|
||||||
# Colors for output
|
# Colors for output
|
||||||
RED='\033[0;31m'
|
RED='\033[0;31m'
|
||||||
GREEN='\033[0;32m'
|
GREEN='\033[0;32m'
|
||||||
|
|
@ -79,44 +82,27 @@ EOF
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Helper function to safely update allowed_nodes section in config file
|
ensure_allowed_nodes_file_reference() {
|
||||||
# Removes any existing allowed_nodes section before adding new one to prevent duplicates
|
if [[ ! -f "$CONFIG_FILE" ]]; then
|
||||||
update_allowed_nodes() {
|
return
|
||||||
local config_file="/etc/pulse-sensor-proxy/config.yaml"
|
|
||||||
local comment_line="$1"
|
|
||||||
shift
|
|
||||||
local nodes=("$@")
|
|
||||||
|
|
||||||
mkdir -p "$(dirname "$config_file")"
|
|
||||||
touch "$config_file"
|
|
||||||
|
|
||||||
# Gather existing nodes (if any) to preserve manual edits
|
|
||||||
local existing_nodes=()
|
|
||||||
if command -v perl >/dev/null 2>&1; then
|
|
||||||
mapfile -t existing_nodes < <(perl -0ne 'if (m/allowed_nodes:\n((?:[ \t]+-[^\n]*\n)+)/m) { @lines = split /\n/, $1; for (@lines) { s/^[ \t-]+//; s/\s+$//; next unless $_; print "$_\n"; } }' "$config_file" 2>/dev/null || true)
|
|
||||||
fi
|
fi
|
||||||
|
if ! grep -q "allowed_nodes_file" "$CONFIG_FILE" 2>/dev/null; then
|
||||||
|
echo 'allowed_nodes_file: "/etc/pulse-sensor-proxy/allowed_nodes.yaml"' >> "$CONFIG_FILE"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# Merge and de-duplicate nodes
|
remove_allowed_nodes_block() {
|
||||||
local merged_nodes=()
|
if ! command -v python3 >/dev/null 2>&1; then
|
||||||
declare -A seen_nodes=()
|
sed -i '/allowed_nodes:/d' "$CONFIG_FILE" 2>/dev/null || true
|
||||||
for node in "${existing_nodes[@]}" "${nodes[@]}"; do
|
return
|
||||||
node=$(echo "$node" | awk '{$1=$1;print}')
|
fi
|
||||||
if [[ -z "$node" ]]; then
|
python3 - "$CONFIG_FILE" <<'PY'
|
||||||
continue
|
|
||||||
fi
|
|
||||||
if [[ -z "${seen_nodes[$node]+set}" ]]; then
|
|
||||||
merged_nodes+=("$node")
|
|
||||||
seen_nodes["$node"]=1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Remove any existing allowed_nodes block (including descriptive comments) to prevent duplicates
|
|
||||||
if command -v python3 >/dev/null 2>&1; then
|
|
||||||
python3 - "$config_file" <<'PY'
|
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
path = Path(sys.argv[1])
|
path = Path(sys.argv[1])
|
||||||
|
if not path.exists():
|
||||||
|
sys.exit(0)
|
||||||
lines = path.read_text().splitlines()
|
lines = path.read_text().splitlines()
|
||||||
out = []
|
out = []
|
||||||
pending = []
|
pending = []
|
||||||
|
|
@ -131,49 +117,93 @@ i = 0
|
||||||
while i < len(lines):
|
while i < len(lines):
|
||||||
line = lines[i]
|
line = lines[i]
|
||||||
stripped = line.lstrip()
|
stripped = line.lstrip()
|
||||||
|
|
||||||
if skip:
|
if skip:
|
||||||
# Keep skipping until a non-indented, non-comment line appears
|
|
||||||
if stripped == '' or stripped.startswith('#') or stripped.startswith('-') or line.startswith((' ', '\t')):
|
if stripped == '' or stripped.startswith('#') or stripped.startswith('-') or line.startswith((' ', '\t')):
|
||||||
i += 1
|
i += 1
|
||||||
continue
|
continue
|
||||||
skip = False
|
skip = False
|
||||||
|
|
||||||
if stripped.startswith('allowed_nodes:'):
|
if stripped.startswith('allowed_nodes:'):
|
||||||
pending.clear()
|
pending.clear()
|
||||||
skip = True
|
skip = True
|
||||||
i += 1
|
i += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if stripped == '' or stripped.startswith('#'):
|
if stripped == '' or stripped.startswith('#'):
|
||||||
pending.append(line)
|
pending.append(line)
|
||||||
i += 1
|
i += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
flush_pending()
|
flush_pending()
|
||||||
out.append(line)
|
out.append(line)
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
flush_pending()
|
flush_pending()
|
||||||
path.write_text('\n'.join(out) + ('\n' if out else ''))
|
path.write_text('\n'.join(out) + ('\n' if out else ''))
|
||||||
PY
|
PY
|
||||||
else
|
}
|
||||||
# Fallback: truncate the file to remove duplicates if python3 is unavailable
|
|
||||||
grep -v '^allowed_nodes:' "$config_file" > "${config_file}.tmp" && mv "${config_file}.tmp" "$config_file"
|
update_allowed_nodes() {
|
||||||
|
local comment_line="$1"
|
||||||
|
shift
|
||||||
|
local nodes=("$@")
|
||||||
|
|
||||||
|
ensure_allowed_nodes_file_reference
|
||||||
|
remove_allowed_nodes_block
|
||||||
|
|
||||||
|
if ! command -v python3 >/dev/null 2>&1; then
|
||||||
|
print_warn "python3 is required to manage allowed_nodes; skipping update"
|
||||||
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
{
|
python3 - "$ALLOWED_NODES_FILE" "$comment_line" "${nodes[@]}" <<'PY'
|
||||||
echo ""
|
import sys
|
||||||
echo "# ${comment_line}"
|
from pathlib import Path
|
||||||
echo "# These nodes are allowed to request temperature data when cluster IPC validation is unavailable"
|
import yaml
|
||||||
echo "allowed_nodes:"
|
|
||||||
for node in "${merged_nodes[@]}"; do
|
|
||||||
echo " - $node"
|
|
||||||
done
|
|
||||||
} >> "$config_file"
|
|
||||||
|
|
||||||
chmod 0644 "$config_file" 2>/dev/null || true
|
path = Path(sys.argv[1])
|
||||||
chown pulse-sensor-proxy:pulse-sensor-proxy "$config_file" 2>/dev/null || true
|
comment = sys.argv[2]
|
||||||
|
new_nodes = [n.strip() for n in sys.argv[3:] if n.strip()]
|
||||||
|
existing = []
|
||||||
|
if path.exists():
|
||||||
|
text = path.read_text()
|
||||||
|
try:
|
||||||
|
data = yaml.safe_load(text)
|
||||||
|
except yaml.YAMLError:
|
||||||
|
data = None
|
||||||
|
if isinstance(data, dict):
|
||||||
|
arr = data.get('allowed_nodes')
|
||||||
|
if isinstance(arr, list):
|
||||||
|
existing = [str(x).strip() for x in arr if str(x).strip()]
|
||||||
|
elif isinstance(data, list):
|
||||||
|
existing = [str(x).strip() for x in data if str(x).strip()]
|
||||||
|
else:
|
||||||
|
for line in text.splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
if not line or line.startswith('#'):
|
||||||
|
continue
|
||||||
|
if line.startswith('-'):
|
||||||
|
line = line[1:].strip()
|
||||||
|
if line:
|
||||||
|
existing.append(line)
|
||||||
|
|
||||||
|
seen = set()
|
||||||
|
merged = []
|
||||||
|
for entry in existing + new_nodes:
|
||||||
|
entry = entry.strip()
|
||||||
|
if not entry:
|
||||||
|
continue
|
||||||
|
key = entry.lower()
|
||||||
|
if key in seen:
|
||||||
|
continue
|
||||||
|
seen.add(key)
|
||||||
|
merged.append(entry)
|
||||||
|
|
||||||
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
with path.open('w') as fh:
|
||||||
|
fh.write("# Managed by install-sensor-proxy.sh\n")
|
||||||
|
fh.write(f"# {comment}\n")
|
||||||
|
yaml.safe_dump({'allowed_nodes': merged}, fh, default_flow_style=False, sort_keys=False)
|
||||||
|
PY
|
||||||
|
|
||||||
|
chmod 0644 "$ALLOWED_NODES_FILE" 2>/dev/null || true
|
||||||
|
chown pulse-sensor-proxy:pulse-sensor-proxy "$ALLOWED_NODES_FILE" 2>/dev/null || true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1125,6 +1155,8 @@ EOF
|
||||||
chmod 0644 /etc/pulse-sensor-proxy/config.yaml
|
chmod 0644 /etc/pulse-sensor-proxy/config.yaml
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
ensure_allowed_nodes_file_reference
|
||||||
|
|
||||||
# Register socket-mode proxy with Pulse if server provided
|
# Register socket-mode proxy with Pulse if server provided
|
||||||
if [[ "$HTTP_MODE" != true ]]; then
|
if [[ "$HTTP_MODE" != true ]]; then
|
||||||
if [[ -z "$PULSE_SERVER" ]]; then
|
if [[ -z "$PULSE_SERVER" ]]; then
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue