| <!DOCTYPE html> |
| <html lang="ko" data-theme="dark"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title id="html-title">CertBridge 보μ κ°ν λ° μμ΄κ° μ€μ κ°μ΄λ</title> |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Outfit:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
| <style> |
| |
| |
| |
| |
| |
| * { |
| margin: 0; |
| padding: 0; |
| box-sizing: border-box; |
| transition: background-color 0.25s ease, border-color 0.25s ease, color 0.25s ease; |
| } |
| |
| :root { |
| |
| --bg-primary: #ffffff; |
| --bg-secondary: #f8fafc; |
| --bg-card: #ffffff; |
| --bg-glass: rgba(0, 0, 0, 0.015); |
| --bg-hover: rgba(0, 0, 0, 0.025); |
| |
| --text-primary: #1e293b; |
| --text-secondary: #475569; |
| --text-muted: #94a3b8; |
| |
| --accent-blue: #2563eb; |
| --accent-blue-hover: #1d4ed8; |
| --accent-blue-subtle: rgba(37, 99, 235, 0.08); |
| --accent-emerald: #059669; |
| --accent-amber: #d97706; |
| --accent-red: #dc2626; |
| --accent-purple: #7c3aed; |
| |
| --border-subtle: rgba(0, 0, 0, 0.08); |
| --border-active: rgba(37, 99, 235, 0.4); |
| |
| --shadow-card: 0 1px 3px rgba(0, 0, 0, 0.04), 0 1px 2px rgba(0, 0, 0, 0.03); |
| --shadow-card-hover: 0 2px 8px rgba(0, 0, 0, 0.06); |
| --shadow-glow: none; |
| |
| --grad: linear-gradient(135deg, #2563eb 0%, #0284c7 100%); |
| --grad-hero: linear-gradient(135deg, rgba(37, 99, 235, 0.05) 0%, rgba(2, 132, 199, 0.05) 100%); |
| --hero-text: #1e293b; |
| --hero-desc: #475569; |
| } |
| |
| [data-theme='dark'] { |
| |
| --bg-primary: #0f1117; |
| --bg-secondary: #1a1d27; |
| --bg-card: #1e2130; |
| --bg-glass: rgba(255, 255, 255, 0.04); |
| --bg-hover: rgba(255, 255, 255, 0.05); |
| |
| --text-primary: #e8eaed; |
| --text-secondary: #9aa0b0; |
| --text-muted: #5f6577; |
| |
| --accent-blue: #3b82f6; |
| --accent-blue-hover: #2563eb; |
| --accent-blue-subtle: rgba(59, 130, 246, 0.12); |
| --accent-emerald: #10b981; |
| --accent-amber: #f59e0b; |
| --accent-red: #ef4444; |
| --accent-purple: #8b5cf6; |
| |
| --border-subtle: rgba(255, 255, 255, 0.07); |
| --border-active: rgba(59, 130, 246, 0.5); |
| |
| --shadow-card: 0 1px 4px rgba(0, 0, 0, 0.2); |
| --shadow-card-hover: 0 4px 16px rgba(0, 0, 0, 0.3); |
| --shadow-glow: 0 0 20px rgba(59, 130, 246, 0.1); |
| |
| --grad: linear-gradient(135deg, #3b82f6 0%, #0ea5e9 100%); |
| --grad-hero: linear-gradient(135deg, rgba(59, 130, 246, 0.08) 0%, rgba(14, 165, 233, 0.08) 100%); |
| --hero-text: #e8eaed; |
| --hero-desc: #9aa0b0; |
| } |
| |
| body { |
| font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; |
| background-color: var(--bg-primary); |
| color: var(--text-primary); |
| line-height: 1.6; |
| display: flex; |
| min-height: 100vh; |
| } |
| |
| ::selection { |
| background: var(--accent-blue-subtle); |
| color: var(--accent-blue); |
| } |
| |
| a { |
| color: var(--accent-blue); |
| text-decoration: none; |
| font-weight: 500; |
| } |
| a:hover { |
| text-decoration: underline; |
| } |
| |
| |
| .sidebar { |
| position: fixed; |
| top: 0; |
| left: 0; |
| width: 280px; |
| height: 100vh; |
| background: var(--bg-secondary); |
| border-right: 1px solid var(--border-subtle); |
| padding: 24px 0; |
| overflow-y: auto; |
| z-index: 100; |
| } |
| |
| .sidebar-logo { |
| padding: 0 24px 20px; |
| border-bottom: 1px solid var(--border-subtle); |
| margin-bottom: 16px; |
| } |
| |
| .sidebar-logo h1 { |
| font-size: 16px; |
| font-weight: 700; |
| color: var(--accent-blue); |
| letter-spacing: -0.3px; |
| font-family: 'Outfit', sans-serif; |
| } |
| |
| .sidebar-logo p { |
| font-size: 11px; |
| color: var(--text-muted); |
| margin-top: 4px; |
| } |
| |
| .sidebar nav a { |
| display: flex; |
| align-items: center; |
| padding: 10px 24px; |
| font-size: 13px; |
| color: var(--text-secondary); |
| transition: all 0.2s; |
| border-left: 3px solid transparent; |
| text-decoration: none; |
| } |
| |
| .sidebar nav a:hover, .sidebar nav a.active { |
| color: var(--accent-blue); |
| background: var(--accent-blue-subtle); |
| border-left-color: var(--accent-blue); |
| } |
| |
| .sidebar nav a .num { |
| display: inline-flex; |
| align-items: center; |
| justify-content: center; |
| width: 20px; |
| height: 20px; |
| border-radius: 4px; |
| background: var(--bg-glass); |
| font-size: 10px; |
| font-weight: 600; |
| margin-right: 10px; |
| color: var(--text-muted); |
| } |
| |
| .sidebar nav a.active .num { |
| background: var(--accent-blue); |
| color: white; |
| } |
| |
| |
| .main { |
| margin-left: 280px; |
| flex: 1; |
| padding: 0; |
| overflow-y: auto; |
| height: 100vh; |
| } |
| |
| |
| body.in-iframe .sidebar { |
| display: none !important; |
| } |
| |
| body.in-iframe .main { |
| margin-left: 0 !important; |
| width: 100% !important; |
| height: 100% !important; |
| } |
| |
| body.in-iframe .content { |
| padding: 32px 24px 60px; |
| max-width: 100%; |
| } |
| |
| |
| .hero { |
| background: var(--grad-hero); |
| padding: 48px 40px; |
| border-bottom: 1px solid var(--border-subtle); |
| position: relative; |
| overflow: hidden; |
| } |
| |
| .hero h1 { |
| font-size: 26px; |
| font-weight: 700; |
| color: var(--hero-text); |
| font-family: 'Outfit', sans-serif; |
| letter-spacing: -0.5px; |
| } |
| |
| .hero p { |
| font-size: 13.5px; |
| color: var(--hero-desc); |
| margin-top: 8px; |
| max-width: 800px; |
| } |
| |
| .hero .badge { |
| display: inline-block; |
| padding: 4px 12px; |
| background: var(--bg-glass); |
| border: 1px solid var(--border-subtle); |
| border-radius: 20px; |
| font-size: 11px; |
| font-weight: 600; |
| color: var(--text-secondary); |
| margin-top: 12px; |
| } |
| |
| |
| .content { |
| padding: 48px; |
| max-width: 1000px; |
| margin: 0 auto; |
| } |
| |
| .section { |
| margin-bottom: 48px; |
| scroll-margin-top: 40px; |
| } |
| |
| h2 { |
| font-size: 18px; |
| font-weight: 700; |
| margin-bottom: 16px; |
| position: relative; |
| padding-left: 12px; |
| color: var(--text-primary); |
| font-family: 'Outfit', sans-serif; |
| } |
| |
| h2::before { |
| content: ''; |
| position: absolute; |
| left: 0; |
| top: 3px; |
| bottom: 3px; |
| width: 3px; |
| border-radius: 1.5px; |
| background: var(--accent-blue); |
| } |
| |
| h3 { |
| font-size: 14px; |
| font-weight: 600; |
| margin: 24px 0 12px; |
| color: var(--accent-blue); |
| } |
| |
| h4 { |
| font-size: 13px; |
| font-weight: 600; |
| margin: 16px 0 8px; |
| color: var(--text-primary); |
| } |
| |
| p, li { |
| margin-bottom: 8px; |
| color: var(--text-secondary); |
| font-size: 13px; |
| line-height: 1.6; |
| } |
| |
| ul, ol { |
| margin: 0 0 16px 20px; |
| } |
| |
| |
| .card { |
| background: var(--bg-secondary); |
| border: 1px solid var(--border-subtle); |
| border-radius: 12px; |
| padding: 20px; |
| margin: 16px 0; |
| box-shadow: var(--shadow-card); |
| } |
| |
| .card.tip, .card.info { |
| border-color: rgba(37, 99, 235, 0.2); |
| background: var(--accent-blue-subtle); |
| } |
| |
| .card.tip h4, .card.info h4 { |
| color: var(--accent-blue); |
| margin-top: 0; |
| } |
| |
| .card.warn { |
| border-color: rgba(220, 38, 38, 0.2); |
| background: rgba(220, 38, 38, 0.03); |
| } |
| |
| .card.warn h4 { |
| color: var(--accent-red); |
| margin-top: 0; |
| } |
| |
| |
| table { |
| width: 100%; |
| border-collapse: collapse; |
| margin: 16px 0; |
| border-radius: 8px; |
| overflow: hidden; |
| border: 1px solid var(--border-subtle); |
| } |
| |
| th { |
| background: var(--bg-glass); |
| padding: 10px 14px; |
| text-align: left; |
| font-size: 12px; |
| font-weight: 600; |
| color: var(--text-primary); |
| border-bottom: 1px solid var(--border-subtle); |
| } |
| |
| td { |
| padding: 10px 14px; |
| font-size: 12px; |
| border-top: 1px solid var(--border-subtle); |
| color: var(--text-secondary); |
| background: var(--bg-secondary); |
| } |
| |
| tr:hover td { |
| background: var(--bg-hover); |
| } |
| |
| .req-grid { |
| display: grid; |
| grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); |
| gap: 16px; |
| margin: 16px 0; |
| } |
| |
| .req-item { |
| background: var(--bg-secondary); |
| border: 1px solid var(--border-subtle); |
| border-radius: 12px; |
| padding: 16px; |
| text-align: center; |
| box-shadow: var(--shadow-card); |
| } |
| |
| .req-item .icon { |
| font-size: 24px; |
| margin-bottom: 6px; |
| } |
| |
| .req-item .label { |
| font-size: 10px; |
| color: var(--text-muted); |
| text-transform: uppercase; |
| letter-spacing: 0.5px; |
| } |
| |
| .req-item .value { |
| font-size: 13.5px; |
| font-weight: 600; |
| color: var(--text-primary); |
| margin-top: 4px; |
| } |
| |
| .step { |
| display: flex; |
| gap: 16px; |
| margin: 16px 0; |
| padding: 16px; |
| background: var(--bg-secondary); |
| border: 1px solid var(--border-subtle); |
| border-radius: 12px; |
| box-shadow: var(--shadow-card); |
| } |
| |
| .step-num { |
| flex-shrink: 0; |
| width: 28px; |
| height: 28px; |
| border-radius: 6px; |
| background: var(--grad); |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| font-weight: 700; |
| font-size: 12px; |
| color: #fff; |
| } |
| |
| .step-body h4 { |
| margin: 0 0 4px; |
| color: var(--text-primary); |
| } |
| |
| .step-body p { |
| margin: 0; |
| color: var(--text-secondary); |
| font-size: 12.5px; |
| } |
| |
| .port-badge { |
| display: inline-block; |
| padding: 2px 6px; |
| border-radius: 4px; |
| background: var(--accent-blue-subtle); |
| color: var(--accent-blue); |
| font-family: 'JetBrains Mono', monospace; |
| font-size: 11px; |
| font-weight: 600; |
| } |
| |
| .top-toolbar { |
| position: absolute; |
| top: 20px; |
| right: 20px; |
| z-index: 10; |
| display: flex; |
| gap: 8px; |
| } |
| |
| .lang-selector { |
| background: var(--bg-secondary); |
| border: 1px solid var(--border-subtle); |
| padding: 4px 10px; |
| border-radius: 6px; |
| color: var(--text-secondary); |
| font-size: 11.5px; |
| outline: none; |
| cursor: pointer; |
| box-shadow: var(--shadow-card); |
| } |
| |
| .lang-selector:hover { |
| border-color: var(--accent-blue); |
| color: var(--text-primary); |
| } |
| |
| |
| code { |
| font-family: 'JetBrains Mono', 'SF Mono', monospace; |
| font-size: 11px; |
| background: var(--accent-blue-subtle); |
| padding: 2px 5px; |
| border-radius: 4px; |
| color: var(--accent-blue); |
| } |
| |
| .code-wrap { |
| position: relative; |
| margin: 16px 0; |
| border-radius: 8px; |
| overflow: hidden; |
| border: 1px solid var(--border-subtle); |
| box-shadow: var(--shadow-card); |
| } |
| |
| .code-header { |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| padding: 6px 14px; |
| background: var(--bg-hover); |
| border-bottom: 1px solid var(--border-subtle); |
| } |
| |
| .code-header span { |
| font-size: 11px; |
| color: var(--text-muted); |
| font-family: 'JetBrains Mono', monospace; |
| } |
| |
| .copy-btn { |
| padding: 2px 8px; |
| border: 1px solid var(--border-subtle); |
| border-radius: 4px; |
| background: var(--bg-secondary); |
| color: var(--text-secondary); |
| cursor: pointer; |
| font-size: 10px; |
| transition: all 0.15s; |
| } |
| |
| .copy-btn:hover { |
| background: var(--accent-blue); |
| color: white; |
| border-color: var(--accent-blue); |
| } |
| |
| pre { |
| background: var(--bg-card); |
| padding: 12px 16px; |
| overflow-x: auto; |
| font-family: 'JetBrains Mono', monospace; |
| font-size: 11.5px; |
| line-height: 1.5; |
| color: var(--text-secondary); |
| } |
| |
| pre .comment { color: var(--text-muted); font-style: italic; } |
| pre .keyword { color: var(--accent-purple); font-weight: 600; } |
| pre .string { color: var(--accent-emerald); } |
| pre .cmd { color: var(--accent-blue); } |
| |
| @media (max-width: 900px) { |
| .sidebar { display: none; } |
| .main { margin-left: 0; } |
| .content { padding: 24px; } |
| .hero { padding: 32px 24px; } |
| .hero h1 { font-size: 22px; } |
| } |
| </style> |
| </head> |
| <body> |
|
|
| <aside class="sidebar"> |
| <div class="sidebar-logo"> |
| <h1>π‘οΈ CertBridge Security</h1> |
| <p data-i18n="sidebar.ver">보μ λ° μμ΄κ° κ°μ΄λ v0.1.0</p> |
| </div> |
| <nav id="nav"> |
| <a href="#overview" class="active"><span class="num">1</span><span data-i18n="nav.overview">μν€ν
μ² κ°μ</span></a> |
| <a href="#airgap-prep"><span class="num">2</span><span data-i18n="nav.prep">μμ΄κ° ν¨ν€μ§ μ€λΉ</span></a> |
| <a href="#server-install"><span class="num">3</span><span data-i18n="nav.install">μ€νλΌμΈ μ€μΉ νλ‘ν μ½</span></a> |
| <a href="#npm-setup"><span class="num">4</span><span data-i18n="nav.npm">Nginx Proxy Manager</span></a> |
| <a href="#authentik-setup"><span class="num">5</span><span data-i18n="nav.authentik">Authentik SSO μ°λ</span></a> |
| <a href="#troubleshooting"><span class="num">6</span><span data-i18n="nav.trouble">λ¬Έμ ν΄κ²°</span></a> |
| </nav> |
| </aside> |
|
|
| <div class="main"> |
| <div class="top-toolbar"> |
| <select id="lang-select" class="lang-selector" onchange="changeLanguage(this.value)"> |
| <option value="ko">νκ΅μ΄</option> |
| <option value="en">English</option> |
| </select> |
| </div> |
|
|
| <div class="hero"> |
| <h1 data-i18n="hero.title">CertBridge 보μ κ°ν λ° μμ΄κ° κ°μ΄λ</h1> |
| <p data-i18n="hero.desc">μ€νλΌμΈ(Air-Gap) μ°λΆν¬ νκ²½μμ Nginx Proxy Managerμ Authentik SSOλ₯Ό νμ©νμ¬ SSL 보μ ν°λ―Έλ€μ΄μ
λ° κ³μ ν΅ν© μΈμ¦ μμ€ν
μ μμ νκ² κ΅¬μ±νλ κ³ κΈ κ°μ΄λμ
λλ€.</p> |
| <span class="badge" data-i18n="hero.badge">π v0.1.0 Β· Ubuntu amd64 Β· NPM & Authentik</span> |
| </div> |
|
|
| <div class="content"> |
|
|
| |
| <section class="section" id="overview"> |
| <h2 data-i18n="overview.title">1. 보μ κ°ν μν€ν
μ² κ°μ</h2> |
| <p data-i18n="overview.desc1">κΈ°μ‘΄ μ€μ μλ² μΈνλΌλ ν¬νΈ λ
ΈμΆ λ° λ¬΄μΈμ¦ ν΅μ μΌλ‘ μΈν΄ 보μ μνμ μ·¨μ½ν μ μμ΅λλ€. λ³Έ κ°μ μ€νμμλ <b>Nginx Proxy Manager (NPM)</b>μ <b>Authentik SSO</b>λ₯Ό μ€μ μΈνλΌμ ν΅ν©νμ¬ μ λ°© 보μ μ₯λ²½μ ꡬμΆν©λλ€.</p> |
| |
| <div class="card info"> |
| <h4 data-i18n="overview.strategy_title">π‘ μμ μ΅μ ν μ€κ³ μ λ΅ (Postgres/Redis 곡μ )</h4> |
| <p data-i18n="overview.strategy_desc">μλ‘ μΆκ°λλ SSO(Authentik) μλΉμ€μ μ€λ²ν€λλ₯Ό μ μ΄νκΈ° μν΄, κΈ°μ‘΄ PostgreSQL λ° Redis 컨ν
μ΄λ μμμ κ³ μ€λν μ¬νμ©ν©λλ€.</p> |
| <ul> |
| <li><b>PostgreSQL 16:</b> <code>authentik</code> μ΄λΌλ λ°μ΄ν°λ² μ΄μ€λ₯Ό κΈ°μ‘΄ PostgreSQL μΈνλΌ λ΄μ μμ±νμ¬ μμμ 곡μ ν©λλ€.</li> |
| <li><b>Redis 7:</b> DB Index <code>3</code>μ λ
립 ν λΉνμ¬ κΈ°μ‘΄ CertBridgeμ© μΈλ±μ€ <code>2</code> λ° n8n λ±κ³Όμ μΆ©λμ λ°©μ§ν©λλ€.</li> |
| </ul> |
| </div> |
|
|
| <h3 data-i18n="overview.net_title">π μλΉμ€ ν¬νΈ λ§΅ & λ€νΈμν¬ ν ν΄λ‘μ§</h3> |
| <table> |
| <thead> |
| <tr> |
| <th>컨ν
μ΄λλͺ
</th> |
| <th>κΈ°λ³Έ ν¬νΈ</th> |
| <th>μν </th> |
| <th>보μ μν</th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr> |
| <td><code>certbridge-npm</code></td> |
| <td><span class="port-badge">80</span>, <span class="port-badge">443</span>, <span class="port-badge">81</span></td> |
| <td>Edge Reverse Proxy & SSL (Web UI)</td> |
| <td>μΈλΆ λ
ΈμΆ (μ μΌν μ§μ
μ )</td> |
| </tr> |
| <tr> |
| <td><code>certbridge-authentik-server</code></td> |
| <td><span class="port-badge">9000</span>, <span class="port-badge">9443</span></td> |
| <td>Authentik Core Portal & API</td> |
| <td>λ΄λΆ λΈλ¦Ώμ§λ§ λλ NPM μ°λ</td> |
| </tr> |
| <tr> |
| <td><code>certbridge-api</code></td> |
| <td><span class="port-badge">8090</span></td> |
| <td>CertBridge REST API Server</td> |
| <td>λ΄λΆλ§ μ μ© (NPMμ ν΅ν΄μλ§ HTTPS μΈλΆ λ
ΈμΆ)</td> |
| </tr> |
| <tr> |
| <td><code>certbridge-minio</code></td> |
| <td><span class="port-badge">9010</span>, <span class="port-badge">9011</span></td> |
| <td>Object Storage & Management Console</td> |
| <td>NPM Proxy Providerλ‘ SSO μ°λ ν΅μ </td> |
| </tr> |
| <tr> |
| <td><code>certbridge-neo4j</code></td> |
| <td><span class="port-badge">7474</span>, <span class="port-badge">7687</span></td> |
| <td>Neo4j Graph Database & Browser UI</td> |
| <td>NPM Proxy Providerλ‘ SSO μ°λ ν΅μ </td> |
| </tr> |
| </tbody> |
| </table> |
| </section> |
|
|
| |
| <section class="section" id="airgap-prep"> |
| <h2 data-i18n="prep.title">2. μμ΄κ°(μ€νλΌμΈ) ν¨ν€μ§ μ€λΉ</h2> |
| <p data-i18n="prep.desc">μΈν°λ· μ°κ²°μ΄ μ ν λΆκ°λ₯ν <b>μμ΄κ°(Air-Gap) νμλ§ μ°λΆν¬ μλ²</b>μμ μ€μΉνκΈ° μν΄μλ, μΈν°λ·μ΄ μ°κ²°λ μ¬μ λ¨Έμ μμ νμν λͺ¨λ Docker μ΄λ―Έμ§μ ν¨ν€μ§λ₯Ό λ€μ΄λ‘λν΄μΌ ν©λλ€.</p> |
| |
| <h3 data-i18n="prep.step1">2-1. Hugging Faceμμ μμ΄κ° νμΌ λ€μ΄λ‘λ</h3> |
| <p data-i18n="prep.desc2">μ°λ¦¬λ Hugging Face μ μ₯μ (<code>VanyaJ/certbridge</code>)μ 미리 λΉλλ <b>linux/amd64</b> νλ«νΌ μ μ© Docker μ΄λ―Έμ§ μμΉ΄μ΄λΈμ μλ² ν¨ν€μ§λ₯Ό λ°°ν¬ν΄ λμμ΅λλ€.</p> |
| |
| <div class="code-wrap"> |
| <div class="code-header"><span>Download Assets</span><button class="copy-btn" onclick="copyCode(this)">볡μ¬</button></div> |
| <pre><span class="comment"># 1. CertBridge ν΅μ¬ μλ² μμΉ΄μ΄λΈ λ€μ΄λ‘λ</span> |
| <span class="cmd">wget</span> https://huggingface.co/VanyaJ/certbridge/resolve/main/certbridge-server-v0.1.0-ubuntu.tar.gz |
|
|
| <span class="comment"># 2. μμ΄κ° Docker μ΄λ―Έμ§ λ€μ΄λ‘λ (docker-images/ λλ ν 리μ λ°°μΉν κ²)</span> |
| <span class="cmd">mkdir</span> -p docker-images |
| <span class="cmd">wget</span> -P docker-images/ https://huggingface.co/VanyaJ/certbridge/resolve/main/docker-images/pgvector_pgvector_pg16.tar.gz |
| <span class="cmd">wget</span> -P docker-images/ https://huggingface.co/VanyaJ/certbridge/resolve/main/docker-images/redis_7-alpine.tar.gz |
| <span class="cmd">wget</span> -P docker-images/ https://huggingface.co/VanyaJ/certbridge/resolve/main/docker-images/minio_minio_latest.tar.gz |
| <span class="cmd">wget</span> -P docker-images/ https://huggingface.co/VanyaJ/certbridge/resolve/main/docker-images/neo4j_5-community.tar.gz |
| <span class="cmd">wget</span> -P docker-images/ https://huggingface.co/VanyaJ/certbridge/resolve/main/docker-images/node_20-alpine.tar.gz |
| <span class="cmd">wget</span> -P docker-images/ https://huggingface.co/VanyaJ/certbridge/resolve/main/docker-images/jc21_nginx-proxy-manager_latest.tar.gz |
| <span class="cmd">wget</span> -P docker-images/ https://huggingface.co/VanyaJ/certbridge/resolve/main/docker-images/ghcr.io_goauthentik_server_2024.4.2.tar.gz</pre> |
| </div> |
|
|
| <h3 data-i18n="prep.step2">2-2. (λμ) prepare-airgap-package.sh νμ©</h3> |
| <p data-i18n="prep.desc3">μΈν°λ·μ΄ μ°κ²°λ μΈλΆ μ°λΆν¬ PCμμ μ§μ μ΄ μ€ν¬λ¦½νΈλ₯Ό μννλ©΄, Docker μ΄λ―Έμ§ λ° Node/Docker deb μ€μΉ ν¨ν€μ§κΉμ§ νλμ λ²λ€λ‘ λ¬Άμ΄ μμΆν΄ μ€λλ€.</p> |
| <div class="code-wrap"> |
| <div class="code-header"><span>bash</span><button class="copy-btn" onclick="copyCode(this)">볡μ¬</button></div> |
| <pre><span class="comment"># μ€ν¬λ¦½νΈ μ€ννμ¬ νλμ λμ©λ ν¨ν€μ§λ‘ μμΉ΄μ΄λΉ</span> |
| <span class="cmd">bash</span> prepare-airgap-package.sh</pre> |
| </div> |
| </section> |
|
|
| |
| <section class="section" id="server-install"> |
| <h2 data-i18n="install.title">3. μ€νλΌμΈ μ€μΉ νλ‘ν μ½ (Ubuntu)</h2> |
| <p data-i18n="install.desc">λ€μ΄λ‘λν ν¨ν€μ§μ <code>docker-images</code> ν΄λλ₯Ό μ΄λμ μ μ₯μ₯μΉ(USB/μΈμ₯νλ λ±) λλ λ΄λΆλ§ νμΌ κ³΅μ λ₯Ό ν΅ν΄ μμ΄κ° λμ μ°λΆν¬ μλ²λ‘ 볡μ¬ν©λλ€.</p> |
|
|
| <div class="step"> |
| <div class="step-num">1</div> |
| <div class="step-body"> |
| <h4 data-i18n="install.step1_title">ν¨ν€μ§ μμΆ ν΄μ λ° μ΄λ―Έμ§ λλ ν 리 λ³ν©</h4> |
| <p data-i18n="install.step1_desc">μλ² ν¨ν€μ§λ₯Ό ν΄μ ν κ²½λ‘ μλλ‘, κ°λ³ λ€μ΄λ‘λν <code>docker-images</code> λλ ν 리λ₯Ό λ³ν©ν©λλ€.</p> |
| <div class="code-wrap"> |
| <pre><span class="cmd">tar</span> -xzf certbridge-server-v0.1.0-ubuntu.tar.gz |
| <span class="cmd">cd</span> certbridge-server-ubuntu |
| <span class="comment"># λ€μ΄λ‘λν docker-images ν΄λλ₯Ό μ΄κ³³μΌλ‘ 볡μ¬</span> |
| <span class="cmd">mv</span> /path/to/downloaded/docker-images ./</pre> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="step"> |
| <div class="step-num">2</div> |
| <div class="step-body"> |
| <h4 data-i18n="install.step2_title">μλ μ
μ
μ€ν¬λ¦½νΈ μ€ν</h4> |
| <p data-i18n="install.step2_desc">μ€νλΌμΈ μ€μΉμ© μ
μ
μ€ν¬λ¦½νΈ <code>setup-airgap.sh</code>μ μ€ν κΆνμ λΆμ¬νκ³ κ°λν©λλ€. μ΄ μ€ν¬λ¦½νΈλ Docker load λͺ
λ Ήμ ν΅ν΄ μ€νλΌμΈ tar λ° tar.gz μ΄λ―Έμ§λ₯Ό μ 체 볡ꡬνκ³ μΈνλΌ μλΉμ€λ₯Ό κΈ°λν©λλ€.</p> |
| <div class="code-wrap"> |
| <pre><span class="cmd">chmod</span> +x setup-airgap.sh |
| <span class="cmd">sudo</span> ./setup-airgap.sh</pre> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="step"> |
| <div class="step-num">3</div> |
| <div class="step-body"> |
| <h4 data-i18n="install.step3_title">λ
립 λͺ¨λ(--profile standalone) μλΉμ€ κΈ°λ μν μ‘°ν</h4> |
| <p data-i18n="install.step3_desc">NPM λ° Authentik μλΉμ€λ₯Ό ν¬ν¨νμ¬ μ μ ꡬλ μ€μΈμ§ μλ λͺ
λ ΉμΌλ‘ νμΈν©λλ€.</p> |
| <div class="code-wrap"> |
| <pre><span class="cmd">sudo docker compose</span> --profile standalone ps</pre> |
| </div> |
| </div> |
| </div> |
| </section> |
|
|
| |
| <section class="section" id="npm-setup"> |
| <h2 data-i18n="npm.title">4. Nginx Proxy Manager μ€μ κ°μ΄λ</h2> |
| <p data-i18n="npm.desc">Nginx Proxy Manager(NPM)λ CertBridge μ€νμ μ΅μ λ°©μμ λͺ¨λ μΈλΆ μ μ
μμ²μ μ²λ¦¬νκ³ , SSL μνΈν ν΅μ μ μ’
λ¨νλ©°, λλ©μΈ/κ²½λ‘ λΆκΈ°λ₯Ό μνν©λλ€.</p> |
| |
| <div class="step"> |
| <div class="step-num">1</div> |
| <div class="step-body"> |
| <h4 data-i18n="npm.step1_title">κ΄λ¦¬μ νλ©΄(Admin UI) λ‘κ·ΈμΈ</h4> |
| <p data-i18n="npm.step1_desc">μΉ λΈλΌμ°μ λ‘ <code>http://<SERVER_IP>:81</code> μ μ μν©λλ€. μ΅μ΄ κΈ°λ³Έ λ‘κ·ΈμΈ κ³μ μ λ€μκ³Ό κ°μ΅λλ€.</p> |
| <ul> |
| <li><b>Email Address:</b> <code>admin@example.com</code></li> |
| <li><b>Password:</b> <code>changeme</code></li> |
| </ul> |
| <p class="comment">β» λ‘κ·ΈμΈ μ§ν μ¦μ κ΄λ¦¬μ μ΄λ©μΌκ³Ό λΉλ°λ²νΈλ₯Ό λ³κ²½ν΄μΌ ν©λλ€.</p> |
| </div> |
| </div> |
|
|
| <div class="step"> |
| <div class="step-num">2</div> |
| <div class="step-body"> |
| <h4 data-i18n="npm.step2_title">μ¬μ€ SSL/TLS μΈμ¦μ μΆκ° (μμ΄κ° νκ²½)</h4> |
| <p data-i18n="npm.step2_desc">μΈνλΌλ§μ΄ μμ΄κ° μ€νλΌμΈμ΄λ―λ‘ Let's Encrypt μλ λ°κΈμ μ¬μ©ν μ μμ΅λλ€. κΈ°κ΄ λ΄λΆ CA λλ μ¬μ€λ‘ λ°κΈλ°μ μΈμ¦μ νμΌμ μλ λ±λ‘ν΄μΌ ν©λλ€.</p> |
| <ol> |
| <li>NPM μλ¨ λ©λ΄μ <b>"SSL Certificates"</b> ν ν΄λ¦</li> |
| <li>μ°μΈ‘ μλ¨ <b>"Add SSL Certificate"</b> > <b>"Custom"</b> μ ν</li> |
| <li>μΈμ¦μ μ΄λ¦ μ
λ ₯ ν, <b>Certificate Key File</b> (.key) λ° <b>Certificate File</b> (.crt/pem) μ
λ‘λ ν μ μ₯</li> |
| </ol> |
| </div> |
| </div> |
|
|
| <div class="step"> |
| <div class="step-num">3</div> |
| <div class="step-body"> |
| <h4 data-i18n="npm.step3_title">Proxy Host μΆκ° (CertBridge API λ±)</h4> |
| <p data-i18n="npm.step3_desc">NPM νλ‘μ νΈμ€νΈλ₯Ό μμ±νμ¬ μΈλΆ HTTPS μμ²μ λ΄λΆ 컨ν
μ΄λλ‘ μ λ¬ν©λλ€.</p> |
| <ul> |
| <li><b>Domain Names:</b> <code>certbridge.local</code> (λλ κΈ°κ΄μ© λλ©μΈ)</li> |
| <li><b>Scheme:</b> <code>http</code></li> |
| <li><b>Forward Hostname / IP:</b> <code>certbridge-api</code> (Docker λΈλ¦Ώμ§ 컨ν
μ΄λ νΈμ€νΈλͺ
)</li> |
| <li><b>Forward Port:</b> <code>8090</code></li> |
| <li><b>Block Common Exploits:</b> νμ±ν</li> |
| <li><b>SSL ν:</b> μμμ λ±λ‘ν Custom SSL μ ν λ° <b>Force SSL</b> νμ±ν</li> |
| </ul> |
| </div> |
| </div> |
| </section> |
|
|
| |
| <section class="section" id="authentik-setup"> |
| <h2 data-i18n="authentik.title">5. Authentik SSO / MFA μ€μ νλ‘ν μ½</h2> |
| <p data-i18n="authentik.desc">Authentikμ μ±κΈ μ¬μΈμ¨(SSO)κ³Ό λ©ν°ν©ν° μΈμ¦(MFA)μ μΌκ΄λκ² μ 곡νμ¬, API, MinIO Console, Neo4j Graph DBλ‘μ λ¬΄λ¨ μΉ¨μ
μ ν΅μ ν©λλ€.</p> |
| |
| <div class="step"> |
| <div class="step-num">1</div> |
| <div class="step-body"> |
| <h4 data-i18n="authentik.step1_title">μ΅μ΄ μΈμ€ν΄μ€ μ
μ
λ° μ΄κΈ°ν</h4> |
| <p data-i18n="authentik.step1_desc">μΉ λΈλΌμ°μ λ‘ <code>http://<SERVER_IP>:9000/if/flow/initial-setup/</code> μ μ μν©λλ€. μ΅μ΄ λΆνΈμ€νΈλ© νλ©΄μμ κ΄λ¦¬μ κ³μ <code>akadmin</code>μ λν μμ ν λΉλ°λ²νΈλ₯Ό μ€μ νμ¬ μ΄κΈ°νλ₯Ό μλ£ν©λλ€.</p> |
| </div> |
| </div> |
|
|
| <div class="step"> |
| <div class="step-num">2</div> |
| <div class="step-body"> |
| <h4 data-i18n="authentik.step2_title">Proxy Provider μμ± (NPM Forward Auth μ°λμ©)</h4> |
| <p data-i18n="authentik.step2_desc">MinIOμ Neo4j λ± κ°λ³ 보μμ΄ νμν λ κ±°μ νλ©΄ 보νΈλ₯Ό μν΄ Proxy Providerλ₯Ό μμ±ν©λλ€.</p> |
| <ol> |
| <li>Authentik Admin Interface μ§μ
(μ°μΈ‘ μλ¨ <b>"Admin interface"</b>)</li> |
| <li>μ’μΈ‘ λ©λ΄ <b>Applications</b> > <b>Providers</b> > <b>"Create"</b> ν΄λ¦</li> |
| <li><b>Proxy Provider</b> μ ν ν μ€μ : |
| <ul> |
| <li><b>Name:</b> <code>infra-proxy-provider</code></li> |
| <li><b>Authorization flow:</b> κΈ°λ³Έ flow μ ν (e.g. default-authorization-flow)</li> |
| <li><b>External host:</b> <code>http://minio.certbridge.local</code> (μΈλΆ λ
ΈμΆλ URL)</li> |
| <li><b>Mode:</b> <code>Forward auth (single application)</code> μ ν</li> |
| </ul> |
| </li> |
| </ol> |
| </div> |
| </div> |
|
|
| <div class="step"> |
| <div class="step-num">3</div> |
| <div class="step-body"> |
| <h4 data-i18n="authentik.step3_title">NPM Custom Nginx Configuration μ μ©</h4> |
| <p data-i18n="authentik.step3_desc">Nginx Proxy Managerμμ ν΄λΉ Proxy Hostλ‘ λ€μ΄μ€λ νΈλν½μ΄ Authentikμ κ±°μ³ μΈμ¦λλλ‘ Custom Nginx μ€μ λΈλ‘μ λ£μ΄μ€λλ€.</p> |
| <div class="code-wrap"> |
| <div class="code-header"><span>NPM Advanced Config (Custom Nginx Configuration)</span></div> |
| <pre># Authentik Forward Auth Integration |
| location / { |
| # Authentik Server IP / Container Port |
| auth_request /outpost.goauthentik.io/auth/request; |
| error_page 401 = @goauthentik_login; |
|
|
| # Pass the actual request back to Authentik |
| proxy_pass $forward_scheme://$server:$port; |
|
|
| # Auth variables sync |
| auth_request_set $auth_cookie $upstream_http_set_cookie; |
| add_header Set-Cookie $auth_cookie; |
| |
| # Headers |
| auth_request_set $authentik_username $upstream_http_x_authentik_username; |
| auth_request_set $authentik_groups $upstream_http_x_authentik_groups; |
| auth_request_set $authentik_email $upstream_http_x_authentik_email; |
| auth_request_set $authentik_name $upstream_http_x_authentik_name; |
|
|
| proxy_set_header X-Authentik-Username $authentik_username; |
| proxy_set_header X-Authentik-Groups $authentik_groups; |
| proxy_set_header X-Authentik-Email $authentik_email; |
| proxy_set_header X-Authentik-Name $authentik_name; |
| } |
|
|
| location = /outpost.goauthentik.io/auth/request { |
| proxy_pass http://certbridge-authentik-server:9000/outpost.goauthentik.io/auth/request; |
| proxy_set_header Host $host; |
| proxy_set_header X-Original-URL $scheme://$http_host$request_uri; |
| proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
| proxy_set_header Content-Length ""; |
| proxy_method GET; |
| } |
|
|
| location @goauthentik_login { |
| return 302 http://certbridge-authentik-server:9000/outpost.goauthentik.io/auth/start?rd=$scheme://$http_host$request_uri; |
| }</pre> |
| </div> |
| </div> |
| </div> |
| </section> |
|
|
| |
| <section class="section" id="troubleshooting"> |
| <h2 data-i18n="trouble.title">6. λ¬Έμ ν΄κ²° (Troubleshooting)</h2> |
| |
| <div class="card warn"> |
| <h4>1. DB Connection Refused (postgres/redis)</h4> |
| <p>Authentik 컨ν
μ΄λκ° μμν λ PostgreSQL λλ Redisμ μ μνμ§ λͺ»ν΄ ν¬λμλλ κ²½μ°:</p> |
| <p><b>μμΈ:</b> postgresλ redisκ° μμ§ κΈ°λλμ§ μμκ±°λ ν¬μ€μ²΄ν¬ κ²μ¦ λ¨κ³μ μκ°μ΄ νμν κ²½μ°μ
λλ€.</p> |
| <p><b>ν΄κ²°μ±
:</b> <code>docker compose logs -f certbridge-db</code> λͺ
λ ΉμΌλ‘ DB μλ² μμ λ‘κ·Έλ₯Ό νμΈνκ³ , <code>docker compose restart certbridge-authentik-server</code>λ₯Ό ν΅ν΄ Authentikμ μ¬μμν΄ μ£Όμμμ€.</p> |
| </div> |
|
|
| <div class="card warn"> |
| <h4>2. Authentik DB init failure (Postgres)</h4> |
| <p>Authentik DBκ° μλμΌλ‘ μμ±λμ§ μμ μΈμ¦ μμ€ν
μ΄ κ΅¬λλμ§ μλ κ²½μ°:</p> |
| <p><b>μμΈ:</b> docker-compose.ymlμ λ³Όλ₯¨μ <code>00-init-authentik-db.sql</code> μ΄ μ λλ‘ λ°μΈλ©λμ§ μμ <code>authentik</code> μ΄λΌλ λ°μ΄ν°λ² μ΄μ€κ° Postgres λ΄μ λλ½λ κ²½μ°μ
λλ€.</p> |
| <p><b>ν΄κ²°μ±
:</b> <code>docker compose exec -it certbridge-db psql -U certbridge -c "CREATE DATABASE authentik;"</code> μ ν°λ―Έλμ μ§μ μννμ¬ DBλ₯Ό μλ μμ±νκ³ Authentikμ μ¬κΈ°λνμμμ€.</p> |
| </div> |
| </section> |
| </div> |
| </div> |
|
|
| <script> |
| |
| |
| |
| const i18n = { |
| ko: { |
| "sidebar.ver": "보μ λ° μμ΄κ° κ°μ΄λ v0.1.0", |
| "nav.overview": "μν€ν
μ² κ°μ", |
| "nav.prep": "μμ΄κ° ν¨ν€μ§ μ€λΉ", |
| "nav.install": "μ€νλΌμΈ μ€μΉ νλ‘ν μ½", |
| "nav.npm": "Nginx Proxy Manager", |
| "nav.authentik": "Authentik SSO μ°λ", |
| "nav.trouble": "λ¬Έμ ν΄κ²°", |
| "hero.title": "CertBridge 보μ κ°ν λ° μμ΄κ° κ°μ΄λ", |
| "hero.desc": "μ€νλΌμΈ(Air-Gap) μ°λΆν¬ νκ²½μμ Nginx Proxy Managerμ Authentik SSOλ₯Ό νμ©νμ¬ SSL 보μ ν°λ―Έλ€μ΄μ
λ° κ³μ ν΅ν© μΈμ¦ μμ€ν
μ μμ νκ² κ΅¬μ±νλ κ³ κΈ κ°μ΄λμ
λλ€.", |
| "hero.badge": "π v0.1.0 Β· Ubuntu amd64 Β· NPM & Authentik", |
| "overview.title": "1. 보μ κ°ν μν€ν
μ² κ°μ", |
| "overview.desc1": "κΈ°μ‘΄ μ€μ μλ² μΈνλΌλ ν¬νΈ λ
ΈμΆ λ° λ¬΄μΈμ¦ ν΅μ μΌλ‘ μΈν΄ 보μ μνμ μ·¨μ½ν μ μμ΅λλ€. λ³Έ κ°μ μ€νμμλ Nginx Proxy Manager (NPM)μ Authentik SSOλ₯Ό μ€μ μΈνλΌμ ν΅ν©νμ¬ μ λ°© 보μ μ₯λ²½μ ꡬμΆν©λλ€.", |
| "overview.strategy_title": "π‘ μμ μ΅μ ν μ€κ³ μ λ΅ (Postgres/Redis 곡μ )", |
| "overview.strategy_desc": "μλ‘ μΆκ°λλ SSO(Authentik) μλΉμ€μ μ€λ²ν€λλ₯Ό μ μ΄νκΈ° μν΄, κΈ°μ‘΄ PostgreSQL λ° Redis 컨ν
μ΄λ μμμ κ³ μ€λν μ¬νμ©ν©λλ€.", |
| "overview.net_title": "π μλΉμ€ ν¬νΈ λ§΅ & λ€νΈμν¬ ν ν΄λ‘μ§", |
| "prep.title": "2. μμ΄κ°(μ€νλΌμΈ) ν¨ν€μ§ μ€λΉ", |
| "prep.desc": "μΈν°λ· μ°κ²°μ΄ μ ν λΆκ°λ₯ν μμ΄κ°(Air-Gap) νμλ§ μ°λΆν¬ μλ²μμ μ€μΉνκΈ° μν΄μλ, μΈν°λ·μ΄ μ°κ²°λ μ¬μ λ¨Έμ μμ νμν λͺ¨λ Docker μ΄λ―Έμ§μ ν¨ν€μ§λ₯Ό λ€μ΄λ‘λν΄μΌ ν©λλ€.", |
| "prep.step1": "2-1. Hugging Faceμμ μμ΄κ° νμΌ λ€μ΄λ‘λ", |
| "prep.desc2": "μ°λ¦¬λ Hugging Face μ μ₯μ (VanyaJ/certbridge)μ 미리 λΉλλ linux/amd64 νλ«νΌ μ μ© Docker μ΄λ―Έμ§ μμΉ΄μ΄λΈμ μλ² ν¨ν€μ§λ₯Ό λ°°ν¬ν΄ λμμ΅λλ€.", |
| "prep.step2": "2-2. (λμ) prepare-airgap-package.sh νμ©", |
| "prep.desc3": "μΈν°λ·μ΄ μ°κ²°λ μΈλΆ μ°λΆν¬ PCμμ μ§μ μ΄ μ€ν¬λ¦½νΈλ₯Ό μννλ©΄, Docker μ΄λ―Έμ§ λ° Node/Docker deb μ€μΉ ν¨ν€μ§κΉμ§ νλμ λ²λ€λ‘ λ¬Άμ΄ μμΆν΄ μ€λλ€.", |
| "install.title": "3. μ€νλΌμΈ μ€μΉ νλ‘ν μ½ (Ubuntu)", |
| "install.desc": "λ€μ΄λ‘λν ν¨ν€μ§μ docker-images ν΄λλ₯Ό μ΄λμ μ μ₯μ₯μΉ(USB/μΈμ₯νλ λ±) λλ λ΄λΆλ§ νμΌ κ³΅μ λ₯Ό ν΅ν΄ μμ΄κ° λμ μ°λΆν¬ μλ²λ‘ 볡μ¬ν©λλ€.", |
| "install.step1_title": "ν¨ν€μ§ μμΆ ν΄μ λ° μ΄λ―Έμ§ λλ ν 리 λ³ν©", |
| "install.step1_desc": "μλ² ν¨ν€μ§λ₯Ό ν΄μ ν κ²½λ‘ μλλ‘, κ°λ³ λ€μ΄λ‘λν docker-images λλ ν 리λ₯Ό λ³ν©ν©λλ€.", |
| "install.step2_title": "μλ μ
μ
μ€ν¬λ¦½νΈ μ€ν", |
| "install.step2_desc": "μ€νλΌμΈ μ€μΉμ© μ
μ
μ€ν¬λ¦½νΈ setup-airgap.shμ μ€ν κΆνμ λΆμ¬νκ³ κ°λν©λλ€. μ΄ μ€ν¬λ¦½νΈλ Docker load λͺ
λ Ήμ ν΅ν΄ μ€νλΌμΈ tar λ° tar.gz μ΄λ―Έμ§λ₯Ό μ 체 볡ꡬνκ³ μΈνλΌ μλΉμ€λ₯Ό κΈ°λν©λλ€.", |
| "install.step3_title": "λ
립 λͺ¨λ(--profile standalone) μλΉμ€ κΈ°λ μν μ‘°ν", |
| "install.step3_desc": "NPM λ° Authentik μλΉμ€λ₯Ό ν¬ν¨νμ¬ μ μ ꡬλ μ€μΈμ§ μλ λͺ
λ ΉμΌλ‘ νμΈν©λλ€.", |
| "npm.title": "4. Nginx Proxy Manager μ€μ κ°μ΄λ", |
| "npm.desc": "Nginx Proxy Manager(NPM)λ CertBridge μ€νμ μ΅μ λ°©μμ λͺ¨λ μΈλΆ μ μ
μμ²μ μ²λ¦¬νκ³ , SSL μνΈν ν΅μ μ μ’
λ¨νλ©°, λλ©μΈ/κ²½λ‘ λΆκΈ°λ₯Ό μνν©λλ€.", |
| "npm.step1_title": "κ΄λ¦¬μ νλ©΄(Admin UI) λ‘κ·ΈμΈ", |
| "npm.step1_desc": "μΉ λΈλΌμ°μ λ‘ http://<SERVER_IP>:81 μ μ μν©λλ€. μ΅μ΄ κΈ°λ³Έ λ‘κ·ΈμΈ κ³μ μ λ€μκ³Ό κ°μ΅λλ€.", |
| "npm.step2_title": "μ¬μ€ SSL/TLS μΈμ¦μ μΆκ° (μμ΄κ° νκ²½)", |
| "npm.step2_desc": "μΈνλΌλ§μ΄ μμ΄κ° μ€νλΌμΈμ΄λ―λ‘ Let's Encrypt μλ λ°κΈμ μ¬μ©ν μ μμ΅λλ€. κΈ°κ΄ λ΄λΆ CA λλ μ¬μ€λ‘ λ°κΈλ°μ μΈμ¦μ νμΌμ μλ λ±λ‘ν΄μΌ ν©λλ€.", |
| "npm.step3_title": "Proxy Host μΆκ° (CertBridge API λ±)", |
| "npm.step3_desc": "NPM νλ‘μ νΈμ€νΈλ₯Ό μμ±νμ¬ μΈλΆ HTTPS μμ²μ λ΄λΆ 컨ν
μ΄λλ‘ μ λ¬ν©λλ€.", |
| "authentik.title": "5. Authentik SSO / MFA μ€μ νλ‘ν μ½", |
| "authentik.desc": "Authentikμ μ±κΈ μ¬μΈμ¨(SSO)κ³Ό λ©ν°ν©ν° μΈμ¦(MFA)μ μΌκ΄λκ² μ 곡νμ¬, API, MinIO Console, Neo4j Graph DBλ‘μ λ¬΄λ¨ μΉ¨μ
μ ν΅μ ν©λλ€.", |
| "authentik.step1_title": "μ΅μ΄ μΈμ€ν΄μ€ μ
μ
λ° μ΄κΈ°ν", |
| "authentik.step1_desc": "μΉ λΈλΌμ°μ λ‘ http://<SERVER_IP>:9000/if/flow/initial-setup/ μ μ μν©λλ€. μ΅μ΄ λΆνΈμ€νΈλ© νλ©΄μμ κ΄λ¦¬μ κ³μ akadminμ λν μμ ν λΉλ°λ²νΈλ₯Ό μ€μ νμ¬ μ΄κΈ°νλ₯Ό μλ£ν©λλ€.", |
| "authentik.step2_title": "Proxy Provider μμ± (NPM Forward Auth μ°λμ©)", |
| "authentik.step2_desc": "MinIOμ Neo4j λ± κ°λ³ 보μμ΄ νμν λ κ±°μ νλ©΄ 보νΈλ₯Ό μν΄ Proxy Providerλ₯Ό μμ±ν©λλ€.", |
| "authentik.step3_title": "NPM Custom Nginx Configuration μ μ©", |
| "authentik.step3_desc": "Nginx Proxy Managerμμ ν΄λΉ Proxy Hostλ‘ λ€μ΄μ€λ νΈλν½μ΄ Authentikμ κ±°μ³ μΈμ¦λλλ‘ Custom Nginx μ€μ λΈλ‘μ λ£μ΄μ€λλ€.", |
| "trouble.title": "6. λ¬Έμ ν΄κ²° (Troubleshooting)" |
| }, |
| en: { |
| "sidebar.ver": "Security & Air-gap Guide v0.1.0", |
| "nav.overview": "Architecture Overview", |
| "nav.prep": "Air-gap Package Prep", |
| "nav.install": "Offline Install Protocol", |
| "nav.npm": "Nginx Proxy Manager", |
| "nav.authentik": "Authentik SSO Setup", |
| "nav.trouble": "Troubleshooting", |
| "hero.title": "CertBridge Security & Air-gap Guide", |
| "hero.desc": "An advanced guide on securely configuring SSL termination and Single Sign-On (SSO) with MFA using Nginx Proxy Manager and Authentik in an offline (Air-Gap) Ubuntu environment.", |
| "hero.badge": "π v0.1.0 Β· Ubuntu amd64 Β· NPM & Authentik", |
| "overview.title": "1. Security-Hardened Architecture Overview", |
| "overview.desc1": "The existing core server infrastructure could be vulnerable due to exposed ports and unauthenticated communication. In this revised stack, Nginx Proxy Manager (NPM) and Authentik SSO are integrated to establish a robust front-line security barrier.", |
| "overview.strategy_title": "π‘ Resource Optimization Strategy (Shared Postgres/Redis)", |
| "overview.strategy_desc": "To control the overhead of the newly added SSO (Authentik) services, existing PostgreSQL and Redis container resources are fully reused.", |
| "overview.net_title": "π Port Mapping & Network Topology", |
| "prep.title": "2. Offline Air-gap Package Preparation", |
| "prep.desc": "To deploy on an air-gapped Ubuntu server without internet access, you must first pre-download all required Docker images and installer packages on an internet-enabled machine.", |
| "prep.step1": "2-1. Downloading Air-gap Assets from Hugging Face", |
| "prep.desc2": "We have pre-packaged and published the linux/amd64 compatible Docker image archives and server deployment bundles in our Hugging Face repository (VanyaJ/certbridge).", |
| "prep.step2": "2-2. (Alternative) Using prepare-airgap-package.sh", |
| "prep.desc3": "Running this script on an internet-enabled Ubuntu PC will pull and package all Docker images, along with Node and Docker deb setup files, into a single archive.", |
| "install.title": "3. Offline Ubuntu Installation Protocol", |
| "install.desc": "Copy the downloaded packages and docker-images directory to your air-gapped target Ubuntu server using a portable USB storage or local network file sharing.", |
| "install.step1_title": "Extract Packages and Merge Image Directories", |
| "install.step1_desc": "Extract the server package and merge the separately downloaded docker-images directory into it.", |
| "install.step2_title": "Execute Automatic Setup Script", |
| "install.step2_desc": "Grant execution permissions to setup-airgap.sh and run it. The script loads offline tar and tar.gz images and fires up all system services.", |
| "install.step3_title": "Inspect Standalone Mode Services Status", |
| "install.step3_desc": "Verify that all services, including NPM and Authentik, are successfully running with the command below.", |
| "npm.title": "4. Nginx Proxy Manager Configuration Guide", |
| "npm.desc": "Nginx Proxy Manager (NPM) sits at the front line, routing all incoming external traffic, terminating SSL, and managing domain name resolutions.", |
| "npm.step1_title": "Login to NPM Admin UI", |
| "npm.step1_desc": "Open your web browser and navigate to http://<SERVER_IP>:81. The default bootstrap credentials are:", |
| "npm.step2_title": "Add Private SSL/TLS Certificate (Air-gap)", |
| "npm.step2_desc": "Since Let's Encrypt cannot auto-issue certificates in an offline environment, you must manually upload a private CA-signed certificate.", |
| "npm.step3_title": "Add Proxy Host (for CertBridge API etc.)", |
| "npm.step3_desc": "Create a new proxy host in NPM to forward external HTTPS requests to internal docker containers safely.", |
| "authentik.title": "5. Authentik SSO / MFA Federation Protocol", |
| "authentik.desc": "Authentik provides centralized SSO and MFA, preventing unauthorized entry to the API, MinIO Console, and Neo4j Graph DB.", |
| "authentik.step1_title": "Initial Setup and Admin Initialization", |
| "authentik.step1_desc": "Navigate to http://<SERVER_IP>:9000/if/flow/initial-setup/ on your browser. Complete initialization by defining a secure password for the default admin account akadmin.", |
| "authentik.step2_title": "Create Proxy Provider (for NPM Forward Auth)", |
| "authentik.step2_desc": "Create a Proxy Provider in Authentik to protect web applications like MinIO and Neo4j.", |
| "authentik.step3_title": "Apply Custom Nginx Configuration in NPM", |
| "authentik.step3_desc": "Insert custom Nginx configurations into NPM proxy hosts so that all requests route through Authentik authentication filters.", |
| "trouble.title": "6. Troubleshooting Guide" |
| } |
| }; |
| |
| |
| |
| |
| function changeLanguage(lang) { |
| document.querySelectorAll("[data-i18n]").forEach(el => { |
| const key = el.getAttribute("data-i18n"); |
| if (i18n[lang] && i18n[lang][key]) { |
| el.innerHTML = i18n[lang][key]; |
| } |
| }); |
| document.getElementById("html-title").innerText = i18n[lang]["hero.title"] || "CertBridge Security Guide"; |
| } |
| |
| |
| |
| |
| function copyCode(btn) { |
| const pre = btn.closest('.code-wrap').querySelector('pre'); |
| const text = pre.innerText; |
| navigator.clipboard.writeText(text).then(() => { |
| const originalText = btn.innerText; |
| btn.innerText = "볡μ¬λ¨!"; |
| btn.style.background = "var(--accent-emerald)"; |
| btn.style.borderColor = "var(--accent-emerald)"; |
| btn.style.color = "white"; |
| setTimeout(() => { |
| btn.innerText = originalText; |
| btn.style.background = "var(--bg-secondary)"; |
| btn.style.borderColor = "var(--border-subtle)"; |
| btn.style.color = "var(--text-secondary)"; |
| }, 2000); |
| }); |
| } |
| |
| |
| const sections = document.querySelectorAll(".section"); |
| const navLinks = document.querySelectorAll(".sidebar nav a"); |
| |
| window.addEventListener("scroll", () => { |
| let current = ""; |
| sections.forEach(section => { |
| const sectionTop = section.offsetTop; |
| if (pageYOffset >= sectionTop - 60) { |
| current = section.getAttribute("id"); |
| } |
| }); |
| navLinks.forEach(link => { |
| link.classList.remove("active"); |
| if (link.getAttribute("href").substring(1) === current) { |
| link.classList.add("active"); |
| } |
| }); |
| }); |
| </script> |
| </body> |
| </html> |
|
|