GovStack: 디지털 정부를 위한 모듈식 프레임워크

GovStack 2020년에 시작된 국제 이니셔티브입니다. 에스토니아, 독일, ITU(국제전기통신연합) e DIAL(디지털 임팩트 얼라이언스) 와 서비스 구축에 필요한 도구, 지식, 모범 사례를 공유하는 목표 매번 처음부터 시작할 필요 없이 규모에 맞게 디지털 시청자를 확보할 수 있습니다.

기본적인 아이디어는 간단하지만 강력합니다. 즉, 국가별로 단일 시스템을 개발하는 대신 각 국가가 정부 디지털 서비스는 다음과 같이 분해됩니다. 빌딩 블록 (기능 모듈) 특정 기능(신원 확인, 결제, 메시징, 레지스터 등)을 제공합니다. 자유롭게 결합하여 어떤 서비스든 구축할 수 있습니다. 빌딩 블록은 상호 운용 가능하고 재사용 가능하며 구현 독립적: GovStack은 다음을 정의합니다. 명세서, 소프트웨어가 아닙니다.

2025년에는 출시와 함께 GovSpecs 2.0 (2025-2027 전략) GovStack은 새로운 빌딩 블록을 통합하고, 상호 운용성 사양을 업데이트하고, 정의하여 프레임워크를 구축합니다. 디지털 역량이 풍부한 국가를 지원하기 위한 계층화된 성숙도 모델 다르다. 20개 이상의 국가에서 GovStack 접근 방식을 적극적으로 사용하고 있습니다.

무엇을 배울 것인가

  • GovStack의 9가지 기본 빌딩 블록과 기술 사양
  • 기존 이탈리아 PA 서비스(SPID, CIE, pagoPA)를 GovStack 빌딩 블록에 매핑하는 방법
  • GovStack 참조 아키텍처: 계층형 모델 및 통합 버스
  • GovSpecs 2.0: 2025-2027 전략의 새로운 내용
  • OpenID Connect를 통한 Building Block Identity의 실제 구현
  • 빌딩 블록 지불: 국가 지불 시스템과의 통합
  • 동의 빌딩 블록: GDPR 준수 동의 관리
  • GovStack이 귀하의 상황에 적합한지 평가하는 방법

9가지 기본 빌딩 블록

GovStack은 다음을 포괄하는 9가지 핵심 빌딩 블록(새로운 2025 사양에 게시됨)을 정의합니다. 모든 디지털 정부 서비스에 필요한 교차 기능:

빌딩 블록 기능 프로토콜/표준 이탈리아의 예
신원 인증 및 신원 관리 OIDC, SAML, W3C가 수행했습니다. SPID, CIE, EUDI 지갑
결제 결제 및 이체 처리 ISO 20022, REST API 페이파
동의 동의 수집 및 관리 GDPR, DPIA, OAuth 범위 GDPR-by-Design을 사용한 CMP
디지털 레지스트리 권한 있는 등기부 관리(등기소, 토지 등기부) REST API, FHIR, CKAN ANPR, 토지 등록, 전문 등록
메시징 정부와 시민 간의 안전한 커뮤니케이션 SMTP, 푸시, WebSocket, MQTT IO 앱, PEC, pagoPA 알림
정보중개 시스템 간 안전한 데이터 교환 X-로드, REST, GraphQL PDND, AgID 상호 운용성
등록 서비스를 위한 사람/단체 등록 REST API, OAuth 2.0 INPS 포털, SUAP, 지자체 포털
스케줄러 약속 및 예약 관리 iCalendar, REST API 헬스컵, 디지털카운터
작업 흐름 프로세스 및 절차의 조정 BPMN, SAGA pattern, REST PA 실습 관리 시스템

GovStack 참조 아키텍처

GovStack은 빌딩 블록이 레이어에 배치되는 계층형 아키텍처를 제안합니다. 명확한 책임으로 구별됩니다.

  • 레이어 0 - 인프라: 클라우드, 네트워크, 보안. BB는 특정 클라우드에 종속되지 않습니다. they can run on AWS, Azure, GCP or on-premise infrastructure.
  • 레이어 1 - 핵심 빌딩 블록: the 9 fundamental BBs. 그들은 노출하는 자율 서비스입니다. 표준화된 RESTful API. 각 BB에는 공개 사양(OpenAPI + JSON 스키마)이 있습니다.
  • 레이어 2 - 공유 서비스: 중앙 집중식 로깅, 서비스 메시 등 횡단 서비스, API 게이트웨이, 서비스 검색. 이 서비스는 모든 BB를 지원합니다.
  • 레이어 3 - 애플리케이션: 실제 디지털 서비스(학교 입학, certificate request, stamp duty payment) that orchestrate the underlying BBs.
# Architettura di un servizio PA con Building Block GovStack
# Esempio: Servizio di iscrizione scolastica online

# Il servizio orchestra 5 Building Block:
# 1. Identity BB - autenticazione genitore con SPID/CIE
# 2. Registration BB - raccolta dati del bambino
# 3. Digital Registries BB - verifica iscrizione anagrafica
# 4. Consent BB - consenso al trattamento dati minori
# 5. Messaging BB - conferma iscrizione via IO App/email
# 6. Payments BB - pagamento tassa iscrizione via pagoPA

from dataclasses import dataclass
from typing import Optional
import httpx

@dataclass
class SchoolEnrollmentRequest:
    parent_session_token: str   # Token da Identity BB (SPID/CIE)
    child_fiscal_code: str
    school_code: str
    year: int

class SchoolEnrollmentService:
    """
    Servizio iscrizione scolastica che orchestra i Building Block GovStack.
    Pattern: Saga orchestrator (gestione stati distribuiti con compensazioni).
    """

    def __init__(
        self,
        identity_bb_url: str,
        registry_bb_url: str,
        consent_bb_url: str,
        messaging_bb_url: str,
        payments_bb_url: str
    ):
        self.identity_url = identity_bb_url
        self.registry_url = registry_bb_url
        self.consent_url = consent_bb_url
        self.messaging_url = messaging_bb_url
        self.payments_url = payments_bb_url

    async def process_enrollment(self, request: SchoolEnrollmentRequest) -> dict:
        """
        Saga: processo iscrizione con compensazioni in caso di errore.
        Ogni step è idempotente e reversibile.
        """
        saga_log = []

        try:
            # Step 1: Verifica identità genitore tramite Identity BB
            parent_identity = await self._verify_parent_identity(
                request.parent_session_token
            )
            saga_log.append({"step": "identity_verified", "status": "ok"})

            # Step 2: Verifica residenza bambino tramite Digital Registries BB
            child_registry = await self._verify_child_in_registry(
                request.child_fiscal_code,
                parent_identity["fiscal_number"]
            )
            saga_log.append({"step": "registry_verified", "status": "ok"})

            # Step 3: Raccolta consenso GDPR tramite Consent BB
            consent_id = await self._collect_consent(
                parent_identity["fiscal_number"],
                purpose="school_enrollment_data_processing"
            )
            saga_log.append({"step": "consent_collected", "consent_id": consent_id})

            # Step 4: Registrazione iscrizione tramite Registration BB
            enrollment_id = await self._register_enrollment(
                child_fc=request.child_fiscal_code,
                school_code=request.school_code,
                year=request.year,
                parent_fc=parent_identity["fiscal_number"]
            )
            saga_log.append({"step": "enrollment_registered", "enrollment_id": enrollment_id})

            # Step 5: Pagamento tassa (se prevista) tramite Payments BB
            payment_url = await self._create_payment(
                parent_fc=parent_identity["fiscal_number"],
                enrollment_id=enrollment_id,
                amount_cents=1500  # 15 euro
            )
            saga_log.append({"step": "payment_created", "payment_url": payment_url})

            # Step 6: Notifica conferma tramite Messaging BB
            await self._send_confirmation(
                parent_fc=parent_identity["fiscal_number"],
                enrollment_id=enrollment_id,
                payment_url=payment_url
            )
            saga_log.append({"step": "notification_sent", "status": "ok"})

            return {
                "status": "success",
                "enrollment_id": enrollment_id,
                "payment_url": payment_url,
                "saga_log": saga_log
            }

        except Exception as e:
            # Compensazione: rollback degli step completati in ordine inverso
            await self._compensate(saga_log, e)
            raise

    async def _verify_parent_identity(self, session_token: str) -> dict:
        """Chiama il Building Block Identity per validare il token SPID/CIE."""
        async with httpx.AsyncClient() as client:
            response = await client.post(
                f"{self.identity_url}/v1/tokens/validate",
                json={"token": session_token}
            )
            response.raise_for_status()
            return response.json()  # Returns: fiscal_number, name, surname, etc.

    async def _verify_child_in_registry(self, child_fc: str, parent_fc: str) -> dict:
        """Chiama il Building Block Digital Registries per verificare l'anagrafe."""
        async with httpx.AsyncClient() as client:
            response = await client.get(
                f"{self.registry_url}/v1/citizens/{child_fc}/family-relations",
                params={"parent_fiscal_code": parent_fc}
            )
            response.raise_for_status()
            return response.json()

    async def _collect_consent(self, parent_fc: str, purpose: str) -> str:
        """Registra il consenso tramite il Building Block Consent."""
        async with httpx.AsyncClient() as client:
            response = await client.post(
                f"{self.consent_url}/v1/consents",
                json={
                    "citizen_pseudonym": self._pseudonymize(parent_fc),
                    "purpose": purpose,
                    "legal_basis": "GDPR Art. 6(1)(e)",
                    "version": "2025-01"
                }
            )
            response.raise_for_status()
            return response.json()["consent_id"]

    async def _compensate(self, saga_log: list, error: Exception):
        """
        Compensazione Saga: rollback in ordine inverso degli step completati.
        Garantisce consistenza anche in caso di errori parziali.
        """
        for step in reversed(saga_log):
            try:
                if step["step"] == "consent_collected":
                    await self._revoke_consent(step["consent_id"])
                elif step["step"] == "enrollment_registered":
                    await self._cancel_enrollment(step["enrollment_id"])
            except Exception as compensation_error:
                # Logga l'errore di compensazione ma continua
                print(f"Compensation error for {step['step']}: {compensation_error}")

    def _pseudonymize(self, fiscal_code: str) -> str:
        import hashlib, hmac
        key = b"secret-vault-key"  # In produzione: usa un HSM
        return hmac.new(key, fiscal_code.encode(), hashlib.sha256).hexdigest()[:32]

GovSpecs 2.0: 2025-2027 전략의 새로운 사항

GovSpecs 2.0, announced by GovStack in 2025, introduces important updates compared 이전 버전의 사양으로:

  • 4단계 성숙도 모델: "초기 단계"부터(학습 및 아키텍처만 해당) 높은 수준)에서 "고급 통합"(국가 상호 운용성 프레임워크에 통합된 여러 BB). 이를 통해 국가는 각자의 우선순위와 역량에 따라 GovStack을 채택할 수 있습니다.
  • Supporto a Verifiable Credentials: Building Block Identity에는 이제 다음에 대한 사양이 포함됩니다. eIDAS 2.0 및 유럽 EUDI 지갑 프로그램에 부합하는 W3C 검증 가능한 자격 증명.
  • BB에게: a new Building Block for the integration of AI components (language models, 예측, 분류) 투명성과 설명 가능성에 중점을 두고 정부 서비스에 적용됩니다.
  • OpenAPI 3.1 사양: 모든 BB API는 이제 OpenAPI 3.1로 문서화되었습니다. SwaggerUI를 통해 JSON 스키마와 테스트 가능한 예제를 완성하세요.
  • 적합성 테스트: 구현이 완료되었는지 확인하기 위한 자동화된 프레임워크 of BB complies with GovStack specifications.

OIDC를 통한 빌딩 블록 아이덴티티 구현

빌딩 블록 아이덴티티는 구현하기 가장 중요하고 가장 복잡합니다. GovStack은 다음 API를 공개해야 한다고 지정합니다.

# Building Block Identity - Implementazione minima conforme GovStack
# OpenAPI 3.1 compatible - FastAPI implementation

from fastapi import FastAPI, HTTPException, Header, Depends
from fastapi.security import HTTPBearer
from pydantic import BaseModel
from typing import Optional
import jwt

app = FastAPI(
    title="Identity Building Block",
    description="GovStack Identity BB - Conforme a GovSpecs 2.0",
    version="2.0.0"
)

security = HTTPBearer()

# --- Modelli ---

class TokenValidationRequest(BaseModel):
    token: str
    expected_acr: Optional[str] = None  # Livello autenticazione richiesto

class IdentityResponse(BaseModel):
    sub: str                    # Identificativo presso l'IdP
    fiscal_number: Optional[str] = None  # Codice Fiscale (SPID/CIE)
    given_name: str
    family_name: str
    birthdate: Optional[str] = None
    acr: str                    # Livello autenticazione effettivo
    auth_time: int              # Timestamp autenticazione
    session_valid_until: int    # Scadenza sessione

class SessionCreationRequest(BaseModel):
    idp_id: str                 # Identity Provider scelto dall'utente
    redirect_uri: str
    acr_values: str = "https://www.spid.gov.it/SpidL2"
    scope: list = ["openid", "profile"]
    ui_locales: str = "it"

# --- Endpoints del Building Block Identity ---

@app.post("/v1/sessions",
    summary="Avvia sessione di autenticazione",
    tags=["Sessions"],
    response_model=dict)
async def create_authentication_session(request: SessionCreationRequest) -> dict:
    """
    GovStack Identity BB - Avvia il flusso di autenticazione.
    Restituisce l'URL di redirect verso l'IdP.
    """
    # Genera state e nonce per sicurezza
    import secrets
    session_id = secrets.token_urlsafe(32)
    state = secrets.token_urlsafe(32)
    nonce = secrets.token_urlsafe(32)

    # Recupera metadata dell'IdP dalla federazione OIDC
    idp_metadata = await get_idp_metadata(request.idp_id)

    # Costruisce URL autorizzazione (PKCE + Request Object)
    auth_url = build_oidc_auth_url(
        idp_auth_endpoint=idp_metadata["authorization_endpoint"],
        client_id=CLIENT_ID,
        redirect_uri=request.redirect_uri,
        scope=request.scope,
        state=state,
        nonce=nonce,
        acr_values=request.acr_values,
        private_key=SIGNING_KEY
    )

    # Salva sessione (Redis o DB)
    await session_store.save(session_id, {
        "state": state, "nonce": nonce, "idp_id": request.idp_id
    })

    return {"session_id": session_id, "authorization_url": auth_url}

@app.post("/v1/tokens/validate",
    summary="Valida un token di sessione",
    tags=["Tokens"],
    response_model=IdentityResponse)
async def validate_token(request: TokenValidationRequest) -> IdentityResponse:
    """
    GovStack Identity BB - Valida un token e restituisce l'identità.
    I servizi chiamanti usano questo endpoint per verificare l'autenticazione.
    """
    try:
        # Decodifica e valida il token (firma, scadenza, audience)
        claims = jwt.decode(
            request.token,
            JWKS,
            algorithms=["RS256"],
            audience=CLIENT_ID
        )

        # Verifica livello autenticazione se richiesto
        if request.expected_acr:
            actual_acr = claims.get("acr", "")
            if not _meets_acr_requirement(actual_acr, request.expected_acr):
                raise HTTPException(
                    status_code=403,
                    detail=f"Insufficient authentication level. Required: {request.expected_acr}"
                )

        return IdentityResponse(
            sub=claims["sub"],
            fiscal_number=claims.get("fiscal_number"),
            given_name=claims["given_name"],
            family_name=claims["family_name"],
            birthdate=claims.get("birthdate"),
            acr=claims.get("acr", ""),
            auth_time=claims.get("auth_time", 0),
            session_valid_until=claims.get("exp", 0)
        )
    except jwt.ExpiredSignatureError:
        raise HTTPException(status_code=401, detail="Token expired")
    except jwt.InvalidTokenError as e:
        raise HTTPException(status_code=401, detail=f"Invalid token: {str(e)}")

@app.delete("/v1/sessions/{session_id}",
    summary="Termina sessione (logout)",
    tags=["Sessions"])
async def end_session(session_id: str) -> dict:
    """
    GovStack Identity BB - Logout con propagazione verso l'IdP.
    Implementa OIDC Back-Channel Logout per notificare tutti i RP attivi.
    """
    session = await session_store.get(session_id)
    if not session:
        raise HTTPException(status_code=404, detail="Session not found")

    # Notifica logout all'IdP (OIDC Back-Channel Logout)
    idp_metadata = await get_idp_metadata(session["idp_id"])
    if "end_session_endpoint" in idp_metadata:
        await propagate_logout(idp_metadata["end_session_endpoint"], session)

    await session_store.delete(session_id)
    return {"status": "session_terminated"}

def _meets_acr_requirement(actual: str, required: str) -> bool:
    """Verifica che il livello di autenticazione effettivo soddisfi il requisito."""
    ACR_LEVELS = {
        "https://www.spid.gov.it/SpidL1": 1,
        "https://www.spid.gov.it/SpidL2": 2,
        "https://www.spid.gov.it/SpidL3": 3,
        "https://www.cie.gov.it/cie/aa": 2,
    }
    return ACR_LEVELS.get(actual, 0) >= ACR_LEVELS.get(required, 0)

빌딩 블록 메시징: IO 앱 및 정부 알림

Building Block Messaging은 정부와 시민 간의 커뮤니케이션을 관리합니다. 이탈리아에서는 서비스 GovStack 메시징 사양에 더 부합하는 것은 아이앱 (io.italia.it), PagoPA S.p.A에서 관리하는 PA와의 통신을 위한 전국 앱

# Building Block Messaging - Integrazione con IO App
# API REST di IO App per invio messaggi ai cittadini

import httpx
from pydantic import BaseModel
from typing import Optional

class IOMessage(BaseModel):
    fiscal_code: str           # Codice Fiscale del destinatario
    time_to_live: int = 3600  # Secondi di validità notifica push
    content: dict              # Contenuto del messaggio
    default_addresses: Optional[dict] = None  # Fallback email

class IOAppClient:
    """
    Client per le API di IO App.
    Le API IO App sono disponibili su https://developer.io.italia.it
    """

    def __init__(self, api_key: str):
        self.api_key = api_key
        self.base_url = "https://api.io.italia.it/api/v1"

    async def send_message(self, message: IOMessage) -> dict:
        """
        Invia un messaggio a un cittadino tramite IO App.
        Il cittadino deve aver attivato il proprio profilo IO.
        """
        async with httpx.AsyncClient() as client:
            response = await client.post(
                f"{self.base_url}/messages",
                headers={
                    "Ocp-Apim-Subscription-Key": self.api_key,
                    "Content-Type": "application/json"
                },
                json={
                    "fiscal_code": message.fiscal_code,
                    "time_to_live": message.time_to_live,
                    "content": message.content,
                    "default_addresses": message.default_addresses
                }
            )
            response.raise_for_status()
            return response.json()

    async def check_profile(self, fiscal_code: str) -> bool:
        """
        Verifica se un cittadino ha attivato il profilo IO e accetta messaggi dalla PA.
        """
        async with httpx.AsyncClient() as client:
            response = await client.get(
                f"{self.base_url}/profiles/{fiscal_code}",
                headers={"Ocp-Apim-Subscription-Key": self.api_key}
            )
            if response.status_code == 404:
                return False
            response.raise_for_status()
            profile = response.json()
            return profile.get("sender_allowed", False)

# Utilizzo: notifica iscrizione scolastica
async def notify_enrollment(fiscal_code: str, enrollment_id: str, payment_url: str):
    io_client = IOAppClient(api_key="your-io-api-key")

    # Verifica se il cittadino usa IO App
    has_io = await io_client.check_profile(fiscal_code)

    if has_io:
        # Messaggio strutturato IO App con CTA pagamento
        message = IOMessage(
            fiscal_code=fiscal_code,
            content={
                "subject": f"Iscrizione Scolastica {enrollment_id} - Conferma",
                "markdown": f"""
## La tua iscrizione è stata registrata

L'iscrizione con codice **{enrollment_id}** è stata registrata con successo.

Per completare la procedura, effettua il pagamento della tassa di iscrizione tramite il
link qui sotto.

**Importo**: 15,00 €

[Paga ora]({payment_url})

Per assistenza: [Ufficio Scolastico](https://istruzione.comune.esempio.it)
""",
                "payment_data": {
                    "amount": 1500,
                    "notice_number": enrollment_id,
                    "payee": {
                        "fiscal_code": "COMUNE_FC",
                        "name": "Comune di Esempio"
                    }
                }
            },
            default_addresses={"email": None}  # No fallback email
        )
        await io_client.send_message(message)
    else:
        # Fallback: email tradizionale (da implementare)
        await send_email_notification(fiscal_code, enrollment_id, payment_url)

이탈리아 생태계의 GovStack 지도

이탈리아에는 GovStack 빌딩 블록에 자연스럽게 매핑되는 고급 PPE 생태계가 이미 있습니다. 이탈리아 개발자에게 GovStack을 채택한다는 것은 처음부터 시작하는 것을 의미하지 않습니다. 표준화된 모듈형 모델에 따라 기존 통합을 재구성합니다..

BB 정부 스택 이탈리아어 구현 성숙도 채워야 할 공백
신원 SPID, CIE, eIDAS 높음(레벨 3-4) OIDC 통합 완료, EUDI Wallet
결제 페이파 높음(레벨 4) 완전한 GPD REST API
메시징 IO 앱, PEC 높음(레벨 3) IO 앱 침투율(일부 지역에서는 여전히 낮음)
디지털 레지스트리 ANPR, 토지 등록, PDND 중간(레벨 2-3) 레지스터 간의 상호 운용성은 여전히 ​​부분적입니다.
정보중개 PDND 중간(레벨 2) PDND 채택이 여전히 증가하고 있음
동의 다양함(표준화되지 않음) 낮음(레벨 1) 중앙 집중식 합의 플랫폼이 필요합니다
스케줄러 지역 헬스컵 낮음-중간(레벨 1-2) 지역적 단편화; 국가 표준 없음
작업 흐름 PA 실습 시스템(이기종) 낮음(레벨 1) 큰 이질성; 표준화가 필요하다

GovStack을 채택해야 하는 시기

GovStack은 다음과 같은 경우에 특히 적합합니다.

  • 당신은 건물을 짓고 있습니다 새로운 PA 디지털 서비스 처음부터 공급업체 종속성을 피하고 싶습니다.
  • 기존 시스템을 통합하고 공유 참조 아키텍처 모델을 원합니다.
  • 당신은 상황에 따라 작업합니다 다국가 (국제협력, 크로스보더 서비스)
  • GovStack 오픈 소스 생태계에 기여하고 다른 국가의 구현으로부터 이익을 얻고 싶습니다.

GovStack은 이탈리아 상황에 매우 구체적인 서비스가 있을 때 적합하지 않습니다. 기존 것(SPID, pagoPA)은 추가 추상화의 오버헤드 없이 이미 요구 사항을 충족합니다. 또는 분산 연합의 복잡성을 관리하기 위한 리소스가 제한된 경우.

결론: GovTech 시리즈의 관점

이 기사는 공공 행정의 디지털화에 관한 GovTech 시리즈를 마무리합니다. 우리는 공공 디지털 인프라(DPI)부터 ID까지 전체 생태계를 탐색했습니다. 유럽 연합(eIDAS 2.0, EUDI Wallet), SPID 및 CIE의 구체적인 구현부터 데이터 보호까지 (GDPR-by-Design) 접근성(WCAG 2.1 AA)부터 개방형 데이터(DCAT-AP_IT, CKAN)까지 국제 모듈식 프레임워크(GovStack).

공통점은 항상 동일합니다. 공공 디지털 서비스는 포함, 안전하고 상호 운용 가능하며 재사용 가능. 표준화된 빌딩 블록을 갖춘 GovStack은 다음을 제공합니다. 글로벌 규모로 이러한 목표를 달성하기 위한 공통 어휘 및 공유 아키텍처 모델입니다.

전체 GovTech 시리즈

  • #00: 디지털 공공 인프라 - 아키텍처 및 빌딩 블록
  • #01: eIDAS 2.0 및 EUDI 지갑 - 개발자 가이드
  • #02: 정부 신원을 위한 OpenID Connect
  • #03: 개방형 데이터 API 설계 - 공공 데이터 게시 및 소비
  • #04: GDPR-by-Design - 공공 서비스를 위한 아키텍처 패턴
  • #05: PA용 액세스 가능한 UI - WCAG 2.1 AA 구현
  • #06: 정부 API 통합 - SPID, CIE 및 IT 디지털 서비스
  • #07: GovStack 빌딩 블록 - (이 기사)