포털 사이트 링크에 SSO 자동로그인 적용 (Coder/Gitea OIDC 시작 URL + 뱃지)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
0310700
2026-06-22 17:53:07 +09:00
parent 9eb3fd1b4c
commit 792b9b9c72
6 changed files with 59 additions and 4 deletions

View File

@@ -10,7 +10,11 @@ SESSION_SECRET=change-me-to-a-long-random-string
# --- DB (OpenEverest appdb, portal schema 전용 role) --- # --- DB (OpenEverest appdb, portal schema 전용 role) ---
# host 는 클러스터 내부 OpenEverest pgbouncer (워크스페이스/Kubero 양쪽 도달). # host 는 클러스터 내부 OpenEverest pgbouncer (워크스페이스/Kubero 양쪽 도달).
DATABASE_URL=postgresql://portal_app:7e2950ce6ed0c5a1457ea5b5@postgresql-6ox-pgbouncer.everest.svc.cluster.local:5432/appdb?options=-c%20search_path%3Dportal # 주의: pgbouncer 가 SSL 필수 → sslmode=require. node pg 최신은 require 를 verify-full 로 취급하므로
# 자체서명 인증서 통과를 위해 uselibpqcompat=true 동반 필요.
# search_path 는 portal_app role 기본값(ALTER ROLE ... SET search_path=portal)으로 고정돼 있어
# URL 의 options 파라미터는 불필요(pgbouncer 가 startup options 거부함).
DATABASE_URL=postgresql://portal_app:7e2950ce6ed0c5a1457ea5b5@postgresql-6ox-pgbouncer.everest.svc:5432/appdb?uselibpqcompat=true&sslmode=require
# --- Keycloak OIDC (realm bokdev, client portal) --- # --- Keycloak OIDC (realm bokdev, client portal) ---
OIDC_ISSUER=https://keycloak.bokdev.in/realms/bokdev OIDC_ISSUER=https://keycloak.bokdev.in/realms/bokdev

17
Dockerfile Normal file
View File

@@ -0,0 +1,17 @@
# AI DEV 포털 이미지. Kubero buildpack(node22 + npm install + node index.js)과 동일 구성.
# Kubero v3.1.1 buildpack 빌드잡이 harbor 단일세그먼트 경로로 push 실패하는 이슈 우회용:
# 이 이미지를 harbor.bokdev.in/kubero/ai-dev-portal-portal:main 으로 직접 push 하면
# Kubero 가 만든 web 파드가 그대로 pull 한다.
FROM node:22-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install --omit=dev
COPY . .
ENV PORT=8080
EXPOSE 8080
CMD ["node", "index.js"]

26
deploy/portal-route.yaml Normal file
View File

@@ -0,0 +1,26 @@
# portal.bokdev.in → Kubero 가 배포한 portal Service.
# Kubero 파이프라인 ai-dev, production phase → ns ai-dev-production.
# Service 이름=portal-kuberoapp, port=80.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: portal-route
namespace: ai-dev-production
spec:
hostnames:
- portal.bokdev.in
parentRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: bokdev-gateway
namespace: istio-ingress
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- group: ""
kind: Service
name: portal-kuberoapp
port: 80

View File

@@ -82,6 +82,7 @@ a:hover { text-decoration: underline; }
.site-card .ico { font-size: 22px; line-height: 1; } .site-card .ico { font-size: 22px; line-height: 1; }
.site-card .name { font-weight: 600; color: var(--fg); } .site-card .name { font-weight: 600; color: var(--fg); }
.site-card .desc { color: var(--fg-muted); font-size: 12px; } .site-card .desc { color: var(--fg-muted); font-size: 12px; }
.sso-badge { display: inline-block; font-size: 10px; font-weight: 600; color: #1a7f37; background: #dafbe1; border: 1px solid #aceebb; border-radius: 999px; padding: 1px 7px; vertical-align: middle; margin-left: 4px; }
.group-title { font-size: 13px; font-weight: 600; color: var(--fg-muted); margin: 20px 0 8px; text-transform: uppercase; letter-spacing: .04em; } .group-title { font-size: 13px; font-weight: 600; color: var(--fg-muted); margin: 20px 0 8px; text-transform: uppercase; letter-spacing: .04em; }
/* 버튼 */ /* 버튼 */

View File

@@ -1,11 +1,18 @@
// 포털에 모아둘 사이트 카탈로그. 카테고리: 개발 핵심 / AI·데이터. // 포털에 모아둘 사이트 카탈로그. 카테고리: 개발 핵심 / AI·데이터.
// icon 은 이모지(추가 의존성 없이 GitHub 감성 카드에 표시). // icon 은 이모지(추가 의존성 없이 GitHub 감성 카드에 표시).
//
// url: 각 앱의 'OIDC 로그인 바로 시작' 엔드포인트를 쓰면, Keycloak SSO 세션이 있을 때
// 로그인 폼 없이 즉시 통과(자동 로그인)된다. 포털도 같은 Keycloak realm 으로 로그인하므로
// 포털 로그인 후 이 링크를 누르면 바로 들어간다.
// - Coder: /api/v2/users/oidc/callback (code 없으면 keycloak authorize 로 307)
// - Gitea: /user/oauth2/keycloak (auth source 명=keycloak, 307 로 keycloak authorize)
// SSO 미지원(Kubero=oauth2 미완성, Harbor 등)은 기본 URL 유지.
export const siteGroups = [ export const siteGroups = [
{ {
group: "개발 핵심", group: "개발 핵심",
sites: [ sites: [
{ name: "Coder", url: "https://coder.bokdev.in", icon: "💻", desc: "웹 개발 워크스페이스 (VS Code + AI CLI)" }, { name: "Coder", url: "https://coder.bokdev.in/api/v2/users/oidc/callback", icon: "💻", desc: "웹 개발 워크스페이스 (VS Code + AI CLI)", sso: true },
{ name: "Gitea", url: "https://gitea.bokdev.in", icon: "🍵", desc: "사내 Git 저장소" }, { name: "Gitea", url: "https://gitea.bokdev.in/user/oauth2/keycloak", icon: "🍵", desc: "사내 Git 저장소", sso: true },
{ name: "Kubero", url: "https://kubero.bokdev.in", icon: "🚀", desc: "앱 배포 PaaS (*.apps.bokdev.in)" }, { name: "Kubero", url: "https://kubero.bokdev.in", icon: "🚀", desc: "앱 배포 PaaS (*.apps.bokdev.in)" },
{ name: "Harbor", url: "https://harbor.bokdev.in", icon: "📦", desc: "컨테이너 이미지 레지스트리" }, { name: "Harbor", url: "https://harbor.bokdev.in", icon: "📦", desc: "컨테이너 이미지 레지스트리" },
], ],

View File

@@ -13,7 +13,7 @@
<a class="site-card" href="<%= s.url %>" target="_blank" rel="noopener"> <a class="site-card" href="<%= s.url %>" target="_blank" rel="noopener">
<span class="ico"><%= s.icon %></span> <span class="ico"><%= s.icon %></span>
<span> <span>
<span class="name"><%= s.name %></span><br/> <span class="name"><%= s.name %><% if (s.sso) { %> <span class="sso-badge">SSO 자동로그인</span><% } %></span><br/>
<span class="desc"><%= s.desc %></span> <span class="desc"><%= s.desc %></span>
</span> </span>
</a> </a>