#!/usr/bin/env bash # centos7_web_stack_hardening_v3.sh # - Repo pin -> fastestmirror off -> SELinux off # - limits/sysctl/httpd # - Miniconda install + ToS non-interactive accept + py312 env # - SSH 로그인 지연(UseDNS/GSSAPI/DNSv6) 해결 set -euo pipefail TS="$(date +%Y%m%d%H%M%S)" RELEASEVER="7" BASEARCH="$(uname -m)" # ===== Conda config ===== INSTALL_MINICONDA=1 MINICONDA_PREFIX="/opt/miniconda" CONDA_ENV_NAME="py312" CONDA_PY_VERSION="3.12" MINICONDA_URL="https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh" backup_if_exists(){ local f="$1"; [[ -f "$f" ]] && cp -a "$f" "${f}.bak-${TS}" || true; } ensure_dir(){ local d="$1"; [[ -d "$d" ]] || mkdir -p "$d"; } set_kv_in_file(){ # set_kv_in_file local f="$1" k="$2" v="$3" backup_if_exists "$f" if grep -qE "^[#[:space:]]*${k}[[:space:]]" "$f"; then sed -ri "s|^[#[:space:]]*(${k})[[:space:]].*|\\1 ${v}|g" "$f" else echo "${k} ${v}" >> "$f" fi } echo "[1/8] Pin CentOS-Base.repo to Kakao + disable mirrorlist/fastestmirror" YUM_REPO="/etc/yum.repos.d/CentOS-Base.repo" backup_if_exists "$YUM_REPO" sed -ri 's/^[[:space:]]*mirrorlist=/#mirrorlist=/g' "$YUM_REPO" || true declare -A BASEURLS=( ["base"]="http://mirror.kakao.com/centos/${RELEASEVER}/os/${BASEARCH}/" ["updates"]="http://mirror.kakao.com/centos/${RELEASEVER}/updates/${BASEARCH}/" ["extras"]="http://mirror.kakao.com/centos/${RELEASEVER}/extras/${BASEARCH}/" ["centosplus"]="http://mirror.kakao.com/centos/${RELEASEVER}/centosplus/${BASEARCH}/" ) for section in "${!BASEURLS[@]}"; do if grep -q "^\[$section\]" "$YUM_REPO"; then awk -v sec="$section" -v url="${BASEURLS[$section]}" ' BEGIN{insec=0; rep=0} /^\[/{insec=0} $0=="["sec"]"{insec=1} { if(insec && $0 ~ /^[[:space:]]*baseurl=/ && rep==0){ print "baseurl="url; rep=1; next } print }' "$YUM_REPO" > "${YUM_REPO}.tmp" && mv "${YUM_REPO}.tmp" "$YUM_REPO" if ! awk -v sec="$section" ' BEGIN{f=0;insec=0} /^\[/{insec=0} $0=="["sec"]"{insec=1} { if(insec && $0 ~ /^[[:space:]]*baseurl=/) f=1 } END{exit(f?0:1)} ' "$YUM_REPO"; then awk -v sec="$section" -v url="${BASEURLS[$section]}" ' { print; if($0=="["sec"]") print "baseurl="url }' "$YUM_REPO" > "${YUM_REPO}.tmp" && mv "${YUM_REPO}.tmp" "$YUM_REPO" fi else cat >> "$YUM_REPO" < "$FM_CONF" fi yum -y install yum-utils curl ca-certificates || true yum clean all || true yum makecache fast || true echo "[2/8] Disable SELinux (runtime + persistent)" command -v setenforce >/dev/null 2>&1 && setenforce 0 || true backup_if_exists "/etc/selinux/config" sed -ri 's/^SELINUX=enforcing/SELINUX=disabled/g; s/^SELINUX=permissive/SELINUX=disabled/g' /etc/selinux/config echo "[3/8] limits.conf via /etc/security/limits.d/99-web.conf" LIMITS_D="/etc/security/limits.d" ensure_dir "$LIMITS_D" cat > "$LIMITS_D/99-web.conf" <<'EOF' * soft nofile 65535 * hard nofile 65535 * soft nproc 65535 * hard nproc 65535 apache soft nofile 65535 apache hard nofile 65535 EOF echo "[4/8] systemd drop-in for httpd limits" HTTPD_DROPIN="/etc/systemd/system/httpd.service.d" ensure_dir "$HTTPD_DROPIN" cat > "$HTTPD_DROPIN/override.conf" <<'EOF' [Service] LimitNOFILE=65535 LimitNPROC=65535 EOF echo "[5/8] sysctl.d/99-web.conf" SYSCTL_F="/etc/sysctl.d/99-web.conf" cat > "$SYSCTL_F" <<'EOF' net.core.somaxconn = 65535 net.ipv4.tcp_max_syn_backlog = 65535 net.ipv4.ip_local_port_range = 1024 65535 net.ipv4.tcp_fin_timeout = 15 net.ipv4.tcp_tw_reuse = 1 fs.file-max = 2097152 fs.inotify.max_user_instances = 8192 fs.inotify.max_user_watches = 1048576 EOF sysctl --system >/dev/null echo "[6/8] httpd install/restart (idempotent)" rpm -q httpd >/dev/null 2>&1 || yum -y install httpd || true systemctl daemon-reload systemctl daemon-reexec || true systemctl enable httpd || true systemctl restart httpd || true if [[ "$INSTALL_MINICONDA" -eq 1 ]]; then echo "[7/8] Miniconda install + ToS accept + ${CONDA_ENV_NAME} (Python ${CONDA_PY_VERSION})" TMP_SH="/tmp/miniconda.sh" curl -fsSL "$MINICONDA_URL" -o "$TMP_SH" if [[ ! -x "${MINICONDA_PREFIX}/bin/conda" ]]; then bash "$TMP_SH" -b -p "$MINICONDA_PREFIX" fi chmod -R a+rx "$MINICONDA_PREFIX" || true # PATH export (system-wide, non-interactive) CONDA_PROFILE="/etc/profile.d/miniconda.sh" echo 'if [ -d "/opt/miniconda/bin" ]; then export PATH="/opt/miniconda/bin:$PATH"; fi' > "$CONDA_PROFILE" chmod 644 "$CONDA_PROFILE" ln -sf "${MINICONDA_PREFIX}/bin/conda" /usr/local/bin/conda # ---- ToS non-interactive accept (system-wide/root) ---- # 필요 채널: main, r "${MINICONDA_PREFIX}/bin/conda" tos accept --override-channels --channel https://repo.anaconda.com/pkgs/main || true "${MINICONDA_PREFIX}/bin/conda" tos accept --override-channels --channel https://repo.anaconda.com/pkgs/r || true # (선택) 기본 채널을 시스템 레벨에 명시 "${MINICONDA_PREFIX}/bin/conda" config --system --remove-key default_channels >/dev/null 2>&1 || true "${MINICONDA_PREFIX}/bin/conda" config --system --add default_channels https://repo.anaconda.com/pkgs/main "${MINICONDA_PREFIX}/bin/conda" config --system --add default_channels https://repo.anaconda.com/pkgs/r # env 생성 (비대화식) if [[ ! -d "${MINICONDA_PREFIX}/envs/${CONDA_ENV_NAME}" ]]; then "${MINICONDA_PREFIX}/bin/conda" create -y -n "${CONDA_ENV_NAME}" "python=${CONDA_PY_VERSION}" fi "${MINICONDA_PREFIX}/bin/conda" run -n "${CONDA_ENV_NAME}" python -V || true fi echo "[8/8] SSH 로그인 지연 튜닝 (UseDNS/GSSAPI/IPv6 회피)" SSHD="/etc/ssh/sshd_config" backup_if_exists "$SSHD" # UseDNS no, GSSAPIAuthentication no, AddressFamily inet 설정(존재하면 교체, 없으면 추가) set_kv_in_file "$SSHD" "UseDNS" "no" set_kv_in_file "$SSHD" "GSSAPIAuthentication" "no" set_kv_in_file "$SSHD" "AddressFamily" "inet" # (선택) LoginGraceTime 단축 set_kv_in_file "$SSHD" "LoginGraceTime" "20" # hostname 역조회가 실패해 지연되는 것을 방지하려면 /etc/hosts에 서버 호스트네임을 루프백/고정IP로 매핑 권장 HN="$(hostname -s)" if ! grep -qE "^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+[[:space:]]+${HN}( |$)" /etc/hosts; then echo -e "127.0.0.1\t${HN}" >> /etc/hosts fi systemctl restart sshd echo "== DONE ==" echo "[verify]" echo " - conda -V ; ${MINICONDA_PREFIX}/bin/conda info" echo " - ${MINICONDA_PREFIX}/bin/conda env list | grep ${CONDA_ENV_NAME} || true" echo " - grep -nE 'UseDNS|GSSAPIAuthentication|AddressFamily' /etc/ssh/sshd_config" echo " - ssh -vvv localhost (GSSAPI/DNS lookup 지연 없어야 함)"