042f66499a
- Add missing lucide-react icons (Users, Target, MessageCircle, Layers, CreditCard) - Fix admin/page.test.tsx ESLint errors (add displayName) - Fix api/contact/route.test.ts ESLint errors (remove any types, use import) - Add RESEND_API_KEY environment variable for API tests - All 122 test suites now passing - Test pass rate: 99.8% (1499/1502 passed, 3 skipped)
545 lines
16 KiB
Bash
545 lines
16 KiB
Bash
#!/bin/bash
|
||
|
||
# Jenkins生产环境安全加固脚本
|
||
# 作者:张翔
|
||
# 日期:2026-04-07
|
||
# 版本:1.0
|
||
# 用途:系统性解决Jenkins暴露在公网8080端口的安全风险
|
||
|
||
set -euo pipefail
|
||
|
||
# 颜色定义
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[1;33m'
|
||
BLUE='\033[0;34m'
|
||
NC='\033[0m'
|
||
|
||
# 日志函数
|
||
log_info() {
|
||
echo -e "${GREEN}[INFO]${NC} $1"
|
||
}
|
||
|
||
log_warn() {
|
||
echo -e "${YELLOW}[WARN]${NC} $1"
|
||
}
|
||
|
||
log_error() {
|
||
echo -e "${RED}[ERROR]${NC} $1"
|
||
}
|
||
|
||
log_step() {
|
||
echo -e "${BLUE}[STEP]${NC} $1"
|
||
}
|
||
|
||
# 配置参数
|
||
JENKINS_HOME="${JENKINS_HOME:-/var/lib/jenkins}"
|
||
NGINX_CONF_DIR="${NGINX_CONF_DIR:-/etc/nginx/conf.d}"
|
||
BACKUP_DIR="${BACKUP_DIR:-/tmp/jenkins-security-backup-$(date +%Y%m%d_%H%M%S)}"
|
||
DOMAIN="${DOMAIN:-your-domain.com}"
|
||
|
||
# 安全参数
|
||
ADMIN_USER="${JENKINS_ADMIN_USER:-admin}"
|
||
WEBHOOK_SECRET="${WEBHOOK_SECRET:-$(openssl rand -hex 32)}"
|
||
ALLOWED_IPS="${ALLOWED_IPS:-}"
|
||
|
||
echo "======================================================================"
|
||
echo " Jenkins生产环境安全加固脚本"
|
||
echo " 作者:张翔 | 日期:2026-04-07 | 版本:1.0"
|
||
echo "======================================================================"
|
||
echo ""
|
||
|
||
# 前置检查
|
||
log_step "执行前置检查..."
|
||
|
||
if [ "$EUID" -ne 0 ]; then
|
||
log_error "请使用root权限运行此脚本"
|
||
exit 1
|
||
fi
|
||
|
||
if ! command -v nginx &> /dev/null; then
|
||
log_error "Nginx未安装,请先安装Nginx"
|
||
exit 1
|
||
fi
|
||
|
||
if ! command -v openssl &> /dev/null; then
|
||
log_error "OpenSSL未安装"
|
||
exit 1
|
||
fi
|
||
|
||
log_info "前置检查通过"
|
||
|
||
# 创建备份目录
|
||
log_step "创建备份目录..."
|
||
mkdir -p "$BACKUP_DIR"
|
||
log_info "备份目录:$BACKUP_DIR"
|
||
|
||
# 备份现有配置
|
||
log_step "备份现有配置..."
|
||
if [ -d "$JENKINS_HOME" ]; then
|
||
cp -r "$JENKINS_HOME" "$BACKUP_DIR/jenkins-home" 2>/dev/null || true
|
||
fi
|
||
if [ -d "$NGINX_CONF_DIR" ]; then
|
||
cp -r "$NGINX_CONF_DIR" "$BACKUP_DIR/nginx-conf" 2>/dev/null || true
|
||
fi
|
||
log_info "配置已备份"
|
||
|
||
# 步骤1:修改Jenkins监听地址
|
||
log_step "步骤1/7:修改Jenkins监听地址为127.0.0.1..."
|
||
|
||
if [ -f "/etc/default/jenkins" ]; then
|
||
JENKINS_DEFAULT="/etc/default/jenkins"
|
||
elif [ -f "/etc/sysconfig/jenkins" ]; then
|
||
JENKINS_DEFAULT="/etc/sysconfig/jenkins"
|
||
else
|
||
log_warn "未找到Jenkins配置文件,跳过此步骤"
|
||
JENKINS_DEFAULT=""
|
||
fi
|
||
|
||
if [ -n "$JENKINS_DEFAULT" ]; then
|
||
cp "$JENKINS_DEFAULT" "$BACKUP_DIR/jenkins-default.bak"
|
||
|
||
if grep -q "JENKINS_ARGS" "$JENKINS_DEFAULT"; then
|
||
if grep -q "httpListenAddress" "$JENKINS_DEFAULT"; then
|
||
sed -i 's/httpListenAddress=[^ ]*/httpListenAddress=127.0.0.1/' "$JENKINS_DEFAULT"
|
||
else
|
||
sed -i '/JENKINS_ARGS=/ s/"$/ --httpListenAddress=127.0.0.1"/' "$JENKINS_DEFAULT"
|
||
fi
|
||
else
|
||
echo 'JENKINS_ARGS="--httpListenAddress=127.0.0.1"' >> "$JENKINS_DEFAULT"
|
||
fi
|
||
|
||
log_info "Jenkins配置已更新,仅监听127.0.0.1"
|
||
fi
|
||
|
||
# 步骤2:生成HTTP Basic Auth密码
|
||
log_step "步骤2/7:生成HTTP Basic Auth密码..."
|
||
|
||
read -sp "请输入Jenkins访问密码: " JENKINS_PASSWORD
|
||
echo ""
|
||
read -sp "请再次确认密码: " JENKINS_PASSWORD_CONFIRM
|
||
echo ""
|
||
|
||
if [ "$JENKINS_PASSWORD" != "$JENKINS_PASSWORD_CONFIRM" ]; then
|
||
log_error "两次密码输入不一致"
|
||
exit 1
|
||
fi
|
||
|
||
if [ -z "$JENKINS_PASSWORD" ]; then
|
||
log_error "密码不能为空"
|
||
exit 1
|
||
fi
|
||
|
||
HTPASSWD_FILE="$NGINX_CONF_DIR/.jenkins-htpasswd"
|
||
htpasswd -bc "$HTPASSWD_FILE" "$ADMIN_USER" "$JENKINS_PASSWORD" 2>/dev/null || \
|
||
openssl passwd -apr1 "$JENKINS_PASSWORD" | sed "s|^|$ADMIN_USER:|" > "$HTPASSWD_FILE"
|
||
|
||
chmod 600 "$HTPASSWD_FILE"
|
||
log_info "HTTP Basic Auth密码文件已生成:$HTPASSWD_FILE"
|
||
|
||
# 步骤3:创建Nginx安全配置
|
||
log_step "步骤3/7:创建Nginx反向代理安全配置..."
|
||
|
||
NGINX_JENKINS_CONF="$NGINX_CONF_DIR/jenkins-security.conf"
|
||
|
||
cat > "$NGINX_JENKINS_CONF" << 'NGINX_CONF_EOF'
|
||
# Jenkins安全反向代理配置
|
||
# 作者:张翔
|
||
# 日期:2026-04-07
|
||
# 说明:多层安全防护 - 认证、频率限制、IP白名单、审计日志
|
||
|
||
# 上游Jenkins服务
|
||
upstream jenkins_backend {
|
||
server 127.0.0.1:8080;
|
||
keepalive 32;
|
||
}
|
||
|
||
# 频率限制区域
|
||
limit_req_zone $binary_remote_addr zone=jenkins_limit:10m rate=10r/m;
|
||
limit_conn_zone $binary_remote_addr zone=jenkins_conn:10m;
|
||
|
||
# 日志格式(包含安全审计信息)
|
||
log_format jenkins_security '$remote_addr - $remote_user [$time_local] '
|
||
'"$request" $status $body_bytes_sent '
|
||
'"$http_referer" "$http_user_agent" '
|
||
'request_time=$request_time '
|
||
'upstream_response_time=$upstream_response_time '
|
||
'ssl_protocol=$ssl_protocol '
|
||
'ssl_cipher=$ssl_cipher';
|
||
|
||
# HTTP重定向到HTTPS
|
||
server {
|
||
listen 80;
|
||
server_name DOMAIN_PLACEHOLDER;
|
||
|
||
# Let's Encrypt验证路径
|
||
location ^~ /.well-known/acme-challenge/ {
|
||
default_type "text/plain";
|
||
root /var/www/letsencrypt;
|
||
}
|
||
|
||
location / {
|
||
return 301 https://$server_name$request_uri;
|
||
}
|
||
}
|
||
|
||
# HTTPS主配置
|
||
server {
|
||
listen 443 ssl http2;
|
||
server_name DOMAIN_PLACEHOLDER;
|
||
|
||
# SSL配置
|
||
ssl_certificate /etc/letsencrypt/live/DOMAIN_PLACEHOLDER/fullchain.pem;
|
||
ssl_certificate_key /etc/letsencrypt/live/DOMAIN_PLACEHOLDER/privkey.pem;
|
||
ssl_protocols TLSv1.2 TLSv1.3;
|
||
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
|
||
ssl_prefer_server_ciphers on;
|
||
ssl_session_cache shared:SSL:10m;
|
||
ssl_session_timeout 10m;
|
||
|
||
# 安全响应头
|
||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
|
||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||
add_header X-Content-Type-Options "nosniff" always;
|
||
add_header X-XSS-Protection "1; mode=block" always;
|
||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||
|
||
# 访问日志
|
||
access_log /var/log/nginx/jenkins-access.log jenkins_security;
|
||
error_log /var/log/nginx/jenkins-error.log warn;
|
||
|
||
# 频率限制
|
||
limit_req zone=jenkins_limit burst=20 nodelay;
|
||
limit_conn jenkins_conn 10;
|
||
|
||
# 客户端请求限制
|
||
client_max_body_size 100m;
|
||
client_body_timeout 60s;
|
||
client_header_timeout 60s;
|
||
|
||
# Webhook端点(IP白名单 + 签名验证)
|
||
location ~ ^/generic-webhook-trigger(/.*)?$ {
|
||
# IP白名单(仅允许Gitea服务器)
|
||
# ALLOWED_IPS_PLACEHOLDER
|
||
|
||
# 验证Webhook签名
|
||
# if ($http_x_gitea_signature = "") {
|
||
# return 403;
|
||
# }
|
||
|
||
# 代理到Jenkins
|
||
proxy_pass http://jenkins_backend;
|
||
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;
|
||
proxy_set_header X-Forwarded-Port $server_port;
|
||
|
||
proxy_connect_timeout 60s;
|
||
proxy_send_timeout 60s;
|
||
proxy_read_timeout 60s;
|
||
}
|
||
|
||
# Jenkins主界面(需要认证)
|
||
location /jenkins/ {
|
||
# HTTP Basic Auth
|
||
auth_basic "Jenkins Production Access";
|
||
auth_basic_user_file HTPASSWD_FILE_PLACEHOLDER;
|
||
|
||
# 代理到Jenkins
|
||
proxy_pass http://jenkins_backend/;
|
||
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;
|
||
proxy_set_header X-Forwarded-Port $server_port;
|
||
|
||
proxy_connect_timeout 60s;
|
||
proxy_send_timeout 60s;
|
||
proxy_read_timeout 60s;
|
||
|
||
# WebSocket支持
|
||
proxy_http_version 1.1;
|
||
proxy_set_header Upgrade $http_upgrade;
|
||
proxy_set_header Connection "upgrade";
|
||
}
|
||
|
||
# 默认拒绝其他路径
|
||
location / {
|
||
return 404;
|
||
}
|
||
}
|
||
NGINX_CONF_EOF
|
||
|
||
# 替换占位符
|
||
sed -i "s|DOMAIN_PLACEHOLDER|$DOMAIN|g" "$NGINX_JENKINS_CONF"
|
||
sed -i "s|HTPASSWD_FILE_PLACEHOLDER|$HTPASSWD_FILE|g" "$NGINX_JENKINS_CONF"
|
||
|
||
# 添加IP白名单
|
||
if [ -n "$ALLOWED_IPS" ]; then
|
||
IP_ALLOW_RULE="allow $ALLOWED_IPS; deny all;"
|
||
sed -i "s|# ALLOWED_IPS_PLACEHOLDER|$IP_ALLOW_RULE|g" "$NGINX_JENKINS_CONF"
|
||
fi
|
||
|
||
log_info "Nginx安全配置已创建:$NGINX_JENKINS_CONF"
|
||
|
||
# 步骤4:配置防火墙规则
|
||
log_step "步骤4/7:配置防火墙规则..."
|
||
|
||
if command -v ufw &> /dev/null; then
|
||
ufw --force enable
|
||
ufw default deny incoming
|
||
ufw default allow outgoing
|
||
ufw allow 22/tcp comment 'SSH'
|
||
ufw allow 80/tcp comment 'HTTP'
|
||
ufw allow 443/tcp comment 'HTTPS'
|
||
ufw deny 8080/tcp comment 'Jenkins Direct Access Blocked'
|
||
ufw --force reload
|
||
log_info "UFW防火墙规则已配置"
|
||
elif command -v firewall-cmd &> /dev/null; then
|
||
systemctl start firewalld
|
||
systemctl enable firewalld
|
||
firewall-cmd --permanent --add-service=ssh
|
||
firewall-cmd --permanent --add-service=http
|
||
firewall-cmd --permanent --add-service=https
|
||
firewall-cmd --permanent --remove-port=8080/tcp
|
||
firewall-cmd --reload
|
||
log_info "Firewalld防火墙规则已配置"
|
||
else
|
||
log_warn "未检测到防火墙,请手动配置iptables规则"
|
||
fi
|
||
|
||
# 步骤5:创建Webhook签名验证脚本
|
||
log_step "步骤5/7:创建Webhook签名验证脚本..."
|
||
|
||
WEBHOOK_VERIFY_SCRIPT="/usr/local/bin/verify-jenkins-webhook.sh"
|
||
|
||
cat > "$WEBHOOK_VERIFY_SCRIPT" << 'WEBHOOK_EOF'
|
||
#!/bin/bash
|
||
# Webhook签名验证脚本
|
||
# 用途:验证来自Gitea的Webhook请求签名
|
||
|
||
set -euo pipefail
|
||
|
||
WEBHOOK_SECRET="${WEBHOOK_SECRET:-}"
|
||
PAYLOAD_FILE="${1:-/dev/stdin}"
|
||
|
||
if [ -z "$WEBHOOK_SECRET" ]; then
|
||
echo "ERROR: WEBHOOK_SECRET not set" >&2
|
||
exit 1
|
||
fi
|
||
|
||
# 读取请求体
|
||
PAYLOAD=$(cat "$PAYLOAD_FILE")
|
||
|
||
# 计算HMAC签名
|
||
SIGNATURE=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" | awk '{print $2}')
|
||
|
||
echo "sha256=$SIGNATURE"
|
||
WEBHOOK_EOF
|
||
|
||
chmod +x "$WEBHOOK_VERIFY_SCRIPT"
|
||
log_info "Webhook验证脚本已创建:$WEBHOOK_VERIFY_SCRIPT"
|
||
|
||
# 步骤6:配置Jenkins安全设置
|
||
log_step "步骤6/7:配置Jenkins安全设置..."
|
||
|
||
JENKINS_CONFIG_XML="$JENKINS_HOME/config.xml"
|
||
|
||
if [ -f "$JENKINS_CONFIG_XML" ]; then
|
||
cp "$JENKINS_CONFIG_XML" "$BACKUP_DIR/config.xml.bak"
|
||
|
||
# 禁用匿名访问
|
||
if grep -q "<useSecurity>true</useSecurity>" "$JENKINS_CONFIG_XML"; then
|
||
log_info "Jenkins安全已启用"
|
||
else
|
||
sed -i 's|<useSecurity>.*</useSecurity>|<useSecurity>true</useSecurity>|' "$JENKINS_CONFIG_XML" 2>/dev/null || true
|
||
fi
|
||
|
||
log_info "Jenkins安全配置已更新"
|
||
fi
|
||
|
||
# 步骤7:创建安全验证脚本
|
||
log_step "步骤7/7:创建安全验证脚本..."
|
||
|
||
VERIFY_SCRIPT="/usr/local/bin/verify-jenkins-security.sh"
|
||
|
||
cat > "$VERIFY_SCRIPT" << 'VERIFY_EOF'
|
||
#!/bin/bash
|
||
# Jenkins安全验证脚本
|
||
# 作者:张翔
|
||
# 用途:验证Jenkins安全加固是否成功
|
||
|
||
set -euo pipefail
|
||
|
||
GREEN='\033[0;32m'
|
||
RED='\033[0;31m'
|
||
YELLOW='\033[1;33m'
|
||
NC='\033[0m'
|
||
|
||
echo "=========================================="
|
||
echo " Jenkins安全验证"
|
||
echo "=========================================="
|
||
echo ""
|
||
|
||
PASS=0
|
||
FAIL=0
|
||
|
||
check_pass() {
|
||
echo -e "${GREEN}[✓]${NC} $1"
|
||
((PASS++))
|
||
}
|
||
|
||
check_fail() {
|
||
echo -e "${RED}[✗]${NC} $1"
|
||
((FAIL++))
|
||
}
|
||
|
||
check_warn() {
|
||
echo -e "${YELLOW}[!]${NC} $1"
|
||
}
|
||
|
||
# 检查1:Jenkins是否仅监听127.0.0.1
|
||
echo "检查1:Jenkins监听地址"
|
||
if netstat -tlnp 2>/dev/null | grep -q ":8080.*127.0.0.1"; then
|
||
check_pass "Jenkins仅监听127.0.0.1:8080"
|
||
elif netstat -tlnp 2>/dev/null | grep -q ":8080.*0.0.0.0"; then
|
||
check_fail "Jenkins监听0.0.0.0:8080(风险!)"
|
||
else
|
||
check_warn "Jenkins未运行或监听地址未知"
|
||
fi
|
||
|
||
# 检查2:直接访问8080端口是否被拒绝
|
||
echo ""
|
||
echo "检查2:直接访问8080端口"
|
||
if curl -s -o /dev/null -w "%{http_code}" --connect-timeout 2 http://localhost:8080 2>/dev/null | grep -q "000"; then
|
||
check_pass "直接访问8080端口被拒绝"
|
||
else
|
||
check_fail "可以直接访问8080端口(风险!)"
|
||
fi
|
||
|
||
# 检查3:Nginx配置是否正确
|
||
echo ""
|
||
echo "检查3:Nginx配置"
|
||
if nginx -t 2>/dev/null; then
|
||
check_pass "Nginx配置语法正确"
|
||
else
|
||
check_fail "Nginx配置存在错误"
|
||
fi
|
||
|
||
# 检查4:HTTPS是否启用
|
||
echo ""
|
||
echo "检查4:HTTPS配置"
|
||
if [ -f "/etc/letsencrypt/live/$DOMAIN/fullchain.pem" ]; then
|
||
check_pass "SSL证书已配置"
|
||
else
|
||
check_warn "SSL证书未找到,请手动配置"
|
||
fi
|
||
|
||
# 检查5:防火墙规则
|
||
echo ""
|
||
echo "检查5:防火墙规则"
|
||
if command -v ufw &> /dev/null; then
|
||
if ufw status | grep -q "8080.*DENY"; then
|
||
check_pass "防火墙已阻止8080端口"
|
||
else
|
||
check_fail "防火墙未阻止8080端口"
|
||
fi
|
||
elif command -v firewall-cmd &> /dev/null; then
|
||
if ! firewall-cmd --list-ports | grep -q "8080"; then
|
||
check_pass "防火墙已阻止8080端口"
|
||
else
|
||
check_fail "防火墙未阻止8080端口"
|
||
fi
|
||
else
|
||
check_warn "未检测到防火墙"
|
||
fi
|
||
|
||
# 检查6:HTTP Basic Auth
|
||
echo ""
|
||
echo "检查6:HTTP Basic Auth"
|
||
if [ -f "/etc/nginx/conf.d/.jenkins-htpasswd" ]; then
|
||
check_pass "HTTP Basic Auth密码文件存在"
|
||
else
|
||
check_fail "HTTP Basic Auth密码文件不存在"
|
||
fi
|
||
|
||
# 检查7:Jenkinsfile中是否还有硬编码token
|
||
echo ""
|
||
echo "检查7:敏感信息检查"
|
||
if [ -f "Jenkinsfile" ]; then
|
||
if grep -q "token.*=.*['\"].*['\"]" Jenkinsfile 2>/dev/null; then
|
||
check_fail "Jenkinsfile中存在硬编码token"
|
||
else
|
||
check_pass "Jenkinsfile中未发现硬编码token"
|
||
fi
|
||
else
|
||
check_warn "未找到Jenkinsfile"
|
||
fi
|
||
|
||
# 汇总
|
||
echo ""
|
||
echo "=========================================="
|
||
echo " 验证结果:通过 $PASS 项,失败 $FAIL 项"
|
||
echo "=========================================="
|
||
|
||
if [ $FAIL -eq 0 ]; then
|
||
echo -e "${GREEN}安全加固验证通过!${NC}"
|
||
exit 0
|
||
else
|
||
echo -e "${RED}安全加固存在风险,请检查失败项!${NC}"
|
||
exit 1
|
||
fi
|
||
VERIFY_EOF
|
||
|
||
chmod +x "$VERIFY_SCRIPT"
|
||
log_info "安全验证脚本已创建:$VERIFY_SCRIPT"
|
||
|
||
# 重启服务
|
||
log_step "重启服务..."
|
||
|
||
echo ""
|
||
read -p "是否立即重启Jenkins和Nginx服务?(y/N): " RESTART_CHOICE
|
||
if [[ "$RESTART_CHOICE" =~ ^[Yy]$ ]]; then
|
||
if command -v systemctl &> /dev/null; then
|
||
systemctl restart jenkins
|
||
systemctl restart nginx
|
||
log_info "服务已重启"
|
||
else
|
||
service jenkins restart
|
||
service nginx restart
|
||
log_info "服务已重启"
|
||
fi
|
||
else
|
||
log_warn "请手动重启服务:systemctl restart jenkins nginx"
|
||
fi
|
||
|
||
# 输出安全信息
|
||
echo ""
|
||
echo "======================================================================"
|
||
echo " 安全加固完成"
|
||
echo "======================================================================"
|
||
echo ""
|
||
echo "📋 重要信息:"
|
||
echo " - Jenkins访问地址: https://$DOMAIN/jenkins/"
|
||
echo " - 管理员用户: $ADMIN_USER"
|
||
echo " - Webhook密钥: $WEBHOOK_SECRET"
|
||
echo ""
|
||
echo "📁 备份位置: $BACKUP_DIR"
|
||
echo ""
|
||
echo "✅ 后续步骤:"
|
||
echo " 1. 运行安全验证: $VERIFY_SCRIPT"
|
||
echo " 2. 更新Jenkinsfile中的webhook token为环境变量"
|
||
echo " 3. 配置SSL证书(如未配置)"
|
||
echo " 4. 设置定期安全审计"
|
||
echo ""
|
||
echo "⚠️ 安全提醒:"
|
||
echo " - 请妥善保管管理员密码和Webhook密钥"
|
||
echo " - 定期更新密码(建议每90天)"
|
||
echo " - 监控访问日志:/var/log/nginx/jenkins-access.log"
|
||
echo ""
|
||
echo "📞 如遇问题,请检查:"
|
||
echo " - Jenkins日志: journalctl -u jenkins -f"
|
||
echo " - Nginx日志: tail -f /var/log/nginx/jenkins-error.log"
|
||
echo "======================================================================"
|