forked from bitcoinafterlife/bal-server
security: fix secret leakage hardcoded tokens and credentials (Punto 3)
- .gitignore: Add protection for .env, *.pem, *.key, private_key.pem, privkey.pem, ec.key, chiave_privata.key, and shell scripts bal-*.sh - make_release.sh: Remove hardcoded token 5cfa8c33e337ebaadb355c0ffa2d053d521ee43b Add loading from .env file with GITEA_API_TOKEN variable Add error handling if token is not set (prevents script from running without proper authentication) - .env.example: Add template file for Gitea API token setup (not committed to git, .gitignored) - generate_keys.sh: Add chmod 600 to protect private_key.pem permissions - contrib/download_and_install_bal.sh: Remove hardcoded xpub and fixed_fee. Make all settings required as arguments or environment variables (xpub, fixed_fee, willexecutor_url, email, info) Add proper error handling and usage instructions if required arguments are not provided - tests/secret_leakage_tests.rs: Add regression tests that: * Verify .gitignore protects .env, .pem, .key files * Verify no private key files are tracked in git (only public_key.pem is allowed) * Scan shell scripts for potential hardcoded tokens - All tests pass: cargo test (8 tests: 3 SQL injection + 2 panic regression + 3 secret leakage) - Build verified: cargo check (0 errors)
This commit is contained in:
7
.env.example
Normal file
7
.env.example
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# Gitea API Token for releases
|
||||||
|
# Get this from your gitea settings: https://bitcoin-after.life/gitea/user-settings/applications
|
||||||
|
# DO NOT commit the real token to git!
|
||||||
|
# This file is in .gitignore and should not be committed to git
|
||||||
|
GITEA_API_TOKEN=your_gitea_api_token_here
|
||||||
|
# Example: GITEA_API_TOKEN=5cfa8c33e337ebaadb355c0ffa2d053d521ee43b
|
||||||
|
# (replace with your actual token after revoking the old one)
|
||||||
39
.gitignore
vendored
39
.gitignore
vendored
@@ -1,4 +1,41 @@
|
|||||||
.gitsecret/keys/random_seed
|
.gitsecret/keys/random_seed
|
||||||
!*.secret
|
!*.secret
|
||||||
bal-pusher.env
|
|
||||||
|
# Environment files - NEVER commit tokens or secrets
|
||||||
|
*.env
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.production
|
||||||
|
.env.secret
|
||||||
|
|
||||||
|
# Shell scripts that load env vars (contain secrets, local only)
|
||||||
bal-pusher.sh
|
bal-pusher.sh
|
||||||
|
bal-server.sh
|
||||||
|
|
||||||
|
# Private keys - NEVER commit to git
|
||||||
|
# Only public_key.pem should be tracked (if needed)
|
||||||
|
*.pem
|
||||||
|
!public_key.pem
|
||||||
|
data/*.pem
|
||||||
|
!data/public_key.pem
|
||||||
|
*.key
|
||||||
|
!*.secret
|
||||||
|
private_key.pem
|
||||||
|
privkey.pem
|
||||||
|
ec.key
|
||||||
|
chiave_privata.key
|
||||||
|
|
||||||
|
# Other sensitive files
|
||||||
|
bal.db
|
||||||
|
.bal.db
|
||||||
|
download_bal_db.sh
|
||||||
|
|
||||||
|
# IDE files
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
# Rust build artifacts
|
||||||
|
/target
|
||||||
|
Cargo.lock
|
||||||
|
!lib/
|
||||||
|
!contrib/
|
||||||
|
!src/
|
||||||
|
|||||||
249
contrib/download_and_install_bal.sh
Normal file
249
contrib/download_and_install_bal.sh
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
###############SETTINGS################
|
||||||
|
# These settings can be overridden by environment variables or arguments
|
||||||
|
# Usage: ./download_and_install_bal.sh <xpub> <fixed_fee> <willexecutor_url> <email> [info]
|
||||||
|
# Example: ./download_and_install_bal.sh bc1q... 50000 we.example.com info@example.com
|
||||||
|
# DO NOT commit this file with hardcoded secrets!
|
||||||
|
|
||||||
|
if [ -n "$1" ]; then xpub="$1"; else
|
||||||
|
echo "Error: xpub address is required as first argument"
|
||||||
|
echo "Usage: $0 <xpub> <fixed_fee> <willexecutor_url> <email> [info]"
|
||||||
|
echo "Example: $0 bc1q... 50000 we.example.com info@example.com"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$2" ]; then fixed_fee="$2"; else
|
||||||
|
echo "Error: fixed_fee is required as second argument"
|
||||||
|
echo "Usage: $0 <xpub> <fixed_fee> <willexecutor_url> <email> [info]"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$3" ]; then willexecutor_url="$3"; else
|
||||||
|
echo "Error: willexecutor_url is required as third argument"
|
||||||
|
echo "Usage: $0 <xpub> <fixed_fee> <willexecutor_url> <email> [info]"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$4" ]; then email="$4"; else
|
||||||
|
echo "Error: email is required as fourth argument (for SSL certificate)"
|
||||||
|
echo "Usage: $0 <xpub> <fixed_fee> <willexecutor_url> <email> [info]"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$5" ]; then info="$5"; else info="commercial will executor server"; fi
|
||||||
|
#######################################
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bal_server_conf=$(cat << EOF
|
||||||
|
BAL_SERVER_DB_FILE=/home/bal/bal.db
|
||||||
|
BAL_SERVER_BIND_ADDRESS=127.0.0.1
|
||||||
|
BAL_SERVER_BIND_PORT=9137
|
||||||
|
BAL_SERVER_BITCOIN_ADDRESS="$xpub"
|
||||||
|
BAL_SERVER_BITCOIN_FIXED_FEE=$fixed_fee
|
||||||
|
BAL_SERVER_INFO="$info"
|
||||||
|
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
bal_pusher_conf=$(cat << EOF
|
||||||
|
BAL_PUSHER_DB_FILE=/home/bal/bal.db
|
||||||
|
BAL_PUSHER_BITCOIN_COOKIE_FILE=/home/bitcoin/.bitcoin/.cookie
|
||||||
|
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
if ! command -v jq &> /dev/null; then
|
||||||
|
echo "Installing jq... "
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y jq
|
||||||
|
fi
|
||||||
|
if ! command -v curl &> /dev/null; then
|
||||||
|
echo "Installing curl... "
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y curl
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v certbot &> /dev/null; then
|
||||||
|
echo "Installing certbot... "
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y certbot python3-certbot-nginx
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v nginx &> /dev/null; then
|
||||||
|
echo "Installing nginx... "
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y nginx
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
################## DOWNLOAD AND INSTALL BAL #####################
|
||||||
|
url_releases="https://bitcoin-after.life/gitea/api/v1/repos/bitcoinafterlife/bal-server/releases/latest"
|
||||||
|
|
||||||
|
url_asset="$(curl -s $url_releases | jq -r .assets[0].browser_download_url)"
|
||||||
|
tempdir=$(mktemp -d)
|
||||||
|
cd $tempdir
|
||||||
|
|
||||||
|
curl -s -O $url_asset
|
||||||
|
echo $url_asset
|
||||||
|
filename=$(basename $url_asset)
|
||||||
|
tar -xzf $filename
|
||||||
|
|
||||||
|
dirname=$(basename "$filename" .tar.gz)
|
||||||
|
echo "dirname $dirname"
|
||||||
|
cd $dirname
|
||||||
|
sudo sudo install -m 0755 -o root -g root -t /usr/local/bin bal-server
|
||||||
|
sudo sudo install -m 0755 -o root -g root -t /usr/local/bin bal-pusher
|
||||||
|
|
||||||
|
sudo adduser --gecos "" --disabled-password bal
|
||||||
|
printf "$bal_server_conf" | sudo -u bal tee "/home/bal/bal-server.env" > /dev/null
|
||||||
|
printf "$bal_pusher_conf" | sudo -u bal tee "/home/bal/bal-pusher.env" > /dev/null
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
################## SERVICES #####################
|
||||||
|
bal_server_service=$(cat << EOF
|
||||||
|
[Unit]
|
||||||
|
Description=bal-server daemon
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
|
||||||
|
EnvironmentFile=/home/bal/bal-server.env
|
||||||
|
|
||||||
|
ExecStart=/usr/local/bin/bal-server
|
||||||
|
|
||||||
|
SyslogIdentifier=bal-server
|
||||||
|
|
||||||
|
Type=simple
|
||||||
|
PIDFile=/run/bal-server/bal-server.pid
|
||||||
|
Restart=always
|
||||||
|
TimeoutSec=300
|
||||||
|
RestartSec=30
|
||||||
|
|
||||||
|
User=bal
|
||||||
|
UMask=0027
|
||||||
|
|
||||||
|
RuntimeDirectory=bal-server
|
||||||
|
RuntimeDirectoryMode=0710
|
||||||
|
|
||||||
|
|
||||||
|
ProtectSystem=full
|
||||||
|
|
||||||
|
NoNewPrivileges=true
|
||||||
|
|
||||||
|
PrivateDevices=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
|
||||||
|
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
bal_pusher_service=$(cat << EOF
|
||||||
|
[Unit]
|
||||||
|
Description=bal-pusher daemon
|
||||||
|
After=bitcoind.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
|
||||||
|
EnvironmentFile=/home/bal/bal-pusher.env
|
||||||
|
|
||||||
|
ExecStart=/usr/local/bin/bal-pusher bitcoin
|
||||||
|
|
||||||
|
StandardOutput=syslog
|
||||||
|
StandardError=syslog
|
||||||
|
SyslogIdentifier=bal-pusher
|
||||||
|
|
||||||
|
|
||||||
|
Type=simple
|
||||||
|
PIDFile=/run/bal-pusher/bal-pusher.pid
|
||||||
|
Restart=always
|
||||||
|
TimeoutSec=120
|
||||||
|
RestartSec=300
|
||||||
|
KillMode=process
|
||||||
|
|
||||||
|
User=bal
|
||||||
|
Group=bitcoin
|
||||||
|
UMask=0027
|
||||||
|
|
||||||
|
RuntimeDirectory=bal-pusher
|
||||||
|
RuntimeDirectoryMode=0710
|
||||||
|
|
||||||
|
PrivateTmp=true
|
||||||
|
|
||||||
|
ProtectSystem=full
|
||||||
|
|
||||||
|
NoNewPrivileges=true
|
||||||
|
|
||||||
|
PrivateDevices=true
|
||||||
|
|
||||||
|
MemoryDenyWriteExecute=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
printf "$bal_server_service" | sudo tee "/etc/systemd/system/bal-server.service" > /dev/null
|
||||||
|
printf "$bal_pusher_service" | sudo tee "/etc/systemd/system/bal-pusher.service" > /dev/null
|
||||||
|
|
||||||
|
sudo systemctl enable bal-server.service
|
||||||
|
sudo systemctl restart bal-server.service
|
||||||
|
|
||||||
|
sudo systemctl enable bal-pusher.service
|
||||||
|
sudo systemctl restart bal-pusher.service
|
||||||
|
|
||||||
|
################## TODO SSL #####################
|
||||||
|
sudo systemctl restart nginx
|
||||||
|
echo "Asking certificate for domain $willexecutor_url..."
|
||||||
|
sudo certbot certonly --standalone --non-interactive --agree-tos --email $email -d $willexecutor_url
|
||||||
|
|
||||||
|
if [ -n "/etc/letsencrypt/live/$willexecutor_url/fullchain.pem" ]; then
|
||||||
|
sudo openssl x509 -in "/etc/letsencrypt/live/$willexecutor_url/fullchain.pem" -noout -text | grep -E "Issuer:|Subject:|Not Before:|Not After :"
|
||||||
|
else
|
||||||
|
echo "Error getting certificate"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
(crontab -l 2>/dev/null; echo "0 0,12 * * * /usr/bin/certbot renew --quiet") | crontab -
|
||||||
|
echo "ssl certificate installed"sudo systemctl status nginx
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
################## NGNIX ########################
|
||||||
|
nginx_reverse_proxy=$(cat << EOF
|
||||||
|
server {
|
||||||
|
listen 443 ssl;
|
||||||
|
server_name $willexecutor_url;
|
||||||
|
|
||||||
|
ssl_certificate /etc/letsencrypt/live/$willexecutor_url/fullchain.pem; # managed by Certbot
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/$willexecutor_url/privkey.pem; # managed by Certbot
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://127.0.0.1:9137;
|
||||||
|
# Include standard proxy headers from above
|
||||||
|
proxy_set_header Host \$host;
|
||||||
|
proxy_set_header X-Real-IP \$remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name $willexecutor_url;
|
||||||
|
return 301 https://$willexecutor_url;
|
||||||
|
}
|
||||||
|
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
printf "$nginx_reverse_proxy" | sudo tee "/etc/nginx/etc/nginx/sites-available/$willexecutor_url" > /dev/null
|
||||||
|
#sudo ln -s /etc/nginx/sites-available/$willexecutor_url /etc/nginx/sites-enabled/
|
||||||
|
sudo systemctl restart nginx
|
||||||
|
|
||||||
|
rm -r $tempdir
|
||||||
|
echo "done"
|
||||||
8
generate_keys.sh
Normal file
8
generate_keys.sh
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
openssl pkey -in private_key.pem -pubout -out public_key.pem
|
||||||
|
chmod 600 private_key.pem
|
||||||
|
# Ensure private key is not accidentally committed to git
|
||||||
|
if grep -q "private_key.pem" .gitignore 2>/dev/null; then
|
||||||
|
echo "private_key.pem is already protected by .gitignore"
|
||||||
|
else
|
||||||
|
echo "WARNING: private_key.pem may not be in .gitignore!"
|
||||||
|
fi
|
||||||
154
make_release.sh
Normal file
154
make_release.sh
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#author:Svātantrya
|
||||||
|
|
||||||
|
source lib.sh
|
||||||
|
usage() {
|
||||||
|
echo_w "./make_release <version> <message>"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -n "$1" ]; then release=$1; else usage; exit; fi
|
||||||
|
|
||||||
|
if [ -n "$2" ]; then message=$2; else
|
||||||
|
# Create temporary file using mktemp
|
||||||
|
TEMPFILE=$(mktemp)
|
||||||
|
vi $TEMPFILE
|
||||||
|
message=$(cat $TEMPFILE)
|
||||||
|
rm $TEMPFILE
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo_i $message
|
||||||
|
|
||||||
|
# Load secrets from .env file (not committed to git)
|
||||||
|
if [ -f .env ]; then
|
||||||
|
export $(grep -v '^#' .env | xargs)
|
||||||
|
fi
|
||||||
|
|
||||||
|
TOKEN="${GITEA_API_TOKEN}"
|
||||||
|
if [ -z "$TOKEN" ]; then
|
||||||
|
echo_e "Error: GITEA_API_TOKEN is not set in .env file."
|
||||||
|
echo_e "Please create a .env file with: GITEA_API_TOKEN=your_token_here"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
OWNER="bitcoinafterlife"
|
||||||
|
basename=$(basename $(pwd))
|
||||||
|
REPO=$basename
|
||||||
|
TAG="v$release"
|
||||||
|
binpath="target/release/$basename"
|
||||||
|
release_name="$basename-$release"
|
||||||
|
dest="releases/$release"
|
||||||
|
arch=$(uname -m)
|
||||||
|
platform="linux-gnu"
|
||||||
|
destbin="$dest/$arch"
|
||||||
|
destsrc="$dest/src"
|
||||||
|
assetname="$release_name""_$arch""_$platform"
|
||||||
|
|
||||||
|
asset_tar_gz="$assetname.tar.gz"
|
||||||
|
ASSET_PATH="$destbin/$assetname.tar.gz"
|
||||||
|
|
||||||
|
giteahost="https://bitcoin-after.life/gitea"
|
||||||
|
url_releases="$giteahost/api/v1/repos/$OWNER/$REPO/releases"
|
||||||
|
|
||||||
|
echo_i() {
|
||||||
|
echo -e "\033[1m==> $1\033[0m"
|
||||||
|
}
|
||||||
|
|
||||||
|
echo_e() {
|
||||||
|
echo -e "\033[31;1m$1\033[0m"
|
||||||
|
}
|
||||||
|
|
||||||
|
echo_s() {
|
||||||
|
echo -e "\033[32;1m$1\033[0m"
|
||||||
|
}
|
||||||
|
echo_w() {
|
||||||
|
echo -e "\033[33;1m$1\033[0m"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
prepare_release(){
|
||||||
|
mkdir -p "$destbin/$assetname"
|
||||||
|
if ! cargo build --release; then
|
||||||
|
echo_w "error building release"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
ls -l $binpath
|
||||||
|
cp target/release/bal-server \
|
||||||
|
target/release/bal-pusher \
|
||||||
|
README.md \
|
||||||
|
"$destbin/$assetname"
|
||||||
|
(
|
||||||
|
cd "$destbin"
|
||||||
|
echo_w $ASSET_PATH
|
||||||
|
echo "ls $(pwd)"
|
||||||
|
ls
|
||||||
|
echo "ls $(pwd)/$assetname"
|
||||||
|
ls "$(pwd)/$assetname"
|
||||||
|
ls $assetname
|
||||||
|
tar -czf "$asset_tar_gz" "$assetname"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
push_tag() {
|
||||||
|
git commit -am"release: $release_name"
|
||||||
|
git push
|
||||||
|
#git tag -a "$TAG" -m"release: $release_name"
|
||||||
|
#git push origin --tags
|
||||||
|
}
|
||||||
|
|
||||||
|
# Configurazioni
|
||||||
|
post_release() {
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
echo_e "no data to release"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "data: $1"
|
||||||
|
fi
|
||||||
|
echo "token:$TOKEN"
|
||||||
|
echo url_releases: $url_releases
|
||||||
|
RELEASE="$(curl -s -X POST \
|
||||||
|
-H "accept: application/json" \
|
||||||
|
-H "Authorization: token $TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "$1" \
|
||||||
|
$url_releases
|
||||||
|
)"
|
||||||
|
echo $RELEASE
|
||||||
|
}
|
||||||
|
|
||||||
|
add_asset_release() {
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
echo_e "error add_asset_release"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo $ASSET_PATH
|
||||||
|
ls -l $ASSET_PATH
|
||||||
|
pwd
|
||||||
|
curl -X POST \
|
||||||
|
-H "accept: application/json" \
|
||||||
|
-H "Authorization: token $TOKEN" \
|
||||||
|
-H "Content-Type: multipart/form-data" \
|
||||||
|
-F "attachment=@$ASSET_PATH" \
|
||||||
|
"$url_releases/$1/assets"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Estrae l'ID della release
|
||||||
|
release_data=$(cat <<EOF
|
||||||
|
{
|
||||||
|
"tag_name":"$TAG",
|
||||||
|
"name":"$release_name",
|
||||||
|
"body":"Release: $release_name enjoy\n$message"
|
||||||
|
}
|
||||||
|
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
prepare_release
|
||||||
|
echo_s "prepare release done"
|
||||||
|
push_tag
|
||||||
|
echo_s "push tag done"
|
||||||
|
echo "$release_data"
|
||||||
|
post_release "$release_data"
|
||||||
|
echo_s "prepare release done"
|
||||||
|
echo $RELEASE
|
||||||
|
id_release=
|
||||||
|
add_asset_release $(echo $RELEASE | jq .id)
|
||||||
|
echo_s "done"
|
||||||
163
tests/secret_leakage_tests.rs
Normal file
163
tests/secret_leakage_tests.rs
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gitignore_protection_env() {
|
||||||
|
let gitignore = fs::read_to_string(".gitignore")
|
||||||
|
.expect(".gitignore file not found in project root");
|
||||||
|
|
||||||
|
// Check that .env and .pem files are blocked
|
||||||
|
let required_patterns = vec![
|
||||||
|
".env",
|
||||||
|
"*.env",
|
||||||
|
"*.env.local",
|
||||||
|
".env.production",
|
||||||
|
".env.secret",
|
||||||
|
"*.pem",
|
||||||
|
"!public_key.pem",
|
||||||
|
"*.key",
|
||||||
|
"private_key.pem",
|
||||||
|
"privkey.pem",
|
||||||
|
"ec.key",
|
||||||
|
"chiave_privata.key",
|
||||||
|
];
|
||||||
|
|
||||||
|
for pattern in required_patterns {
|
||||||
|
let has_exact = gitignore.contains(&pattern);
|
||||||
|
let has_wildcard = gitignore.contains(&format!("*.env.local")) || gitignore.contains(&format!(".env.local"));
|
||||||
|
let has_env = gitignore.contains("*.env") || gitignore.contains(".env");
|
||||||
|
|
||||||
|
// For .env.local, either .env.local or *.env.local is acceptable
|
||||||
|
let is_env_local = pattern == "*.env.local" || pattern == ".env.local";
|
||||||
|
if is_env_local {
|
||||||
|
assert!(
|
||||||
|
has_wildcard,
|
||||||
|
".gitignore must contain pattern '*.env.local' or '.env.local' to protect secrets",
|
||||||
|
);
|
||||||
|
} else if pattern == ".env.production" || pattern == ".env.secret" {
|
||||||
|
assert!(
|
||||||
|
gitignore.contains(pattern),
|
||||||
|
".gitignore must contain pattern '{}' to protect secrets",
|
||||||
|
pattern
|
||||||
|
);
|
||||||
|
} else if pattern == "*.env" {
|
||||||
|
assert!(
|
||||||
|
has_env,
|
||||||
|
".gitignore must contain pattern '*.env' or '.env' to protect secrets",
|
||||||
|
);
|
||||||
|
} else if pattern == ".env" {
|
||||||
|
assert!(
|
||||||
|
has_env,
|
||||||
|
".gitignore must contain pattern '*.env' or '.env' to protect secrets",
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
assert!(
|
||||||
|
gitignore.contains(pattern),
|
||||||
|
".gitignore must contain pattern '{}' to protect secrets",
|
||||||
|
pattern
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!(".gitignore properly protects .env, .pem, and .key files");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_no_private_key_in_git() {
|
||||||
|
// Check that .gitignore includes private_key.pem
|
||||||
|
let gitignore = match fs::read_to_string(".gitignore") {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => {
|
||||||
|
println!("WARNING: .gitignore not found: {}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
gitignore.contains("private_key.pem"),
|
||||||
|
".gitignore must block private_key.pem"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
gitignore.contains("privkey.pem"),
|
||||||
|
".gitignore must block privkey.pem"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
gitignore.contains("ec.key"),
|
||||||
|
".gitignore must block ec.key"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
gitignore.contains("chiave_privata.key"),
|
||||||
|
".gitignore must block chiave_privata.key"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check that no private key files are tracked by git
|
||||||
|
let output = std::process::Command::new("git")
|
||||||
|
.args(&["ls-files", "*.pem", "*.key"])
|
||||||
|
.output()
|
||||||
|
.expect("Failed to run git ls-files");
|
||||||
|
|
||||||
|
let tracked_keys = String::from_utf8(output.stdout).unwrap();
|
||||||
|
let tracked_keys: Vec<&str> = tracked_keys.lines().collect();
|
||||||
|
|
||||||
|
// Only non-empty entries and only public_key.pem should be tracked
|
||||||
|
for tracked in tracked_keys.iter().filter(|s| !s.is_empty()) {
|
||||||
|
if !tracked.contains("public_key.pem") {
|
||||||
|
assert!(false,
|
||||||
|
"Private key file is tracked by git: {}. Remove it with git rm --cached",
|
||||||
|
tracked
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("PASS: No private keys tracked in git (only public_key.pem allowed)");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_no_token_in_source_files() {
|
||||||
|
// Scan source files for hardcoded tokens
|
||||||
|
let mut found_issues = Vec::new();
|
||||||
|
|
||||||
|
// Scan .sh files for hardcoded 40-char hex strings
|
||||||
|
for entry in fs::read_dir(".").unwrap().filter_map(|e| e.ok()) {
|
||||||
|
let path = entry.path();
|
||||||
|
if !path.is_file() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Some(ext) = path.extension() {
|
||||||
|
if ext == "sh" {
|
||||||
|
let content = fs::read_to_string(&path).unwrap();
|
||||||
|
for (line_num, line) in content.lines().enumerate() {
|
||||||
|
// Skip comments and example/template files
|
||||||
|
if line.trim().starts_with("#") || line.to_lowercase().contains("example") || line.to_lowercase().contains("template") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Check for 40-64 hex chars that could be API tokens (not in .env.example comments)
|
||||||
|
if line.trim().len() >= 40 {
|
||||||
|
let hex_chars = line.trim().chars().filter(|c| c.is_ascii_hexdigit()).collect::<Vec<_>>();
|
||||||
|
if hex_chars.len() >= 40 && hex_chars.len() <= 64 {
|
||||||
|
// Check if it looks like it's part of a TOKEN assignment
|
||||||
|
if line.to_lowercase().contains("token") || line.to_lowercase().contains("api") || line.to_lowercase().contains("secret") {
|
||||||
|
found_issues.push(format!(
|
||||||
|
"Potential hardcoded token in {}: line {}: {}",
|
||||||
|
path.display(), line_num + 1, line.trim()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found_issues.is_empty() {
|
||||||
|
println!("FAIL: Found potential hardcoded tokens:");
|
||||||
|
for issue in &found_issues {
|
||||||
|
println!(" {}", issue);
|
||||||
|
}
|
||||||
|
assert!(false, "Found potential hardcoded tokens in shell scripts: {:?}", found_issues);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
println!("PASS: No hardcoded tokens found in shell scripts");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user