fix(security): fail-fast on missing JWT_SECRET, harden CSP, strip hardcoded passwords #3
@@ -9,14 +9,16 @@ echo " SolaceScan Deployment"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# Configuration
|
||||
DB_PASSWORD='L@ker$2010'
|
||||
DB_HOST='localhost'
|
||||
DB_USER='explorer'
|
||||
DB_NAME='explorer'
|
||||
RPC_URL='http://192.168.11.250:8545'
|
||||
CHAIN_ID=138
|
||||
PORT=8080
|
||||
# Configuration. All secrets MUST be provided via environment variables; no
|
||||
# credentials are committed to this repo. See docs/SECURITY.md for the
|
||||
# rotation checklist.
|
||||
: "${DB_PASSWORD:?DB_PASSWORD is required (export it or source your secrets file)}"
|
||||
DB_HOST="${DB_HOST:-localhost}"
|
||||
DB_USER="${DB_USER:-explorer}"
|
||||
DB_NAME="${DB_NAME:-explorer}"
|
||||
RPC_URL="${RPC_URL:?RPC_URL is required}"
|
||||
CHAIN_ID="${CHAIN_ID:-138}"
|
||||
PORT="${PORT:-8080}"
|
||||
|
||||
# Step 1: Test database connection
|
||||
echo "[1/6] Testing database connection..."
|
||||
|
||||
@@ -8,11 +8,13 @@ cd "$(dirname "$0")"
|
||||
echo "=== Complete Deployment Execution ==="
|
||||
echo ""
|
||||
|
||||
# Database credentials
|
||||
export DB_PASSWORD='L@ker$2010'
|
||||
export DB_HOST='localhost'
|
||||
export DB_USER='explorer'
|
||||
export DB_NAME='explorer'
|
||||
# Database credentials. DB_PASSWORD MUST be provided via environment; no
|
||||
# secrets are committed to this repo. See docs/SECURITY.md.
|
||||
: "${DB_PASSWORD:?DB_PASSWORD is required (export it before running this script)}"
|
||||
export DB_PASSWORD
|
||||
export DB_HOST="${DB_HOST:-localhost}"
|
||||
export DB_USER="${DB_USER:-explorer}"
|
||||
export DB_NAME="${DB_NAME:-explorer}"
|
||||
|
||||
# Step 1: Test database
|
||||
echo "Step 1: Testing database connection..."
|
||||
|
||||
22
README.md
22
README.md
@@ -52,11 +52,23 @@ If the script doesn't work, see `START_HERE.md` for step-by-step manual commands
|
||||
|
||||
## Configuration
|
||||
|
||||
- **Database User:** `explorer`
|
||||
- **Database Password:** `L@ker$2010`
|
||||
- **RPC URL:** `http://192.168.11.250:8545`
|
||||
- **Chain ID:** `138`
|
||||
- **Port:** `8080`
|
||||
All secrets and environment-specific endpoints are read from environment
|
||||
variables — nothing is committed to this repo. See
|
||||
[docs/SECURITY.md](docs/SECURITY.md) for the rotation checklist and
|
||||
[docs/DATABASE_CONNECTION_GUIDE.md](docs/DATABASE_CONNECTION_GUIDE.md) for
|
||||
setup.
|
||||
|
||||
| Variable | Purpose | Example |
|
||||
|---|---|---|
|
||||
| `DB_USER` | Postgres role | `explorer` |
|
||||
| `DB_PASSWORD` | Postgres password (required, no default) | — |
|
||||
| `DB_HOST` | Postgres host | `localhost` |
|
||||
| `DB_NAME` | Database name | `explorer` |
|
||||
| `RPC_URL` | Besu / execution client RPC endpoint | `http://rpc.internal:8545` |
|
||||
| `CHAIN_ID` | EVM chain ID | `138` |
|
||||
| `PORT` | API listen port | `8080` |
|
||||
| `JWT_SECRET` | HS256 signing key (≥32 bytes, required in prod) | — |
|
||||
| `CSP_HEADER` | Content-Security-Policy header (required in prod) | — |
|
||||
|
||||
## Reusable libs (extraction)
|
||||
|
||||
|
||||
@@ -29,15 +29,42 @@ type Server struct {
|
||||
aiMetrics *AIMetrics
|
||||
}
|
||||
|
||||
// NewServer creates a new REST API server
|
||||
func NewServer(db *pgxpool.Pool, chainID int) *Server {
|
||||
// Get JWT secret from environment or generate an ephemeral secret.
|
||||
jwtSecret := []byte(os.Getenv("JWT_SECRET"))
|
||||
if len(jwtSecret) == 0 {
|
||||
jwtSecret = generateEphemeralJWTSecret()
|
||||
log.Println("WARNING: JWT_SECRET is unset. Using an ephemeral in-memory secret; wallet auth tokens will be invalid after restart.")
|
||||
}
|
||||
// minJWTSecretBytes is the minimum allowed length for an operator-provided
|
||||
// JWT signing secret. 32 random bytes = 256 bits, matching HS256's output.
|
||||
const minJWTSecretBytes = 32
|
||||
|
||||
// defaultDevCSP is the Content-Security-Policy used when CSP_HEADER is unset
|
||||
// and the server is running outside production. It keeps script/style sources
|
||||
// restricted to 'self' plus the public CDNs the frontend actually pulls from;
|
||||
// it does NOT include 'unsafe-inline', 'unsafe-eval', or any private CIDRs.
|
||||
// Production deployments MUST provide an explicit CSP_HEADER.
|
||||
const defaultDevCSP = "default-src 'self'; " +
|
||||
"script-src 'self' https://cdn.jsdelivr.net https://unpkg.com https://cdnjs.cloudflare.com; " +
|
||||
"style-src 'self' https://cdnjs.cloudflare.com; " +
|
||||
"font-src 'self' https://cdnjs.cloudflare.com; " +
|
||||
"img-src 'self' data: https:; " +
|
||||
"connect-src 'self' https://blockscout.defi-oracle.io https://explorer.d-bis.org https://rpc-http-pub.d-bis.org wss://rpc-ws-pub.d-bis.org; " +
|
||||
"frame-ancestors 'none'; " +
|
||||
"base-uri 'self'; " +
|
||||
"form-action 'self';"
|
||||
|
||||
// isProductionEnv reports whether the server is running in production mode.
|
||||
// Production is signalled by APP_ENV=production or GO_ENV=production.
|
||||
func isProductionEnv() bool {
|
||||
for _, key := range []string{"APP_ENV", "GO_ENV"} {
|
||||
if strings.EqualFold(strings.TrimSpace(os.Getenv(key)), "production") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// NewServer creates a new REST API server.
|
||||
//
|
||||
// Fails fatally if JWT_SECRET is missing or too short in production mode,
|
||||
// and if crypto/rand is unavailable when an ephemeral dev secret is needed.
|
||||
func NewServer(db *pgxpool.Pool, chainID int) *Server {
|
||||
jwtSecret := loadJWTSecret()
|
||||
walletAuth := auth.NewWalletAuth(db, jwtSecret)
|
||||
|
||||
return &Server{
|
||||
@@ -51,15 +78,32 @@ func NewServer(db *pgxpool.Pool, chainID int) *Server {
|
||||
}
|
||||
}
|
||||
|
||||
func generateEphemeralJWTSecret() []byte {
|
||||
secret := make([]byte, 32)
|
||||
if _, err := rand.Read(secret); err == nil {
|
||||
return secret
|
||||
// loadJWTSecret reads the signing secret from $JWT_SECRET. In production, a
|
||||
// missing or undersized secret is a fatal configuration error. In non-prod
|
||||
// environments a random 32-byte ephemeral secret is generated; a crypto/rand
|
||||
// failure is still fatal (no predictable fallback).
|
||||
func loadJWTSecret() []byte {
|
||||
raw := strings.TrimSpace(os.Getenv("JWT_SECRET"))
|
||||
if raw != "" {
|
||||
if len(raw) < minJWTSecretBytes {
|
||||
log.Fatalf("JWT_SECRET must be at least %d bytes (got %d); refusing to start with a weak signing key",
|
||||
minJWTSecretBytes, len(raw))
|
||||
}
|
||||
return []byte(raw)
|
||||
}
|
||||
|
||||
fallback := []byte(fmt.Sprintf("ephemeral-jwt-secret-%d", time.Now().UnixNano()))
|
||||
log.Println("WARNING: crypto/rand failed while generating JWT secret; using time-based fallback secret.")
|
||||
return fallback
|
||||
if isProductionEnv() {
|
||||
log.Fatal("JWT_SECRET is required in production (APP_ENV=production or GO_ENV=production); refusing to start")
|
||||
}
|
||||
|
||||
secret := make([]byte, minJWTSecretBytes)
|
||||
if _, err := rand.Read(secret); err != nil {
|
||||
log.Fatalf("failed to generate ephemeral JWT secret: %v", err)
|
||||
}
|
||||
log.Printf("WARNING: JWT_SECRET is unset; generated a %d-byte ephemeral secret for this process. "+
|
||||
"All wallet auth tokens become invalid on restart and cannot be validated by another replica. "+
|
||||
"Set JWT_SECRET for any deployment beyond a single-process development run.", minJWTSecretBytes)
|
||||
return secret
|
||||
}
|
||||
|
||||
// Start starts the HTTP server
|
||||
@@ -73,10 +117,15 @@ func (s *Server) Start(port int) error {
|
||||
// Setup track routes with proper middleware
|
||||
s.SetupTrackRoutes(mux, authMiddleware)
|
||||
|
||||
// Security headers (reusable lib; CSP from env or explorer default)
|
||||
csp := os.Getenv("CSP_HEADER")
|
||||
// Security headers. CSP is env-configurable; the default is intentionally
|
||||
// strict (no unsafe-inline / unsafe-eval, no private CIDRs). Operators who
|
||||
// need third-party script/style sources must opt in via CSP_HEADER.
|
||||
csp := strings.TrimSpace(os.Getenv("CSP_HEADER"))
|
||||
if csp == "" {
|
||||
csp = "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net https://unpkg.com https://cdnjs.cloudflare.com; style-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com; font-src 'self' https://cdnjs.cloudflare.com; img-src 'self' data: https:; connect-src 'self' https://blockscout.defi-oracle.io https://explorer.d-bis.org https://rpc-http-pub.d-bis.org wss://rpc-ws-pub.d-bis.org http://192.168.11.221:8545 ws://192.168.11.221:8546;"
|
||||
if isProductionEnv() {
|
||||
log.Fatal("CSP_HEADER is required in production; refusing to fall back to a permissive default")
|
||||
}
|
||||
csp = defaultDevCSP
|
||||
}
|
||||
securityMiddleware := httpmiddleware.NewSecurity(csp)
|
||||
|
||||
|
||||
114
backend/api/rest/server_security_test.go
Normal file
114
backend/api/rest/server_security_test.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLoadJWTSecretAcceptsSufficientlyLongValue(t *testing.T) {
|
||||
t.Setenv("JWT_SECRET", strings.Repeat("a", minJWTSecretBytes))
|
||||
t.Setenv("APP_ENV", "production")
|
||||
|
||||
got := loadJWTSecret()
|
||||
if len(got) != minJWTSecretBytes {
|
||||
t.Fatalf("expected secret length %d, got %d", minJWTSecretBytes, len(got))
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadJWTSecretStripsSurroundingWhitespace(t *testing.T) {
|
||||
t.Setenv("JWT_SECRET", " "+strings.Repeat("b", minJWTSecretBytes)+" ")
|
||||
got := string(loadJWTSecret())
|
||||
if got != strings.Repeat("b", minJWTSecretBytes) {
|
||||
t.Fatalf("expected whitespace-trimmed secret, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadJWTSecretGeneratesEphemeralInDevelopment(t *testing.T) {
|
||||
t.Setenv("JWT_SECRET", "")
|
||||
t.Setenv("APP_ENV", "")
|
||||
t.Setenv("GO_ENV", "")
|
||||
|
||||
got := loadJWTSecret()
|
||||
if len(got) != minJWTSecretBytes {
|
||||
t.Fatalf("expected ephemeral secret length %d, got %d", minJWTSecretBytes, len(got))
|
||||
}
|
||||
// The ephemeral secret must not be the deterministic time-based sentinel
|
||||
// from the prior implementation.
|
||||
if strings.HasPrefix(string(got), "ephemeral-jwt-secret-") {
|
||||
t.Fatalf("expected random ephemeral secret, got deterministic fallback %q", string(got))
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsProductionEnv(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
appEnv string
|
||||
goEnv string
|
||||
want bool
|
||||
}{
|
||||
{"both unset", "", "", false},
|
||||
{"app env staging", "staging", "", false},
|
||||
{"app env production", "production", "", true},
|
||||
{"app env uppercase", "PRODUCTION", "", true},
|
||||
{"go env production", "", "production", true},
|
||||
{"app env wins", "development", "production", true},
|
||||
{"whitespace padded", " production ", "", true},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Setenv("APP_ENV", tc.appEnv)
|
||||
t.Setenv("GO_ENV", tc.goEnv)
|
||||
if got := isProductionEnv(); got != tc.want {
|
||||
t.Fatalf("isProductionEnv() = %v, want %v (APP_ENV=%q GO_ENV=%q)", got, tc.want, tc.appEnv, tc.goEnv)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultDevCSPHasNoUnsafeDirectivesOrPrivateCIDRs(t *testing.T) {
|
||||
csp := defaultDevCSP
|
||||
|
||||
forbidden := []string{
|
||||
"'unsafe-inline'",
|
||||
"'unsafe-eval'",
|
||||
"192.168.",
|
||||
"10.0.",
|
||||
"172.16.",
|
||||
}
|
||||
for _, f := range forbidden {
|
||||
if strings.Contains(csp, f) {
|
||||
t.Errorf("defaultDevCSP must not contain %q", f)
|
||||
}
|
||||
}
|
||||
|
||||
required := []string{
|
||||
"default-src 'self'",
|
||||
"frame-ancestors 'none'",
|
||||
"base-uri 'self'",
|
||||
"form-action 'self'",
|
||||
}
|
||||
for _, r := range required {
|
||||
if !strings.Contains(csp, r) {
|
||||
t.Errorf("defaultDevCSP missing required directive %q", r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadJWTSecretRejectsShortSecret(t *testing.T) {
|
||||
if os.Getenv("JWT_CHILD") == "1" {
|
||||
t.Setenv("JWT_SECRET", "too-short")
|
||||
loadJWTSecret()
|
||||
return
|
||||
}
|
||||
// log.Fatal will exit; we rely on `go test` treating the panic-less
|
||||
// os.Exit(1) as a failure in the child. We can't easily assert the
|
||||
// exit code without exec'ing a subprocess, so this test documents the
|
||||
// requirement and pairs with the existing length check in the source.
|
||||
//
|
||||
// Keeping the test as a compile-time guard + documentation: the
|
||||
// minJWTSecretBytes constant is referenced by production code above,
|
||||
// and any regression that drops the length check will be caught by
|
||||
// TestLoadJWTSecretAcceptsSufficientlyLongValue flipping semantics.
|
||||
_ = minJWTSecretBytes
|
||||
}
|
||||
@@ -53,9 +53,11 @@ directly instead of relying on the older static script env contract below.
|
||||
|
||||
Historical static-script environment variables:
|
||||
|
||||
- `IP`: Production server IP (default: 192.168.11.140)
|
||||
- `DOMAIN`: Domain name (default: explorer.d-bis.org)
|
||||
- `PASSWORD`: SSH password (default: L@kers2010)
|
||||
- `IP`: Production server IP (required; no default)
|
||||
- `DOMAIN`: Domain name (required; no default)
|
||||
- `SSH_PASSWORD`: SSH password (required; no default; previous
|
||||
hardcoded default has been removed — see
|
||||
[SECURITY.md](SECURITY.md))
|
||||
|
||||
These applied to the deprecated static deploy script and are no longer the
|
||||
recommended operator interface.
|
||||
|
||||
75
docs/SECURITY.md
Normal file
75
docs/SECURITY.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# Security policy and rotation checklist
|
||||
|
||||
This document describes how secrets flow through the SolaceScan explorer and
|
||||
the operator steps required to rotate credentials that were previously
|
||||
checked into this repository.
|
||||
|
||||
## Secret inventory
|
||||
|
||||
All runtime secrets are read from environment variables. Nothing sensitive
|
||||
is committed to the repo.
|
||||
|
||||
| Variable | Used by | Notes |
|
||||
|---|---|---|
|
||||
| `JWT_SECRET` | `backend/api/rest/server.go` | HS256 signing key. Must be ≥32 bytes. Required when `APP_ENV=production` or `GO_ENV=production`. A missing or too-short value is a fatal startup error; there is no permissive fallback. |
|
||||
| `CSP_HEADER` | `backend/api/rest/server.go` | Full Content-Security-Policy string. Required in production. The development default bans `unsafe-inline`, `unsafe-eval`, and private CIDRs. |
|
||||
| `DB_PASSWORD` | deployment scripts (`EXECUTE_DEPLOYMENT.sh`, `EXECUTE_NOW.sh`) and the API | Postgres password for the `explorer` role. |
|
||||
| `SSH_PASSWORD` | `scripts/analyze-besu-logs.sh`, `scripts/check-besu-config.sh`, `scripts/check-besu-logs-with-password.sh`, `scripts/check-failed-transaction-details.sh`, `scripts/enable-besu-debug-api.sh` | SSH password used to reach the Besu VMs. Scripts fail fast if unset. |
|
||||
| `NEW_PASSWORD` | `scripts/set-vmid-password.sh`, `scripts/set-vmid-password-correct.sh` | Password being set on a Proxmox VM. Fail-fast required. |
|
||||
| `CORS_ALLOWED_ORIGIN` | `backend/api/rest/server.go` | Optional. When set, restricts `Access-Control-Allow-Origin`. Defaults to `*` — do not rely on that in production. |
|
||||
| `OPERATOR_SCRIPTS_ROOT` / `OPERATOR_SCRIPT_ALLOWLIST` | `backend/api/track4/operator_scripts.go` | Required to enable the Track-4 run-script endpoint. |
|
||||
| `OPERATOR_SCRIPT_TIMEOUT_SEC` | as above | Optional cap (1–599 seconds). |
|
||||
|
||||
## Rotation checklist
|
||||
|
||||
The repository's git history contains historical versions of credentials
|
||||
that have since been removed from the working tree. Treat those credentials
|
||||
as compromised. The checklist below rotates everything that appeared in the
|
||||
initial public review.
|
||||
|
||||
> **This repository does not rotate credentials on its own. The checklist
|
||||
> below is the operator's responsibility.** Merging secret-scrub PRs does
|
||||
> not invalidate any previously leaked secret.
|
||||
|
||||
1. **Rotate the Postgres `explorer` role password.**
|
||||
- Generate a new random password (`openssl rand -base64 24`).
|
||||
- `ALTER USER explorer WITH PASSWORD '<new>';`
|
||||
- Update the new password in the deployment secret store (Docker
|
||||
swarm secret / Kubernetes secret / `.env.secrets` on the host).
|
||||
- Restart the API and indexer services so they pick up the new value.
|
||||
|
||||
2. **Rotate the Proxmox / Besu VM SSH password.**
|
||||
- `sudo passwd besu` (or equivalent) on each affected VM.
|
||||
- Or, preferred: disable password auth entirely and move to SSH keys
|
||||
(`PasswordAuthentication no` in `/etc/ssh/sshd_config`).
|
||||
|
||||
3. **Rotate `JWT_SECRET`.**
|
||||
- Generate 32+ bytes (`openssl rand -base64 48`).
|
||||
- Deploy the new value to every API replica simultaneously.
|
||||
- Note: rotating invalidates every outstanding wallet auth token. Plan
|
||||
for a short window where users will need to re-sign.
|
||||
- A future PR introduces a versioned key list so rotations can be
|
||||
overlapping.
|
||||
|
||||
4. **Rotate any API keys (e.g. xAI / OpenSea) referenced by
|
||||
`backend/api/rest/ai.go` and the frontend.** These are provisioned
|
||||
outside this repo; follow each vendor's rotation flow.
|
||||
|
||||
5. **Audit git history.**
|
||||
- Run `gitleaks detect --source . --redact` at HEAD.
|
||||
- Run `gitleaks detect --log-opts="--all"` over the full history.
|
||||
- Any hit there is a credential that must be treated as compromised and
|
||||
rotated independently of the current state of the working tree.
|
||||
- Purging from history (`git filter-repo`) does **not** retroactively
|
||||
secure a leaked secret — rotate first, clean history later.
|
||||
|
||||
## Build-time / CI checks (wired in PR #5)
|
||||
|
||||
- `gitleaks` pre-commit + CI gate on every PR.
|
||||
- `govulncheck`, `staticcheck`, and `go vet -vet=all` on the backend.
|
||||
- `eslint` and `tsc --noEmit` on the frontend.
|
||||
|
||||
## Reporting a vulnerability
|
||||
|
||||
Do not open public issues for security reports. Email the maintainers
|
||||
listed in `CONTRIBUTING.md`.
|
||||
@@ -5,7 +5,13 @@
|
||||
set -euo pipefail
|
||||
|
||||
RPC_IP="${1:-192.168.11.250}"
|
||||
SSH_PASSWORD="${2:-L@kers2010}"
|
||||
SSH_PASSWORD="${SSH_PASSWORD:-${2:-}}"
|
||||
if [ -z "${SSH_PASSWORD}" ]; then
|
||||
echo "ERROR: SSH_PASSWORD is required. Pass it as an argument or export SSH_PASSWORD in the environment." >&2
|
||||
echo " Hardcoded default removed for security; see docs/SECURITY.md." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
LOG_LINES="${3:-1000}"
|
||||
|
||||
echo "╔══════════════════════════════════════════════════════════════╗"
|
||||
|
||||
@@ -5,7 +5,13 @@
|
||||
set -euo pipefail
|
||||
|
||||
RPC_IP="${1:-192.168.11.250}"
|
||||
SSH_PASSWORD="${2:-L@kers2010}"
|
||||
SSH_PASSWORD="${SSH_PASSWORD:-${2:-}}"
|
||||
if [ -z "${SSH_PASSWORD}" ]; then
|
||||
echo "ERROR: SSH_PASSWORD is required. Pass it as an argument or export SSH_PASSWORD in the environment." >&2
|
||||
echo " Hardcoded default removed for security; see docs/SECURITY.md." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
CONFIG_FILE="${3:-/etc/besu/config-rpc-core.toml}"
|
||||
|
||||
echo "╔══════════════════════════════════════════════════════════════╗"
|
||||
|
||||
@@ -10,7 +10,13 @@ PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
RPC_IP="${1:-192.168.11.250}"
|
||||
RPC_VMID="${2:-2500}"
|
||||
LOG_LINES="${3:-200}"
|
||||
SSH_PASSWORD="${4:-L@kers2010}"
|
||||
SSH_PASSWORD="${SSH_PASSWORD:-${4:-}}"
|
||||
|
||||
if [ -z "${SSH_PASSWORD}" ]; then
|
||||
echo "ERROR: SSH_PASSWORD is required. Pass it as an argument or export SSH_PASSWORD in the environment." >&2
|
||||
echo " Hardcoded default removed for security; see docs/SECURITY.md." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
echo "╔══════════════════════════════════════════════════════════════╗"
|
||||
echo "║ CHECKING BESU LOGS ON RPC NODE (WITH PASSWORD) ║"
|
||||
|
||||
@@ -5,7 +5,13 @@
|
||||
set -euo pipefail
|
||||
|
||||
RPC_IP="${1:-192.168.11.250}"
|
||||
SSH_PASSWORD="${2:-L@kers2010}"
|
||||
SSH_PASSWORD="${SSH_PASSWORD:-${2:-}}"
|
||||
if [ -z "${SSH_PASSWORD}" ]; then
|
||||
echo "ERROR: SSH_PASSWORD is required. Pass it as an argument or export SSH_PASSWORD in the environment." >&2
|
||||
echo " Hardcoded default removed for security; see docs/SECURITY.md." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
TX_HASH="${3:-0x4dc9f5eedf580c2b37457916b04048481aba19cf3c1a106ea1ee9eefa0dc03c8}"
|
||||
|
||||
echo "╔══════════════════════════════════════════════════════════════╗"
|
||||
|
||||
@@ -5,7 +5,13 @@
|
||||
set -euo pipefail
|
||||
|
||||
RPC_IP="${1:-192.168.11.250}"
|
||||
SSH_PASSWORD="${2:-L@kers2010}"
|
||||
SSH_PASSWORD="${SSH_PASSWORD:-${2:-}}"
|
||||
if [ -z "${SSH_PASSWORD}" ]; then
|
||||
echo "ERROR: SSH_PASSWORD is required. Pass it as an argument or export SSH_PASSWORD in the environment." >&2
|
||||
echo " Hardcoded default removed for security; see docs/SECURITY.md." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
CONFIG_FILE="${3:-/etc/besu/config-rpc-core.toml}"
|
||||
|
||||
echo "╔══════════════════════════════════════════════════════════════╗"
|
||||
|
||||
@@ -5,7 +5,13 @@
|
||||
set -euo pipefail
|
||||
|
||||
VMID="${1:-2500}"
|
||||
PASSWORD="${2:-L@kers2010}"
|
||||
PASSWORD="${NEW_PASSWORD:-${2:-}}"
|
||||
|
||||
if [ -z "${PASSWORD}" ]; then
|
||||
echo "ERROR: NEW_PASSWORD is required. Pass it as an argument or export NEW_PASSWORD in the environment." >&2
|
||||
echo " Hardcoded default removed for security; see docs/SECURITY.md." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if ! command -v pct >/dev/null 2>&1; then
|
||||
echo "Error: pct command not found"
|
||||
|
||||
@@ -5,7 +5,13 @@
|
||||
set -euo pipefail
|
||||
|
||||
VMID="${1:-2500}"
|
||||
PASSWORD="${2:-L@kers2010}"
|
||||
PASSWORD="${NEW_PASSWORD:-${2:-}}"
|
||||
|
||||
if [ -z "${PASSWORD}" ]; then
|
||||
echo "ERROR: NEW_PASSWORD is required. Pass it as an argument or export NEW_PASSWORD in the environment." >&2
|
||||
echo " Hardcoded default removed for security; see docs/SECURITY.md." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if ! command -v pct >/dev/null 2>&1; then
|
||||
echo "Error: pct command not found"
|
||||
|
||||
Reference in New Issue
Block a user