didi-lot2-backend/backend/services/gateway-auth-layer/didiKong/declarative/kong-cluster.yml
EVOTECH IT SRL 7e4f23d4c4 Optimizare scripturi build/CI si functionalitati noi Lot 2
- CI/CD GitLab (.gitlab-ci.yml): verify -> test -> build -> publish ->
  deploy cu health-gate (/api/v3/health/all) + rollback manual
- Publicare pe retele sociale (LinkedIn/Facebook) din Analysis History
  (social.ts, linkedin.ts, facebook.ts, SocialPostModal)
- Specificatii OpenAPI: agent-v3 (87 op.) si didi-framework (287 op.)
- Matrice testare acceptanta beneficiar + script dovezi API
- Fix nginx SSL: redirect port 3001 (error_page 497, absolute_redirect off)
- Adrese interne inlocuite cu hostname-uri generice (.local)
2026-07-12 13:36:08 -07:00

537 lines
21 KiB
YAML

_format_version: "3.0"
_info:
select_tags:
- product:didi
- env:prod
defaults:
route:
# Routes accept both HTTP and HTTPS (matches cluster pattern for lege365/rafai/etc).
# HAProxy LB terminates TLS at edge, forwards HTTP to Kong DP — Kong must accept HTTP
# internally or it returns 426 "Please use HTTPS protocol".
protocols: [http, https]
# ============================================================
# DIDI tenant configuration for shared Kong cluster
# Cluster CP: kong-cp.local:8001 | DP1: kong-dp1.local | DP2: kong-dp2.local | LB: kong-lb.local
# Hosts: didi365.eu (public) + www.didi365.eu + localhost (internal alias)
# Upstreams: didi.local (DIDI host) on exposed ports
# Plugins are applied per-service (NOT global) — cluster shared with lege365/rafai/biddie/notify
# ============================================================
# ============================================================
# CONSUMERS — JWT issuers preserved from local Kong (Keycloak realm didi-clients)
# ============================================================
consumers:
- username: didi-keycloak-users
custom_id: didi-keycloak-users
tags: [product:didi, env:prod]
jwt_secrets:
- algorithm: RS256
key: https://localhost/auth/realms/didi-clients
secret: 1qG51hpfTte1TGQzcAhKWjVkXsMWNIun
rsa_public_key: |-
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlpv0lfjAFPGV4hLK6jp6
EsVlxax2nPA9I2IzGwNUIK8AsKwC9qu+737jarsjewx3ya/0s1uDP0ilbdh+wEzW
Do/8jjWd0DxgTxMxCTS7DU07UZKXJWGc/Z+ansUPUcjqJ+uLTdu331z7ajK2FIZF
7yYH2WgjzApF6YSMx/dqybp/bdmBrvsPDGv1EJK4a72jV3P86WCW4ZDax2Qayw1t
iKnO3+o6xvyoVSMeVJbs9ArjpAldueMLfTZqYBSmWe/rlBSIMWYkKTSgS+pdakez
G71qs2RcSkI+GxlfJw0DJA8TfSjol6zc+EIUtAYG0pwwAqPB2PvkxMoDbL2UDkZE
/wIDAQAB
-----END PUBLIC KEY-----
- algorithm: RS256
key: https://didi365.eu/auth/realms/didi-clients
secret: IS8AJNbvC3taNROALc4bHslsZnSKELQD
rsa_public_key: |
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlpv0lfjAFPGV4hLK6jp6
EsVlxax2nPA9I2IzGwNUIK8AsKwC9qu+737jarsjewx3ya/0s1uDP0ilbdh+wEzW
Do/8jjWd0DxgTxMxCTS7DU07UZKXJWGc/Z+ansUPUcjqJ+uLTdu331z7ajK2FIZF
7yYH2WgjzApF6YSMx/dqybp/bdmBrvsPDGv1EJK4a72jV3P86WCW4ZDax2Qayw1t
iKnO3+o6xvyoVSMeVJbs9ArjpAldueMLfTZqYBSmWe/rlBSIMWYkKTSgS+pdakez
G71qs2RcSkI+GxlfJw0DJA8TfSjol6zc+EIUtAYG0pwwAqPB2PvkxMoDbL2UDkZE
/wIDAQAB
-----END PUBLIC KEY-----
# SSO cluster (sso.local) — added 2026-04-29 after migration la Keycloak SSO public.
# Same RSA public key (realm exported/imported from local with key preserved).
- algorithm: RS256
key: https://sso.local/realms/didi-clients
secret: lEyVoR2eXLgWmQQ5q6fZRbCkVJhSaTuB
rsa_public_key: |-
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlpv0lfjAFPGV4hLK6jp6
EsVlxax2nPA9I2IzGwNUIK8AsKwC9qu+737jarsjewx3ya/0s1uDP0ilbdh+wEzW
Do/8jjWd0DxgTxMxCTS7DU07UZKXJWGc/Z+ansUPUcjqJ+uLTdu331z7ajK2FIZF
7yYH2WgjzApF6YSMx/dqybp/bdmBrvsPDGv1EJK4a72jV3P86WCW4ZDax2Qayw1t
iKnO3+o6xvyoVSMeVJbs9ArjpAldueMLfTZqYBSmWe/rlBSIMWYkKTSgS+pdakez
G71qs2RcSkI+GxlfJw0DJA8TfSjol6zc+EIUtAYG0pwwAqPB2PvkxMoDbL2UDkZE
/wIDAQAB
-----END PUBLIC KEY-----
# Internal SSO host (used during transition; also kept for proxy fallback paths)
- algorithm: RS256
key: https://sso-admin.local/realms/didi-clients
secret: kPwQjA4nFsUzMxRyV8tDeBcGhJlOvIuS
rsa_public_key: |-
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlpv0lfjAFPGV4hLK6jp6
EsVlxax2nPA9I2IzGwNUIK8AsKwC9qu+737jarsjewx3ya/0s1uDP0ilbdh+wEzW
Do/8jjWd0DxgTxMxCTS7DU07UZKXJWGc/Z+ansUPUcjqJ+uLTdu331z7ajK2FIZF
7yYH2WgjzApF6YSMx/dqybp/bdmBrvsPDGv1EJK4a72jV3P86WCW4ZDax2Qayw1t
iKnO3+o6xvyoVSMeVJbs9ArjpAldueMLfTZqYBSmWe/rlBSIMWYkKTSgS+pdakez
G71qs2RcSkI+GxlfJw0DJA8TfSjol6zc+EIUtAYG0pwwAqPB2PvkxMoDbL2UDkZE
/wIDAQAB
-----END PUBLIC KEY-----
# didi-admins realm — admin-dashboard SPA auth (added 2026-05-04)
# Different RSA key than didi-clients (separate realm).
- algorithm: RS256
key: https://didi365.eu/auth/realms/didi-admins
secret: aDmInS001PuBlIcEdge2026May04PaSsKonGvErY
rsa_public_key: |-
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkcx2UE88P4j4XzWQ/bsd
0C69xZuxo/VXvhyKPj/t7u6ILIpD3/KL0sI0Ei4ZOlfjZ8PHmmEhWVwOYRCyhxgN
aq5EMk4MKmUJ0KZd6pixvufKp8ddnI/xKdUROLbdzEHSxP251uHUUCYzpsKYisZ7
t6UidWgDBDrcU42YsV4OggvPlvemtQKFawv0CpQ7BLhyTm2WUD4iq7H17OEI975i
ocC7Zqrk9itl2o0w8x9fSMOyMgsEafkVQ/KIuZA1/kKugtpG9eyYE49RFH7iQZd9
KmQow/XDxtKHfaLi1CEU4X2fHCThMWKdRQeK8N1n6qS+yanygcYJlUf8FKybgRxZ
NwIDAQAB
-----END PUBLIC KEY-----
- algorithm: RS256
key: https://sso.local/realms/didi-admins
secret: aDmInS002SsOcLuStEr2026May04PaSsKonGvErY
rsa_public_key: |-
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkcx2UE88P4j4XzWQ/bsd
0C69xZuxo/VXvhyKPj/t7u6ILIpD3/KL0sI0Ei4ZOlfjZ8PHmmEhWVwOYRCyhxgN
aq5EMk4MKmUJ0KZd6pixvufKp8ddnI/xKdUROLbdzEHSxP251uHUUCYzpsKYisZ7
t6UidWgDBDrcU42YsV4OggvPlvemtQKFawv0CpQ7BLhyTm2WUD4iq7H17OEI975i
ocC7Zqrk9itl2o0w8x9fSMOyMgsEafkVQ/KIuZA1/kKugtpG9eyYE49RFH7iQZd9
KmQow/XDxtKHfaLi1CEU4X2fHCThMWKdRQeK8N1n6qS+yanygcYJlUf8FKybgRxZ
NwIDAQAB
-----END PUBLIC KEY-----
- algorithm: RS256
key: https://sso-admin.local/realms/didi-admins
secret: aDmInS003LoCaLcLuStEr2026May04PaSsKonGv
rsa_public_key: |-
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkcx2UE88P4j4XzWQ/bsd
0C69xZuxo/VXvhyKPj/t7u6ILIpD3/KL0sI0Ei4ZOlfjZ8PHmmEhWVwOYRCyhxgN
aq5EMk4MKmUJ0KZd6pixvufKp8ddnI/xKdUROLbdzEHSxP251uHUUCYzpsKYisZ7
t6UidWgDBDrcU42YsV4OggvPlvemtQKFawv0CpQ7BLhyTm2WUD4iq7H17OEI975i
ocC7Zqrk9itl2o0w8x9fSMOyMgsEafkVQ/KIuZA1/kKugtpG9eyYE49RFH7iQZd9
KmQow/XDxtKHfaLi1CEU4X2fHCThMWKdRQeK8N1n6qS+yanygcYJlUf8FKybgRxZ
NwIDAQAB
-----END PUBLIC KEY-----
- algorithm: RS256
key: https://localhost/auth/realms/didi-admins
secret: aDmInS004DiDi11LoCaL2026May04PaSsKonGvE
rsa_public_key: |-
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkcx2UE88P4j4XzWQ/bsd
0C69xZuxo/VXvhyKPj/t7u6ILIpD3/KL0sI0Ei4ZOlfjZ8PHmmEhWVwOYRCyhxgN
aq5EMk4MKmUJ0KZd6pixvufKp8ddnI/xKdUROLbdzEHSxP251uHUUCYzpsKYisZ7
t6UidWgDBDrcU42YsV4OggvPlvemtQKFawv0CpQ7BLhyTm2WUD4iq7H17OEI975i
ocC7Zqrk9itl2o0w8x9fSMOyMgsEafkVQ/KIuZA1/kKugtpG9eyYE49RFH7iQZd9
KmQow/XDxtKHfaLi1CEU4X2fHCThMWKdRQeK8N1n6qS+yanygcYJlUf8FKybgRxZ
NwIDAQAB
-----END PUBLIC KEY-----
# ============================================================
# SERVICES (4 consolidated from 9 local) + ROUTES + PER-SERVICE PLUGINS
# ============================================================
services:
# ----------------------------------------------------------
# SERVICE 1: didi-agent-v3 (was: agent-api + agent-v3 + agent-v3-api)
# ----------------------------------------------------------
- name: didi-agent-v3
protocol: http
host: didi.local
port: 24803
retries: 5
connect_timeout: 60000
write_timeout: 660000
read_timeout: 660000
tags: [product:didi, env:prod, kind:api]
plugins:
- name: cors
tags: [product:didi, env:prod]
config:
# chrome-extension://* and moz-extension://* required for browser extension API
origins: [https://didi365.eu, https://www.didi365.eu, "chrome-extension://[a-z]+", "moz-extension://[a-z0-9-]+"]
methods: [GET, POST, PUT, DELETE, OPTIONS, PATCH]
# X-API-Key header required for extension auth
headers: [Accept, Authorization, Content-Type, X-Request-ID, X-API-Key]
exposed_headers: [X-Request-ID]
credentials: true
max_age: 3600
preflight_continue: false
- name: request-size-limiting
tags: [product:didi, env:prod]
config:
allowed_payload_size: 104857600
size_unit: bytes
require_content_length: false
- name: response-transformer
tags: [product:didi, env:prod]
config:
add:
headers: ["X-Gateway:DIDI-Kong-Cluster", "X-API-Version:2.0"]
remove:
headers: [Server, Via]
- name: correlation-id
tags: [product:didi, env:prod]
config:
header_name: X-Request-ID
generator: uuid
echo_downstream: true
routes:
# JWT-protected endpoints
- name: didi-agent-abort
paths: [/api/abort]
methods: [POST, OPTIONS]
hosts: &didi-hosts [didi365.eu, www.didi365.eu, localhost]
strip_path: false
preserve_host: true
tags: [product:didi, env:prod]
plugins:
- name: jwt
config: &jwt-config
key_claim_name: iss
claims_to_verify: [exp]
header_names: [authorization]
uri_param_names: [jwt]
run_on_preflight: true
secret_is_base64: false
maximum_expiration: 0
- name: didi-agent-admin-services
paths: [/api/v1/admin/services]
methods: [GET, OPTIONS]
hosts: *didi-hosts
strip_path: false
preserve_host: true
tags: [product:didi, env:prod]
plugins:
- name: jwt
config: *jwt-config
- name: didi-agent-analysis
paths: [/api/analysis]
hosts: *didi-hosts
strip_path: false
preserve_host: true
tags: [product:didi, env:prod]
plugins:
- name: jwt
config: *jwt-config
- name: didi-agent-analyze
paths: [/api/analyze]
methods: [POST, OPTIONS]
hosts: *didi-hosts
strip_path: false
preserve_host: true
tags: [product:didi, env:prod]
plugins:
- name: jwt
config: *jwt-config
- name: didi-agent-jobs
paths: [/api/v1/jobs]
methods: [GET, POST, DELETE, OPTIONS]
hosts: *didi-hosts
strip_path: false
preserve_host: true
tags: [product:didi, env:prod]
plugins:
- name: jwt
config: *jwt-config
- name: didi-agent-pipelines
paths: [/api/pipelines]
methods: [GET, OPTIONS]
hosts: *didi-hosts
strip_path: false
preserve_host: true
tags: [product:didi, env:prod]
plugins:
- name: jwt
config: *jwt-config
- name: didi-agent-progress
paths: [/api/progress]
hosts: *didi-hosts
strip_path: false
preserve_host: true
tags: [product:didi, env:prod]
plugins:
- name: jwt
config: *jwt-config
- name: didi-agent-sessions
paths: [/api/sessions]
methods: [GET, OPTIONS]
hosts: *didi-hosts
strip_path: false
preserve_host: true
tags: [product:didi, env:prod]
plugins:
- name: jwt
config: *jwt-config
- name: didi-agent-storage
paths: [/api/storage]
methods: [GET, POST, DELETE, OPTIONS]
hosts: *didi-hosts
strip_path: false
preserve_host: true
tags: [product:didi, env:prod]
plugins:
- name: jwt
config: *jwt-config
- name: didi-agent-subscriptions-v1
# Legacy v1 endpoint on agent-v3 (current v2 is on framework-api)
paths: [/api/v1/subscriptions]
methods: [GET, POST, DELETE, OPTIONS]
hosts: *didi-hosts
strip_path: false
preserve_host: true
tags: [product:didi, env:prod, legacy:v1]
plugins:
- name: jwt
config: *jwt-config
- name: didi-agent-upload
paths: [/api/upload]
methods: [POST, OPTIONS]
hosts: *didi-hosts
strip_path: false
preserve_host: true
tags: [product:didi, env:prod]
plugins:
- name: jwt
config: *jwt-config
# Public endpoints (no JWT)
- name: didi-agent-health
# Local had strip_path=true: /agent/health -> "/" on agent-v3 (root, returns 200)
paths: [/agent/health]
methods: [GET, OPTIONS]
hosts: *didi-hosts
strip_path: true
preserve_host: true
tags: [product:didi, env:prod, public:true]
- name: didi-agent-status
paths: [/api/status]
methods: [GET, OPTIONS]
hosts: *didi-hosts
strip_path: false
preserve_host: true
tags: [product:didi, env:prod, public:true]
- name: didi-agent-media-public
# Public media playback proxy (range-supported, served by agent-v3 itself)
paths: [/api/v3/media/file]
hosts: *didi-hosts
strip_path: false
preserve_host: true
tags: [product:didi, env:prod, public:true]
- name: didi-agent-v3-prefix
# Edge nginx forwards /agent-v3/* here. Local Kong stripped the prefix
# (e.g. /agent-v3/api/v3/health -> /api/v3/health on agent-v3).
# Local had JWT plugin on this route — preserved here to match security model.
paths: [/agent-v3]
hosts: *didi-hosts
strip_path: true
preserve_host: true
tags: [product:didi, env:prod]
plugins:
- name: jwt
config: *jwt-config
# Browser extension API endpoints — NO JWT (X-API-Key validated in agent-v3).
# All four routes share the same request-transformer pattern + rate-limit.
# Regex paths (~ prefix) with `$` anchor prevent /analyze matching /analyze-async.
- name: didi-agent-extension-analyze
# Sync analyze (legacy — small text only, hits Cloudflare 100s timeout otherwise)
paths: [~/agent-v3/api/v3/pipeline/extension/analyze$]
methods: [POST, OPTIONS]
hosts: *didi-hosts
strip_path: false
preserve_host: true
tags: [product:didi, env:prod, public:true, auth:api-key]
plugins:
- name: request-transformer
config:
replace:
uri: /api/v3/pipeline/extension/analyze
- name: rate-limiting
config: &ext-rate-limit
minute: 30
hour: 500
policy: local
limit_by: header
header_name: X-API-Key
fault_tolerant: true
hide_client_headers: false
error_code: 429
error_message: "API rate limit exceeded"
- name: didi-agent-extension-analyze-async
# Async dispatch — returns 202 + session_id (used by extension for all flows)
paths: [~/agent-v3/api/v3/pipeline/extension/analyze-async$]
methods: [POST, OPTIONS]
hosts: *didi-hosts
strip_path: false
preserve_host: true
tags: [product:didi, env:prod, public:true, auth:api-key]
plugins:
- name: request-transformer
config:
replace:
uri: /api/v3/pipeline/extension/analyze-async
- name: rate-limiting
config: *ext-rate-limit
- name: didi-agent-extension-upload
# Multipart media upload (screenshot/video) — multer expects "file" field
paths: [~/agent-v3/api/v3/pipeline/extension/upload$]
methods: [POST, OPTIONS]
hosts: *didi-hosts
strip_path: false
preserve_host: true
tags: [product:didi, env:prod, public:true, auth:api-key]
plugins:
- name: request-transformer
config:
replace:
uri: /api/v3/pipeline/extension/upload
- name: rate-limiting
config: *ext-rate-limit
- name: didi-agent-extension-status
# Polling endpoint with sessionId capture — rewrite preserves the UUID
paths: ['~/agent-v3/api/v3/pipeline/extension/status/(?<sid>[\w-]+)$']
methods: [GET, OPTIONS]
hosts: *didi-hosts
strip_path: false
preserve_host: true
tags: [product:didi, env:prod, public:true, auth:api-key]
plugins:
- name: request-transformer
config:
replace:
uri: /api/v3/pipeline/extension/status/$(uri_captures.sid)
- name: rate-limiting
config:
# Polling can hit this every 2s — bump per-minute limit
minute: 120
hour: 2000
policy: local
limit_by: header
header_name: X-API-Key
fault_tolerant: true
hide_client_headers: false
error_code: 429
error_message: "Polling rate limit exceeded"
# ----------------------------------------------------------
# SERVICE 2: didi-framework (was: framework-api + didi-framework)
# ----------------------------------------------------------
- name: didi-framework
protocol: http
host: didi.local
port: 3005
retries: 5
connect_timeout: 60000
write_timeout: 60000
read_timeout: 60000
tags: [product:didi, env:prod, kind:api]
plugins:
- name: cors
tags: [product:didi, env:prod]
config:
origins: [https://didi365.eu, https://www.didi365.eu]
methods: [GET, POST, PUT, DELETE, OPTIONS, PATCH]
headers: [Accept, Authorization, Content-Type, X-Request-ID]
exposed_headers: [X-Request-ID]
credentials: true
max_age: 3600
preflight_continue: false
- name: request-size-limiting
tags: [product:didi, env:prod]
config:
allowed_payload_size: 104857600
size_unit: bytes
require_content_length: false
- name: response-transformer
tags: [product:didi, env:prod]
config:
add:
headers: ["X-Gateway:DIDI-Kong-Cluster", "X-API-Version:2.0"]
remove:
headers: [Server, Via]
- name: correlation-id
tags: [product:didi, env:prod]
config:
header_name: X-Request-ID
generator: uuid
echo_downstream: true
routes:
- name: didi-framework-auth
paths: [/api/auth]
methods: [GET, POST, PUT, DELETE, OPTIONS]
hosts: *didi-hosts
strip_path: false
preserve_host: true
tags: [product:didi, env:prod]
plugins:
- name: jwt
config: *jwt-config
- name: didi-framework-auth-verify-email
# PUBLIC route — must beat /api/auth (JWT) on priority
paths: [/api/auth/verify-email]
methods: [GET, POST]
hosts: *didi-hosts
strip_path: false
preserve_host: true
regex_priority: 100
tags: [product:didi, env:prod, public:true]
- name: didi-framework-history
paths: [/api/history]
hosts: *didi-hosts
strip_path: false
preserve_host: true
tags: [product:didi, env:prod]
plugins:
- name: jwt
config: *jwt-config
- name: didi-framework-subscriptions
# Current /api/subscriptions endpoint (v2). Legacy /api/v1/subscriptions is on agent-v3.
paths: [/api/subscriptions]
methods: [GET, POST, DELETE, OPTIONS]
hosts: *didi-hosts
strip_path: false
preserve_host: true
tags: [product:didi, env:prod]
plugins:
- name: jwt
config: *jwt-config
- name: didi-framework-waitlist
# PUBLIC waitlist signup. Local Kong had this duplicated (one with JWT, one without).
# Keeping no-JWT version — public signup is correct behavior.
paths: [/api/waitlist]
methods: [GET, POST, DELETE, OPTIONS]
hosts: *didi-hosts
strip_path: false
preserve_host: true
tags: [product:didi, env:prod, public:true]
- name: didi-framework-direct
# Legacy /framework prefix — local had strip_path=true so /framework/api/X -> /api/X on framework
paths: [/framework]
hosts: *didi-hosts
strip_path: true
preserve_host: true
tags: [product:didi, env:prod]
# ----------------------------------------------------------
# NOTE: didi-admin NOT migrated — internal-only (VPN access to didi.local directly).
# Will be revisited after admin nginx is replaced with simpler setup.
# ----------------------------------------------------------
# ----------------------------------------------------------
# NOTE: didi-keycloak service REMOVED 2026-04-29.
# DIDI now uses the external SSO cluster (https://sso.local/realms/didi-clients) directly
# — SPA goes browser→sso.local, no proxy through cluster Kong needed.
# JWT consumer didi-keycloak-users still validates tokens for SSO issuer.
# ----------------------------------------------------------