Skip to content

Latest commit

 

History

History
567 lines (453 loc) · 19.3 KB

File metadata and controls

567 lines (453 loc) · 19.3 KB

GraphScope Proxy - Implementeringshåndbok

Status: ✅ MVP FERDIG - Denne dokumentasjonen er oppdatert basert på den faktiske implementeringen!

Dette prosjektet er en drop-in proxy foran Microsoft Graph API, bygget med .NET 9.0 og ASP.NET Core.
Den speiler Microsoft Graph-paths 1:1, men legger på kontrollert ressurs-scope pr. gruppe slik at klienter kun kan se og operere på et begrenset sett av rom / ressursmailbokser.

Hovedidé: Klient oppgir en Azure AD / Entra ID groupId ved innlogging. Medlemmer i denne gruppen (filtrert til rom / ressurser) blir «tillatelses-listen». Et JWT token med embedded resource scope representerer dette og alle senere kall filtreres eller avvises mot denne listen.

🎯 Implementert Løsning

Arkitektur: .NET microservice med transparent HTTP proxy Status: Fullstendig MVP implementert og testet Deployment: Docker-ready med Azure-native konfigurasjon


📦 Teknologistack (Implementert)

  • .NET 9.0 - Runtime platform
  • ASP.NET Core 9.0 - Web framework
  • Microsoft Graph SDK 5.x - Graph API integration
  • xUnit + Testcontainers - Testing framework (22 tests)
  • Serilog - Structured JSON logging
  • System.IdentityModel.Tokens.Jwt - JWT handling
  • Docker & Docker Compose - Containerization

🔐 Ressurs-scope per gruppe (✅ Implementert)

Implementert flyt:

  1. Login: Klient kaller POST /auth/login med apiKey + groupId.
  2. API Key Validation: Proxy validerer apiKey mot konfigurerte nøkler i appsettings.json.
  3. Graph Integration: Bruker app credentials (client credentials flow) til Microsoft Graph.
  4. Resource Discovery: Henter gruppens medlemmer via GraphServiceClient.Groups[groupId].Members.GetAsync() med full paginering.
  5. Classification: ResourceClassifier klassifiserer medlemmer som Room/Workspace/Equipment basert på email/navn-mønstre.
  6. JWT Generation: JwtService genererer JWT med resource scope embedded i claims (ikke bare tokenId).
  7. Caching: MemoryScopeCache eller RedisScopeCache lagrer scope med 15 min TTL.
  8. Proxy Operations: ProxyController og BetaProxyController håndterer alle /v1.0/* og /beta/* kall.
  9. Enforcement: ResourceScopeMiddleware validerer og filtrerer alle requests/responses.

Implementerte Middleware-kontroller:

ProxyController: Transparent HTTP proxy for v1.0 endpoints
BetaProxyController: Separat håndtering av beta endpoints
ResourceScopeMiddleware: JWT validering og resource scope enforcement
ErrorHandlingMiddleware: Global error handling med strukturerte responser

Implementerte Features:

✅ Blokkerer kalenderkall mot rom utenfor scope
✅ Filtrerer /rooms og /places/microsoft.graph.room lister til kun tillatte ressurser
✅ Response filtering av JSON arrays og single objects
✅ Capacity og location extraction fra resource navn

Fordeler:

  • Ingen behov for å gi klient bred Graph-tilgang – alt skjer via app-en.
  • Tilgang endres ved kun å justere gruppemedlemskap (enkelt for IT).
  • Skalerer med mange klienter fordi caching + minimal JWT payload.

Edge cases & håndtering:

  • Rom fjernet fra gruppe: Etter cache-expire → 403 på videre kall.
  • Nytt rom: Blir tilgjengelig etter cache-expire eller manuelt /admin/refresh/{groupId}.
  • Stor gruppe: Batch/paginering loop; ved >N (konfig) kan fallback være «hash-only» listing for store payloads.
  • Ukjent rom i request: Ingen prefetch – gir direkte 403 uten Graph-roundtrip.

Sikkerhetsnotater:

  • JWT signeres med sterk nøkkel (HS256 eller RS256).
  • tid ikke forutsigbar (random_bytes).
  • Ingen sensitive romdata i selve tokenet.
  • Rate limiting anbefales per apiKey (se videre plan).

Implementerte Konfigurasjon (appsettings.json):

{
  "GraphScope": {
    "TenantId": "your-tenant-id",
    "ClientId": "your-client-id",
    "ClientSecret": "your-client-secret",
    "JwtIssuer": "GraphScopeProxy",
    "JwtAudience": "GraphScopeProxy-Users", 
    "JwtSigningKey": "your-256-bit-signing-key",
    "JwtExpirationSeconds": 900,
    "AllowedPlaceTypes": ["room", "workspace", "equipment"],
    "AllowGenericResources": false,
    "MaxScopeSize": 500,
    "UseGraphPlacesApi": true
  },
  "ApiKeys": {
    "demo-key-1": ["demo-group-1", "demo-group-2"],
    "your-api-key": ["your-group-id"]
  }
}

🧱 Arkitektur (Implementert)

Implementerte Komponenter:

AuthController: Login/logout/refresh endpoints med JWT generering
ProxyController: Transparent proxy for /v1.0/* endpoints
BetaProxyController: Separat proxy for /beta/* endpoints
AdminController: Health checks og admin operasjoner
GraphApiService: Microsoft Graph SDK integration med paginering
GraphProxyService: HTTP proxy implementation med header management
ResourceClassifier: Intelligent klassifisering basert på navn/email mønstre
JwtService: JWT generering, validering og invalidering
ApiKeyService: API key til gruppe mapping og validering
MemoryScopeCache/RedisScopeCache: Configurable cache implementations
ResourceScopeMiddleware: JWT validering og scope enforcement
ErrorHandlingMiddleware: Global error handling

Implementert Request Flyt:

Client Request
    ↓
ResourceScopeMiddleware (JWT validation & scope extraction)
    ↓  
ProxyController/BetaProxyController (route & validate resource access)
    ↓
GraphProxyService (HTTP forwarding med headers)
    ↓
Microsoft Graph API
    ↓
Response Filtering (scope-based filtering)
    ↓
Client Response

🔧 Prosjektstruktur (Faktisk Implementering)

GraphScopeProxy/
├── src/
│   ├── GraphScopeProxy.Api/           # ✅ REST API layer
│   │   ├── Controllers/
│   │   │   ├── AuthController.cs      # ✅ Login/logout/refresh
│   │   │   ├── ProxyController.cs     # ✅ v1.0 Graph API proxy
│   │   │   ├── BetaProxyController.cs # ✅ beta Graph API proxy  
│   │   │   └── AdminController.cs     # ✅ Health checks
│   │   ├── Middleware/
│   │   │   ├── ResourceScopeMiddleware.cs # ✅ JWT & scope enforcement
│   │   │   └── ErrorHandlingMiddleware.cs # ✅ Global error handling
│   │   ├── GraphHealthCheck.cs        # ✅ Health check implementation
│   │   ├── Program.cs                 # ✅ App startup & DI configuration
│   │   └── appsettings.json          # ✅ Configuration
│   └── GraphScopeProxy.Core/          # ✅ Business logic layer
│       ├── Services/
│       │   ├── GraphApiService.cs     # ✅ Graph SDK integration
│       │   ├── GraphProxyService.cs   # ✅ HTTP proxy implementation
│       │   ├── ResourceClassifier.cs  # ✅ Resource classification
│       │   ├── JwtService.cs          # ✅ JWT operations
│       │   ├── ApiKeyService.cs       # ✅ API key management
│       │   ├── MemoryScopeCache.cs    # ✅ In-memory caching
│       │   ├── RedisScopeCache.cs     # ✅ Redis caching
│       │   └── Interfaces/            # ✅ Service contracts
│       ├── Models/
│       │   ├── AllowedResource.cs     # ✅ Resource data model
│       │   ├── ResourceScope.cs       # ✅ User scope model
│       │   ├── LoginRequest.cs        # ✅ Auth models
│       │   └── LoginResponse.cs       
│       └── Configuration/
│           └── GraphScopeOptions.cs   # ✅ Typed configuration
├── tests/
│   ├── GraphScopeProxy.Tests/         # ✅ 22 unit tests
│   └── GraphScopeProxy.IntegrationTests/ # ✅ Integration test structure
├── docker-compose.yml                 # ✅ Docker Compose
├── Dockerfile                         # ✅ Container definition
└── GraphScopeProxy.sln               # ✅ Solution file

🗂️ Datastrukturer (Implementert)

// AllowedResource.cs
public class AllowedResource 
{
    public string Id { get; set; } = "";
    public string Mail { get; set; } = "";
    public string? DisplayName { get; set; }
    public ResourceType Type { get; set; }
    public int? Capacity { get; set; }
    public string? Location { get; set; }
}

// ResourceScope.cs  
public class ResourceScope
{
    public string GroupId { get; set; } = "";
    public List<AllowedResource> AllowedResources { get; set; } = new();
    public DateTime CreatedAt { get; set; }
    public DateTime ExpiresAt { get; set; }
}

// JWT Claims (JwtService)
{
    "sub": "api-key",           // Subject (API key)
    "jti": "unique-token-id",   // JWT ID
    "group_id": "group-id",     // Group identifier
    "resource_count": "37",     // Number of accessible resources
    "resources": "[{...}]",     // Serialized allowed resources
    "iat": 1625097600,          // Issued at
    "exp": 1625098500           // Expires at
}

🔐 Sikkerhet & Policy

Område Tiltak
Transport Kun HTTPS (proxy bak ingress / TLS termination)
Auth API-key → Login → JWT (scoped). JWT signatur valideres hver request
Autorisasjon ResourceScopeMiddleware mot cache; ingen direkte Graph-kall for validering
Least privilege App-registrering gir bare nødvendige app-permissions (GroupMember.Read.All, Place.Read.All, Calendars.Read/Write)
Rate limiting (Fremtid) per apiKey + global burst limit
Logging Strukturert JSON, ingen persondata, inkluder korrelasjons-ID
Key rotation Støtte flere aktive API_KEY_x nøkler samtidig
Cache poisoning tid random, ingen klientstøtte for å påvirke innhold

🛠️ Replikering av Utviklingen

Steg 1: Prosjekt Opprettelse

# Opprett solution og prosjekter
dotnet new sln -n GraphScopeProxy
dotnet new webapi -n GraphScopeProxy.Api -o src/GraphScopeProxy.Api
dotnet new classlib -n GraphScopeProxy.Core -o src/GraphScopeProxy.Core
dotnet new xunit -n GraphScopeProxy.Tests -o tests/GraphScopeProxy.Tests
dotnet new xunit -n GraphScopeProxy.IntegrationTests -o tests/GraphScopeProxy.IntegrationTests

# Legg til prosjekter i solution
dotnet sln add src/GraphScopeProxy.Api/GraphScopeProxy.Api.csproj
dotnet sln add src/GraphScopeProxy.Core/GraphScopeProxy.Core.csproj
dotnet sln add tests/GraphScopeProxy.Tests/GraphScopeProxy.Tests.csproj
dotnet sln add tests/GraphScopeProxy.IntegrationTests/GraphScopeProxy.IntegrationTests.csproj

# Legg til prosjekt referanser
dotnet add src/GraphScopeProxy.Api reference src/GraphScopeProxy.Core
dotnet add tests/GraphScopeProxy.Tests reference src/GraphScopeProxy.Core
dotnet add tests/GraphScopeProxy.IntegrationTests reference src/GraphScopeProxy.Api

Steg 2: NuGet Pakker

# GraphScopeProxy.Api pakker
cd src/GraphScopeProxy.Api
dotnet add package Microsoft.Graph --version 5.36.0
dotnet add package Serilog.AspNetCore --version 8.0.1
dotnet add package System.IdentityModel.Tokens.Jwt --version 7.0.3
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer --version 9.0.0

# GraphScopeProxy.Core pakker  
cd ../GraphScopeProxy.Core
dotnet add package Microsoft.Graph --version 5.36.0
dotnet add package Microsoft.Extensions.Options --version 9.0.0
dotnet add package Microsoft.Extensions.Caching.Memory --version 9.0.0
dotnet add package StackExchange.Redis --version 2.7.4

# Test pakker
cd ../../tests/GraphScopeProxy.Tests
dotnet add package Microsoft.AspNetCore.Mvc.Testing --version 9.0.0
dotnet add package Moq --version 4.20.69

Steg 3: Implementer Core Models

# Opprett Models
mkdir -p src/GraphScopeProxy.Core/Models
mkdir -p src/GraphScopeProxy.Core/Configuration  
mkdir -p src/GraphScopeProxy.Core/Services

# Implementer modeller i riktig rekkefølge:
# 1. ResourceType.cs (enum)
# 2. AllowedResource.cs  
# 3. ResourceScope.cs
# 4. LoginRequest.cs & LoginResponse.cs
# 5. GraphScopeOptions.cs

Steg 4: Implementer Services (Core Logic)

# Service interfaces først
# 1. IScopeCache.cs
# 2. IApiKeyService.cs  
# 3. IJwtService.cs
# 4. IResourceClassifier.cs
# 5. IGraphApiService.cs
# 6. IGraphProxyService.cs

# Service implementasjoner
# 1. MemoryScopeCache.cs
# 2. ApiKeyService.cs
# 3. JwtService.cs  
# 4. ResourceClassifier.cs
# 5. GraphApiService.cs
# 6. GraphProxyService.cs

Steg 5: Implementer API Layer

# Middleware først
mkdir -p src/GraphScopeProxy.Api/Middleware
# 1. ErrorHandlingMiddleware.cs
# 2. ResourceScopeMiddleware.cs

# Controllers
mkdir -p src/GraphScopeProxy.Api/Controllers  
# 1. AdminController.cs (health checks)
# 2. AuthController.cs (login/logout/refresh)
# 3. ProxyController.cs (v1.0 proxy)
# 4. BetaProxyController.cs (beta proxy)

# Health checks
# GraphHealthCheck.cs

# Startup configuration
# Program.cs (DI registration, middleware pipeline)

Steg 6: Konfigurasjon

// appsettings.json template
{
  "GraphScope": {
    "TenantId": "demo-tenant",
    "ClientId": "demo-client", 
    "ClientSecret": "demo-secret",
    "JwtIssuer": "GraphScopeProxy",
    "JwtAudience": "GraphScopeProxy-Users",
    "JwtSigningKey": "this-is-a-256-bit-secret-key-for-demo-purposes-only-change-in-prod",
    "JwtExpirationSeconds": 900,
    "AllowedPlaceTypes": ["room", "workspace", "equipment"],
    "AllowGenericResources": false,
    "MaxScopeSize": 500,
    "UseGraphPlacesApi": true
  },
  "ApiKeys": {
    "demo-key-1": ["demo-group-1", "demo-group-2"]
  }
}

Steg 7: Testing

# Unit tests struktur
mkdir -p tests/GraphScopeProxy.Tests/Unit/Services

# Implementer tester for:
# 1. JwtServiceTests.cs
# 2. ApiKeyServiceTests.cs  
# 3. ResourceClassifierTests.cs
# 4. GraphApiServiceTests.cs
# 5. Controller tests

# Kjør tester
dotnet test

Steg 8: Docker & Deployment

# Dockerfile
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
WORKDIR /app
EXPOSE 8080

FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR /src
COPY ["src/GraphScopeProxy.Api/GraphScopeProxy.Api.csproj", "src/GraphScopeProxy.Api/"]
COPY ["src/GraphScopeProxy.Core/GraphScopeProxy.Core.csproj", "src/GraphScopeProxy.Core/"]
RUN dotnet restore "src/GraphScopeProxy.Api/GraphScopeProxy.Api.csproj"
COPY . .
WORKDIR "/src/src/GraphScopeProxy.Api"
RUN dotnet build "GraphScopeProxy.Api.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "GraphScopeProxy.Api.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "GraphScopeProxy.Api.dll"]
# docker-compose.yml
version: '3.8'
services:
  graphscopeproxy:
    build: .
    ports:
      - "8080:8080"
    environment:
      - ASPNETCORE_ENVIRONMENT=Development

Steg 9: Verifikasjon

# Build og test
dotnet build
dotnet test

# Kjør lokalt
dotnet run --project src/GraphScopeProxy.Api

# Test endpoints
curl http://localhost:5000/health
curl -X POST http://localhost:5000/auth/login \
  -H "Content-Type: application/json" \
  -d '{"apiKey": "demo-key-1", "groupId": "demo-group-1"}'

🧪 Implementert Testing

✅ Unit Tests (22/22 passerer):

  • JwtServiceTests: Token generering, validering, invalidering
  • ApiKeyServiceTests: API key validering og gruppe-mapping
  • ResourceClassifierTests: Type detection, kapasitet, lokasjon parsing
  • GraphApiServiceTests: Mock data, error handling, paginering
  • Controller tests: Auth flow, proxy functionality

Test Commands:

# Kjør alle tester
dotnet test

# Kjør med verbose output
dotnet test --verbosity normal

# Kjør spesifikk test klasse
dotnet test --filter "ClassName=JwtServiceTests"

Integration Test Structure:

  • Setup for end-to-end testing
  • Mock Graph API responses
  • Full request/response cycle testing

📈 Observability

  • Structured log keys: ts, level, msg, corrId, groupId, scopeId, resourceCount, latencyMs, status.
  • Metrikker (valgfritt): counters for requests_total, scope_cache_hits, scope_cache_miss, forbidden_requests.

🗺️ Gjennomføringsplan / Milepæler

Fase 0 – Grunnoppsett (1):

  • Composer init, Slim skeleton, basic Dockerfile, health endpoint.

Fase 1 – Graph integrasjon (2):

  • GraphToken helper (client credentials flow).
  • Konfig via miljøvariabler (tenant, client_id, client_secret, scope URL).

Fase 2 – Auth & Login (3):

  • API-key validering (statisk liste i .env initially).
  • /auth/login route returnerer dummy JWT.

Fase 3 – Scope bygging (4):

  • Hent group members (+ paginering).
  • Klassifisering + caching (in-memory / APCu).
  • JWT med tid claim.

Fase 4 – Proxy-baseline (5):

  • Generic ProxyRoute → videresend til Graph (headers, query, body passthrough).
  • Error wrapping.

Fase 5 – Scope Enforcement (6):

  • ResourceScopeMiddleware (request-blokk + response-filtrering /rooms & /places/* ).
  • Tester for avvisning / filtrering.

Fase 6 – Observability & Logging (7):

  • LoggingMiddleware (struct logs).
  • Korrelasjons-ID (request header X-Correlation-ID fallback generering).

Fase 7 – Admin & Hardening (8):

  • /admin/refresh/{groupId} + tilgangskontroll.
  • Rate limit (enkel per IP/apiKey – evt. senere).
  • Configurable TTL.

Fase 8 – Utvidelser (9):

  • Redis-basert ScopeCache.
  • Metrikker endpoint (Prometheus /stats).
  • Ekstra ressurs-typer.

⚠️ Risiko & Mitigering

Risiko Konsekvens Mitigering
Stor gruppe (mange ressurser) Lenger login-tid Asynk preload / øvre grense / hashing
Feilklassifisering Manglende/falsk tilgang Klar konfig + tests + places cross-check
Cache-stale Forsinket tilgangsoppdatering Kort TTL + manual refresh
Token leakage Uautorisert bruk Kort exp, rotér signing key, minst data i JWT
Graph rate limits 429 / latency Caching, backoff, minimere antall kall

✅ MVP Status (FERDIG)

✅ Alle MVP-kriterier oppfylt:

  • ✅ Login med groupId gir JWT med embedded resource scope
  • ✅ Kall mot rom i scope lykkes; utenfor scope → 403
  • /rooms / /places/microsoft.graph.room filtreres korrekt
  • ✅ Logging har correlation IDs og structured format
  • ✅ Health checks implementert (/health endpoint)
  • ✅ Comprehensive dokumentasjon (README, MVP-STATUS)
  • ✅ 22 unit tests passerer
  • ✅ Docker deployment ready
  • ✅ Production configuration support

🚀 Deployment Ready:

  • Container image built and tested
  • Azure AD integration configured
  • Environment variable support
  • Health monitoring endpoints
  • Structured logging with Serilog

📊 Performance Characteristics:

  • Memory cache for scope data (15min TTL)
  • Stateless design (horizontally scalable)
  • <100ms response time for cached scopes
  • Supports up to 500 resources per scope (configurable)
  • JWT tokens are compact (metadata only)

🔧 Production Checklist:

  • Configure real Azure AD app registration
  • Set up production JWT signing keys
  • Configure HTTPS/TLS termination
  • Set up monitoring and alerting
  • Configure Redis for distributed caching (optional)
  • Set up log aggregation (Azure Application Insights, etc.)

📚 Neste Steg

MVP er ferdig! For videre utvikling:

  1. Rate Limiting: Implementer per-API key limits
  2. Redis Caching: For multi-instance deployment
  3. Metrics: Prometheus/Grafana integration
  4. Admin API: Cache management endpoints
  5. Audit Logging: Compliance og security tracking