// 이 파일이 하는 일: "방화벽과 보안 기초" 코스 — 방화벽이라는 문지기의 역할에서 출발해
// 포트의 의미, 우리 AWS 보안그룹의 실제 설정, 최소 권한 원칙, 흔한 공격 상식을 거쳐
// 서버 보안 체크리스트까지 7개 섹션으로 안내하는 정적 학습 페이지.
// (방화벽=문지기 → 포트 여닫기 → 윈도우 방화벽 실습 → AWS 보안그룹 실례
//  → 최소 권한 원칙 → 공격 유형 상식 → 보안 체크리스트 → 마무리 카드)
// 학습 포인트: API 호출이 없는 '내용 고정' 페이지다. 스타일은 global.css의
// 가이드 전용 클래스(step-card, code-block, tip 등)를 재사용한다 — 색을 여기서 하드코딩하지 않는다.
// 텍스트 다이어그램은 JSX 중괄호/백틱 충돌을 피하려고 파일 상단의 백틱 문자열 상수로 정의한다.

import { Link } from 'react-router-dom';

// ── 텍스트 다이어그램 · 표 상수들 ──

const CODE_GATEKEEPER = `방화벽 = 서버(건물) 정문의 문지기

              ┌─────────────────────────┐
  인터넷 ───▶ │ 방화벽(문지기)           │ ───▶ 서버 내부
              │ "어디서 왔죠?"            │
              │ "몇 호(포트)에 가시죠?"   │
              │ → 규칙표와 대조 → 통과/차단│
              └─────────────────────────┘

두 방향을 다 지켜요:
인바운드(Inbound)   = 밖 → 안, "들어오는 손님" 검사  ← 보통 여기가 빡빡함
아웃바운드(Outbound) = 안 → 밖, "나가는 사람" 검사   ← 보통 느슨하지만
                                  악성코드가 밖으로 정보를 빼돌릴 때 잡는 마지막 그물`;

const CODE_PORT_DOOR = `포트를 '연다/닫는다'는 것의 진짜 의미

서버 x.x.x.x (건물)
├── :443  HTTPS   [열림]  ← 문지기: "443호 손님은 누구든 통과"
├── :80   HTTP    [열림]  ← (들어오면 443으로 안내)
├── :3000 Gitea   [열림]  ← 우리 Git 서버의 웹 창구
├── :22   SSH     [조건부] ← "사무실 IP에서 온 분만 통과"
├── :5432 PostgreSQL [닫힘] ← "5432호요? 그런 방 없습니다" (묵묵부답)
└── :8080 백엔드     [닫힘] ← 건물 안(같은 서버)에서만 노크 가능

포트가 '열려 있다' = 두 조건이 동시에 성립:
1) 그 포트에서 귀 기울이는 프로그램이 실제로 실행 중이고 (방에 사람이 있고)
2) 방화벽이 그 포트로의 접속을 허용함           (문지기가 통과시킴)

둘 중 하나라도 아니면 밖에서는 연결 불가!`;

const CODE_WIN_FIREWALL = `# 윈도우 방화벽 구경하기 (설정은 건드리지 말고 눈으로만!)

1) 시작 버튼 → "Windows Defender 방화벽" 검색 → 실행
   → 내 PC가 지금 어떤 네트워크(개인/공용)로 분류돼 있는지 확인

2) 왼쪽 메뉴 "고급 설정" 클릭 → "인바운드 규칙" 선택
   → 수백 개의 규칙이 주르륵! 프로그램마다
     "이 앱은 이 포트로 들어오는 연결 허용/차단" 규칙이 있어요

3) 아무 규칙이나 더블클릭 → [프로토콜 및 포트] 탭
   → TCP/UDP, 포트 번호가 보이면 — 방금 배운 개념 그대로!

# 관찰 포인트: '차단' 규칙(빨간 아이콘)도 찾아보세요.
# 기본값이 "명시적으로 허용된 것 외엔 차단"이라는 걸 확인할 수 있어요.`;

const CODE_NETSTAT = `# 내 PC에서 지금 열려 있는(듣고 있는) 포트 직접 보기
# 터미널(PowerShell)에서:
netstat -ano | findstr LISTENING

  TCP  0.0.0.0:135    ...  LISTENING  ...
  TCP  127.0.0.1:5173 ...  LISTENING  ...  ← Vite 개발 서버가 켜져 있다면!

# 0.0.0.0   = "모든 네트워크에서 듣는 중" (외부에서도 노크 가능)
# 127.0.0.1 = "내 PC 안에서만 듣는 중"   (밖에선 안 보임)
# 맨 끝 숫자(PID)로 어떤 프로그램인지 추적:
tasklist | findstr <PID>`;

const CODE_SECURITY_GROUP = `AWESOMEDEV 서버(AWS EC2 서울)의 보안그룹 — 실제 방침

인바운드 규칙 (들어오는 문)
──────────────────────────────────────────────
포트    대상            허용 범위        이유
80      HTTP            전 세계(0.0.0.0/0)  → Caddy가 443으로 안내
443     HTTPS           전 세계(0.0.0.0/0)  웹 서비스니까 손님은 환영
3000    Gitea(웹·clone)  전 세계(0.0.0.0/0)  사내 Git 서버 — 로그인으로 보호
222     Gitea SSH        전 세계(0.0.0.0/0)  SSH 방식 clone/push용 (키 인증)
22      SSH(원격 접속)   지정한 IP만!        관리자 전용 뒷문

그 외 전부: 닫힘 (규칙에 없으면 = 차단)
──────────────────────────────────────────────
PostgreSQL(5432)? 닫힘 — DB는 같은 서버 안의 백엔드만 접근
Spring Boot(8080)? 닫힘 — 밖에서는 Caddy를 거쳐야만 도달

아웃바운드: 허용 (서버가 배포 파일을 받거나 외부 API를 부를 수 있게)`;

const CODE_LEAST_PRIVILEGE = `최소 권한 원칙(Principle of Least Privilege)을 문에 비유하면:

나쁜 건물: "귀찮으니까 모든 방문을 다 열어 두자"
  → 도둑이 한 번 들어오면 전 층을 다 털어 감

좋은 건물: "각자 자기 일에 필요한 문만, 필요한 만큼만"
  → 손님(전 세계)     : 로비(80/443)까지만
  → 관리자(지정 IP)   : 관리실(22)까지
  → DB(5432)          : 아예 외부 문 없음, 내부 복도로만 연결

코드·계정에도 똑같이 적용돼요:
  → DB 계정: 서비스가 쓸 테이블 권한만 (DROP 권한 주지 않기)
  → 배포 키: 배포에 필요한 저장소만 접근
  → 신규 입사자 계정: 처음엔 최소로, 필요할 때 하나씩 추가`;

const CODE_ATTACKS = `서버를 인터넷에 올리는 순간 실제로 벌어지는 일들

1) 포트 스캔 (Port Scan) — "빈집 문고리 돌려보기"
   공격 봇이 1번부터 65535번까지 모든 포트를 두드려 봄
   → 열린 포트 발견 = "이 건물엔 이 문이 열려 있네" 목록 작성
   → 대응: 안 쓰는 포트는 전부 닫기 (보안그룹 기본 차단!)

2) 브루트포스 (Brute Force) — "비밀번호 무차별 대입"
   SSH(22)나 로그인 화면에 아이디/비번 조합을 기계로 수만 번 시도
   root/1234, admin/admin, admin/password ...
   → 실제로 SSH를 전 세계에 열어 두면 몇 분 안에 시도 로그가 쌓여요
   → 대응: 22번을 IP 제한 (우리 보안그룹!) + 강한 비밀번호/키 인증
           + 로그인 시도 횟수 제한

핵심: 공격자는 '우리를 노려서'가 아니라 '봇으로 전부 다' 두드립니다.
      작은 회사 서버라고 안전한 게 아니에요 — 그래서 기본기가 중요!`;

const CODE_CHECKLIST = `AWESOMEDEV 서버 보안 체크리스트 — 새 서버를 열기 전 셀프 점검

[ ] 1. 보안그룹: 서비스에 꼭 필요한 포트(80/443/3000)만 공개했는가?
[ ] 2. SSH(22): 허용 IP를 지정했는가? (0.0.0.0/0 금지!)
[ ] 3. SSH: 비밀번호 대신 키 파일 인증을 쓰는가?
[ ] 4. DB(PostgreSQL): 외부 포트가 닫혀 있고, 접속 계정 권한이 최소인가?
[ ] 5. 비밀값(DB 비번, API 키): 코드/Git에 없고 환경변수로 주입하는가?
[ ] 6. HTTPS: Caddy가 인증서를 정상 발급/갱신하고 있는가?
[ ] 7. OS·Docker 이미지: 보안 업데이트를 주기적으로 적용하는가?
[ ] 8. 백업: DB 백업이 자동으로 돌고, 복원 테스트를 해봤는가?
[ ] 9. 로그: 이상한 로그인 시도를 나중에 추적할 로그가 남는가?

→ 8번이 왜 '보안' 항목일까요?
  공격이나 실수로 데이터가 날아갔을 때 되살릴 수 있는 능력도 보안입니다.`;

// ── 이 페이지 안에서만 쓰는 작은 부품들 ──
// 학습 포인트: 같은 모양이 반복되면 컴포넌트로 뽑는다. props만 바꿔 재사용.
function Section({ n, title, sub, children }) {
  return (
    <section className="step-card" id={`sec-${n}`}>
      <div className="step-head">
        <span className="step-num">{n}</span>
        <div>
          <h3>{title}</h3>
          {sub && <span className="step-sub">{sub}</span>}
        </div>
      </div>
      <div className="step-body">{children}</div>
    </section>
  );
}

function Code({ children }) {
  return <pre className="code-block"><code>{children}</code></pre>;
}

const SECTIONS = [
  { n: 1, label: '방화벽 = 문지기' },
  { n: 2, label: '포트를 여닫는다는 것' },
  { n: 3, label: '윈도우 방화벽 구경' },
  { n: 4, label: '우리 AWS 보안그룹' },
  { n: 5, label: '최소 권한 원칙' },
  { n: 6, label: '공격 유형 상식' },
  { n: 7, label: '보안 체크리스트' },
];

export default function FirewallPage() {
  return (
    <div>
      {/* 히어로: 이 코스가 어디서 출발해 어디까지 가는지 */}
      <div className="hero">
        <div className="eyebrow">Course · 네트워크</div>
        <h1>방화벽과 보안 기초<br />— 서버의 문을 지키는 법</h1>
        <p>
          서버를 인터넷에 올리는 순간, 전 세계의 공격 봇이 몇 분 안에 문을 두드리기
          시작해요. 이 코스에서는 방화벽이라는 문지기가 무엇을 검사하는지, 그리고
          우리 회사 AWS 서버가 실제로 어떤 규칙으로 문을 여닫는지 배웁니다.
        </p>
        <div className="chip-row">
          <span className="chip">예상 소요 50분</span>
          <span className="chip">실습 2개 (내 PC에서)</span>
          <span className="chip">선수 코스: 네트워크의 이해</span>
        </div>
      </div>

      {/* 섹션 바로가기 */}
      <nav className="pill-nav">
        {SECTIONS.map((s) => (
          <a key={s.n} href={`#sec-${s.n}`}>
            <b>{s.n}</b>
            {s.label}
          </a>
        ))}
      </nav>

      <Section n={1} title="방화벽 = 건물 정문의 문지기" sub="들어오는 손님도, 나가는 사람도 검사한다">
        <p>
          <strong>방화벽(Firewall)</strong>은 네트워크의 문지기예요. 서버(건물)를 드나드는
          모든 데이터에게 <strong>"어디서 왔죠? 몇 호(포트)에 가시죠?"</strong>를 묻고,
          미리 정해 둔 <strong>규칙표</strong>와 대조해서 통과시키거나 돌려보냅니다.
          규칙표에 없는 손님은? 기본은 <strong>차단</strong>이에요.
        </p>
        <Code>{CODE_GATEKEEPER}</Code>
        <p>
          방향이 두 가지라는 점이 중요해요. <strong>인바운드</strong>는 밖에서 서버로
          들어오는 연결(손님), <strong>아웃바운드</strong>는 서버에서 밖으로 나가는
          연결이에요. 공격은 대부분 인바운드로 들어오니까 인바운드 규칙이 빡빡하고,
          아웃바운드는 보통 느슨하게 둡니다 — 다만 악성코드가 서버 안에서 데이터를
          <strong> 밖으로 빼돌릴 때</strong>는 아웃바운드 검사가 마지막 그물이 돼요.
        </p>
        <div className="tip">
          <b>비유 하나 더</b> 학교 정문 지도 선생님을 떠올려 보세요. 등교(인바운드)는
          학생증을 꼼꼼히 확인하지만, 하교(아웃바운드)는 비교적 자유롭죠. 그래도
          수업 시간에 몰래 나가는 건(이상한 아웃바운드) 잡습니다.
        </div>
      </Section>

      <Section n={2} title="포트를 '연다/닫는다'는 것" sub="방문을 여는 것과 문지기가 통과시키는 것, 둘 다 필요">
        <p>
          <Link to="/learn/network">네트워크의 이해</Link> 코스에서 <strong>IP는 건물 주소,
          포트는 방 번호</strong>라고 배웠죠. "포트를 연다"는 말은 그 방 번호로 오는 손님을
          문지기가 통과시키도록 규칙표에 적어 준다는 뜻이에요.
        </p>
        <Code>{CODE_PORT_DOOR}</Code>
        <p>
          여기서 중요한 사실 — 포트가 진짜로 "열려 있다"고 하려면 <strong>두 가지가
          동시에</strong> 필요해요. 방 안에 손님을 맞을 프로그램이 실제로 실행 중이어야
          하고(예: 443에서 기다리는 Caddy), 방화벽이 그 방으로 가는 길을 허용해야 하죠.
          프로그램만 켜고 방화벽이 막고 있으면? 밖에서는 아무리 노크해도 조용합니다.
          반대로 방화벽만 열고 프로그램이 없어도 연결은 안 돼요.
        </p>
        <svg className="diagram" viewBox="0 0 640 240" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="포트가 열려 있으려면 방화벽 허용과 프로그램 실행이라는 두 조건이 모두 성립해야 연결에 성공한다는 도해">
          <rect x="20" y="68" width="104" height="56" rx="8" fill="var(--card)" stroke="var(--line)" />
          <text x="72" y="92" textAnchor="middle" fontSize="13" fontWeight="700" fill="var(--ink)">손님</text>
          <text x="72" y="110" textAnchor="middle" fontSize="11" fill="var(--muted)">접속 시도</text>
          <line x1="124" y1="96" x2="160" y2="96" stroke="var(--muted)" strokeWidth="1.8" />
          <path d="M170 96 l-9 -4.5 l0 9 z" fill="var(--muted)" />
          <rect x="170" y="52" width="150" height="88" rx="8" fill="var(--card)" stroke="var(--primary)" strokeWidth="1.5" />
          <text x="245" y="78" textAnchor="middle" fontSize="11" fill="var(--muted)">조건 ①</text>
          <text x="245" y="100" textAnchor="middle" fontSize="13" fontWeight="700" fill="var(--ink)">방화벽이 허용</text>
          <text x="245" y="120" textAnchor="middle" fontSize="11" fill="var(--muted)">문지기가 통과시킴</text>
          <line x1="320" y1="96" x2="360" y2="96" stroke="var(--muted)" strokeWidth="1.8" />
          <path d="M370 96 l-9 -4.5 l0 9 z" fill="var(--muted)" />
          <rect x="370" y="52" width="150" height="88" rx="8" fill="var(--card)" stroke="var(--primary)" strokeWidth="1.5" />
          <text x="445" y="78" textAnchor="middle" fontSize="11" fill="var(--muted)">조건 ②</text>
          <text x="445" y="100" textAnchor="middle" fontSize="13" fontWeight="700" fill="var(--ink)">프로그램이 듣는 중</text>
          <text x="445" y="120" textAnchor="middle" fontSize="11" fill="var(--muted)">방에 사람이 있음</text>
          <line x1="520" y1="96" x2="542" y2="96" stroke="var(--teal)" strokeWidth="1.8" />
          <path d="M552 96 l-9 -4.5 l0 9 z" fill="var(--teal)" />
          <rect x="552" y="68" width="72" height="56" rx="8" fill="var(--card)" stroke="var(--teal)" strokeWidth="1.5" />
          <text x="588" y="92" textAnchor="middle" fontSize="13" fontWeight="700" fill="var(--teal)">연결</text>
          <text x="588" y="110" textAnchor="middle" fontSize="13" fontWeight="700" fill="var(--teal)">성공</text>
          <text x="320" y="186" textAnchor="middle" fontSize="12.5" fill="var(--ink)">둘 중 <tspan fill="var(--rose)" fontWeight="700">하나라도 아니면</tspan> 밖에서는 연결 불가</text>
          <text x="320" y="206" textAnchor="middle" fontSize="11" fill="var(--muted)">아무리 노크해도 조용함 — 묵묵부답</text>
        </svg>
        <p className="muted" style={{ fontSize: 12.5, textAlign: 'center', marginTop: 2 }}>그림: 포트가 '열려 있다' = 방화벽 허용 + 프로그램 실행 중, 두 조건이 모두 성립</p>
        <div className="warn">
          <b>닫힌 포트의 좋은 매너: 묵묵부답</b> — "그 방은 잠겨 있어요"라고 친절하게
          답해 주는 것보다 <strong>아예 대답하지 않는 것</strong>이 더 안전해요.
          대답 자체가 "여기 서버가 살아 있다"는 힌트가 되기 때문이에요. AWS 보안그룹이
          기본으로 이렇게(응답 없이 버리기) 동작합니다.
        </div>
      </Section>

      <Section n={3} title="직접 확인해 보기 ① — 윈도우 방화벽 구경" sub="내 PC에도 문지기가 이미 일하고 있다">
        <p>
          방화벽은 서버에만 있는 게 아니에요. 지금 이 페이지를 보고 있는 여러분의
          윈도우 PC에도 문지기가 <strong>이미 근무 중</strong>입니다. 규칙표를 직접
          구경해 볼까요? (보기만 하고 <strong>설정은 바꾸지 마세요!</strong>)
        </p>
        <Code>{CODE_WIN_FIREWALL}</Code>
        <p>
          이어서 <strong>내 PC가 지금 어떤 포트에서 귀 기울이고 있는지</strong>도 직접
          볼 수 있어요. 개발 서버(<span className="icode">npm run dev</span>)를 켠 상태로
          해 보면 더 재밌습니다.
        </p>
        <Code>{CODE_NETSTAT}</Code>
        <div className="tip">
          <b>관찰 미션</b> <span className="icode">npm run dev</span>로 개발 서버를 켠 뒤
          <span className="icode"> netstat</span>을 다시 실행해서 <strong>5173 포트가 새로
          나타나는지</strong> 확인해 보세요. 끄면 사라지고요 — "포트가 열린다 = 프로그램이
          그 방에 입주한다"를 눈으로 확인하는 순간입니다.
        </div>
      </Section>

      <Section n={4} title="우리 AWS 보안그룹 — 실제 규칙표" sub="필요한 문만 열고, SSH는 IP 제한 — 왜?">
        <p>
          이제 진짜 우리 이야기를 해볼게요. AWESOMEDEV의 서버는 <strong>AWS EC2(서울)</strong>에서
          돌고 있고, AWS에서 방화벽 역할을 하는 게 <strong>보안그룹(Security Group)</strong>이에요.
          서버 앞에 서 있는 클라우드 문지기라고 보면 됩니다. 우리 규칙표는 이렇게 생겼어요.
        </p>
        <Code>{CODE_SECURITY_GROUP}</Code>
        <p>왜 이렇게 정했는지 한 줄씩 뜯어 보면 —</p>
        <ol className="olist">
          <li>
            <strong>80/443은 전 세계 공개</strong> — 웹 서비스니까요. 여러분이 학습 플랫폼에
            접속하려면 지구 어디서든 이 문은 열려 있어야 해요. 80으로 온 손님은
            Caddy가 "안전한 문(443)으로 오세요" 하고 안내합니다.
          </li>
          <li>
            <strong>SSH(22)는 지정 IP만</strong> — SSH는 서버를 원격 조종하는
            <strong> 관리자 전용 뒷문</strong>이에요. 뚫리면 서버 전체를 내주는 문이라,
            "정해진 곳에서 온 관리자만 통과"로 잠가 뒀어요. 전 세계에 열어 두는 순간
            공격 봇의 비밀번호 대입 시도가 쏟아집니다(섹션 6에서 자세히!).
          </li>
          <li>
            <strong>PostgreSQL(5432)·백엔드(8080)는 아예 비공개</strong> — DB는 같은 서버 안의
            Spring Boot만 접근하면 되고, 백엔드는 Caddy를 거쳐서만 만나면 돼요.
            밖에서 직접 노크할 이유가 없는 문은 <strong>존재 자체를 숨깁니다.</strong>
          </li>
        </ol>
        <svg className="diagram" viewBox="0 0 640 360" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="AWESOMEDEV 보안그룹 문지기 도해: 80·443과 3000은 전 세계에 열림, 22는 지정 IP만 조건부 허용, 5432는 닫혀서 응답하지 않음">
          <rect x="16" y="100" width="128" height="60" rx="8" fill="var(--card)" stroke="var(--line)" />
          <text x="80" y="125" textAnchor="middle" fontSize="13" fontWeight="700" fill="var(--ink)">인터넷</text>
          <text x="80" y="143" textAnchor="middle" fontSize="11" fill="var(--muted)">전 세계 손님</text>
          <text x="80" y="176" textAnchor="middle" fontSize="10.5" fill="var(--muted)">공격 봇도 섞여 있음</text>
          <rect x="16" y="205" width="128" height="60" rx="8" fill="var(--card)" stroke="var(--line)" />
          <text x="80" y="230" textAnchor="middle" fontSize="13" fontWeight="700" fill="var(--ink)">관리자</text>
          <text x="80" y="248" textAnchor="middle" fontSize="11" fill="var(--muted)">지정한 IP만</text>
          <polyline points="144,112 220,97 241,97" fill="none" stroke="var(--teal)" strokeWidth="1.8" />
          <path d="M250 97 l-9 -4.5 l0 9 z" fill="var(--teal)" />
          <polyline points="144,130 220,139 241,139" fill="none" stroke="var(--teal)" strokeWidth="1.8" />
          <path d="M250 139 l-9 -4.5 l0 9 z" fill="var(--teal)" />
          <line x1="144" y1="148" x2="218" y2="176" stroke="var(--rose)" strokeWidth="1.8" strokeDasharray="5 4" />
          <text x="232" y="187" textAnchor="middle" fontSize="15" fontWeight="700" fill="var(--rose)">✕</text>
          <polyline points="144,232 220,223 241,223" fill="none" stroke="var(--amber)" strokeWidth="1.8" />
          <path d="M250 223 l-9 -4.5 l0 9 z" fill="var(--amber)" />
          <text x="192" y="214" textAnchor="middle" fontSize="10.5" fill="var(--muted)">허용 IP에서만</text>
          <rect x="252" y="44" width="148" height="260" rx="10" fill="var(--card)" stroke="var(--line)" />
          <text x="326" y="68" textAnchor="middle" fontSize="13" fontWeight="700" fill="var(--ink)">보안그룹 = 문지기</text>
          <rect x="262" y="80" width="128" height="34" rx="6" fill="none" stroke="var(--teal)" strokeWidth="1.5" />
          <text x="270" y="101" fontSize="12" fill="var(--ink)">80 · 443 웹</text>
          <text x="382" y="101" textAnchor="end" fontSize="12" fontWeight="700" fill="var(--teal)">열림</text>
          <rect x="262" y="122" width="128" height="34" rx="6" fill="none" stroke="var(--teal)" strokeWidth="1.5" />
          <text x="270" y="143" fontSize="12" fill="var(--ink)">3000 Gitea</text>
          <text x="382" y="143" textAnchor="end" fontSize="12" fontWeight="700" fill="var(--teal)">열림</text>
          <rect x="262" y="164" width="128" height="34" rx="6" fill="none" stroke="var(--rose)" strokeWidth="1.5" />
          <text x="270" y="185" fontSize="12" fill="var(--ink)">5432 DB</text>
          <text x="382" y="185" textAnchor="end" fontSize="12" fontWeight="700" fill="var(--rose)">닫힘</text>
          <rect x="262" y="206" width="128" height="34" rx="6" fill="none" stroke="var(--amber)" strokeWidth="1.5" />
          <text x="270" y="227" fontSize="12" fill="var(--ink)">22 SSH</text>
          <text x="382" y="227" textAnchor="end" fontSize="12" fontWeight="700" fill="var(--amber)">조건부</text>
          <text x="326" y="268" textAnchor="middle" fontSize="11.5" fill="var(--muted)">그 외 전부: 닫힘</text>
          <text x="326" y="286" textAnchor="middle" fontSize="10.5" fill="var(--muted)">규칙에 없으면 차단</text>
          <line x1="400" y1="97" x2="461" y2="97" stroke="var(--teal)" strokeWidth="1.8" />
          <path d="M470 97 l-9 -4.5 l0 9 z" fill="var(--teal)" />
          <line x1="400" y1="139" x2="461" y2="139" stroke="var(--teal)" strokeWidth="1.8" />
          <path d="M470 139 l-9 -4.5 l0 9 z" fill="var(--teal)" />
          <line x1="400" y1="223" x2="461" y2="223" stroke="var(--amber)" strokeWidth="1.8" />
          <path d="M470 223 l-9 -4.5 l0 9 z" fill="var(--amber)" />
          <rect x="458" y="44" width="166" height="260" rx="10" fill="var(--card)" stroke="var(--line)" />
          <text x="541" y="68" textAnchor="middle" fontSize="13" fontWeight="700" fill="var(--ink)">EC2 서버 (서울)</text>
          <rect x="470" y="82" width="142" height="30" rx="6" fill="none" stroke="var(--line)" />
          <text x="541" y="101" textAnchor="middle" fontSize="12" fill="var(--ink)">Caddy · 80/443</text>
          <rect x="470" y="124" width="142" height="30" rx="6" fill="none" stroke="var(--line)" />
          <text x="541" y="143" textAnchor="middle" fontSize="12" fill="var(--ink)">Gitea · 3000</text>
          <rect x="470" y="166" width="142" height="30" rx="6" fill="none" stroke="var(--muted)" strokeDasharray="4 3" />
          <text x="541" y="185" textAnchor="middle" fontSize="12" fill="var(--muted)">PostgreSQL · 5432</text>
          <rect x="470" y="208" width="142" height="30" rx="6" fill="none" stroke="var(--line)" />
          <text x="541" y="227" textAnchor="middle" fontSize="12" fill="var(--ink)">SSH 원격 관리</text>
          <text x="541" y="262" textAnchor="middle" fontSize="10.5" fill="var(--muted)">8080 백엔드도 내부 전용</text>
          <text x="541" y="278" textAnchor="middle" fontSize="10.5" fill="var(--muted)">밖에선 Caddy를 거쳐야 도달</text>
          <circle cx="140" cy="332" r="5" fill="var(--teal)" />
          <text x="152" y="336" fontSize="11" fill="var(--muted)">열림 · 누구나</text>
          <circle cx="280" cy="332" r="5" fill="var(--amber)" />
          <text x="292" y="336" fontSize="11" fill="var(--muted)">조건부 · 지정 IP만</text>
          <circle cx="450" cy="332" r="5" fill="var(--rose)" />
          <text x="462" y="336" fontSize="11" fill="var(--muted)">닫힘 · 묵묵부답</text>
        </svg>
        <p className="muted" style={{ fontSize: 12.5, textAlign: 'center', marginTop: 2 }}>그림: 우리 보안그룹 규칙표 — 필요한 문(80·443·3000)만 열고, SSH는 IP 제한, 나머지는 잠금</p>
        <div className="tip">
          <b>직접 확인해 보기 ②</b> 브라우저 주소창에 우리 도메인
          <span className="icode"> edu.awesomedevapp.com</span> 뒤에
          <span className="icode">:5432</span>를 붙여 접속해 보세요. 한참 기다리다
          실패하죠? 문지기가 <strong>응답 없이 버린 것</strong>입니다(섹션 2의 묵묵부답!).
          반면 그냥 도메인만 치면(443) 바로 열리고요 — 보안그룹 규칙표를 몸으로 확인한 거예요.
        </div>
      </Section>

      <Section n={5} title="최소 권한 원칙" sub="필요한 문만, 필요한 사람에게, 필요한 만큼만">
        <p>
          섹션 4의 규칙표를 관통하는 철학이 하나 있어요. 바로 <strong>최소 권한 원칙</strong>
          (Principle of Least Privilege) — <strong>"일하는 데 꼭 필요한 최소한의 권한만
          준다"</strong>는 보안의 제1원칙입니다.
        </p>
        <Code>{CODE_LEAST_PRIVILEGE}</Code>
        <p>
          핵심은 <strong>"뚫렸을 때 피해를 최소로"</strong>예요. 완벽한 방어는 없다고
          가정하고, 한 문이 뚫려도 도둑이 그 방 하나에서 멈추게 설계하는 거죠.
          모든 문을 열어 두면 편하긴 해요 — 하지만 편한 만큼 한 번의 사고가
          전체 사고가 됩니다. 보안은 늘 <strong>편리함과 안전함 사이의 저울질</strong>이고,
          기본값은 항상 "잠금"이어야 해요.
        </p>
        <div className="warn">
          <b>수습 개발자가 가장 흔하게 하는 실수</b> — "테스트하려고 잠깐 다 열어 뒀어요"를
          그대로 두는 것. 잠깐 연 문은 반드시 <strong>다시 닫는 것까지가 한 세트</strong>입니다.
          공격 봇은 여러분이 문을 여는 순간을 기다려 주지 않고, 항상 두드리고 있으니까요.
        </div>
      </Section>

      <Section n={6} title="공격 유형 상식 — 포트 스캔과 브루트포스" sub="공격자는 '우리'가 아니라 '전부'를 노린다">
        <p>
          "우리같이 작은 회사를 누가 공격해요?"라고 생각하기 쉬운데, 현실은 반대예요.
          공격 대부분은 사람이 아니라 <strong>봇</strong>이 하고, 봇은 인터넷의
          <strong> 모든 서버를 자동으로</strong> 두드립니다. 표적이 아니라
          <strong> 그물</strong>이에요. 대표적인 두 가지만 알아 둡시다.
        </p>
        <Code>{CODE_ATTACKS}</Code>
        <p>
          <strong>포트 스캔</strong>은 도둑이 골목을 돌며 모든 집 문고리를 돌려 보는 것과
          같아요. 그 자체로 뭘 훔치진 않지만, "열린 문 목록"이 다음 공격의 지도가 되죠.
          <strong> 브루트포스</strong>는 그렇게 찾은 로그인 문 앞에서 비밀번호를 기계로
          수만 번 대입하는 공격이고요. 우리가 섹션 4에서 <strong>SSH를 IP 제한</strong>으로
          잠근 이유가 바로 이거예요 — 문고리를 돌려 볼 기회 자체를 주지 않는 겁니다.
        </p>
        <div className="tip">
          <b>우리 플랫폼과 연결하기</b> 로그인 화면에서 비밀번호를 여러 번 틀리면 잠시
          막아 두는 것, 비밀번호에 최소 길이를 요구하는 것 — 전부 브루트포스의 시도
          횟수를 견딜 수 없게 만드는 방어예요. 여러분이 만들 서비스의 로그인에도
          똑같이 넣어야 할 기본기입니다.
        </div>
      </Section>

      <Section n={7} title="서버 보안 체크리스트" sub="새 서버를 인터넷에 올리기 전, 이것만은">
        <p>
          지금까지 배운 걸 <strong>실전 점검표</strong>로 정리했어요. 나중에 여러분이
          직접 EC2 인스턴스를 하나 받아서 서비스를 올리게 되는 날, 이 표를 그대로
          꺼내 쓰면 됩니다.
        </p>
        <Code>{CODE_CHECKLIST}</Code>
        <p>
          1~4번은 이 코스에서 배운 <strong>문단속</strong>이고, 5번부터는 문 안쪽의
          살림살이 — 비밀값 관리, 암호화, 업데이트, 백업, 로그예요. 방화벽은 보안의
          <strong> 첫 번째 층</strong>일 뿐, 층을 여러 겹 쌓아야(이걸 <strong>심층 방어</strong>라고
          해요) 하나가 뚫려도 다음 층이 막아 줍니다.
        </p>
        <div className="tip">
          <b>셀프 체크</b> 체크리스트의 각 항목을 보고 "이게 왜 필요하지?"를
          <strong> 말로 설명</strong>해 보세요. 1~4번은 이 코스의 섹션 번호와 연결되니,
          막히면 해당 섹션으로 돌아가 복습! 5~9번 중 낯선 항목은 멘토에게 물어보면
          우리 서버의 실제 설정을 보여 줄 거예요.
        </div>
      </Section>

      <div className="step-card">
        <div className="step-body">
          <h3 style={{ marginBottom: 6 }}>🛡️ 여기까지 왔다면</h3>
          <p className="muted">
            이제 방화벽이 무엇을 검사하는지, 포트를 여닫는다는 게 무슨 뜻인지, 그리고
            우리 AWS 서버가 <strong>왜 필요한 포트만 열고 SSH를 IP로 잠갔는지</strong>까지
            설명할 수 있게 됐어요. 핵심은 한 문장 — <strong>"필요한 문만 열고, 기본은
            잠금."</strong> 아직 <Link to="/learn/network"><strong>네트워크의 이해</strong></Link> 코스를
            안 봤다면 IP·포트 개념을 먼저 다지고 오세요. 다음으로는 이 문단속 위에서
            실제 서비스가 어떻게 배포되는지, 배포 관련 코스와 과제에서 이어집니다.
          </p>
        </div>
      </div>
    </div>
  );
}
