├── .github └── workflows │ ├── main.yml │ └── sonar.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── SECURITY.md ├── helm ├── .helmignore ├── Chart.yaml ├── templates │ ├── auto.yaml │ ├── configmap-certificate.yaml │ ├── configmap-truststore.yaml │ ├── eks-ingress.yaml │ ├── hazelcast-service.yaml │ ├── internal-capi-service.yaml │ ├── local-ingress.yaml │ ├── metrics.yaml │ └── server.yaml └── values.yaml ├── pom.xml └── src ├── main ├── java │ └── io │ │ └── surisoft │ │ └── capi │ │ ├── CapiGateway.java │ │ ├── builder │ │ ├── ConsistencyRouteBuilder.java │ │ ├── ConsulKVStoreBuilder.java │ │ ├── DirectRouteProcessor.java │ │ ├── ErrorRoute.java │ │ └── KafkaProcessor.java │ │ ├── cache │ │ ├── CacheConfiguration.java │ │ ├── KafkaConfig.java │ │ └── StickySessionCacheManager.java │ │ ├── configuration │ │ ├── CamelStartupListener.java │ │ ├── CapiApplicationListener.java │ │ ├── CapiConfiguration.java │ │ ├── CapiCorsFilter.java │ │ ├── CapiCorsFilterStrategy.java │ │ ├── CapiSslContextHolder.java │ │ ├── CapiTracerConfiguration.java │ │ ├── ConsulAutoConfiguration.java │ │ └── UndertowErrorListener.java │ │ ├── controller │ │ ├── CapiErrorInterface.java │ │ ├── ClientController.java │ │ ├── DefinitionController.java │ │ ├── ErrorController.java │ │ └── PublicHealthController.java │ │ ├── exception │ │ ├── AuthorizationException.java │ │ ├── CapiUndertowException.java │ │ └── RestTemplateErrorHandler.java │ │ ├── kafka │ │ ├── CapiEventSerializer.java │ │ ├── CapiInstance.java │ │ ├── CapiKafkaEvent.java │ │ └── CapiKafkaEventDeserializer.java │ │ ├── metrics │ │ ├── HealthController.java │ │ ├── Info.java │ │ ├── KVStore.java │ │ ├── OpenAPIDefinition.java │ │ ├── Routes.java │ │ ├── Truststore.java │ │ └── WSRoutes.java │ │ ├── oidc │ │ ├── Oauth2ClientManager.java │ │ ├── Oauth2Constants.java │ │ ├── Oauth2Exception.java │ │ ├── SSEAuthorization.java │ │ └── WebsocketAuthorization.java │ │ ├── processor │ │ ├── AuthorizationProcessor.java │ │ ├── ContentTypeValidator.java │ │ ├── HttpErrorProcessor.java │ │ ├── MetricsProcessor.java │ │ ├── OpenApiProcessor.java │ │ ├── StickyLoadBalancer.java │ │ ├── TenantAwareLoadBalancer.java │ │ └── ThrottleProcessor.java │ │ ├── schema │ │ ├── AliasInfo.java │ │ ├── CapiEvent.java │ │ ├── CapiInfo.java │ │ ├── CapiRestError.java │ │ ├── ConsulKeyStoreEntry.java │ │ ├── ConsulObject.java │ │ ├── Group.java │ │ ├── HttpMethod.java │ │ ├── HttpProtocol.java │ │ ├── Mapping.java │ │ ├── MappingId.java │ │ ├── OIDCClient.java │ │ ├── OpaResult.java │ │ ├── RouteDetails.java │ │ ├── RouteDetailsEndpointInfo.java │ │ ├── RouteEndpointInfo.java │ │ ├── RunningTenant.java │ │ ├── SSEClient.java │ │ ├── Service.java │ │ ├── ServiceMeta.java │ │ ├── State.java │ │ ├── StickySession.java │ │ ├── SubscriptionGroup.java │ │ ├── ThrottleServiceObject.java │ │ └── WebsocketClient.java │ │ ├── service │ │ ├── CapiEventNotifier.java │ │ ├── CapiTrustManager.java │ │ ├── ConsistencyChecker.java │ │ ├── ConsulKVStore.java │ │ ├── ConsulNodeDiscovery.java │ │ └── OpaService.java │ │ ├── tracer │ │ ├── CapiTracer.java │ │ ├── CapiTracerServerRequestAdapter.java │ │ ├── CapiTracerServerResponseAdapter.java │ │ └── CapiUndertowTracer.java │ │ ├── undertow │ │ ├── CAPILoadBalancerProxyClient.java │ │ ├── CAPIProxyHandler.java │ │ ├── SSEGateway.java │ │ └── WebsocketGateway.java │ │ └── utils │ │ ├── Constants.java │ │ ├── ErrorMessage.java │ │ ├── HttpUtils.java │ │ ├── RouteUtils.java │ │ ├── SSEUtils.java │ │ ├── ServiceUtils.java │ │ └── WebsocketUtils.java └── resources │ ├── application.yaml │ ├── capi.txt │ └── logback-spring.xml └── test ├── java └── io │ └── surisoft │ └── capi │ ├── configuration │ ├── CapiCorsFilterStrategyTest.java │ └── CapiCorsFilterTest.java │ ├── controller │ ├── CapiErrorInterfaceTest.java │ ├── ErrorControllerTest.java │ ├── TestCapiConfiguration.java │ ├── TestCertificateController.java │ ├── TestConsulAutoConfiguration.java │ ├── TestConsulNodeDiscovery.java │ ├── TestHttpUtils.java │ ├── TestLoadBalancer.java │ ├── TestRouteUtils.java │ └── TestServiceUtils.java │ ├── processor │ └── OpenApiProcessorTest.java │ └── schema │ ├── AliasInfoTest.java │ ├── CapiInfoTest.java │ ├── CapiRestErrorTest.java │ ├── ConsulObjectTest.java │ ├── MappingIdTest.java │ ├── MappingTest.java │ ├── OIDCClientTest.java │ ├── RouteDetailsEndpointInfoTest.java │ ├── RouteDetailsTest.java │ ├── RouteEndpointInfoTest.java │ ├── RunningTenantTest.java │ ├── ServiceMetaTest.java │ ├── ServiceTest.java │ ├── StickySessionTest.java │ └── WebsocketClientTest.java └── resources ├── application.yaml ├── logback-test.xml ├── test-cert-application.properties ├── test-consul-application.properties ├── test-consul-kv-application.properties ├── test-observability-application.properties └── test-openapi-application.properties /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: capi 2 | on: 3 | push: 4 | branches: [ master ] 5 | pull_request: 6 | branches: [ master ] 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Set up JDK 21 12 | uses: actions/setup-java@v1.4.4 13 | with: 14 | java-version: 21 15 | - uses: actions/checkout@v3.5.3 16 | 17 | - name: Set up Maven settings 18 | run: | 19 | mkdir -p ~/.m2 20 | echo "github${{ github.actor }}${{ secrets.GITHUB_TOKEN }}" > ~/.m2/settings.xml 21 | - name: Set Release version env variable 22 | run: | 23 | echo "RELEASE_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)" >> $GITHUB_ENV 24 | - name: Build with Maven 25 | run: mvn package -DskipTests --file pom.xml 26 | - name: Docker login 27 | run: | 28 | docker login -u surisoft -p ${{ secrets.DOCKER_HUB_PWD }} 29 | - name: Build and push multi-platform image 30 | run: | 31 | docker buildx create --use 32 | docker buildx build . \ 33 | --platform linux/amd64,linux/arm64 \ 34 | --build-arg "CAPI_VERSION=${{ env.RELEASE_VERSION }}" \ 35 | --file Dockerfile \ 36 | --tag surisoft/capi:${{ env.RELEASE_VERSION }} \ 37 | --tag surisoft/capi:latest \ 38 | --push 39 | -------------------------------------------------------------------------------- /.github/workflows/sonar.yml: -------------------------------------------------------------------------------- 1 | name: Sonar Scan 2 | on: 3 | push: 4 | branches: 5 | - master 6 | - 4.3.02 7 | pull_request: 8 | types: [opened, synchronize, reopened] 9 | jobs: 10 | build: 11 | name: Build 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3.5.3 15 | with: 16 | fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis 17 | - name: Set up JDK 19 18 | uses: actions/setup-java@v1.4.4 19 | with: 20 | java-version: 19 21 | - name: Cache SonarCloud packages 22 | uses: actions/cache@v1 23 | with: 24 | path: ~/.sonar/cache 25 | key: ${{ runner.os }}-sonar 26 | restore-keys: ${{ runner.os }}-sonar 27 | - name: Cache Maven packages 28 | uses: actions/cache@v1 29 | with: 30 | path: ~/.m2 31 | key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} 32 | restore-keys: ${{ runner.os }}-m2 33 | - name: Build and analyze 34 | env: 35 | GITHUB_TOKEN: ${{ secrets.REPO_TOKEN }} # Needed to get PR information, if any 36 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 37 | run: mvn -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=surisoft-io_capi-lb -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | target 3 | *.iml 4 | 5 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM eclipse-temurin:21-jdk-alpine 2 | 3 | ARG CAPI_VERSION=4.8.20 4 | 5 | RUN mkdir /capi 6 | RUN mkdir /capi/logs 7 | 8 | ARG JAR_FILE=target/capi-${CAPI_VERSION}.jar 9 | COPY ${JAR_FILE} /capi/app.jar 10 | 11 | ENTRYPOINT exec java -XX:InitialHeapSize=512m \ 12 | -XX:+UseG1GC \ 13 | -XX:MaxGCPauseMillis=100 \ 14 | -XX:+ParallelRefProcEnabled \ 15 | -XX:+HeapDumpOnOutOfMemoryError \ 16 | -XX:HeapDumpPath=/capi/logs/heap-dump.hprof \ 17 | -jar /capi/app.jar 18 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Currently supported versions: 6 | 7 | | Version | Supported | 8 | | ------- | ------------------ | 9 | | 4.4.03 | :white_check_mark: | 10 | | 4.4.04 | :white_check_mark: | 11 | | < 4.0 | :x: | 12 | 13 | ## Reporting a Vulnerability 14 | 15 | If you find any vulnerability or bug, please, open an issue, and we will contact you back. 16 | You can also send us an email to info@surisoft.io 17 | Thanks. 18 | -------------------------------------------------------------------------------- /helm/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /helm/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: capi 3 | description: CAPI Helm chart for Kubernetes 4 | type: application 5 | version: 0.1.0 6 | appVersion: "1.0.0" 7 | -------------------------------------------------------------------------------- /helm/templates/auto.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: autoscaling/v2 2 | kind: HorizontalPodAutoscaler 3 | metadata: 4 | namespace: {{ .Values.capi.namespace }} 5 | name: {{ .Values.capi.name }} 6 | spec: 7 | scaleTargetRef: 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | name: {{ .Values.capi.name }} 11 | minReplicas: 3 12 | maxReplicas: 30 13 | behavior: 14 | scaleUp: 15 | stabilizationWindowSeconds: 60 16 | policies: 17 | - type: Pods 18 | value: 1 19 | periodSeconds: 30 20 | selectPolicy: Max 21 | scaleDown: 22 | stabilizationWindowSeconds: 90 23 | policies: 24 | - type: Pods 25 | value: 1 26 | periodSeconds: 30 27 | selectPolicy: Max 28 | metrics: 29 | - type: Resource 30 | resource: 31 | name: cpu 32 | target: 33 | type: Utilization 34 | averageUtilization: 60 35 | - type: Resource 36 | resource: 37 | name: memory 38 | target: 39 | type: Utilization 40 | averageUtilization: 70 -------------------------------------------------------------------------------- /helm/templates/configmap-certificate.yaml: -------------------------------------------------------------------------------- 1 | {{ if .Values.server.ssl.enabled }} 2 | apiVersion: v1 3 | binaryData: 4 | {{ .Values.certificate.name }}: {{ .Values.certificate.encoded }} 5 | kind: ConfigMap 6 | metadata: 7 | namespace: {{ .Values.namespace }} 8 | name: capi-certificate 9 | {{ end }} 10 | -------------------------------------------------------------------------------- /helm/templates/configmap-truststore.yaml: -------------------------------------------------------------------------------- 1 | {{ if .Values.capi.trust.store.enabled }} 2 | apiVersion: v1 3 | binaryData: 4 | {{ .Values.trust.store.name}}: {{ .Values.trust.store.encoded}} 5 | kind: ConfigMap 6 | metadata: 7 | namespace: {{ .Values.namespace }} 8 | name: truststore 9 | {{ end }} 10 | -------------------------------------------------------------------------------- /helm/templates/eks-ingress.yaml: -------------------------------------------------------------------------------- 1 | {{ if .Values.capi.deployment.eks }} 2 | apiVersion: networking.k8s.io/v1 3 | kind: Ingress 4 | metadata: 5 | namespace: {{ .Values.capi.namespace }} 6 | labels: 7 | service: {{ .Values.capi.name }} 8 | name: {{ .Values.capi.name }} 9 | annotations: 10 | alb.ingress.kubernetes.io/certificate-arn: {{ .Values.ssl.certificate}} 11 | alb.ingress.kubernetes.io/subnets: {{ .Values.network.subnet }} 12 | alb.ingress.kubernetes.io/security-groups: {{ .Values.network.security.group }} 13 | alb.ingress.kubernetes.io/scheme: {{ .Values.network.scheme }} 14 | external-dns.alpha.kubernetes.io/hostname: {{ .Values.network.host }} 15 | alb.ingress.kubernetes.io/load-balancer-name: {{ .Values.network.loadbalancer }} 16 | alb.ingress.kubernetes.io/backend-protocol: {{ .Values.network.backend.protocol }} 17 | alb.ingress.kubernetes.io/target-type: {{ .Values.network.target.ip }} 18 | alb.ingress.kubernetes.io/healthcheck-path: /health 19 | alb.ingress.kubernetes.io/success-codes: "200" 20 | alb.ingress.kubernetes.io/listen-port: '[{"HTTPS": 443}]' 21 | spec: 22 | ingressClassName: alb 23 | tls: 24 | - hosts: 25 | - {{ .Values.network.host }} 26 | rules: 27 | - host: {{ .Values.network.host }} 28 | http: 29 | paths: 30 | - path: / 31 | pathType: Prefix 32 | backend: 33 | service: 34 | name: {{ .Values.capi.name }} 35 | port: 36 | number: 8380 37 | {{ end }} -------------------------------------------------------------------------------- /helm/templates/hazelcast-service.yaml: -------------------------------------------------------------------------------- 1 | {{ if .Values.capi.throttling.enabled }} 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: hazelcast-service 6 | namespace: {{ .Values.capi.namespace }} 7 | spec: 8 | clusterIP: None 9 | selector: 10 | app: {{ .Values.capi.name }} 11 | ports: 12 | - port: 5701 13 | targetPort: 5701 14 | {{ end }} -------------------------------------------------------------------------------- /helm/templates/internal-capi-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | namespace: {{ .Values.capi.namespace }} 5 | labels: 6 | service: {{ .Values.capi.name }} 7 | name: {{ .Values.capi.name }} 8 | annotations: 9 | spec: 10 | ports: 11 | - name: http 12 | port: 8380 13 | targetPort: 8380 14 | type: ClusterIP 15 | selector: 16 | service: {{ .Values.capi.name }} -------------------------------------------------------------------------------- /helm/templates/local-ingress.yaml: -------------------------------------------------------------------------------- 1 | {{ if .Values.capi.deployment.local }} 2 | apiVersion: networking.k8s.io/v1 3 | kind: Ingress 4 | metadata: 5 | namespace: {{ .Values.capi.namespace }} 6 | name: {{ .Values.capi.name }}-ingress 7 | spec: 8 | rules: 9 | - host: ingress.local 10 | http: 11 | paths: 12 | - path: / 13 | pathType: Prefix 14 | backend: 15 | service: 16 | name: {{ .Values.capi.name }} 17 | port: 18 | number: 8380 19 | {{ end }} -------------------------------------------------------------------------------- /helm/templates/metrics.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | namespace: {{ .Values.capi.namespace }} 5 | labels: 6 | service: capi 7 | name: capi-metrics 8 | spec: 9 | ports: 10 | - name: http 11 | port: 80 12 | targetPort: 8381 13 | type: ClusterIP 14 | selector: 15 | service: {{ .Values.capi.name }} -------------------------------------------------------------------------------- /helm/templates/server.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | namespace: {{ .Values.capi.namespace }} 5 | labels: 6 | service: {{ .Values.capi.name }} 7 | name: {{ .Values.capi.name }} 8 | spec: 9 | selector: 10 | matchLabels: 11 | service: {{ .Values.capi.name }} 12 | strategy: 13 | type: Recreate 14 | template: 15 | metadata: 16 | namespace: {{ .Values.capi.namespace }} 17 | labels: 18 | service: {{ .Values.capi.name }} 19 | spec: 20 | containers: 21 | - env: 22 | - name: capi.strict 23 | value: {{ quote .Values.capi.instance.strict }} 24 | - name: capi.namespace 25 | value: {{ .Values.capi.instance.name }} 26 | - name: spring.profiles.active 27 | value: {{ quote .Values.spring.profiles.active }} 28 | - name: capi.consul.discovery.enabled 29 | value: {{ quote .Values.capi.consul.discovery.enabled }} 30 | - name: spring.servlet.multipart.max-file-size 31 | value: 80MB 32 | - name: spring.servlet.multipart.max-request-size 33 | value: 80MB 34 | - name: capi.consul.discovery.timer.interval 35 | value: {{ quote .Values.capi.consul.discovery.timer.interval }} 36 | - name: capi.consul.hosts 37 | value: {{ quote .Values.capi.consul.hosts }} 38 | ## Trust Store Configuration 39 | - name: capi.trust.store.enabled 40 | value: {{ quote .Values.capi.trust.store.enabled }} 41 | - name: capi.trust.store.path 42 | value: {{ quote .Values.capi.trust.store.path }} 43 | - name: capi.trust.store.password 44 | value: {{ quote .Values.capi.trust.store.password }} 45 | - name: capi.oauth2.provider.enabled 46 | value: {{ quote .Values.oauth2.provider.enabled }} 47 | - name: capi.oauth2.provider.keys 48 | value: {{ quote .Values.oauth2.provider.keys }} 49 | - name: capi.gateway.cors.management.enabled 50 | value: 'true' 51 | - name: capi.opa.enabled 52 | value: {{ quote .Values.opa.enabled }} 53 | - name: capi.opa.endpoint 54 | value: {{ quote .Values.opa.endpoint }} 55 | - name: camel.servlet.mapping.context-path 56 | value: {{ quote .Values.capi.context.path }} 57 | - name: capi.traces.enabled 58 | value: {{ quote .Values.capi.traces.enabled }} 59 | - name: capi.traces.endpoint 60 | value: {{ quote .Values.capi.traces.endpoint }} 61 | - name: server.undertow.accesslog.enabled 62 | value: 'false' 63 | - name: server.undertow.accesslog.rotate 64 | value: 'true' 65 | - name: server.undertow.accesslog.dir 66 | value: accesslogs 67 | - name: pod-name 68 | valueFrom: 69 | fieldRef: 70 | fieldPath: metadata.name 71 | image: {{ .Values.image.repository }} 72 | imagePullPolicy: Always 73 | name: {{ quote .Values.capi.name }} 74 | ports: 75 | - containerPort: 8380 76 | resources: 77 | requests: 78 | memory: "512Mi" 79 | cpu: "250m" 80 | limits: 81 | memory: "2Gi" 82 | cpu: "1" 83 | volumeMounts: 84 | {{ if .Values.server.ssl.enabled }} 85 | - name: capi-certificate 86 | mountPath: /keys/capi.jks 87 | subPath: capi.jks 88 | {{ end }} 89 | {{ if .Values.capi.trust.store.enabled }} 90 | - name: truststore 91 | mountPath: /keys/truststore.jks 92 | subPath: truststore.jks 93 | {{ end }} 94 | restartPolicy: Always 95 | volumes: 96 | {{ if .Values.server.ssl.enabled }} 97 | - name: capi-certificate 98 | configMap: 99 | name: capi-certificate 100 | {{ end }} 101 | {{ if .Values.capi.trust.store.enabled }} 102 | - name: truststore 103 | configMap: 104 | name: truststore 105 | {{ end }} 106 | status: {} 107 | 108 | -------------------------------------------------------------------------------- /helm/values.yaml: -------------------------------------------------------------------------------- 1 | replicaCount: 2 2 | 3 | image: 4 | repository: surisoft/capi:4.11.1 5 | pullPolicy: Always 6 | tag: "" 7 | 8 | ssl: 9 | certificate: 10 | 11 | network: 12 | subnet: 13 | security: 14 | group: 15 | scheme: 16 | host: 17 | loadbalancer: 18 | backend: 19 | protocol: 20 | target: 21 | ip: 22 | 23 | service: 24 | port: 8380 25 | 26 | management: 27 | port: 8381 28 | capi: 29 | instance: 30 | name: capi 31 | strict: false 32 | deployment: 33 | eks: false 34 | local: false 35 | name: capi 36 | namespace: capi-default 37 | throttling: 38 | enabled: true 39 | metrics: 40 | host: capi-metrics.capi-default.svc.cluster.local 41 | context: 42 | path: /api/* 43 | trust: 44 | store: 45 | enabled: false 46 | path: 47 | password: 48 | consul: 49 | hosts: http://host.docker.internal:8500 50 | discovery: 51 | enabled: true 52 | timer: 53 | interval: 30000 54 | traces: 55 | enabled: false 56 | endpoint: 57 | 58 | oauth2: 59 | provider: 60 | enabled: false 61 | keys: 62 | 63 | opa: 64 | enabled: false 65 | endpoint: 66 | 67 | logging: 68 | level: 69 | root: INFO 70 | io: 71 | surisoft: 72 | capi: 73 | lb: TRACE 74 | server: 75 | ssl: 76 | enabled: false 77 | key: 78 | store: 79 | type: JKS 80 | path: 81 | alias: 82 | password: 83 | spring: 84 | profiles: 85 | active: dev 86 | #Default trust store and certificate for localhost 87 | trust: 88 | store: 89 | name: 90 | encoded: 91 | certificate: 92 | name: 93 | encoded: 94 | 95 | serviceAccount: 96 | create: true 97 | annotations: {} 98 | name: "" 99 | 100 | podAnnotations: {} 101 | 102 | podSecurityContext: {} 103 | 104 | securityContext: {} 105 | 106 | resources: {} 107 | 108 | autoscaling: 109 | enabled: true 110 | minReplicas: 2 111 | maxReplicas: 10 112 | targetCPUUtilizationPercentage: 80 113 | 114 | nodeSelector: {} 115 | 116 | tolerations: [] 117 | 118 | affinity: {} 119 | -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/CapiGateway.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class CapiGateway { 8 | public static void main(String[] args) { 9 | SpringApplication.run(CapiGateway.class, args); 10 | } 11 | } -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/builder/ConsistencyRouteBuilder.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.builder; 2 | 3 | import org.apache.camel.builder.RouteBuilder; 4 | import org.springframework.stereotype.Component; 5 | 6 | @Component 7 | public class ConsistencyRouteBuilder extends RouteBuilder { 8 | @Override 9 | public void configure() { 10 | log.debug("Creating CAPI Consistency Checker"); 11 | from("timer:consistency-checker?period=20000") 12 | .to("bean:consistencyChecker?method=process") 13 | .routeId("consistency-checker-service"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/builder/ConsulKVStoreBuilder.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.builder; 2 | 3 | import org.apache.camel.builder.RouteBuilder; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | @ConditionalOnProperty(prefix = "capi.consul.kv", name = "enabled", havingValue = "true") 10 | public class ConsulKVStoreBuilder extends RouteBuilder { 11 | 12 | private final int interval; 13 | 14 | public ConsulKVStoreBuilder(@Value("${capi.consul.kv.timer.interval}") int interval) { 15 | this.interval = interval; 16 | } 17 | 18 | @Override 19 | public void configure() throws Exception { 20 | log.debug("Creating CAPI Consul KV Store"); 21 | from("timer:consul-KV-Store?period=" + interval) 22 | .to("bean:consulKVStore?method=process") 23 | .routeId("consul-key-value-store"); 24 | } 25 | } -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/builder/ErrorRoute.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.builder; 2 | 3 | import io.surisoft.capi.schema.CapiRestError; 4 | import io.surisoft.capi.utils.Constants; 5 | import io.surisoft.capi.utils.HttpUtils; 6 | import org.apache.camel.Exchange; 7 | import org.apache.camel.Processor; 8 | import org.apache.camel.builder.RouteBuilder; 9 | import org.springframework.stereotype.Component; 10 | 11 | @Component 12 | public class ErrorRoute extends RouteBuilder { 13 | 14 | private final HttpUtils httpUtils; 15 | 16 | public ErrorRoute(HttpUtils httpUtils) { 17 | this.httpUtils = httpUtils; 18 | } 19 | 20 | @Override 21 | public void configure() { 22 | from("direct:error") 23 | .setHeader("Content-Type", constant("application/json")) 24 | .process(new Processor() { 25 | @Override 26 | public void process(Exchange exchange) throws Exception { 27 | CapiRestError capiRestError = new CapiRestError(); 28 | if(exchange.getIn().getHeader(Constants.CAPI_URI_IN_ERROR) != null) { 29 | capiRestError.setHttpUri(exchange.getIn().getHeader(Constants.CAPI_URI_IN_ERROR, String.class)); 30 | } 31 | if(exchange.getIn().getHeader(Constants.ROUTE_ID_HEADER) != null) { 32 | capiRestError.setRouteID(exchange.getIn().getHeader(Constants.ROUTE_ID_HEADER, String.class)); 33 | } 34 | if(exchange.getIn().getHeader("x-b3-traceid") != null || exchange.getIn().getHeader("X-B3-Traceid", String.class) != null) { 35 | capiRestError.setTraceID(exchange.getIn().getHeader(Constants.TRACE_ID_HEADER, String.class)); 36 | } 37 | if(exchange.getIn().getHeader(Constants.REASON_MESSAGE_HEADER) != null && exchange.getIn().getHeader(Constants.REASON_CODE_HEADER) != null) { 38 | exchange.setProperty("serviceResponseCode", exchange.getIn().getHeader(Constants.REASON_CODE_HEADER)); 39 | capiRestError.setErrorMessage(exchange.getIn().getHeader(Constants.REASON_MESSAGE_HEADER, String.class)); 40 | capiRestError.setErrorCode(exchange.getIn().getHeader(Constants.REASON_CODE_HEADER, Integer.class)); 41 | } else { 42 | exchange.setProperty("serviceResponseCode", 400); 43 | capiRestError.setErrorMessage("Unknown error"); 44 | capiRestError.setErrorCode(400); 45 | } 46 | exchange.getIn().setBody(httpUtils.proxyErrorMapper(capiRestError)); 47 | } 48 | }) 49 | .setHeader(Exchange.HTTP_RESPONSE_CODE, exchangeProperty("serviceResponseCode")) 50 | .removeHeader(Constants.REASON_MESSAGE_HEADER) 51 | .removeHeader(Constants.REASON_CODE_HEADER) 52 | .routeId("error-route") 53 | .end(); 54 | } 55 | } -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/builder/KafkaProcessor.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.builder; 2 | 3 | import org.apache.camel.builder.RouteBuilder; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | @ConditionalOnProperty(prefix = "capi.kafka", name = "enabled", havingValue = "true") 10 | public class KafkaProcessor extends RouteBuilder { 11 | 12 | @Value("${capi.kafka.host}") 13 | private String capiKafkaHost; 14 | 15 | @Value("${capi.kafka.topic}") 16 | private String capiKafkaTopic; 17 | 18 | @Value("${capi.kafka.group-instance}") 19 | private String capiKafkaGroupInstance; 20 | 21 | @Value("${capi.kafka.group-id}") 22 | private String capiKafkaGroupId; 23 | 24 | @Value("${capi.kafka.ssl.enabled}") 25 | private boolean capiKafkaSslEnabled; 26 | 27 | @Value("${capi.kafka.ssl.keystore.location}") 28 | private String capiKafkaSslKeystoreLocation; 29 | 30 | @Value("${capi.kafka.ssl.keystore.password}") 31 | private String capiKafkaSslKeystorePassword; 32 | 33 | @Value("${capi.kafka.ssl.truststore.location}") 34 | private String capiKafkaSslTruststoreLocation; 35 | 36 | @Value("${capi.kafka.ssl.truststore.password}") 37 | private String capiKafkaSslTruststorePassword; 38 | 39 | @Override 40 | public void configure() { 41 | from("kafka:" + buildEndpoint()).to("bean:capiKafkaEventProcessor?method=process(${body})"); 42 | } 43 | 44 | private String buildEndpoint() { 45 | if(capiKafkaSslEnabled) { 46 | return capiKafkaTopic + 47 | "?brokers=" + capiKafkaHost + 48 | "&securityProtocol=SSL" + 49 | "&sslKeystoreLocation=" + capiKafkaSslKeystoreLocation + 50 | "&sslKeystorePassword=" + capiKafkaSslKeystorePassword + 51 | "&sslKeyPassword=" + capiKafkaSslKeystorePassword + 52 | "&sslTruststoreLocation=" + capiKafkaSslTruststoreLocation + 53 | "&sslTruststorePassword=" + capiKafkaSslTruststorePassword + 54 | "&groupInstanceId=" + capiKafkaGroupInstance + 55 | "&autoOffsetReset=latest" + 56 | "&groupId=" + capiKafkaGroupId + 57 | "&valueDeserializer=io.surisoft.capi.kafka.CapiKafkaEventDeserializer"; 58 | } else { 59 | return capiKafkaTopic + 60 | "?brokers=" + capiKafkaHost + 61 | "&groupId=" + capiKafkaGroupId + 62 | "&autoOffsetReset=latest" + 63 | "&consumersCount=1" + 64 | "&valueDeserializer=io.surisoft.capi.kafka.CapiKafkaEventDeserializer"; 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/cache/CacheConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.cache; 2 | 3 | import io.surisoft.capi.schema.ConsulKeyStoreEntry; 4 | import io.surisoft.capi.schema.Service; 5 | import io.surisoft.capi.schema.StickySession; 6 | import io.surisoft.capi.schema.ThrottleServiceObject; 7 | import io.surisoft.capi.utils.Constants; 8 | import org.cache2k.Cache; 9 | import org.cache2k.Cache2kBuilder; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | import org.springframework.beans.factory.annotation.Value; 13 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 14 | import org.springframework.context.annotation.Bean; 15 | import org.springframework.context.annotation.Configuration; 16 | import org.springframework.http.ResponseEntity; 17 | import org.springframework.web.client.RestTemplate; 18 | 19 | import java.util.*; 20 | 21 | @Configuration 22 | public class CacheConfiguration { 23 | 24 | private static final Logger log = LoggerFactory.getLogger(CacheConfiguration.class); 25 | private final List allowedHeaders; 26 | private final List capiConsulHosts; 27 | 28 | public CacheConfiguration(@Value("${capi.gateway.cors.management.allowed-headers}") List allowedHeaders, 29 | @Value("${capi.consul.hosts}") List capiConsulHosts) { 30 | this.allowedHeaders = allowedHeaders; 31 | this.capiConsulHosts = capiConsulHosts; 32 | } 33 | 34 | @Bean 35 | public Cache serviceCache() { 36 | log.debug("Creating Service Cache"); 37 | return new Cache2kBuilder(){} 38 | .name("serviceCache-" + hashCode()) 39 | .eternal(true) 40 | .entryCapacity(10000) 41 | .storeByReference(true) 42 | .build(); 43 | } 44 | 45 | @Bean 46 | public Cache stickySessionCache() { 47 | log.debug("Creating Service Cache"); 48 | return new Cache2kBuilder(){} 49 | .name("stickySessionCache-" + hashCode()) 50 | .eternal(true) 51 | .entryCapacity(10000) 52 | .storeByReference(true) 53 | .build(); 54 | } 55 | 56 | @Bean 57 | @ConditionalOnProperty(prefix = "capi.consul.kv", name = "enabled", havingValue = "true") 58 | public Cache> consulKvStoreCache(RestTemplate restTemplate) { 59 | String consulHost = capiConsulHosts.get(0); 60 | log.debug("Creating Consul KV Cache"); 61 | Cache> consulKvStoreCache = new Cache2kBuilder>(){} 62 | .name("consulKvStoreCache-" + hashCode()) 63 | .eternal(true) 64 | .entryCapacity(10000) 65 | .storeByReference(true) 66 | .build(); 67 | 68 | //Processing CORS Headers 69 | log.info("Checking Consul Key Store for CORS Headers key/values"); 70 | try { 71 | ResponseEntity consulKeyValueStoreResponse = restTemplate.getForEntity(consulHost + Constants.CONSUL_KV_STORE_API + Constants.CAPI_CORS_HEADERS_CACHE_KEY, ConsulKeyStoreEntry[].class); 72 | if(!consulKeyValueStoreResponse.getStatusCode().is2xxSuccessful()) { 73 | consulKvStoreCache.put(Constants.CAPI_CORS_HEADERS_CACHE_KEY, allowedHeaders); 74 | } else { 75 | ConsulKeyStoreEntry consulKeyValueStore = Objects.requireNonNull(consulKeyValueStoreResponse.getBody())[0]; 76 | List consulDecodedValueAsList = consulKeyValueAsList(consulKeyValueStore.getValue()); 77 | consulKvStoreCache.put(Constants.CAPI_CORS_HEADERS_CACHE_KEY, consulDecodedValueAsList); 78 | } 79 | } catch(Exception e) { 80 | consulKvStoreCache.put(Constants.CAPI_CORS_HEADERS_CACHE_KEY, allowedHeaders); 81 | } 82 | return consulKvStoreCache; 83 | } 84 | 85 | @Bean 86 | @ConditionalOnProperty(prefix = "capi.throttling", name = "enabled", havingValue = "true") 87 | public Cache createLocalThrottleCache() { 88 | log.debug("Creating Throttle Cache"); 89 | return new Cache2kBuilder(){} 90 | .name("throttleServiceObject-" + hashCode()) 91 | .eternal(true) 92 | .entryCapacity(10000) 93 | .storeByReference(true) 94 | .build(); 95 | } 96 | 97 | private List consulKeyValueAsList(String encodedValue) { 98 | String decodedValue = new String(Base64.getDecoder().decode(encodedValue)); 99 | return Arrays.asList(decodedValue.split(",", -1)); 100 | } 101 | } -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/cache/KafkaConfig.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.cache; 2 | 3 | import io.surisoft.capi.kafka.CapiEventSerializer; 4 | import io.surisoft.capi.kafka.CapiInstance; 5 | import io.surisoft.capi.kafka.CapiKafkaEvent; 6 | import io.surisoft.capi.schema.CapiEvent; 7 | import org.apache.kafka.clients.producer.ProducerConfig; 8 | import org.apache.kafka.common.serialization.StringSerializer; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.beans.factory.annotation.Value; 13 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 14 | import org.springframework.context.annotation.Bean; 15 | import org.springframework.context.annotation.Configuration; 16 | import org.springframework.kafka.core.DefaultKafkaProducerFactory; 17 | import org.springframework.kafka.core.KafkaTemplate; 18 | import org.springframework.kafka.core.ProducerFactory; 19 | 20 | import java.util.HashMap; 21 | import java.util.Map; 22 | import java.util.UUID; 23 | 24 | import static org.apache.kafka.clients.CommonClientConfigs.SECURITY_PROTOCOL_CONFIG; 25 | import static org.apache.kafka.common.config.SslConfigs.*; 26 | 27 | @Configuration 28 | @ConditionalOnProperty(prefix = "capi.kafka", name = "enabled", havingValue = "true") 29 | public class KafkaConfig { 30 | 31 | private static final Logger log = LoggerFactory.getLogger(KafkaConfig.class); 32 | 33 | @Value("${capi.kafka.host}") 34 | private String capiKafkaHost; 35 | 36 | //@Value("${capi.kafka.topic}") 37 | //private String capiKafkaTopic; 38 | 39 | //@Value("${capi.kafka.group-instance}") 40 | //private String capiKafkaGroupInstance; 41 | 42 | //@Value("${capi.kafka.group-id}") 43 | //private String capiKafkaGroupId; 44 | 45 | @Value("${capi.kafka.ssl.enabled}") 46 | private boolean capiKafkaSslEnabled; 47 | 48 | @Value("${capi.kafka.ssl.keystore.location}") 49 | private String capiKafkaSslKeystoreLocation; 50 | 51 | @Value("${capi.kafka.ssl.keystore.password}") 52 | private String capiKafkaSslKeystorePassword; 53 | 54 | @Value("${capi.kafka.ssl.truststore.location}") 55 | private String capiKafkaSslTruststoreLocation; 56 | 57 | @Value("${capi.kafka.ssl.truststore.password}") 58 | private String capiKafkaSslTruststorePassword; 59 | 60 | @Bean 61 | public CapiInstance capiInstance() { 62 | return new CapiInstance(UUID.randomUUID().toString()); 63 | } 64 | 65 | @Bean 66 | @ConditionalOnProperty(prefix = "capi.kafka", name = "enabled", havingValue = "true") 67 | public ProducerFactory producerFactory() { 68 | log.info("Configuring CAPI Kafka Producer"); 69 | Map configProps = new HashMap<>(); 70 | configProps.put( 71 | ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, 72 | capiKafkaHost); 73 | configProps.put( 74 | ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, 75 | StringSerializer.class); 76 | configProps.put( 77 | ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, 78 | CapiEventSerializer.class); 79 | if(capiKafkaSslEnabled) { 80 | configProps.put( 81 | SECURITY_PROTOCOL_CONFIG, 82 | "SSL"); 83 | configProps.put(SSL_TRUSTSTORE_LOCATION_CONFIG, 84 | capiKafkaSslTruststoreLocation); 85 | configProps.put(SSL_TRUSTSTORE_PASSWORD_CONFIG, 86 | capiKafkaSslTruststorePassword); 87 | configProps.put(SSL_KEYSTORE_LOCATION_CONFIG, 88 | capiKafkaSslKeystoreLocation); 89 | configProps.put(SSL_KEYSTORE_PASSWORD_CONFIG, 90 | capiKafkaSslKeystorePassword); 91 | configProps.put(SSL_KEY_PASSWORD_CONFIG, 92 | capiKafkaSslKeystorePassword); 93 | } 94 | return new DefaultKafkaProducerFactory<>(configProps); 95 | } 96 | 97 | @Bean 98 | public KafkaTemplate kafkaTemplate() { 99 | return new KafkaTemplate<>(producerFactory()); 100 | } 101 | } -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/cache/StickySessionCacheManager.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.cache; 2 | 3 | import io.surisoft.capi.kafka.CapiInstance; 4 | import io.surisoft.capi.kafka.CapiKafkaEvent; 5 | import io.surisoft.capi.schema.CapiEvent; 6 | import io.surisoft.capi.schema.StickySession; 7 | import org.cache2k.Cache; 8 | import org.springframework.beans.factory.annotation.Value; 9 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 10 | import org.springframework.kafka.core.KafkaTemplate; 11 | import org.springframework.stereotype.Component; 12 | 13 | import java.util.UUID; 14 | 15 | @Component 16 | @ConditionalOnProperty(value = "capi.kafka.enabled", havingValue = "true") 17 | public class StickySessionCacheManager { 18 | private final Cache stickySessionCache; 19 | private final KafkaTemplate kafkaTemplate; 20 | private final CapiInstance capiInstance; 21 | private final String capiKafkaTopic; 22 | 23 | public StickySessionCacheManager(Cache stickySessionCache, 24 | KafkaTemplate kafkaTemplate, 25 | CapiInstance capiInstance, 26 | @Value("${capi.kafka.topic}") String capiKafkaTopic) { 27 | this.stickySessionCache = stickySessionCache; 28 | this.kafkaTemplate = kafkaTemplate; 29 | this.capiInstance = capiInstance; 30 | this.capiKafkaTopic = capiKafkaTopic; 31 | } 32 | 33 | public void createStickySession(StickySession stickySession, boolean notifyOtherInstances) { 34 | notifyOtherInstances(notifyOtherInstances, stickySession); 35 | stickySession.setId(stickySession.getParamName() + ":" + stickySession.getParamValue()); 36 | stickySessionCache.put(stickySession.getId(), stickySession); 37 | } 38 | 39 | public StickySession getStickySessionById(String paramName, String paramValue) { 40 | String stickySessionId = paramName + ":" + paramValue; 41 | return stickySessionCache.peek(stickySessionId); 42 | } 43 | 44 | public void deleteStickySession(StickySession stickySession) { 45 | stickySessionCache.remove(stickySession.getId()); 46 | } 47 | 48 | private void notifyOtherInstances(boolean notifyOtherInstances, StickySession stickySession) { 49 | if(notifyOtherInstances) { 50 | CapiEvent capiEvent = new CapiEvent(); 51 | capiEvent.setId(UUID.randomUUID().toString()); 52 | //capiEvent.setInstanceId(capiInstance); 53 | capiEvent.setKey(stickySession.getParamName()); 54 | capiEvent.setValue(stickySession.getParamValue()); 55 | capiEvent.setNodeIndex(stickySession.getNodeIndex()); 56 | capiEvent.setType(CapiKafkaEvent.STICKY_SESSION_EVENT_TYPE); 57 | kafkaTemplate.send(capiKafkaTopic, capiEvent); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/configuration/CamelStartupListener.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.configuration; 2 | 3 | import org.apache.camel.CamelContext; 4 | import org.apache.camel.ExtendedStartupListener; 5 | import org.apache.camel.builder.RouteBuilder; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | public class CamelStartupListener implements ExtendedStartupListener { 10 | 11 | private static final Logger log = LoggerFactory.getLogger(CamelStartupListener.class); 12 | private final long consulTimerInterval; 13 | private final boolean capiConsulEnabled; 14 | 15 | public CamelStartupListener(long consulTimerInterval, boolean capiConsulEnabled) { 16 | this.consulTimerInterval = consulTimerInterval; 17 | this.capiConsulEnabled = capiConsulEnabled; 18 | } 19 | 20 | @Override 21 | public void onCamelContextStarted(CamelContext context, boolean alreadyStarted) throws Exception { 22 | } 23 | 24 | @Override 25 | public void onCamelContextFullyStarted(CamelContext context, boolean alreadyStarted) throws Exception { 26 | if(capiConsulEnabled) { 27 | context.addRoutes(routeBuilder()); 28 | } 29 | } 30 | 31 | public RouteBuilder routeBuilder() { 32 | log.debug("Creating Capi Consul Node Discovery"); 33 | return new RouteBuilder() { 34 | @Override 35 | public void configure() { 36 | from("timer:consul-inspect?period=" + consulTimerInterval) 37 | .to("bean:consulNodeDiscovery?method=processInfo") 38 | .routeId("consul-discovery-service"); 39 | } 40 | }; 41 | } 42 | } -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/configuration/CapiApplicationListener.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.configuration; 2 | 3 | import io.surisoft.capi.schema.Service; 4 | import io.surisoft.capi.schema.StickySession; 5 | import io.surisoft.capi.undertow.SSEGateway; 6 | import io.surisoft.capi.undertow.WebsocketGateway; 7 | import org.cache2k.Cache; 8 | import org.jetbrains.annotations.NotNull; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.boot.context.event.ApplicationStartedEvent; 12 | import org.springframework.context.ApplicationEvent; 13 | import org.springframework.context.ApplicationListener; 14 | import org.springframework.context.event.ContextClosedEvent; 15 | import org.springframework.stereotype.Component; 16 | 17 | import java.util.Optional; 18 | 19 | @Component 20 | public class CapiApplicationListener implements ApplicationListener { 21 | 22 | private static final Logger log = LoggerFactory.getLogger(CapiApplicationListener.class); 23 | private final Cache serviceCache; 24 | private final Cache stickySessionCache; 25 | private final Optional websocketGateway; 26 | private final Optional sseGateway; 27 | 28 | public CapiApplicationListener(Cache serviceCache, Cache stickySessionCache, Optional websocketGateway, Optional sseGateway) { 29 | this.serviceCache = serviceCache; 30 | this.stickySessionCache = stickySessionCache; 31 | this.websocketGateway = websocketGateway; 32 | this.sseGateway = sseGateway; 33 | } 34 | 35 | @Override 36 | public void onApplicationEvent(@NotNull ApplicationEvent applicationEvent) { 37 | if(applicationEvent instanceof ApplicationStartedEvent) { 38 | if(websocketGateway.isPresent()) { 39 | log.info("Capi Websocket Gateway starting."); 40 | websocketGateway.get().runProxy(); 41 | } 42 | if(sseGateway.isPresent()) { 43 | log.info("Capi SSE Gateway starting."); 44 | sseGateway.get().runProxy(); 45 | } 46 | } 47 | if(applicationEvent instanceof ContextClosedEvent) { 48 | log.info("Capi is shutting down, time to clear all cache info."); 49 | serviceCache.clear(); 50 | stickySessionCache.clear(); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/configuration/CapiCorsFilterStrategy.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.configuration; 2 | 3 | import io.surisoft.capi.utils.Constants; 4 | import org.apache.camel.Exchange; 5 | import org.apache.camel.support.DefaultHeaderFilterStrategy; 6 | import org.apache.commons.lang3.StringUtils; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | public class CapiCorsFilterStrategy extends DefaultHeaderFilterStrategy { 14 | 15 | private static final Logger log = LoggerFactory.getLogger(CapiCorsFilterStrategy.class); 16 | private final List allowedHeaders; 17 | private Map managedHeaders; 18 | 19 | public CapiCorsFilterStrategy(List allowedHeaders) { 20 | log.info("Capi Filter Strategy initialized"); 21 | this.allowedHeaders = allowedHeaders; 22 | initialize(); 23 | } 24 | 25 | protected void initialize() { 26 | getOutFilter().add("content-length"); 27 | //getOutFilter().add("content-type"); 28 | getOutFilter().add("host"); 29 | getOutFilter().add("cache-control"); 30 | getOutFilter().add("connection"); 31 | getOutFilter().add("date"); 32 | getOutFilter().add("pragma"); 33 | getOutFilter().add("trailer"); 34 | getOutFilter().add("transfer-encoding"); 35 | getOutFilter().add("upgrade"); 36 | getOutFilter().add("via"); 37 | getOutFilter().add("warning"); 38 | getOutFilter().add(Constants.ACCESS_CONTROL_ALLOW_ORIGIN); 39 | 40 | managedHeaders = new java.util.HashMap<>(Constants.CAPI_CORS_MANAGED_HEADERS); 41 | managedHeaders.put("Access-Control-Allow-Headers", StringUtils.join(allowedHeaders, ",")); 42 | managedHeaders.forEach((key, value) -> { 43 | getOutFilter().add(key); 44 | }); 45 | 46 | setLowerCase(true); 47 | 48 | // filter headers begin with "Camel" or "org.apache.camel" 49 | // must ignore case for Http based transports 50 | setOutFilterStartsWith(CAMEL_FILTER_STARTS_WITH); 51 | setInFilterStartsWith(CAMEL_FILTER_STARTS_WITH); 52 | } 53 | 54 | @Override 55 | public boolean applyFilterToExternalHeaders(String headerName, Object headerValue, Exchange exchange) { 56 | if(headerName.equalsIgnoreCase(Constants.ACCESS_CONTROL_ALLOW_ORIGIN)) { 57 | return true; 58 | } 59 | if(managedHeaders.containsKey(headerName)) { 60 | return true; 61 | } 62 | if(headerName.equalsIgnoreCase(Constants.ACCESS_CONTROL_ALLOW_METHODS)) { 63 | return true; 64 | } 65 | if(headerName.equalsIgnoreCase(Constants.ACCESS_CONTROL_ALLOW_CREDENTIALS)) { 66 | return true; 67 | } 68 | return super.applyFilterToExternalHeaders(headerName, headerValue, exchange); 69 | } 70 | } -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/configuration/CapiSslContextHolder.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.configuration; 2 | 3 | import javax.net.ssl.SSLContext; 4 | 5 | public class CapiSslContextHolder { 6 | private SSLContext sslContext; 7 | public CapiSslContextHolder(SSLContext sslContext) { 8 | this.sslContext = sslContext; 9 | } 10 | public SSLContext getSslContext() { 11 | return sslContext; 12 | } 13 | public void setSslContext(SSLContext sslContext) { 14 | this.sslContext = sslContext; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/configuration/CapiTracerConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.configuration; 2 | 3 | import io.surisoft.capi.tracer.CapiTracer; 4 | import io.surisoft.capi.tracer.CapiUndertowTracer; 5 | import io.surisoft.capi.utils.HttpUtils; 6 | import org.apache.camel.CamelContext; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.beans.factory.annotation.Value; 10 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 11 | import org.springframework.context.annotation.Bean; 12 | import org.springframework.context.annotation.Configuration; 13 | import zipkin2.reporter.AsyncReporter; 14 | import zipkin2.reporter.urlconnection.URLConnectionSender; 15 | 16 | import java.io.IOException; 17 | import java.security.KeyManagementException; 18 | import java.security.KeyStoreException; 19 | import java.security.NoSuchAlgorithmException; 20 | import java.security.cert.CertificateException; 21 | import java.util.HashSet; 22 | import java.util.Set; 23 | 24 | @Configuration 25 | @ConditionalOnProperty(prefix = "capi.traces", name = "enabled", havingValue = "true") 26 | public class CapiTracerConfiguration { 27 | 28 | private static final Logger log = LoggerFactory.getLogger(CapiTracerConfiguration.class); 29 | private final String tracesEndpoint; 30 | private final HttpUtils httpUtils; 31 | 32 | public CapiTracerConfiguration(@Value("${capi.traces.endpoint}") String tracesEndpoint, 33 | HttpUtils httpUtils) { 34 | this.tracesEndpoint = tracesEndpoint; 35 | this.httpUtils = httpUtils; 36 | } 37 | 38 | @Bean 39 | public CapiTracer capiTracer(CamelContext camelContext) throws CertificateException, IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { 40 | camelContext.setUseMDCLogging(true); 41 | 42 | log.debug("Traces Enabled!"); 43 | 44 | Set excludePatterns = new HashSet<>(); 45 | excludePatterns.add("timer://"); 46 | excludePatterns.add("bean://consulNodeDiscovery"); 47 | excludePatterns.add("bean://consistencyChecker"); 48 | 49 | CapiTracer capiTracer = new CapiTracer(httpUtils); 50 | 51 | URLConnectionSender sender = URLConnectionSender 52 | .newBuilder() 53 | .readTimeout(100) 54 | .endpoint(tracesEndpoint + "/api/v2/spans") 55 | .build(); 56 | 57 | capiTracer.setSpanReporter(AsyncReporter.builder(sender).build()); 58 | capiTracer.setIncludeMessageBody(true); 59 | capiTracer.setIncludeMessageBodyStreams(true); 60 | 61 | capiTracer.init(camelContext); 62 | return capiTracer; 63 | } 64 | 65 | @Bean 66 | public CapiUndertowTracer capiUndertowTracer() throws Exception { 67 | log.debug("Undertow Traces Enabled!"); 68 | 69 | CapiUndertowTracer capiUndertowTracer = new CapiUndertowTracer(httpUtils); 70 | 71 | URLConnectionSender sender = URLConnectionSender 72 | .newBuilder() 73 | .readTimeout(100) 74 | .endpoint(tracesEndpoint + "/api/v2/spans") 75 | .build(); 76 | 77 | capiUndertowTracer.setSpanReporter(AsyncReporter.builder(sender).build()); 78 | capiUndertowTracer.init(); 79 | return capiUndertowTracer; 80 | } 81 | } -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/configuration/UndertowErrorListener.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.configuration; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import io.surisoft.capi.schema.CapiRestError; 5 | import io.surisoft.capi.utils.Constants; 6 | import io.undertow.Undertow; 7 | import io.undertow.util.HeaderMap; 8 | import org.springframework.beans.factory.annotation.Value; 9 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 10 | import org.springframework.http.MediaType; 11 | import org.springframework.stereotype.Component; 12 | 13 | @Component 14 | @ConditionalOnProperty(prefix = "capi.gateway.error.listener", name = "enabled", havingValue = "true") 15 | public class UndertowErrorListener { 16 | private final ObjectMapper objectMapper = new ObjectMapper(); 17 | private final int listenerPort; 18 | private final String listenerContext; 19 | 20 | public UndertowErrorListener(@Value("${capi.gateway.error.listener.port}") int listenerPort, 21 | @Value("${capi.gateway.error.listener.context}") String listenerContext) { 22 | this.listenerPort = listenerPort; 23 | this.listenerContext = listenerContext; 24 | runProxy(); 25 | } 26 | 27 | public void runProxy() { 28 | Undertow undertow = Undertow.builder() 29 | .addHttpListener(listenerPort, Constants.ERROR_LISTENING_ADDRESS) 30 | .setHandler(httpServerExchange -> { 31 | if(httpServerExchange.getRelativePath().startsWith(listenerContext)) { 32 | httpServerExchange.getResponseHeaders().add(Constants.HTTP_STRING_CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); 33 | HeaderMap headerMap = httpServerExchange.getRequestHeaders(); 34 | CapiRestError capiRestError = buildCapiErrorObject(headerMap); 35 | 36 | if(headerMap.contains(Constants.REASON_CODE_HEADER)) { 37 | httpServerExchange.setStatusCode(Integer.parseInt(headerMap.get(Constants.REASON_CODE_HEADER).get(0))); 38 | } 39 | httpServerExchange.getResponseSender().send(objectMapper.writeValueAsString(capiRestError)); 40 | httpServerExchange.endExchange(); 41 | } else { 42 | httpServerExchange.setStatusCode(400); 43 | httpServerExchange.getResponseSender().send("BAD REQUEST"); 44 | httpServerExchange.endExchange(); 45 | } 46 | }).build(); 47 | undertow.start(); 48 | } 49 | 50 | private CapiRestError buildCapiErrorObject(HeaderMap headerMap) { 51 | CapiRestError capiRestError = new CapiRestError(); 52 | if(headerMap.contains(Constants.REASON_CODE_HEADER)) { 53 | capiRestError.setErrorCode(Integer.parseInt(headerMap.get(Constants.REASON_CODE_HEADER).get(0))); 54 | } 55 | 56 | if(headerMap.contains(Constants.REASON_MESSAGE_HEADER)) { 57 | capiRestError.setErrorMessage(headerMap.get(Constants.REASON_MESSAGE_HEADER).get(0)); 58 | } 59 | 60 | if(headerMap.contains(Constants.ROUTE_ID_HEADER)) { 61 | capiRestError.setRouteID(headerMap.get(Constants.ROUTE_ID_HEADER).get(0)); 62 | } 63 | 64 | if(headerMap.contains(Constants.CAPI_URI_IN_ERROR)) { 65 | capiRestError.setHttpUri(headerMap.get(Constants.CAPI_URI_IN_ERROR).get(0)); 66 | } 67 | 68 | if(headerMap.contains(Constants.TRACE_ID_HEADER)) { 69 | capiRestError.setTraceID(headerMap.get(Constants.TRACE_ID_HEADER).get(0)); 70 | } 71 | 72 | return capiRestError; 73 | } 74 | } -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/controller/CapiErrorInterface.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.controller; 2 | 3 | import io.surisoft.capi.utils.Constants; 4 | import jakarta.servlet.RequestDispatcher; 5 | import jakarta.servlet.http.HttpServletRequest; 6 | import org.apache.camel.util.json.JsonObject; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.boot.web.servlet.error.ErrorController; 10 | import org.springframework.http.HttpStatus; 11 | import org.springframework.http.MediaType; 12 | import org.springframework.http.ResponseEntity; 13 | import org.springframework.stereotype.Controller; 14 | import org.springframework.web.bind.annotation.*; 15 | 16 | @Controller 17 | public class CapiErrorInterface implements ErrorController { 18 | 19 | private static final Logger log = LoggerFactory.getLogger(CapiErrorInterface.class); 20 | 21 | @GetMapping(path = "/error", 22 | produces = MediaType.APPLICATION_JSON_VALUE 23 | ) 24 | public ResponseEntity handleGet(HttpServletRequest request) { 25 | return handleTheError(request); 26 | } 27 | 28 | @PostMapping(path = "/error", 29 | produces = MediaType.APPLICATION_JSON_VALUE 30 | ) 31 | public ResponseEntity handlePost(HttpServletRequest request) { 32 | return handleTheError(request); 33 | } 34 | 35 | @PutMapping(path = "/error", 36 | produces = MediaType.APPLICATION_JSON_VALUE 37 | ) 38 | public ResponseEntity handlePut(HttpServletRequest request) { 39 | return handleTheError(request); 40 | } 41 | 42 | @DeleteMapping(path = "/error", 43 | produces = MediaType.APPLICATION_JSON_VALUE 44 | ) 45 | public ResponseEntity handleDelete(HttpServletRequest request) { 46 | return handleTheError(request); 47 | } 48 | 49 | @PatchMapping(path = "/error", 50 | produces = MediaType.APPLICATION_JSON_VALUE 51 | ) 52 | public ResponseEntity handlePatch(HttpServletRequest request) { 53 | return handleTheError(request); 54 | } 55 | 56 | private ResponseEntity handleTheError(HttpServletRequest request) { 57 | Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE); 58 | if (status != null) { 59 | Integer statusCode = Integer.valueOf(status.toString()); 60 | log.trace("Handling error: {}", statusCode); 61 | JsonObject jsonObject = new JsonObject(); 62 | if(statusCode == HttpStatus.NOT_FOUND.value()) { 63 | jsonObject.put(Constants.ERROR_MESSAGE, "The requested route was not found, please try again later on."); 64 | jsonObject.put(Constants.ERROR_CODE, statusCode); 65 | return new ResponseEntity<>(jsonObject.toJson(), HttpStatus.NOT_FOUND); 66 | } else { 67 | jsonObject.put(Constants.ERROR_MESSAGE, "The requested route is not available, please try again later on."); 68 | jsonObject.put(Constants.ERROR_CODE, HttpStatus.UNAUTHORIZED.value()); 69 | request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, HttpStatus.UNAUTHORIZED.value()); 70 | return new ResponseEntity<>(jsonObject.toJson(), HttpStatus.UNAUTHORIZED); 71 | } 72 | } 73 | JsonObject jsonObject = new JsonObject(); 74 | jsonObject.put(Constants.ERROR_MESSAGE, "The requested route is not available, please try again later on."); 75 | jsonObject.put(Constants.ERROR_CODE, HttpStatus.UNAUTHORIZED.value()); 76 | return new ResponseEntity<>(jsonObject.toJson(), HttpStatus.INTERNAL_SERVER_ERROR); 77 | } 78 | } -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/controller/DefinitionController.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.controller; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import io.surisoft.capi.exception.AuthorizationException; 5 | import io.surisoft.capi.schema.Service; 6 | import io.surisoft.capi.utils.HttpUtils; 7 | import jakarta.servlet.http.HttpServletRequest; 8 | import org.apache.camel.util.json.JsonArray; 9 | import org.apache.camel.util.json.JsonObject; 10 | import org.cache2k.Cache; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | import org.springframework.beans.factory.annotation.Value; 14 | import org.springframework.http.HttpStatus; 15 | import org.springframework.http.ResponseEntity; 16 | import org.springframework.web.bind.annotation.GetMapping; 17 | import org.springframework.web.bind.annotation.PathVariable; 18 | import org.springframework.web.bind.annotation.RequestMapping; 19 | import org.springframework.web.bind.annotation.RestController; 20 | 21 | import java.io.IOException; 22 | import java.net.URI; 23 | import java.net.http.HttpClient; 24 | import java.net.http.HttpRequest; 25 | import java.net.http.HttpResponse; 26 | 27 | @RestController 28 | @RequestMapping("/definitions/openapi") 29 | public class DefinitionController { 30 | 31 | private static final Logger log = LoggerFactory.getLogger(DefinitionController.class); 32 | private final Cache serviceCache; 33 | private final String capiPublicEndpoint; 34 | private final HttpUtils httpUtils; 35 | 36 | public DefinitionController(Cache serviceCache, 37 | @Value("${capi.public-endpoint}") String capiPublicEndpoint, 38 | HttpUtils httpUtils) { 39 | this.serviceCache = serviceCache; 40 | this.capiPublicEndpoint = capiPublicEndpoint; 41 | this.httpUtils = httpUtils; 42 | } 43 | 44 | @GetMapping(path= "/{serviceId}", produces="application/json") 45 | public ResponseEntity getServiceOpenApi(@PathVariable String serviceId, HttpServletRequest request) { 46 | if(serviceCache.containsKey(serviceId)) { 47 | try { 48 | Service service = serviceCache.get(serviceId); 49 | if(service != null && service.getServiceMeta() != null && service.getServiceMeta().getOpenApiEndpoint() != null) { 50 | if(service.getServiceMeta().isExposeOpenApiDefinition()) { 51 | if(service.getServiceMeta().isSecureOpenApiDefinition()) { 52 | String accessToken = httpUtils.processAuthorizationAccessToken(request); 53 | if(accessToken != null) { 54 | if(httpUtils.isAuthorized(accessToken, service.getServiceMeta().getSubscriptionGroup())) { 55 | JsonObject responseObject = getDefinition(service); 56 | return new ResponseEntity<>(responseObject, HttpStatus.OK); 57 | } 58 | } 59 | } else { 60 | JsonObject responseObject = getDefinition(service); 61 | return new ResponseEntity<>(responseObject, HttpStatus.OK); 62 | } 63 | } 64 | } 65 | return new ResponseEntity<>(HttpStatus.NOT_FOUND); 66 | } catch (NullPointerException | IOException | InterruptedException | AuthorizationException e) { 67 | return new ResponseEntity<>(HttpStatus.NOT_FOUND); 68 | } 69 | } 70 | return new ResponseEntity<>(HttpStatus.NOT_FOUND); 71 | } 72 | 73 | private JsonObject getDefinition(Service service) throws IOException, InterruptedException { 74 | ObjectMapper objectMapper = new ObjectMapper(); 75 | HttpClient client = HttpClient.newBuilder().build(); 76 | HttpRequest request = HttpRequest.newBuilder().uri(URI.create(service.getServiceMeta().getOpenApiEndpoint())).build(); 77 | HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); 78 | 79 | JsonObject serverObject = new JsonObject(); 80 | serverObject.put("url", capiPublicEndpoint + service.getId().replaceAll(":", "/")); 81 | JsonObject responseObject = objectMapper.readValue(response.body(), JsonObject.class); 82 | responseObject.remove("servers"); 83 | 84 | JsonArray serversArray = new JsonArray(); 85 | serversArray.add(serverObject); 86 | 87 | JsonObject infoObject = new JsonObject(); 88 | infoObject.put("title", service.getId()); 89 | infoObject.put("description", "Open API definition generated by CAPI"); 90 | responseObject.put("info", infoObject); 91 | responseObject.put("servers", serversArray); 92 | return responseObject; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/controller/ErrorController.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.controller; 2 | 3 | import io.surisoft.capi.schema.CapiRestError; 4 | import io.surisoft.capi.utils.Constants; 5 | import jakarta.servlet.http.HttpServletRequest; 6 | import org.springframework.http.HttpStatus; 7 | import org.springframework.http.MediaType; 8 | import org.springframework.http.ResponseEntity; 9 | import org.springframework.web.bind.annotation.*; 10 | 11 | import java.util.Objects; 12 | 13 | @RestController 14 | public class ErrorController { 15 | @GetMapping(path = Constants.CAPI_INTERNAL_REST_ERROR_PATH + "/**", produces = MediaType.APPLICATION_JSON_VALUE) 16 | public ResponseEntity get(HttpServletRequest request) { 17 | return buildResponse(request); 18 | } 19 | 20 | @PostMapping(path = Constants.CAPI_INTERNAL_REST_ERROR_PATH + "/**", produces = MediaType.APPLICATION_JSON_VALUE) 21 | public ResponseEntity post(HttpServletRequest request) { 22 | return buildResponse(request); 23 | } 24 | 25 | @PutMapping(path = Constants.CAPI_INTERNAL_REST_ERROR_PATH, produces = MediaType.APPLICATION_JSON_VALUE) 26 | public ResponseEntity put(HttpServletRequest request) { 27 | return buildResponse(request); 28 | } 29 | 30 | @DeleteMapping(path = Constants.CAPI_INTERNAL_REST_ERROR_PATH, produces = MediaType.APPLICATION_JSON_VALUE) 31 | public ResponseEntity delete(HttpServletRequest request) { 32 | return buildResponse(request); 33 | } 34 | 35 | private ResponseEntity buildResponse(HttpServletRequest request) { 36 | CapiRestError capiRestError = new CapiRestError(); 37 | 38 | String errorMessage = request.getHeader(Constants.REASON_MESSAGE_HEADER); 39 | 40 | if(request.getHeader(Constants.ROUTE_ID_HEADER) != null) { 41 | capiRestError.setRouteID(request.getHeader(Constants.ROUTE_ID_HEADER)); 42 | } 43 | 44 | if(request.getHeader(Constants.CAPI_URI_IN_ERROR) != null) { 45 | capiRestError.setHttpUri(request.getHeader(Constants.CAPI_URI_IN_ERROR)); 46 | } 47 | 48 | if(Boolean.parseBoolean(request.getHeader(Constants.ERROR_API_SHOW_TRACE_ID))) { 49 | capiRestError.setTraceID(request.getHeader(Constants.TRACE_ID_HEADER)); 50 | } 51 | 52 | capiRestError.setErrorMessage(Objects.requireNonNullElse(errorMessage, "There was an exception connecting to the requested service, please try again later on.")); 53 | 54 | if(request.getHeader(Constants.TRACE_ID_HEADER) != null) { 55 | capiRestError.setTraceID(request.getHeader(Constants.TRACE_ID_HEADER)); 56 | } 57 | 58 | if(request.getHeader(Constants.REASON_CODE_HEADER) != null) { 59 | int returnedCode = Integer.parseInt(request.getHeader(Constants.REASON_CODE_HEADER)); 60 | capiRestError.setErrorCode(returnedCode); 61 | } else { 62 | capiRestError.setErrorCode(HttpStatus.BAD_GATEWAY.value()); 63 | } 64 | return new ResponseEntity<>(capiRestError, HttpStatus.valueOf(capiRestError.getErrorCode())); 65 | } 66 | } -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/controller/PublicHealthController.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.controller; 2 | 3 | import io.surisoft.capi.service.ConsulNodeDiscovery; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | @RestController 11 | @RequestMapping("/health") 12 | public class PublicHealthController { 13 | @GetMapping 14 | public ResponseEntity amIHealthy() { 15 | if(ConsulNodeDiscovery.isConnectedToConsul()) { 16 | return new ResponseEntity<>(HttpStatus.OK); 17 | } 18 | return new ResponseEntity<>(HttpStatus.SERVICE_UNAVAILABLE); 19 | } 20 | } -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/exception/AuthorizationException.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.exception; 2 | 3 | public class AuthorizationException extends Exception { 4 | public AuthorizationException(String message) { 5 | super(message); 6 | } 7 | } -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/exception/CapiUndertowException.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.exception; 2 | 3 | public class CapiUndertowException extends Exception { 4 | public CapiUndertowException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/exception/RestTemplateErrorHandler.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.exception; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import org.springframework.http.client.ClientHttpResponse; 5 | import org.springframework.web.client.ResponseErrorHandler; 6 | 7 | import java.io.IOException; 8 | 9 | public class RestTemplateErrorHandler implements ResponseErrorHandler { 10 | @Override 11 | public boolean hasError(ClientHttpResponse response) throws IOException { 12 | return response.getStatusCode().isError(); 13 | } 14 | 15 | @Override 16 | public void handleError(@NotNull ClientHttpResponse response) { 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/kafka/CapiEventSerializer.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.kafka; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import io.surisoft.capi.schema.CapiEvent; 5 | import org.apache.kafka.common.errors.SerializationException; 6 | import org.apache.kafka.common.serialization.Serializer; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | public class CapiEventSerializer implements Serializer { 11 | private static final Logger log = LoggerFactory.getLogger(CapiEventSerializer.class); 12 | private final ObjectMapper objectMapper = new ObjectMapper(); 13 | 14 | @Override 15 | public byte[] serialize(String topic, CapiEvent data) { 16 | try { 17 | if (data == null){ 18 | log.warn("Null received at serializing"); 19 | return null; 20 | } 21 | return objectMapper.writeValueAsBytes(data); 22 | } catch (Exception e) { 23 | throw new SerializationException("Error when serializing MessageDto to byte[]"); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/kafka/CapiInstance.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.kafka; 2 | 3 | public record CapiInstance(String uuid) { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/kafka/CapiKafkaEvent.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.kafka; 2 | 3 | import io.surisoft.capi.cache.StickySessionCacheManager; 4 | import io.surisoft.capi.schema.CapiEvent; 5 | import io.surisoft.capi.schema.StickySession; 6 | import io.surisoft.capi.schema.ThrottleServiceObject; 7 | import org.cache2k.Cache; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 11 | import org.springframework.context.annotation.Bean; 12 | import org.springframework.context.annotation.Configuration; 13 | 14 | @Configuration 15 | @ConditionalOnProperty(prefix = "capi.kafka", name = "enabled", havingValue = "true") 16 | public class CapiKafkaEvent { 17 | 18 | public static final String STICKY_SESSION_EVENT_TYPE = "sticky-session"; 19 | public static final String THROTTLING_EVENT_TYPE = "throttling"; 20 | 21 | private final CapiInstance capiInstance; 22 | private final StickySessionCacheManager stickySessionCacheManager; 23 | private final Cache throttleServiceObjectCache; 24 | 25 | public CapiKafkaEvent(CapiInstance capiInstance, 26 | StickySessionCacheManager stickySessionCacheManager, 27 | Cache throttleServiceObjectCache) { 28 | this.capiInstance = capiInstance; 29 | this.stickySessionCacheManager = stickySessionCacheManager; 30 | this.throttleServiceObjectCache = throttleServiceObjectCache; 31 | 32 | log.trace("CAPI Instance ID: {}", capiInstance.uuid()); 33 | 34 | } 35 | 36 | private static final Logger log = LoggerFactory.getLogger(CapiKafkaEvent.class); 37 | public void process(CapiEvent incomingEvent) { 38 | //log.trace(capiInstance.uuid()); 39 | if(incomingEvent.getInstanceId().equals(capiInstance.uuid())) { 40 | log.trace("Event {} is from this instance, ignoring", incomingEvent.getId()); 41 | return; 42 | } 43 | if(incomingEvent.getType().equals(STICKY_SESSION_EVENT_TYPE)) { 44 | StickySession stickySession = new StickySession(); 45 | stickySession.setParamValue(incomingEvent.getValue()); 46 | stickySession.setParamName(incomingEvent.getKey()); 47 | stickySession.setNodeIndex(incomingEvent.getNodeIndex()); 48 | stickySessionCacheManager.createStickySession(stickySession, false); 49 | log.trace("Event {} is a sticky session event", incomingEvent.getId()); 50 | return; 51 | } 52 | if(incomingEvent.getType().equals(THROTTLING_EVENT_TYPE) && incomingEvent.getThrottleServiceObject() != null) { 53 | log.trace("Event {} is a throttling service object", incomingEvent.getKey()); 54 | 55 | log.trace("Incoming Object"); 56 | log.trace(incomingEvent.getThrottleServiceObject().getCacheKey()); 57 | log.trace("{}", incomingEvent.getThrottleServiceObject().getTotalCallsAllowed()); 58 | log.trace("{}", incomingEvent.getThrottleServiceObject().getCurrentCalls()); 59 | log.trace("{}", incomingEvent.getThrottleServiceObject().getExpirationTime()); 60 | 61 | throttleServiceObjectCache.put(incomingEvent.getKey(), incomingEvent.getThrottleServiceObject()); 62 | return; 63 | } 64 | log.trace("Received event: {}", incomingEvent); 65 | } 66 | 67 | @Bean(name = "capiKafkaEventProcessor") 68 | public CapiKafkaEvent capiKafkaEvent() { 69 | return this; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/kafka/CapiKafkaEventDeserializer.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.kafka; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import io.surisoft.capi.schema.CapiEvent; 5 | import org.apache.kafka.common.errors.SerializationException; 6 | import org.apache.kafka.common.header.Headers; 7 | import org.apache.kafka.common.serialization.Deserializer; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import java.nio.charset.StandardCharsets; 12 | import java.util.Map; 13 | 14 | public class CapiKafkaEventDeserializer implements Deserializer { 15 | private static final Logger log = LoggerFactory.getLogger(CapiKafkaEventDeserializer.class); 16 | private final ObjectMapper objectMapper = new ObjectMapper(); 17 | @Override 18 | public void configure(Map configs, boolean isKey) { 19 | Deserializer.super.configure(configs, isKey); 20 | } 21 | 22 | @Override 23 | public CapiEvent deserialize(String topic, byte[] data) { 24 | try { 25 | if (data == null){ 26 | log.warn("Null received at deserializing"); 27 | return null; 28 | } 29 | return objectMapper.readValue(new String(data, StandardCharsets.UTF_8), CapiEvent.class); 30 | } catch (Exception e) { 31 | throw new SerializationException("Error when deserializing byte[] to MessageDto"); 32 | } 33 | } 34 | 35 | @Override 36 | public CapiEvent deserialize(String topic, Headers headers, byte[] data) { 37 | return Deserializer.super.deserialize(topic, headers, data); 38 | } 39 | 40 | @Override 41 | public void close() { 42 | Deserializer.super.close(); 43 | } 44 | } -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/metrics/HealthController.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.metrics; 2 | 3 | import io.surisoft.capi.service.ConsulNodeDiscovery; 4 | import org.springframework.boot.actuate.health.Health; 5 | import org.springframework.boot.actuate.health.HealthIndicator; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | public class HealthController implements HealthIndicator { 10 | @Override 11 | public Health health() { 12 | if(ConsulNodeDiscovery.isConnectedToConsul()) { 13 | return Health.up().build(); 14 | } 15 | return Health.down().withDetail("reason", "Consul not available").build(); 16 | } 17 | } -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/metrics/Info.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.metrics; 2 | 3 | import io.surisoft.capi.schema.CapiInfo; 4 | import org.apache.camel.CamelContext; 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.boot.actuate.endpoint.annotation.Endpoint; 7 | import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.util.List; 11 | import java.util.stream.Collectors; 12 | 13 | @Component 14 | @Endpoint(id = "capi") 15 | public class Info { 16 | 17 | private final List oauth2Keys; 18 | private final CamelContext camelContext; 19 | 20 | public Info(List oauth2Keys, CamelContext camelContext) { 21 | this.oauth2Keys = oauth2Keys; 22 | this.camelContext = camelContext; 23 | } 24 | 25 | @Value("${capi.version}") 26 | private String capiVersion; 27 | @Value("${capi.namespace}") 28 | private String capiNameSpace; 29 | @Value("${capi.spring.version}") 30 | private String capiSpringVersion; 31 | @Value("${capi.oauth2.provider.enabled}") 32 | private boolean oauth2Enabled; 33 | 34 | @Value("${capi.opa.enabled}") 35 | private boolean opaEnabled; 36 | @Value("${capi.opa.endpoint}") 37 | private String opaEndpoint; 38 | @Value("${capi.consul.discovery.enabled}") 39 | private boolean consulEnabled; 40 | @Value("${capi.consul.hosts}") 41 | private String consulEndpoint; 42 | @Value("${capi.consul.discovery.timer.interval}") 43 | private int consulTimerInterval; 44 | @Value("${camel.servlet.mapping.context-path}") 45 | private String routeContextPath; 46 | @Value("${management.endpoints.web.base-path}") 47 | private String metricsContextPath; 48 | @Value("${capi.traces.enabled}") 49 | private boolean tracesEnabled; 50 | @Value("${capi.traces.endpoint}") 51 | private String tracesEndpoint; 52 | 53 | 54 | @ReadOperation 55 | public CapiInfo getInfo() { 56 | CapiInfo capiInfo = new CapiInfo(); 57 | capiInfo.setUptime(camelContext.getUptime().toString()); 58 | capiInfo.setCamelVersion(camelContext.getVersion()); 59 | capiInfo.setTotalRoutes(camelContext.getRoutesSize()); 60 | capiInfo.setCapiVersion(capiVersion); 61 | capiInfo.setCapiStringVersion(capiSpringVersion); 62 | capiInfo.setCapiNameSpace(capiNameSpace); 63 | capiInfo.setOpaEnabled(opaEnabled); 64 | capiInfo.setOpaEndpoint(opaEndpoint); 65 | capiInfo.setOauth2Enabled(oauth2Enabled); 66 | capiInfo.setOauth2Endpoint(oauth2Keys.stream().map(String::valueOf).collect(Collectors.joining(","))); 67 | capiInfo.setConsulEnabled(consulEnabled); 68 | capiInfo.setConsulEndpoint(consulEndpoint); 69 | capiInfo.setConsulTimerInterval(consulTimerInterval); 70 | capiInfo.setRoutesContextPath(routeContextPath); 71 | capiInfo.setMetricsContextPath(metricsContextPath); 72 | capiInfo.setTracesEnabled(tracesEnabled); 73 | capiInfo.setTracesEndpoint(tracesEndpoint); 74 | capiInfo.setJavaVersion(String.valueOf(Runtime.version())); 75 | return capiInfo; 76 | } 77 | } -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/metrics/KVStore.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.metrics; 2 | 3 | import org.cache2k.Cache; 4 | import org.cache2k.CacheEntry; 5 | import org.springframework.boot.actuate.endpoint.annotation.*; 6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 7 | import org.springframework.stereotype.Component; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import java.util.Objects; 12 | import java.util.Set; 13 | 14 | @Component 15 | @ConditionalOnProperty(prefix = "capi.consul.kv", name = "enabled", havingValue = "true") 16 | @Endpoint(id = "kv") 17 | public class KVStore { 18 | 19 | private final Cache> corsHeadersCache; 20 | 21 | public KVStore(Cache> corsHeadersCache) { 22 | this.corsHeadersCache = corsHeadersCache; 23 | } 24 | 25 | 26 | @ReadOperation 27 | public Set>> getCache() { 28 | return corsHeadersCache.entries(); 29 | } 30 | 31 | @WriteOperation 32 | public List addHeader(@Selector String key, @Selector String value) { 33 | List headersCached = new ArrayList<>(Objects.requireNonNull(corsHeadersCache.get(key))); 34 | if(!headersCached.isEmpty()) { 35 | headersCached.add(value); 36 | corsHeadersCache.put(key, headersCached); 37 | } 38 | return headersCached; 39 | } 40 | 41 | @DeleteOperation 42 | public List deleteHeader(@Selector String key, @Selector String value) { 43 | List headersToKeep = new ArrayList<>(); 44 | for(String header : Objects.requireNonNull(corsHeadersCache.get(key))) { 45 | if(!header.equals(value)) { 46 | headersToKeep.add(header); 47 | } 48 | corsHeadersCache.put(key, headersToKeep); 49 | } 50 | return headersToKeep; 51 | } 52 | } -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/metrics/OpenAPIDefinition.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.metrics; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import io.surisoft.capi.schema.Service; 5 | import org.apache.camel.util.json.JsonArray; 6 | import org.apache.camel.util.json.JsonObject; 7 | import org.cache2k.Cache; 8 | import org.springframework.beans.factory.annotation.Value; 9 | import org.springframework.boot.actuate.endpoint.annotation.Endpoint; 10 | import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; 11 | import org.springframework.boot.actuate.endpoint.annotation.Selector; 12 | import org.springframework.stereotype.Component; 13 | 14 | import java.io.IOException; 15 | import java.net.URI; 16 | import java.net.http.HttpClient; 17 | import java.net.http.HttpRequest; 18 | import java.net.http.HttpResponse; 19 | 20 | @Component 21 | @Endpoint(id = "openapi") 22 | public class OpenAPIDefinition { 23 | 24 | private final Cache serviceCache; 25 | 26 | @Value("${capi.public-endpoint}") 27 | private String capiPublicEndpoint; 28 | 29 | public OpenAPIDefinition(Cache serviceCache) { 30 | this.serviceCache = serviceCache; 31 | } 32 | 33 | @ReadOperation 34 | public JsonObject getCacheOpenApiDefinition(@Selector String serviceName) { 35 | if(serviceCache.containsKey(serviceName)) { 36 | ObjectMapper objectMapper = new ObjectMapper(); 37 | try { 38 | Service service = serviceCache.get(serviceName); 39 | if(service != null && service.getServiceMeta() != null && service.getServiceMeta().getOpenApiEndpoint() != null) { 40 | HttpClient client = HttpClient.newBuilder().build(); 41 | HttpRequest request = HttpRequest.newBuilder().uri(URI.create(service.getServiceMeta().getOpenApiEndpoint())).build(); 42 | HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); 43 | JsonObject serverObject = new JsonObject(); 44 | serverObject.put("url", capiPublicEndpoint + serviceName.replaceAll(":", "/")); 45 | JsonObject responseObject = objectMapper.readValue(response.body(), JsonObject.class); 46 | responseObject.remove("servers"); 47 | 48 | JsonArray serversArray = new JsonArray(); 49 | serversArray.add(serverObject); 50 | 51 | JsonObject infoObject = new JsonObject(); 52 | infoObject.put("title", service.getId()); 53 | infoObject.put("description", "Open API definition generated by CAPI"); 54 | responseObject.put("info", infoObject); 55 | responseObject.put("servers", serversArray); 56 | return responseObject; 57 | } 58 | return null; 59 | } catch (NullPointerException | IOException | InterruptedException e) { 60 | return null; 61 | } 62 | } 63 | return null; 64 | } 65 | } -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/metrics/Routes.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.metrics; 2 | 3 | import io.surisoft.capi.processor.MetricsProcessor; 4 | import io.surisoft.capi.schema.RouteDetailsEndpointInfo; 5 | import io.surisoft.capi.schema.RouteEndpointInfo; 6 | import io.surisoft.capi.schema.Service; 7 | import io.surisoft.capi.schema.StickySession; 8 | import io.surisoft.capi.utils.Constants; 9 | import io.surisoft.capi.utils.RouteUtils; 10 | import io.surisoft.capi.utils.ServiceUtils; 11 | import org.apache.camel.CamelContext; 12 | import org.cache2k.Cache; 13 | import org.springframework.boot.actuate.endpoint.annotation.Endpoint; 14 | import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; 15 | import org.springframework.boot.actuate.endpoint.annotation.Selector; 16 | import org.springframework.stereotype.Component; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | 21 | @Component 22 | @Endpoint(id = "routes") 23 | public class Routes { 24 | 25 | private final ServiceUtils serviceUtils; 26 | private final Cache serviceCache; 27 | private final Cache stickySessionCache; 28 | private final CamelContext camelContext; 29 | private final RouteUtils routeUtils; 30 | private final MetricsProcessor metricsProcessor; 31 | 32 | public Routes(ServiceUtils serviceUtils, 33 | Cache serviceCache, 34 | Cache stickySessionCache, 35 | CamelContext camelContext, 36 | RouteUtils routeUtils, 37 | MetricsProcessor metricsProcessor) { 38 | this.serviceUtils = serviceUtils; 39 | this.serviceCache = serviceCache; 40 | this.stickySessionCache = stickySessionCache; 41 | this.camelContext = camelContext; 42 | this.routeUtils = routeUtils; 43 | this.metricsProcessor = metricsProcessor; 44 | } 45 | 46 | @ReadOperation 47 | public Service getCachedService(@Selector String serviceName) { 48 | if(serviceCache.containsKey(serviceName)) { 49 | return serviceCache.get(serviceName); 50 | } 51 | return null; 52 | } 53 | 54 | @ReadOperation 55 | public List getAllRoutesInfo() { 56 | List detailInfoList = new ArrayList<>(); 57 | List routeEndpointInfoList = camelContext.getRoutes().stream() 58 | .map(RouteEndpointInfo::new) 59 | .toList(); 60 | for(RouteEndpointInfo routeEndpointInfo : routeEndpointInfoList) { 61 | if(!Constants.CAPI_INTERNAL_ROUTES_PREFIX.contains(routeEndpointInfo.getId())) { 62 | detailInfoList.add(new RouteDetailsEndpointInfo(camelContext, camelContext.getRoute(routeEndpointInfo.getId()))); 63 | } 64 | } 65 | return detailInfoList; 66 | } 67 | } -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/metrics/WSRoutes.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.metrics; 2 | 3 | import io.surisoft.capi.schema.WebsocketClient; 4 | import org.springframework.boot.actuate.endpoint.annotation.Endpoint; 5 | import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; 6 | import org.springframework.stereotype.Component; 7 | 8 | import java.util.Map; 9 | import java.util.Optional; 10 | 11 | @Component 12 | @Endpoint(id = "ws-routes") 13 | public class WSRoutes { 14 | 15 | private final Optional> websocketClientMap; 16 | 17 | public WSRoutes(Optional> websocketClientMap) { 18 | this.websocketClientMap = websocketClientMap; 19 | } 20 | 21 | @ReadOperation 22 | public Map getAllWebsocketRoutesInfo() { 23 | return websocketClientMap.orElse(null); 24 | } 25 | } -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/oidc/Oauth2Constants.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.oidc; 2 | 3 | import okhttp3.MediaType; 4 | 5 | public class Oauth2Constants { 6 | public static final String CLIENT_ID = "client_id"; 7 | public static final String CLIENT_SECRET = "client_secret"; 8 | public static final String CLIENT_NAME = "client_name"; 9 | public static final String GRANT_TYPE = "grant_type"; 10 | public static final String CLIENT_CREDENTIALS_GRANT_TYPE = "client_credentials"; 11 | public static final String ACCESS_TOKEN_ATTRIBUTE = "access_token"; 12 | public static final MediaType JSON_TYPE = MediaType.parse("application/json; charset=utf-8"); 13 | public static final String AUTHORIZATION_HEADER = "Authorization"; 14 | public static final String BEARER_AUTHORIZATION_ATTRIBUTE = "Bearer "; 15 | public static final String SERVICE_ACCOUNT_ENABLE = "serviceAccountsEnabled"; 16 | public static final String NAME_ATTRIBUTE = "name"; 17 | public static final String ID_ATTRIBUTE = "id"; 18 | public static final String USERS_URI = "/users/"; 19 | public static final String ROLE_MAPPING_URI = "/role-mappings/realm"; 20 | public static final String ROLES_URI = "/roles"; 21 | public static final String CLIENTS_URI = "/clients"; 22 | public static final String CLIENT_REGISTRATION_URI = "/clients-registrations/openid-connect"; 23 | public static final String TOKEN_URI = "/protocol/openid-connect/token"; 24 | public static final String CERTS_URI = "/protocol/openid-connect/certs"; 25 | public static final String REALMS_CLAIM = "realm_access"; 26 | public static final String SUBSCRIPTIONS_CLAIM = "subscriptions"; 27 | public static final String ROLES_CLAIM = "roles"; 28 | public static final String CAMEL_SERVLET_CONTEXT_PATH = "CamelServletContextPath"; 29 | public static final String AUTHORIZATION_QUERY = "access_token"; 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/oidc/Oauth2Exception.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.oidc; 2 | 3 | public class Oauth2Exception extends Exception { 4 | 5 | public Oauth2Exception(String message) { 6 | super(message); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/processor/AuthorizationProcessor.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.processor; 2 | 3 | import io.surisoft.capi.exception.AuthorizationException; 4 | import io.surisoft.capi.oidc.Oauth2Constants; 5 | import io.surisoft.capi.schema.Service; 6 | import io.surisoft.capi.service.OpaService; 7 | import io.surisoft.capi.utils.Constants; 8 | import io.surisoft.capi.utils.HttpUtils; 9 | import org.apache.camel.Exchange; 10 | import org.apache.camel.Processor; 11 | import org.cache2k.Cache; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 15 | import org.springframework.http.HttpStatus; 16 | import org.springframework.stereotype.Component; 17 | 18 | import java.text.ParseException; 19 | import java.util.Optional; 20 | 21 | @Component 22 | @ConditionalOnProperty(prefix = "capi.oauth2.provider", name = "enabled", havingValue = "true") 23 | public class AuthorizationProcessor implements Processor { 24 | private static final Logger log = LoggerFactory.getLogger(AuthorizationProcessor.class); 25 | private final HttpUtils httpUtils; 26 | private final Cache serviceCache; 27 | private final Optional opaService; 28 | 29 | public AuthorizationProcessor(HttpUtils httpUtils, Cache serviceCache, Optional opaService) { 30 | this.httpUtils = httpUtils; 31 | this.serviceCache = serviceCache; 32 | this.opaService = opaService; 33 | } 34 | 35 | @Override 36 | public void process(Exchange exchange) { 37 | 38 | String contextPath = (String) exchange.getIn().getHeader(Oauth2Constants.CAMEL_SERVLET_CONTEXT_PATH); 39 | String accessToken; 40 | try { 41 | accessToken = httpUtils.processAuthorizationAccessToken(exchange); 42 | Service service = serviceCache.get(httpUtils.contextToRole(contextPath)); 43 | assert service != null; 44 | 45 | if(accessToken != null) { 46 | if(!httpUtils.isAuthorized(accessToken, contextPath, service, (opaService.orElse(null)))) { 47 | sendException(exchange, "Not subscribed"); 48 | } 49 | httpUtils.propagateAuthorization(exchange, accessToken); 50 | httpUtils.prepareForThrottleIfNeeded(service, accessToken, exchange); 51 | } else { 52 | sendException(exchange, "No authorization header provided"); 53 | } 54 | } catch (AuthorizationException | ParseException e) { 55 | sendException(exchange, e.getMessage()); 56 | } 57 | } 58 | 59 | private void sendException(Exchange exchange, String message) { 60 | exchange.getIn().setHeader(Constants.REASON_MESSAGE_HEADER, message); 61 | exchange.getIn().setHeader(Constants.REASON_CODE_HEADER, HttpStatus.UNAUTHORIZED.value()); 62 | exchange.setException(new AuthorizationException(message)); 63 | } 64 | } -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/processor/ContentTypeValidator.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.processor; 2 | 3 | import io.surisoft.capi.exception.AuthorizationException; 4 | import io.surisoft.capi.utils.Constants; 5 | import org.apache.camel.Exchange; 6 | import org.apache.camel.Processor; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.stereotype.Component; 10 | 11 | @Component 12 | public class ContentTypeValidator implements Processor { 13 | private static final Logger log = LoggerFactory.getLogger(ContentTypeValidator.class); 14 | @Override 15 | public void process(Exchange exchange) throws Exception { 16 | String contentType = (String) exchange.getIn().getHeader(Constants.CONTENT_TYPE); 17 | String accptType = (String) exchange.getIn().getHeader(Constants.ACCEPT_TYPE); 18 | if(contentType == null) { 19 | contentType = (String) exchange.getIn().getHeader("content-type"); 20 | } 21 | if(accptType == null) { 22 | accptType = (String) exchange.getIn().getHeader("accept"); 23 | } 24 | 25 | if (contentType != null && contentType.equalsIgnoreCase("text/event-stream")) { 26 | log.warn("Content type is not supported"); 27 | sendException(exchange); 28 | } 29 | if (accptType != null && accptType.equalsIgnoreCase("text/event-stream")) { 30 | log.warn("Content type is not supported"); 31 | sendException(exchange); 32 | } 33 | } 34 | 35 | private void sendException(Exchange exchange) { 36 | exchange.getIn().setHeader(Constants.REASON_CODE_HEADER, 400); 37 | exchange.getIn().setHeader(Constants.REASON_MESSAGE_HEADER, "Event Stream not allowed in this route, contact your System Administrator"); 38 | exchange.setException(new AuthorizationException("Event Stream not allowed in this route, contact your System Administrator")); 39 | } 40 | } -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/processor/HttpErrorProcessor.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.processor; 2 | 3 | import io.surisoft.capi.utils.Constants; 4 | import org.apache.camel.Exchange; 5 | import org.apache.camel.Processor; 6 | import org.springframework.stereotype.Component; 7 | 8 | import javax.net.ssl.SSLException; 9 | import javax.net.ssl.SSLHandshakeException; 10 | import java.net.SocketTimeoutException; 11 | import java.net.UnknownHostException; 12 | 13 | @Component 14 | public class HttpErrorProcessor implements Processor { 15 | @Override 16 | public void process(Exchange exchange) { 17 | Exception cause = exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class); 18 | if(cause instanceof SSLHandshakeException) { 19 | exchange.getIn().setHeader(Constants.REASON_MESSAGE_HEADER, "Problem with Service certificate"); 20 | exchange.getIn().setHeader(Constants.REASON_CODE_HEADER, 502); 21 | } 22 | if (cause instanceof SSLException) { 23 | exchange.getIn().setHeader(Constants.REASON_MESSAGE_HEADER, "Problem with Service certificate"); 24 | exchange.getIn().setHeader(Constants.REASON_CODE_HEADER, 502); 25 | } else if (cause instanceof UnknownHostException) { 26 | exchange.getIn().setHeader(Constants.REASON_MESSAGE_HEADER, "Problem with Service host"); 27 | exchange.getIn().setHeader(Constants.REASON_CODE_HEADER, 502); 28 | } else if (cause instanceof SocketTimeoutException) { 29 | exchange.getIn().setHeader(Constants.REASON_MESSAGE_HEADER, "The remote server took too long"); 30 | exchange.getIn().setHeader(Constants.REASON_CODE_HEADER, 502); 31 | } 32 | exchange.getIn().setHeader(Constants.CAPI_URI_IN_ERROR, exchange.getIn().getHeader(Exchange.HTTP_URI).toString()); 33 | exchange.getIn().setHeader(Constants.CAPI_URL_IN_ERROR, exchange.getIn().getHeader(Exchange.HTTP_URL).toString()); 34 | } 35 | } -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/processor/MetricsProcessor.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.processor; 2 | 3 | import io.micrometer.core.instrument.composite.CompositeMeterRegistry; 4 | import io.micrometer.core.instrument.search.RequiredSearch; 5 | import org.apache.camel.Exchange; 6 | import org.apache.camel.Processor; 7 | 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Component; 10 | 11 | @Component 12 | public class MetricsProcessor implements Processor { 13 | 14 | @Autowired 15 | private CompositeMeterRegistry meterRegistry; 16 | 17 | @Override 18 | public void process(Exchange exchange) { 19 | if(exchange.getFromRouteId() != null) { 20 | RequiredSearch requiredSearch = meterRegistry.get(exchange.getFromRouteId()); 21 | requiredSearch.counter().increment(); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/schema/AliasInfo.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.schema; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | 5 | import java.util.Date; 6 | 7 | @Schema(hidden = true) 8 | public class AliasInfo { 9 | 10 | private String alias; 11 | private String issuerDN; 12 | private String subjectDN; 13 | private String additionalInfo; 14 | private String serviceId; 15 | private Date notBefore; 16 | private Date notAfter; 17 | 18 | public String getAlias() { 19 | return alias; 20 | } 21 | 22 | public void setAlias(String alias) { 23 | this.alias = alias; 24 | } 25 | 26 | public String getIssuerDN() { 27 | return issuerDN; 28 | } 29 | 30 | public void setIssuerDN(String issuerDN) { 31 | this.issuerDN = issuerDN; 32 | } 33 | 34 | public String getSubjectDN() { 35 | return subjectDN; 36 | } 37 | 38 | public void setSubjectDN(String subjectDN) { 39 | this.subjectDN = subjectDN; 40 | } 41 | 42 | public String getAdditionalInfo() { 43 | return additionalInfo; 44 | } 45 | 46 | public void setAdditionalInfo(String additionalInfo) { 47 | this.additionalInfo = additionalInfo; 48 | } 49 | 50 | public String getServiceId() { 51 | return serviceId; 52 | } 53 | 54 | public void setServiceId(String serviceId) { 55 | this.serviceId = serviceId; 56 | } 57 | 58 | public Date getNotBefore() { 59 | return notBefore; 60 | } 61 | 62 | public void setNotBefore(Date notBefore) { 63 | this.notBefore = notBefore; 64 | } 65 | 66 | public Date getNotAfter() { 67 | return notAfter; 68 | } 69 | 70 | public void setNotAfter(Date notAfter) { 71 | this.notAfter = notAfter; 72 | } 73 | } -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/schema/CapiEvent.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.schema; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | import java.io.Serializable; 6 | 7 | public class CapiEvent implements Serializable { 8 | private String id; 9 | private String type; 10 | private String key; 11 | private String value; 12 | private int nodeIndex; 13 | private String instanceId; 14 | 15 | @JsonProperty("throttleServiceObject") 16 | private ThrottleServiceObject throttleServiceObject; 17 | 18 | public String getKey() { 19 | return key; 20 | } 21 | 22 | public void setKey(String key) { 23 | this.key = key; 24 | } 25 | 26 | public String getId() { 27 | return id; 28 | } 29 | 30 | public void setId(String id) { 31 | this.id = id; 32 | } 33 | 34 | public String getType() { 35 | return type; 36 | } 37 | 38 | public void setType(String type) { 39 | this.type = type; 40 | } 41 | 42 | public String getValue() { 43 | return value; 44 | } 45 | 46 | public void setValue(String value) { 47 | this.value = value; 48 | } 49 | 50 | public String getInstanceId() { 51 | return instanceId; 52 | } 53 | 54 | public void setInstanceId(String instanceId) { 55 | this.instanceId = instanceId; 56 | } 57 | 58 | public int getNodeIndex() { 59 | return nodeIndex; 60 | } 61 | 62 | public void setNodeIndex(int nodeIndex) { 63 | this.nodeIndex = nodeIndex; 64 | } 65 | 66 | public ThrottleServiceObject getThrottleServiceObject() { 67 | return throttleServiceObject; 68 | } 69 | 70 | public void setThrottleServiceObject(ThrottleServiceObject throttleServiceObject) { 71 | this.throttleServiceObject = throttleServiceObject; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/schema/CapiRestError.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.schema; 2 | 3 | public class CapiRestError { 4 | private String routeID; 5 | private String errorMessage; 6 | private int errorCode; 7 | private String httpUri; 8 | private String traceID; 9 | 10 | public String getRouteID() { 11 | return routeID; 12 | } 13 | 14 | public void setRouteID(String routeID) { 15 | this.routeID = routeID; 16 | } 17 | 18 | public String getErrorMessage() { 19 | return errorMessage; 20 | } 21 | 22 | public void setErrorMessage(String errorMessage) { 23 | this.errorMessage = errorMessage; 24 | } 25 | 26 | public int getErrorCode() { 27 | return errorCode; 28 | } 29 | 30 | public void setErrorCode(int errorCode) { 31 | this.errorCode = errorCode; 32 | } 33 | 34 | public String getHttpUri() { 35 | return httpUri; 36 | } 37 | 38 | public void setHttpUri(String httpUri) { 39 | this.httpUri = httpUri; 40 | } 41 | 42 | public String getTraceID() { 43 | return traceID; 44 | } 45 | 46 | public void setTraceID(String traceID) { 47 | this.traceID = traceID; 48 | } 49 | } -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/schema/ConsulKeyStoreEntry.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.schema; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | import java.util.Set; 7 | 8 | @JsonIgnoreProperties(ignoreUnknown = true) 9 | public class ConsulKeyStoreEntry { 10 | @JsonProperty("LocalIndex") 11 | private int localIndex; 12 | @JsonProperty("Key") 13 | private String key; 14 | @JsonProperty("Flags") 15 | private int flags; 16 | @JsonProperty("Value") 17 | private String value; 18 | @JsonProperty("CreateIndex") 19 | private int createIndex; 20 | @JsonProperty("ModifyIndex") 21 | private int modifyIndex; 22 | private Set servicesProcessed; 23 | 24 | public int getLocalIndex() { 25 | return localIndex; 26 | } 27 | 28 | public void setLocalIndex(int localIndex) { 29 | this.localIndex = localIndex; 30 | } 31 | 32 | public String getKey() { 33 | return key; 34 | } 35 | 36 | public void setKey(String key) { 37 | this.key = key; 38 | } 39 | 40 | public int getFlags() { 41 | return flags; 42 | } 43 | 44 | public void setFlags(int flags) { 45 | this.flags = flags; 46 | } 47 | 48 | public String getValue() { 49 | return value; 50 | } 51 | 52 | public void setValue(String value) { 53 | this.value = value; 54 | } 55 | 56 | public int getCreateIndex() { 57 | return createIndex; 58 | } 59 | 60 | public void setCreateIndex(int createIndex) { 61 | this.createIndex = createIndex; 62 | } 63 | 64 | public int getModifyIndex() { 65 | return modifyIndex; 66 | } 67 | 68 | public void setModifyIndex(int modifyIndex) { 69 | this.modifyIndex = modifyIndex; 70 | } 71 | public Set getServicesProcessed() { 72 | return servicesProcessed; 73 | } 74 | public void setServicesProcessed(Set servicesProcessed) { 75 | this.servicesProcessed = servicesProcessed; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/schema/ConsulObject.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.schema; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | @JsonIgnoreProperties(ignoreUnknown = true) 7 | public class ConsulObject { 8 | 9 | @JsonProperty("ID") 10 | private String ID; 11 | 12 | @JsonProperty("ServiceName") 13 | private String serviceName; 14 | 15 | @JsonProperty("ServiceMeta") 16 | private ServiceMeta serviceMeta; 17 | 18 | @JsonProperty("ServiceAddress") 19 | private String serviceAddress; 20 | 21 | @JsonProperty("ServicePort") 22 | private int servicePort; 23 | 24 | @JsonProperty("ModifyIndex") 25 | private int modifyIndex; 26 | 27 | public String getID() { 28 | return ID; 29 | } 30 | 31 | public void setID(String ID) { 32 | this.ID = ID; 33 | } 34 | 35 | public String getServiceName() { 36 | return serviceName; 37 | } 38 | 39 | public void setServiceName(String serviceName) { 40 | this.serviceName = serviceName; 41 | } 42 | 43 | public String getServiceAddress() { 44 | return serviceAddress; 45 | } 46 | 47 | public void setServiceAddress(String serviceAddress) { 48 | this.serviceAddress = serviceAddress; 49 | } 50 | 51 | public int getServicePort() { 52 | return servicePort; 53 | } 54 | 55 | public void setServicePort(int servicePort) { 56 | this.servicePort = servicePort; 57 | } 58 | 59 | public ServiceMeta getServiceMeta() { 60 | return serviceMeta; 61 | } 62 | 63 | public void setServiceMeta(ServiceMeta serviceMeta) { 64 | this.serviceMeta = serviceMeta; 65 | } 66 | 67 | public int getModifyIndex() { 68 | return modifyIndex; 69 | } 70 | 71 | public void setModifyIndex(int modifyIndex) { 72 | this.modifyIndex = modifyIndex; 73 | } 74 | } -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/schema/Group.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.schema; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import com.fasterxml.jackson.annotation.JsonInclude; 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | 7 | @JsonIgnoreProperties(ignoreUnknown = true) 8 | @JsonInclude(JsonInclude.Include.NON_NULL) 9 | public class Group { 10 | @JsonProperty("id") 11 | private String id; 12 | @JsonProperty("name") 13 | private String name; 14 | @JsonProperty("path") 15 | private String path; 16 | 17 | public String getId() { 18 | return id; 19 | } 20 | 21 | public void setId(String id) { 22 | this.id = id; 23 | } 24 | 25 | public String getName() { 26 | return name; 27 | } 28 | 29 | public void setName(String name) { 30 | this.name = name; 31 | } 32 | 33 | public String getPath() { 34 | return path; 35 | } 36 | 37 | public void setPath(String path) { 38 | this.path = path; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/schema/HttpMethod.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.schema; 2 | 3 | public enum HttpMethod { 4 | ALL("all"), 5 | GET("get"), 6 | POST("post"), 7 | PUT("put"), 8 | DELETE("delete"), 9 | PATCH("patch"); 10 | 11 | private String method; 12 | HttpMethod(String method) { 13 | this.method = method; 14 | } 15 | 16 | public String getMethod() { 17 | return this.method; 18 | } 19 | 20 | public void setMethod(String method) { 21 | this.method = method; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/schema/MappingId.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.schema; 2 | 3 | import java.io.Serializable; 4 | 5 | public class MappingId implements Serializable { 6 | private String rootContext; 7 | private String hostname; 8 | private int port; 9 | 10 | public String getRootContext() { 11 | return rootContext; 12 | } 13 | 14 | public void setRootContext(String rootContext) { 15 | this.rootContext = rootContext; 16 | } 17 | 18 | public String getHostname() { 19 | return hostname; 20 | } 21 | 22 | public void setHostname(String hostname) { 23 | this.hostname = hostname; 24 | } 25 | 26 | public int getPort() { 27 | return port; 28 | } 29 | 30 | public void setPort(int port) { 31 | this.port = port; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/schema/OIDCClient.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.schema; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import com.fasterxml.jackson.annotation.JsonInclude; 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | 7 | @JsonIgnoreProperties(ignoreUnknown = true) 8 | @JsonInclude(JsonInclude.Include.NON_NULL) 9 | public class OIDCClient { 10 | 11 | @JsonProperty("client_name") 12 | private String name; 13 | 14 | @JsonProperty("client_id") 15 | private String clientId; 16 | 17 | @JsonProperty("client_secret") 18 | private String secret; 19 | 20 | private boolean serviceAccountsEnabled; 21 | 22 | public String getName() { 23 | return name; 24 | } 25 | 26 | public void setName(String name) { 27 | this.name = name; 28 | } 29 | 30 | public String getClientId() { 31 | return clientId; 32 | } 33 | 34 | public void setClientId(String clientId) { 35 | this.clientId = clientId; 36 | } 37 | 38 | public String getSecret() { 39 | return secret; 40 | } 41 | 42 | public void setSecret(String secret) { 43 | this.secret = secret; 44 | } 45 | 46 | public boolean isServiceAccountsEnabled() { 47 | return serviceAccountsEnabled; 48 | } 49 | 50 | public void setServiceAccountsEnabled(boolean serviceAccountsEnabled) { 51 | this.serviceAccountsEnabled = serviceAccountsEnabled; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/schema/OpaResult.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.schema; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | 5 | @JsonIgnoreProperties(ignoreUnknown = true) 6 | public class OpaResult { 7 | private boolean result; 8 | private long totalCallsAllowed = -1; 9 | private long duration = -1; 10 | private String consumerKey; 11 | 12 | public boolean isAllowed() { 13 | return result; 14 | } 15 | 16 | public void setResult(boolean result) { 17 | this.result = result; 18 | } 19 | public long getTotalCallsAllowed() { 20 | return totalCallsAllowed; 21 | } 22 | public void setTotalCallsAllowed(long totalCallsAllowed) { 23 | this.totalCallsAllowed = totalCallsAllowed; 24 | } 25 | public long getDuration() { 26 | return duration; 27 | } 28 | public void setDuration(long duration) { 29 | this.duration = duration; 30 | } 31 | public String getConsumerKey() { 32 | return consumerKey; 33 | } 34 | public void setConsumerKey(String consumerKey) { 35 | this.consumerKey = consumerKey; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/schema/RouteDetailsEndpointInfo.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.schema; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import org.apache.camel.CamelContext; 5 | import org.apache.camel.Route; 6 | import org.apache.camel.api.management.ManagedCamelContext; 7 | import org.apache.camel.api.management.mbean.ManagedRouteMBean; 8 | 9 | public class RouteDetailsEndpointInfo extends RouteEndpointInfo{ 10 | @JsonProperty("details") 11 | private RouteDetails routeDetails; 12 | 13 | public RouteDetailsEndpointInfo(final CamelContext camelContext, final Route route) { 14 | super(route); 15 | if (camelContext.getManagementStrategy().getManagementAgent() != null) { 16 | ManagedCamelContext mcc = camelContext.getCamelContextExtension().getContextPlugin(ManagedCamelContext.class); 17 | this.routeDetails = new RouteDetails(mcc.getManagedRoute(route.getId(), ManagedRouteMBean.class)); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/schema/RouteEndpointInfo.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.schema; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import com.fasterxml.jackson.annotation.JsonPropertyOrder; 5 | import org.apache.camel.Route; 6 | import org.apache.camel.StatefulService; 7 | 8 | import java.util.Collections; 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | @JsonPropertyOrder({"id", "group", "description", "uptime", "uptimeMillis"}) 13 | @JsonInclude(JsonInclude.Include.NON_EMPTY) 14 | public class RouteEndpointInfo { 15 | private String id; 16 | private String group; 17 | private Map properties; 18 | private String description; 19 | private String uptime; 20 | private long uptimeMillis; 21 | private String status; 22 | 23 | public RouteEndpointInfo(Route route) { 24 | this.id = route.getId(); 25 | this.group = route.getGroup(); 26 | this.description = route.getDescription(); 27 | this.uptime = route.getUptime(); 28 | this.uptimeMillis = route.getUptimeMillis(); 29 | 30 | if (route.getProperties() != null) { 31 | this.properties = new HashMap<>(route.getProperties()); 32 | } else { 33 | this.properties = Collections.emptyMap(); 34 | } 35 | 36 | if (route instanceof StatefulService) { 37 | this.status = ((StatefulService) route).getStatus().name(); 38 | } else { 39 | this.status = null; 40 | } 41 | } 42 | 43 | public String getId() { 44 | return id; 45 | } 46 | 47 | public String getGroup() { 48 | return group; 49 | } 50 | 51 | public Map getProperties() { 52 | return properties; 53 | } 54 | 55 | public String getDescription() { 56 | return description; 57 | } 58 | 59 | public String getUptime() { 60 | return uptime; 61 | } 62 | 63 | public long getUptimeMillis() { 64 | return uptimeMillis; 65 | } 66 | 67 | public String getStatus() { 68 | return status; 69 | } 70 | 71 | public void setId(String id) { 72 | this.id = id; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/schema/RunningTenant.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.schema; 2 | 3 | public class RunningTenant { 4 | private String tenant; 5 | private int nodeIndex; 6 | 7 | public RunningTenant(String tenant, int index) { 8 | this.tenant = tenant; 9 | this.nodeIndex = index; 10 | } 11 | 12 | public String getTenant() { 13 | return tenant; 14 | } 15 | 16 | public void setTenant(String tenant) { 17 | this.tenant = tenant; 18 | } 19 | 20 | public int getNodeIndex() { 21 | return nodeIndex; 22 | } 23 | 24 | public void setNodeIndex(int nodeIndex) { 25 | this.nodeIndex = nodeIndex; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/schema/SSEClient.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.schema; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import io.undertow.server.HttpHandler; 5 | 6 | import java.util.Set; 7 | 8 | public class SSEClient { 9 | 10 | private String apiId; 11 | private String path; 12 | private Set mappingList; 13 | @JsonIgnore 14 | private HttpHandler httpHandler; 15 | private boolean requiresSubscription; 16 | private String subscriptionRole; 17 | 18 | public String getPath() { 19 | return path; 20 | } 21 | 22 | public void setPath(String path) { 23 | this.path = path; 24 | } 25 | 26 | public HttpHandler getHttpHandler() { 27 | return httpHandler; 28 | } 29 | 30 | public void setHttpHandler(HttpHandler httpHandler) { 31 | this.httpHandler = httpHandler; 32 | } 33 | 34 | public boolean requiresSubscription() { 35 | return requiresSubscription; 36 | } 37 | 38 | public void setRequiresSubscription(boolean requiresSubscription) { 39 | this.requiresSubscription = requiresSubscription; 40 | } 41 | 42 | public String getSubscriptionRole() { 43 | return subscriptionRole; 44 | } 45 | 46 | public void setSubscriptionRole(String subscriptionRole) { 47 | this.subscriptionRole = subscriptionRole; 48 | } 49 | 50 | public String getApiId() { 51 | return apiId; 52 | } 53 | 54 | public void setApiId(String apiId) { 55 | this.apiId = apiId; 56 | } 57 | 58 | public Set getMappingList() { 59 | return mappingList; 60 | } 61 | 62 | public void setMappingList(Set mappingList) { 63 | this.mappingList = mappingList; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/schema/Service.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.schema; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import io.swagger.v3.oas.models.OpenAPI; 5 | 6 | import java.io.Serializable; 7 | import java.util.HashSet; 8 | import java.util.Set; 9 | 10 | @Schema(hidden = true) 11 | public class Service implements Serializable { 12 | private String id; 13 | private String name; 14 | private String context; 15 | private Set mappingList = new HashSet<>(); 16 | private ServiceMeta serviceMeta; 17 | private boolean roundRobinEnabled; 18 | private boolean failOverEnabled; 19 | private boolean matchOnUriPrefix; 20 | private boolean forwardPrefix; 21 | private String registeredBy; 22 | private transient OpenAPI openAPI; 23 | private int modifyIndex; 24 | 25 | public String getName() { 26 | return name; 27 | } 28 | 29 | public void setName(String name) { 30 | this.name = name; 31 | } 32 | 33 | public String getContext() { 34 | return context; 35 | } 36 | 37 | public void setContext(String context) { 38 | this.context = context; 39 | } 40 | 41 | public Set getMappingList() { 42 | return mappingList; 43 | } 44 | 45 | public void setMappingList(Set mappingList) { 46 | this.mappingList = mappingList; 47 | } 48 | 49 | public ServiceMeta getServiceMeta() { 50 | return serviceMeta; 51 | } 52 | 53 | public void setServiceMeta(ServiceMeta serviceMeta) { 54 | this.serviceMeta = serviceMeta; 55 | } 56 | 57 | public boolean isRoundRobinEnabled() { 58 | return roundRobinEnabled; 59 | } 60 | 61 | public void setRoundRobinEnabled(boolean roundRobinEnabled) { 62 | this.roundRobinEnabled = roundRobinEnabled; 63 | } 64 | 65 | public boolean isMatchOnUriPrefix() { 66 | return matchOnUriPrefix; 67 | } 68 | 69 | public void setMatchOnUriPrefix(boolean matchOnUriPrefix) { 70 | this.matchOnUriPrefix = matchOnUriPrefix; 71 | } 72 | 73 | public boolean isForwardPrefix() { 74 | return forwardPrefix; 75 | } 76 | 77 | public void setForwardPrefix(boolean forwardPrefix) { 78 | this.forwardPrefix = forwardPrefix; 79 | } 80 | 81 | public String getId() { 82 | return id; 83 | } 84 | 85 | public void setId(String id) { 86 | this.id = id; 87 | } 88 | 89 | public boolean isFailOverEnabled() { 90 | return failOverEnabled; 91 | } 92 | 93 | public void setFailOverEnabled(boolean failOverEnabled) { 94 | this.failOverEnabled = failOverEnabled; 95 | } 96 | 97 | public String getRegisteredBy() { 98 | return registeredBy; 99 | } 100 | 101 | public void setRegisteredBy(String registeredBy) { 102 | this.registeredBy = registeredBy; 103 | } 104 | 105 | public OpenAPI getOpenAPI() { 106 | return openAPI; 107 | } 108 | 109 | public void setOpenAPI(OpenAPI openAPI) { 110 | this.openAPI = openAPI; 111 | } 112 | 113 | public int getModifyIndex() { 114 | return modifyIndex; 115 | } 116 | 117 | public void setModifyIndex(int modifyIndex) { 118 | this.modifyIndex = modifyIndex; 119 | } 120 | } -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/schema/State.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.schema; 2 | 3 | public enum State { 4 | PUBLISHED("published"), 5 | CREATED("created"), 6 | SUSPENDED("suspended"); 7 | 8 | State(String published) { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/schema/StickySession.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.schema; 2 | 3 | 4 | import io.swagger.v3.oas.annotations.media.Schema; 5 | 6 | import java.io.Serializable; 7 | 8 | @Schema(hidden = true) 9 | public class StickySession implements Serializable { 10 | private String id; 11 | private String paramName; 12 | private String paramValue; 13 | private int nodeIndex; 14 | 15 | public String getId() { 16 | return id; 17 | } 18 | 19 | public void setId(String id) { 20 | this.id = id; 21 | } 22 | 23 | public String getParamName() { 24 | return paramName; 25 | } 26 | 27 | public void setParamName(String paramName) { 28 | this.paramName = paramName; 29 | } 30 | 31 | public String getParamValue() { 32 | return paramValue; 33 | } 34 | 35 | public void setParamValue(String paramValue) { 36 | this.paramValue = paramValue; 37 | } 38 | 39 | public int getNodeIndex() { 40 | return nodeIndex; 41 | } 42 | 43 | public void setNodeIndex(int nodeIndex) { 44 | this.nodeIndex = nodeIndex; 45 | } 46 | } -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/schema/SubscriptionGroup.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.schema; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | import java.util.Map; 6 | import java.util.Set; 7 | 8 | public class SubscriptionGroup { 9 | 10 | @JsonProperty("services") 11 | private Map> services; 12 | 13 | public Map> getServices() { 14 | return services; 15 | } 16 | 17 | public void setServices(Map> services) { 18 | this.services = services; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/schema/ThrottleServiceObject.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.schema; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | 5 | import java.io.Serializable; 6 | 7 | public class ThrottleServiceObject implements Serializable { 8 | 9 | private String serviceId; 10 | private String consumerKey; 11 | private long totalCallsAllowed; 12 | private long currentCalls; 13 | private long expirationTime; 14 | 15 | public ThrottleServiceObject() {} 16 | 17 | public ThrottleServiceObject(String serviceId, String consumerKey, long totalCallsAllowed, long expirationDuration) { 18 | this.serviceId = serviceId; 19 | this.consumerKey = consumerKey; 20 | this.totalCallsAllowed = totalCallsAllowed; 21 | this.currentCalls = 1; 22 | this.expirationTime = System.currentTimeMillis() + expirationDuration; 23 | } 24 | 25 | @JsonIgnore 26 | public String getCacheKey() { 27 | return serviceId; 28 | } 29 | 30 | public String getServiceId() { 31 | return serviceId; 32 | } 33 | 34 | public long getCurrentCalls() { 35 | return currentCalls; 36 | } 37 | 38 | @JsonIgnore 39 | public boolean isExpired() { 40 | return System.currentTimeMillis() > expirationTime; 41 | } 42 | 43 | @JsonIgnore 44 | public boolean canCall() { 45 | return !isExpired() && currentCalls < totalCallsAllowed; 46 | } 47 | 48 | @JsonIgnore 49 | public void increment() { 50 | currentCalls++; 51 | } 52 | 53 | public long getExpirationTime() { 54 | return expirationTime; 55 | } 56 | 57 | public long getTotalCallsAllowed() { 58 | return totalCallsAllowed; 59 | } 60 | 61 | public String getConsumerKey() { 62 | return consumerKey; 63 | } 64 | } -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/schema/WebsocketClient.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.schema; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import io.undertow.server.HttpHandler; 5 | 6 | import java.util.Set; 7 | 8 | public class WebsocketClient { 9 | 10 | private String serviceId; 11 | private String path; 12 | private Set mappingList; 13 | @JsonIgnore 14 | private HttpHandler httpHandler; 15 | private boolean requiresSubscription; 16 | private String subscriptionRole; 17 | 18 | public String getPath() { 19 | return path; 20 | } 21 | 22 | public void setPath(String path) { 23 | this.path = path; 24 | } 25 | 26 | public HttpHandler getHttpHandler() { 27 | return httpHandler; 28 | } 29 | 30 | public void setHttpHandler(HttpHandler httpHandler) { 31 | this.httpHandler = httpHandler; 32 | } 33 | 34 | public boolean requiresSubscription() { 35 | return requiresSubscription; 36 | } 37 | 38 | public boolean isRequiresSubscription() { 39 | return requiresSubscription; 40 | } 41 | 42 | public void setRequiresSubscription(boolean requiresSubscription) { 43 | this.requiresSubscription = requiresSubscription; 44 | } 45 | 46 | public String getSubscriptionRole() { 47 | return subscriptionRole; 48 | } 49 | 50 | public void setSubscriptionRole(String subscriptionRole) { 51 | this.subscriptionRole = subscriptionRole; 52 | } 53 | 54 | public String getServiceId() { 55 | return serviceId; 56 | } 57 | 58 | public void setServiceId(String serviceId) { 59 | this.serviceId = serviceId; 60 | } 61 | 62 | public Set getMappingList() { 63 | return mappingList; 64 | } 65 | 66 | public void setMappingList(Set mappingList) { 67 | this.mappingList = mappingList; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/service/CapiEventNotifier.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.service; 2 | 3 | import org.apache.camel.impl.event.ExchangeFailedEvent; 4 | import org.apache.camel.impl.event.RouteRemovedEvent; 5 | import org.apache.camel.impl.event.RouteStoppedEvent; 6 | import org.apache.camel.spi.CamelEvent; 7 | import org.apache.camel.support.EventNotifierSupport; 8 | import org.cache2k.Cache; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.stereotype.Component; 11 | 12 | import java.util.UUID; 13 | 14 | //@Component 15 | public class CapiEventNotifier extends EventNotifierSupport { 16 | @Autowired 17 | private Cache startRouteStoppedEventCache; 18 | @Autowired 19 | private Cache startRouteRemovedEventCache; 20 | @Autowired 21 | private Cache startExchangeFailedEventCache; 22 | 23 | @Override 24 | public void notify(CamelEvent event) { 25 | if (event instanceof RouteStoppedEvent) { 26 | startRouteStoppedEventCache.put(UUID.randomUUID().toString(), ((RouteStoppedEvent) event).getRoute().getRouteId()); 27 | } 28 | if(event instanceof RouteRemovedEvent) { 29 | startRouteRemovedEventCache.put(UUID.randomUUID().toString(), ((RouteRemovedEvent) event).getRoute().getRouteId()); 30 | } 31 | if(event instanceof ExchangeFailedEvent exchangeFailedEvent) { 32 | startExchangeFailedEventCache.put(exchangeFailedEvent.getExchange().getExchangeId(), exchangeFailedEvent.getCause().getMessage()); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/service/CapiTrustManager.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.service; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import javax.net.ssl.TrustManager; 7 | import javax.net.ssl.TrustManagerFactory; 8 | import javax.net.ssl.X509TrustManager; 9 | import java.io.FileInputStream; 10 | import java.io.InputStream; 11 | import java.security.KeyStore; 12 | import java.security.cert.CertificateException; 13 | import java.security.cert.X509Certificate; 14 | 15 | public final class CapiTrustManager implements X509TrustManager { 16 | 17 | private static final Logger log = LoggerFactory.getLogger(CapiTrustManager.class); 18 | private final String capiTrustStorePath; 19 | private X509TrustManager trustManager; 20 | private KeyStore keyStore; 21 | 22 | public CapiTrustManager(InputStream capiTrustStoreStream, String capiTrustStorePath, String capiTrustStorePassword) throws Exception { 23 | log.info("Starting CAPI Trust Store Manager"); 24 | this.capiTrustStorePath = capiTrustStorePath; 25 | reloadTrustManager(capiTrustStoreStream, capiTrustStorePassword); 26 | } 27 | 28 | @Override 29 | public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { 30 | trustManager.checkClientTrusted(chain, authType); 31 | } 32 | 33 | @Override 34 | public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { 35 | trustManager.checkServerTrusted(chain, authType); 36 | } 37 | 38 | 39 | @Override 40 | public X509Certificate[] getAcceptedIssuers() { 41 | return trustManager.getAcceptedIssuers(); 42 | } 43 | 44 | public void reloadTrustManager(InputStream capiTrustStoreStream, String capiTrustStorePassword) throws Exception { 45 | keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); 46 | if(capiTrustStoreStream != null) { 47 | keyStore.load(capiTrustStoreStream, capiTrustStorePassword.toCharArray()); 48 | } else { 49 | try (InputStream in = new FileInputStream(capiTrustStorePath)) { 50 | keyStore.load(in, capiTrustStorePassword.toCharArray()); 51 | } 52 | } 53 | 54 | TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); 55 | trustManagerFactory.init(keyStore); 56 | 57 | TrustManager[] trustManagerList = trustManagerFactory.getTrustManagers(); 58 | for(TrustManager tempTrustManager : trustManagerList) { 59 | if (tempTrustManager instanceof X509TrustManager) { 60 | trustManager = (X509TrustManager) tempTrustManager; 61 | } 62 | } 63 | } 64 | 65 | public KeyStore getKeyStore() { 66 | return keyStore; 67 | } 68 | } -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/service/ConsistencyChecker.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.service; 2 | 3 | import io.surisoft.capi.schema.Service; 4 | import io.surisoft.capi.utils.RouteUtils; 5 | import org.apache.camel.CamelContext; 6 | import org.cache2k.Cache; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | public class ConsistencyChecker { 14 | 15 | private static final Logger log = LoggerFactory.getLogger(ConsistencyChecker.class); 16 | private final RouteUtils routeUtils; 17 | private final CamelContext camelContext; 18 | private final Cache serviceCache; 19 | 20 | public ConsistencyChecker(CamelContext camelContext, RouteUtils routeUtils, Cache serviceCache) { 21 | this.routeUtils = routeUtils; 22 | this.camelContext = camelContext; 23 | this.serviceCache = serviceCache; 24 | } 25 | 26 | public void process() { 27 | if(camelContext.isStarted()) { 28 | log.debug("Looking for inconsistent routes..."); 29 | checkForOpenApiInconsistency(); 30 | } 31 | } 32 | 33 | private void checkForOpenApiInconsistency() { 34 | List servicesToRemove = new ArrayList<>(); 35 | serviceCache.entries().forEach(service -> { 36 | if(service.getValue().getServiceMeta().getOpenApiEndpoint() != null && service.getValue().getOpenAPI() == null) { 37 | log.warn("Inconsistency detected for service {}. Service routes will be destroyed.", service.getKey()); 38 | List serviceRouteIdList = routeUtils.getAllRouteIdForAGivenService(service.getValue()); 39 | for (String routeId : serviceRouteIdList) { 40 | try { 41 | camelContext.getRouteController().stopRoute(routeId); 42 | camelContext.removeRoute(routeId); 43 | servicesToRemove.add(service.getKey()); 44 | } catch (Exception e) { 45 | log.error(e.getMessage(), e); 46 | } 47 | } 48 | 49 | } 50 | }); 51 | servicesToRemove.forEach(serviceCache::remove); 52 | } 53 | } -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/service/OpaService.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.service; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import io.surisoft.capi.configuration.CapiSslContextHolder; 5 | import io.surisoft.capi.schema.OpaResult; 6 | import org.apache.camel.util.json.JsonObject; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.beans.factory.annotation.Value; 10 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 11 | import org.springframework.stereotype.Component; 12 | 13 | import java.io.IOException; 14 | import java.net.URI; 15 | import java.net.URISyntaxException; 16 | import java.net.http.HttpClient; 17 | import java.net.http.HttpRequest; 18 | import java.net.http.HttpResponse; 19 | import java.time.Duration; 20 | 21 | import static java.time.temporal.ChronoUnit.SECONDS; 22 | 23 | @Component 24 | @ConditionalOnProperty(prefix = "capi.opa", name = "enabled", havingValue = "true") 25 | public class OpaService { 26 | 27 | private static final Logger log = LoggerFactory.getLogger(OpaService.class); 28 | private final String opaEndpoint; 29 | private HttpClient httpClient; 30 | private final ObjectMapper objectMapper = new ObjectMapper(); 31 | private final CapiSslContextHolder capiSslContextHolder; 32 | 33 | public OpaService(@Value("${capi.opa.endpoint}") String opaEndpoint, CapiSslContextHolder capiSslContextHolder) { 34 | this.opaEndpoint = opaEndpoint; 35 | this.capiSslContextHolder = capiSslContextHolder; 36 | HttpClient.Builder httpClientBuilder = HttpClient.newBuilder(); 37 | if(capiSslContextHolder != null) { 38 | httpClientBuilder.sslContext(capiSslContextHolder.getSslContext()); 39 | } 40 | httpClientBuilder.connectTimeout(Duration.ofSeconds(10)); 41 | httpClient = httpClientBuilder.build(); 42 | } 43 | 44 | public OpaResult callOpa(String opaRego, String value, boolean isAccessToken) { 45 | HttpResponse httpResponse; 46 | try { 47 | httpResponse = httpClient.send(buildHttpRequest(opaRego, value, isAccessToken), HttpResponse.BodyHandlers.ofString()); 48 | if(httpResponse.statusCode() == 200) { 49 | return objectMapper.readValue(httpResponse.body(), OpaResult.class); 50 | } 51 | } catch (InterruptedException | IOException | URISyntaxException e) { 52 | log.error(e.getMessage(), e); 53 | return null; 54 | } 55 | return null; 56 | } 57 | 58 | private HttpRequest buildHttpRequest(String opaRego, String value, boolean isAccessToken) throws URISyntaxException { 59 | return HttpRequest.newBuilder() 60 | .uri(new URI(opaEndpoint + "/v1/data/" + opaRego + "/allow")) 61 | .setHeader("Media-Type", "application/json") 62 | .timeout(Duration.of(10, SECONDS)) 63 | .POST(HttpRequest.BodyPublishers.ofString(buildRequestBody(value, isAccessToken))) 64 | .build(); 65 | } 66 | 67 | private String buildRequestBody(String value, boolean isAccessToken) { 68 | JsonObject tokenObject = new JsonObject(); 69 | if(isAccessToken) { 70 | tokenObject.put("token", value); 71 | } else { 72 | tokenObject.put("consumerKey", value); 73 | } 74 | JsonObject inputObject = new JsonObject(); 75 | inputObject.put("input", tokenObject); 76 | return inputObject.toJson(); 77 | } 78 | 79 | public void reloadHttpClient() { 80 | HttpClient.Builder httpClientBuilder = HttpClient.newBuilder(); 81 | if(capiSslContextHolder != null) { 82 | httpClientBuilder.sslContext(capiSslContextHolder.getSslContext()); 83 | } 84 | httpClientBuilder.connectTimeout(Duration.ofSeconds(10)); 85 | httpClient = httpClientBuilder.build(); 86 | } 87 | } -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/tracer/CapiTracerServerRequestAdapter.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.tracer; 2 | 3 | import brave.SpanCustomizer; 4 | import com.nimbusds.jwt.JWTClaimsSet; 5 | import com.nimbusds.jwt.SignedJWT; 6 | import io.surisoft.capi.exception.AuthorizationException; 7 | import io.surisoft.capi.utils.Constants; 8 | import org.apache.camel.Endpoint; 9 | import org.apache.camel.Exchange; 10 | import org.apache.camel.util.URISupport; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | import java.text.ParseException; 15 | import java.util.Calendar; 16 | import java.util.Date; 17 | import java.util.Locale; 18 | 19 | public class CapiTracerServerRequestAdapter { 20 | private static final Logger LOG = LoggerFactory.getLogger(CapiTracerServerRequestAdapter.class); 21 | private final String spanName; 22 | private final String url; 23 | private final CapiTracer capiTracer; 24 | 25 | public CapiTracerServerRequestAdapter(Exchange exchange, CapiTracer capiTracer) { 26 | Endpoint endpoint = exchange.getFromEndpoint(); 27 | this.spanName = URISupport.sanitizeUri(endpoint.getEndpointKey()).toLowerCase(Locale.ROOT); 28 | this.url = exchange.getIn().getHeader("CamelHttpUri", String.class); 29 | this.capiTracer = capiTracer; 30 | } 31 | 32 | public void onRequest(Exchange exchange, SpanCustomizer span) { 33 | try { 34 | String accessToken; 35 | try { 36 | accessToken = capiTracer.getHttpUtils().processAuthorizationAccessToken(exchange); 37 | if(accessToken != null) { 38 | SignedJWT signedJWT = SignedJWT.parse(accessToken); 39 | JWTClaimsSet jwtClaimsSet = signedJWT.getJWTClaimsSet(); 40 | Date expirationTime = jwtClaimsSet.getExpirationTime(); 41 | if(expirationTime.before(Calendar.getInstance().getTime())) { 42 | span.tag(Constants.CAPI_TOKEN_EXPIRED, Boolean.toString(true)); 43 | } else { 44 | span.tag(Constants.CAPI_TOKEN_EXPIRED, Boolean.toString(false)); 45 | } 46 | String authorizedParty = jwtClaimsSet.getStringClaim(Constants.AUTHORIZED_PARTY); 47 | if(authorizedParty != null) { 48 | span.tag(Constants.CAPI_EXCHANGE_REQUESTER_ID, authorizedParty); 49 | } 50 | String clientHost = jwtClaimsSet.getStringClaim("clientHost"); 51 | if(clientHost != null) { 52 | span.tag("capi.requester.host", clientHost); 53 | } 54 | String iss = jwtClaimsSet.getStringClaim("iss"); 55 | if(iss != null) { 56 | span.tag(Constants.CAPI_REQUESTER_TOKEN_ISSUER, iss); 57 | } 58 | } 59 | } catch (AuthorizationException e) { 60 | LOG.trace("No Authorization header detected, or access token invalid"); 61 | } 62 | } catch (ParseException e) { 63 | LOG.trace("No Authorization header detected, or access token invalid"); 64 | } 65 | 66 | if(exchange.getIn().getHeader("Content-Type") != null) { 67 | span.tag("capi.incoming.request.content.type", exchange.getIn().getHeader("Content-Type", String.class)); 68 | } 69 | 70 | span.name(spanName); 71 | span.tag(Constants.CAMEL_SERVER_ENDPOINT_URL, url); 72 | span.tag(Constants.CAMEL_SERVER_EXCHANGE_ID, exchange.getExchangeId()); 73 | } 74 | } -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/tracer/CapiTracerServerResponseAdapter.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.tracer; 2 | 3 | import brave.SpanCustomizer; 4 | import io.surisoft.capi.utils.Constants; 5 | import jakarta.servlet.http.HttpServletRequest; 6 | import org.apache.camel.Exchange; 7 | import org.apache.camel.util.ObjectHelper; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | public class CapiTracerServerResponseAdapter { 12 | private static final Logger LOG = LoggerFactory.getLogger(CapiTracerServerResponseAdapter.class); 13 | private final String url; 14 | public CapiTracerServerResponseAdapter(String url) { 15 | this.url = url; 16 | } 17 | 18 | public void onResponse(Exchange exchange, SpanCustomizer span) { 19 | String exchangeId = exchange.getExchangeId(); 20 | 21 | span.tag(Constants.CAMEL_CLIENT_ENDPOINT_URL, url); 22 | span.tag(Constants.CAMEL_SERVER_EXCHANGE_ID, exchangeId); 23 | 24 | if (exchange.getException() != null) { 25 | String message = ObjectHelper.isEmpty(exchange.getException().getMessage()) ? exchange.getException().getClass().getName() : exchange.getException().getMessage(); 26 | span.tag(Constants.CAMEL_SERVER_EXCHANGE_FAILURE, message); 27 | } 28 | 29 | HttpServletRequest httpServletRequest = (HttpServletRequest) exchange.getIn().getHeader(Constants.CAMEL_HTTP_SERVLET_REQUEST); 30 | 31 | if(httpServletRequest != null && httpServletRequest.getMethod() != null) { 32 | span.tag(Constants.CAPI_REQUEST_METHOD, httpServletRequest.getMethod()); 33 | span.name(httpServletRequest.getMethod()); 34 | } 35 | 36 | if(httpServletRequest != null && httpServletRequest.getHeader(Constants.CONTENT_TYPE) != null) { 37 | span.tag(Constants.CAPI_REQUEST_CONTENT_TYPE, httpServletRequest.getHeader(Constants.CONTENT_TYPE)); 38 | } 39 | 40 | if(httpServletRequest != null && httpServletRequest.getContentLength() > -1) { 41 | span.tag(Constants.CAPI_REQUEST_CONTENT_LENGTH, Integer.toString(httpServletRequest.getContentLength())); 42 | } 43 | 44 | if(exchange.getIn().getHeader(Constants.REASON_MESSAGE_HEADER) != null) { 45 | span.tag(Constants.CAPI_REQUEST_ERROR_MESSAGE, (String) exchange.getIn().getHeader(Constants.REASON_MESSAGE_HEADER)); 46 | } 47 | 48 | String responseCode = exchange.getMessage().getHeader(Exchange.HTTP_RESPONSE_CODE, String.class); 49 | if (responseCode != null) { 50 | span.tag(Constants.CAPI_SERVER_EXCHANGE_MESSAGE_RESPONSE_CODE, responseCode); 51 | } 52 | 53 | Long clientResponseTime = getClientResponseTime(exchange); 54 | if(clientResponseTime != null) { 55 | span.tag("capi.client.response.time", clientResponseTime + ""); 56 | } 57 | 58 | String clientEndpoint = exchange.getProperty(Constants.CLIENT_ENDPOINT, String.class); 59 | if(clientEndpoint != null) { 60 | span.tag("capi.client.address", clientEndpoint); 61 | } 62 | 63 | String clientResponseCode = exchange.getProperty(Constants.CLIENT_RESPONSE_CODE, String.class); 64 | if(clientResponseCode != null) { 65 | span.tag("capi.client.response.code", clientResponseCode); 66 | } 67 | } 68 | 69 | private Long getClientResponseTime(Exchange exchange) { 70 | Long clientStartTime = exchange.getProperty(Constants.CLIENT_START_TIME, Long.class); 71 | Long clientEndTime = exchange.getProperty(Constants.CLIENT_END_TIME, Long.class); 72 | if(clientStartTime != null && clientEndTime != null) { 73 | return (clientEndTime - clientStartTime); 74 | } else { 75 | return null; 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/undertow/CAPILoadBalancerProxyClient.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.undertow; 2 | 3 | import io.surisoft.capi.tracer.CapiUndertowTracer; 4 | import io.undertow.server.HttpServerExchange; 5 | import io.undertow.server.handlers.proxy.LoadBalancingProxyClient; 6 | 7 | 8 | public class CAPILoadBalancerProxyClient extends LoadBalancingProxyClient { 9 | 10 | private final CapiUndertowTracer capiUndertowTracer; 11 | 12 | public CAPILoadBalancerProxyClient(CapiUndertowTracer capiUndertowTracer) { 13 | this.capiUndertowTracer = capiUndertowTracer; 14 | } 15 | 16 | public Host selectHost(HttpServerExchange exchange) { 17 | Host host = super.selectHost(exchange); 18 | if(host != null) { 19 | if(capiUndertowTracer != null) { 20 | capiUndertowTracer.capiProxyRequest(host.getUri()); 21 | } 22 | return host; 23 | } 24 | //no available hosts 25 | return null; 26 | } 27 | } -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/undertow/SSEGateway.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.undertow; 2 | 3 | import io.surisoft.capi.exception.CapiUndertowException; 4 | import io.surisoft.capi.oidc.SSEAuthorization; 5 | import io.surisoft.capi.schema.SSEClient; 6 | import io.surisoft.capi.utils.Constants; 7 | import io.surisoft.capi.utils.SSEUtils; 8 | import io.undertow.Undertow; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.beans.factory.annotation.Value; 12 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 13 | import org.springframework.stereotype.Component; 14 | 15 | import javax.net.ssl.SSLContext; 16 | import java.util.Map; 17 | import java.util.Optional; 18 | 19 | @Component 20 | @ConditionalOnProperty(prefix = "capi.sse", name = "enabled", havingValue = "true") 21 | public class SSEGateway { 22 | private static final Logger log = LoggerFactory.getLogger(SSEGateway.class); 23 | private final int port; 24 | private final Map sseClients; 25 | private SSEAuthorization sseAuthorization; 26 | private final SSEUtils sseUtils; 27 | private final Optional sslContext; 28 | 29 | public SSEGateway(@Value("${capi.sse.server.port}") int port, Map sseClients, SSEUtils sseUtils, Optional sslContext) { 30 | this.port = port; 31 | this.sseClients = sseClients; 32 | this.sseUtils = sseUtils; 33 | this.sslContext = sslContext; 34 | } 35 | 36 | public void runProxy() { 37 | try { 38 | sseAuthorization = sseUtils.createSSEAuthorization(); 39 | } catch (CapiUndertowException e) { 40 | log.warn(e.getMessage()); 41 | } 42 | 43 | Undertow.Builder builder = Undertow.builder(); 44 | 45 | if(sslContext.isPresent()) { 46 | builder.addHttpsListener(port, Constants.UNDERTOW_LISTENING_ADDRESS, sslContext.get()); 47 | } else { 48 | builder.addHttpListener(port, Constants.UNDERTOW_LISTENING_ADDRESS); 49 | } 50 | 51 | builder 52 | .setHandler(httpServerExchange -> { 53 | String requestPath = httpServerExchange.getRequestPath(); 54 | String serviceDefinitionPath = sseUtils.getPathDefinition(requestPath); 55 | if (sseClients.containsKey(serviceDefinitionPath)) { 56 | if (sseAuthorization != null) { 57 | if (sseAuthorization.isAuthorized(sseClients.get(serviceDefinitionPath), httpServerExchange)) { 58 | log.info("{} is authorized!", httpServerExchange.getRequestPath()); 59 | httpServerExchange.setRequestURI(sseUtils.normalizePathForForwarding(sseClients.get(serviceDefinitionPath), requestPath)); 60 | httpServerExchange.setRelativePath(sseUtils.normalizePathForForwarding(sseClients.get(serviceDefinitionPath), requestPath)); 61 | sseClients.get(serviceDefinitionPath).getHttpHandler().handleRequest(httpServerExchange); 62 | } else { 63 | log.info("{} is not authorized!", httpServerExchange.getRequestPath()); 64 | httpServerExchange.setStatusCode(403); 65 | httpServerExchange.endExchange(); 66 | } 67 | } else { 68 | if (!sseClients.get(serviceDefinitionPath).requiresSubscription()) { 69 | log.info("{} is authorized!", httpServerExchange.getRequestPath()); 70 | httpServerExchange.setRequestURI(sseUtils.normalizePathForForwarding(sseClients.get(serviceDefinitionPath), requestPath)); 71 | httpServerExchange.setRelativePath(sseUtils.normalizePathForForwarding(sseClients.get(serviceDefinitionPath), requestPath)); 72 | sseClients.get(serviceDefinitionPath).getHttpHandler().handleRequest(httpServerExchange); 73 | } else { 74 | log.info("{} is not authorized!", httpServerExchange.getRequestPath()); 75 | httpServerExchange.setStatusCode(403); 76 | httpServerExchange.endExchange(); 77 | } 78 | } 79 | } 80 | }); 81 | builder.build().start(); 82 | } 83 | } -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/utils/ErrorMessage.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.utils; 2 | 3 | public class ErrorMessage { 4 | private ErrorMessage() { 5 | throw new IllegalStateException("Utility class"); 6 | } 7 | public static final String ERROR_CONNECTING_TO_CONSUL = "Error connecting to Consul, will try again..."; 8 | public static final String IS_AUTHORIZED = "{} is authorized!"; 9 | public static final String IS_NOT_AUTHORIZED = "{} is not authorized!"; 10 | public static final String IS_NOT_PRESENT = "{} is not present!"; 11 | public static final String NO_TOKEN_PROVIDED = "No token provided!"; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/io/surisoft/capi/utils/SSEUtils.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.utils; 2 | 3 | import com.nimbusds.jose.proc.SecurityContext; 4 | import com.nimbusds.jwt.proc.DefaultJWTProcessor; 5 | import io.surisoft.capi.exception.CapiUndertowException; 6 | import io.surisoft.capi.oidc.SSEAuthorization; 7 | import io.surisoft.capi.schema.HttpProtocol; 8 | import io.surisoft.capi.schema.SSEClient; 9 | import io.surisoft.capi.schema.Service; 10 | import io.undertow.server.HttpHandler; 11 | import io.undertow.server.handlers.ResponseCodeHandler; 12 | import io.undertow.server.handlers.proxy.LoadBalancingProxyClient; 13 | import io.undertow.server.handlers.proxy.ProxyHandler; 14 | import org.springframework.beans.factory.annotation.Value; 15 | import org.springframework.stereotype.Component; 16 | 17 | import java.net.URI; 18 | import java.util.List; 19 | import java.util.Optional; 20 | 21 | @Component 22 | public class SSEUtils { 23 | 24 | private final String capiContextPath; 25 | private final Optional>> defaultJWTProcessor; 26 | 27 | public SSEUtils(@Value("${camel.servlet.mapping.context-path}") String capiContextPath, 28 | Optional>> defaultJWTProcessor) { 29 | this.capiContextPath = capiContextPath; 30 | this.defaultJWTProcessor = defaultJWTProcessor; 31 | } 32 | 33 | public HttpHandler createClientHttpHandler(SSEClient webSocketClient, Service service) { 34 | LoadBalancingProxyClient loadBalancingProxyClient = new LoadBalancingProxyClient(); 35 | 36 | webSocketClient.getMappingList().forEach((m) -> { 37 | String schema = service.getServiceMeta().getSchema() == null ? HttpProtocol.HTTP.getProtocol() : service.getServiceMeta().getSchema(); 38 | loadBalancingProxyClient.addHost(URI.create(schema + "://" + m.getHostname() + ":" + m.getPort())); 39 | }); 40 | return ProxyHandler 41 | .builder() 42 | .setProxyClient(loadBalancingProxyClient) 43 | .setMaxRequestTime(360000) 44 | .setNext(ResponseCodeHandler.HANDLE_404) 45 | .build(); 46 | } 47 | 48 | public SSEAuthorization createSSEAuthorization() throws CapiUndertowException { 49 | if(defaultJWTProcessor.isPresent()) { 50 | return new SSEAuthorization(defaultJWTProcessor.get()); 51 | } 52 | throw new CapiUndertowException("No OIDC provider enabled, consider enabling OIDC"); 53 | } 54 | 55 | public String normalizePathForForwarding(SSEClient sseClient, String path) { 56 | String pathWithoutCapiContext = path.replaceAll(Constants.CAPI_CONTEXT, ""); 57 | return pathWithoutCapiContext.replaceAll(sseClient.getApiId(), ""); 58 | } 59 | 60 | public String normalizeBaseContextName() { 61 | return capiContextPath.replaceAll("/", "").replaceAll("\\*", ""); 62 | } 63 | 64 | public String getPathDefinition(String originalRequest) { 65 | String[] pathParts = originalRequest.split("/"); 66 | if(pathParts.length < 4) { 67 | return null; 68 | } 69 | if(!pathParts[1].equals(normalizeBaseContextName())) { 70 | return null; 71 | } 72 | return Constants.CAPI_CONTEXT + "/" + pathParts[2] + "/" + pathParts[3] + "/"; 73 | } 74 | 75 | public SSEClient createSSEClient(Service service) { 76 | 77 | //The path should be the same for all the nodes, so we take the first just to set the path. 78 | String sseContext = Constants.CAPI_CONTEXT + service.getContext() + service.getMappingList().stream().toList().get(0).getRootContext(); 79 | 80 | SSEClient sseClient = new SSEClient(); 81 | 82 | sseClient.setApiId(service.getContext()); 83 | sseClient.setMappingList(service.getMappingList()); 84 | sseClient.setPath(sseContext); 85 | sseClient.setRequiresSubscription(service.getServiceMeta().isSecured()); 86 | sseClient.setHttpHandler(createClientHttpHandler(sseClient, service)); 87 | return sseClient; 88 | } 89 | } -------------------------------------------------------------------------------- /src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | #################################### 2 | ### Application Server specifics ### 3 | #################################### 4 | server: 5 | undertow: 6 | accesslog: 7 | enabled: false 8 | dir: 9 | rotate: false 10 | port: 8380 11 | ssl: 12 | enabled: false 13 | key-store-type: PKCS12 14 | key-store: 15 | key-store-password: 16 | ######################## 17 | ### Spring specifics ### 18 | ######################## 19 | spring: 20 | servlet: 21 | multipart: 22 | max-file-size: 200MB 23 | max-request-size: 200MB 24 | autoconfigure: 25 | exclude: org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration 26 | profiles: 27 | active: default 28 | # CAPI Banner, keep it, it is beautiful! 29 | banner: 30 | location: classpath:capi.txt 31 | # Allow bean definition overriding (Do not change) 32 | main: 33 | allow-bean-definition-overriding: true 34 | # Actuator Metrics Endpoint Configuration (Do not change) 35 | management: 36 | server: 37 | port: 8381 38 | security: 39 | enabled: false 40 | endpoint: 41 | camelroutes: 42 | access: read_only 43 | endpoints: 44 | web: 45 | base-path: /metrics/ 46 | exposure: 47 | include: 'health,prometheus,routes,capi,openapi,truststore,kv,ws-routes' 48 | ####################### 49 | ### Camel Specifics ### 50 | ####################### 51 | camel: 52 | main: 53 | name: capi-gateway 54 | tracing: false 55 | component: 56 | metrics: 57 | enabled: true 58 | servlet: 59 | mapping: 60 | context-path: /capi/* 61 | ###################### 62 | ### CAPI specifics ### 63 | ###################### 64 | capi: 65 | kube: 66 | namespace: 67 | namespace: local 68 | strict: false 69 | mode: full 70 | throttling: 71 | enabled: false 72 | public-endpoint: http://localhost:8380/capi/ 73 | #### Kafka is still experimental #### 74 | kafka: 75 | enabled: false 76 | host: localhost:9092 77 | topic: capi 78 | group-instance: 79 | group-id: 80 | ssl: 81 | enabled: false 82 | keystore: 83 | location: 84 | password: 85 | truststore: 86 | location: 87 | password: 88 | reverse: 89 | proxy: 90 | enabled: false 91 | host: localhost:8380 92 | # DO NOT CHANGE, unless you know exactly that you CAPI to follow Redirects. 93 | # If you want to disable routes from following redirects 94 | disable: 95 | redirect: true 96 | # Websocket Gateway (Editable Property) 97 | websocket: 98 | enabled: false 99 | server: 100 | port: 8382 101 | sse: 102 | enabled: false 103 | server: 104 | port: 8383 105 | # Enable Traces (Editable Property) 106 | traces: 107 | enabled: false 108 | endpoint: http://localhost:9411 109 | # Consul Integration (Editable Property) 110 | consul: 111 | kv: 112 | enabled: false 113 | host: http://localhost:8500 114 | token: 115 | timer: 116 | interval: 10000 117 | token: 118 | hosts: 119 | http://localhost:8500 120 | discovery: 121 | enabled: true 122 | timer: 123 | interval: 40000 124 | # Certificate Management (Editable property) 125 | # If you want to enable certificate management, please provide a trust store (JKS). 126 | trust: 127 | store: 128 | enabled: false 129 | path: 130 | password: 131 | encoded: 132 | route: 133 | socket: 134 | timeout: 180000 135 | connection: 136 | request: 137 | #How long does CAPI waits to get a connection from the Connection Pool. 138 | timeout: 5000 139 | request: 140 | # How long does CAPI waits for establishing a connection to the remote server 141 | timeout: 5000 142 | version: ^project.version^ 143 | name: ^project.name^ 144 | spring: 145 | version: ^project.description^ 146 | gateway: 147 | cors: 148 | management: 149 | enabled: false 150 | allowed-headers: 151 | Origin, 152 | Accept, 153 | X-Requested-With, 154 | Content-Type, 155 | Access-Control-Request-Method, 156 | Access-Control-Request-Headers, 157 | x-referrer, 158 | Authorization, 159 | X-Csrf-Request, 160 | Cache-Control, 161 | pragma, 162 | gem-context, 163 | x-syncmode, 164 | X-Total-Count, 165 | Last-Event-ID, 166 | X-B3-Sampled, 167 | X-B3-SpanId, 168 | X-B3-TraceId, 169 | X-B3-ParentSpanId, 170 | Vary 171 | # oauth2 support to protect the routes. (Editable properties) 172 | oauth2: 173 | cookieName: 174 | provider: 175 | enabled: false 176 | keys: http://localhost:8080/realms/capi/protocol/openid-connect/certs 177 | opa: 178 | enabled: false 179 | endpoint: http://localhost:8181 180 | # Time to live for Sticky Sessions feature 181 | sticky: 182 | session: 183 | time: 184 | to: 185 | live: 2 186 | logging: 187 | level: 188 | com: 189 | hazelcast: 190 | system: 191 | logo: OFF -------------------------------------------------------------------------------- /src/main/resources/capi.txt: -------------------------------------------------------------------------------- 1 | ┌───────────────────────────┐ 2 | │ CAPI │ 3 | │ Gateway │ 4 | └───────────────────────────┘ -------------------------------------------------------------------------------- /src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | ${log-dir}/capi.log 9 | 10 | ${log-dir}/capi.%d{yyyy-MM-dd}.%i.gz 11 | 13 | 10MB 14 | 15 | 30 16 | 17 | 18 | %-4relative [%thread] %-5level %logger{35} -%kvp- %msg%n 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /src/test/java/io/surisoft/capi/configuration/CapiCorsFilterStrategyTest.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.configuration; 2 | 3 | import org.apache.camel.CamelContext; 4 | import org.apache.camel.Exchange; 5 | import org.apache.camel.impl.DefaultCamelContext; 6 | import org.apache.camel.support.DefaultExchange; 7 | import org.junit.jupiter.api.Assertions; 8 | import org.junit.jupiter.api.BeforeEach; 9 | import org.junit.jupiter.api.Test; 10 | 11 | import java.util.Arrays; 12 | 13 | import static org.assertj.core.api.Assertions.assertThat; 14 | 15 | class CapiCorsFilterStrategyTest { 16 | 17 | private CapiCorsFilterStrategy capiCorsFilterStrategyUnderTest; 18 | 19 | public static final String[] CAPI_ACCESS_CONTROL_ALLOW_HEADERS = { 20 | "Origin", 21 | "Accept", 22 | "X-Requested-With", 23 | "Content-Type", 24 | "Access-Control-Request-Method", 25 | "Access-Control-Request-Headers", 26 | "x-referrer", 27 | "Authorization", 28 | "Authorization-Propagation", 29 | "X-Csrf-Request", 30 | "Cache-Control", 31 | "pragma", 32 | "gem-context", 33 | "x-syncmode", 34 | "X-Total-Count", 35 | "Last-Event-ID", 36 | "X-B3-Sampled", 37 | "X-B3-SpanId", 38 | "X-B3-TraceId", 39 | "X-B3-ParentSpanId", 40 | "X-Auth-Url-Index", 41 | "X-Apigateway-Impersonated-Cookie-Name", 42 | "Vary" 43 | }; 44 | 45 | @BeforeEach 46 | void setUp() { 47 | capiCorsFilterStrategyUnderTest = new CapiCorsFilterStrategy(Arrays.stream(CAPI_ACCESS_CONTROL_ALLOW_HEADERS).toList()); 48 | } 49 | 50 | @Test 51 | void testInitialize() { 52 | capiCorsFilterStrategyUnderTest.initialize(); 53 | Assertions.assertTrue(capiCorsFilterStrategyUnderTest.getOutFilter().contains("Access-Control-Allow-Origin")); 54 | Assertions.assertTrue(capiCorsFilterStrategyUnderTest.getOutFilter().contains("Access-Control-Allow-Credentials")); 55 | } 56 | 57 | @Test 58 | void testApplyFilterToExternalHeaders() { 59 | // Setup 60 | CamelContext context = new DefaultCamelContext(); 61 | final Exchange exchange = new DefaultExchange(context); 62 | 63 | 64 | // Run the test 65 | final boolean result1 = capiCorsFilterStrategyUnderTest.applyFilterToExternalHeaders("Access-Control-Allow-Origin", "true", exchange); 66 | final boolean result2 = capiCorsFilterStrategyUnderTest.applyFilterToExternalHeaders("SomeNonFilteredHeader", "true", exchange); 67 | // Verify the results 68 | assertThat(result1).isTrue(); 69 | assertThat(result2).isFalse(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/test/java/io/surisoft/capi/controller/CapiErrorInterfaceTest.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.controller; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.Test; 5 | import org.junit.jupiter.api.extension.ExtendWith; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.http.MediaType; 10 | import org.springframework.mock.web.MockHttpServletResponse; 11 | import org.springframework.test.context.junit.jupiter.SpringExtension; 12 | import org.springframework.test.web.servlet.MockMvc; 13 | import org.springframework.test.web.servlet.setup.MockMvcBuilders; 14 | import org.springframework.web.context.WebApplicationContext; 15 | 16 | import static org.assertj.core.api.Assertions.assertThat; 17 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; 18 | 19 | @ExtendWith(SpringExtension.class) 20 | @SpringBootTest 21 | class CapiErrorInterfaceTest { 22 | 23 | @Autowired 24 | private WebApplicationContext webApplicationContext; 25 | 26 | private MockMvc mockMvc; 27 | 28 | @BeforeEach 29 | public void initialize() { 30 | mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build(); 31 | } 32 | 33 | @Test 34 | void testHandleGet() throws Exception { 35 | // Setup 36 | // Run the test 37 | final MockHttpServletResponse response = mockMvc.perform(get("/error") 38 | .accept(MediaType.APPLICATION_JSON)) 39 | .andReturn().getResponse(); 40 | 41 | // Verify the results 42 | assertThat(response.getStatus()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR.value()); 43 | assertThat(response.getContentAsString()).isEqualTo("{\"errorMessage\":\"The requested route is not available, please try again later on.\",\"errorCode\":401}"); 44 | } 45 | 46 | @Test 47 | void testHandlePost() throws Exception { 48 | // Setup 49 | // Run the test 50 | final MockHttpServletResponse response = mockMvc.perform(post("/error") 51 | .content("content").contentType(MediaType.APPLICATION_JSON) 52 | .accept(MediaType.APPLICATION_JSON)) 53 | .andReturn().getResponse(); 54 | 55 | // Verify the results 56 | assertThat(response.getStatus()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR.value()); 57 | assertThat(response.getContentAsString()).isEqualTo("{\"errorMessage\":\"The requested route is not available, please try again later on.\",\"errorCode\":401}"); 58 | } 59 | 60 | @Test 61 | void testHandlePut() throws Exception { 62 | // Setup 63 | // Run the test 64 | final MockHttpServletResponse response = mockMvc.perform(put("/error") 65 | .content("content").contentType(MediaType.APPLICATION_JSON) 66 | .accept(MediaType.APPLICATION_JSON)) 67 | .andReturn().getResponse(); 68 | 69 | // Verify the results 70 | assertThat(response.getStatus()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR.value()); 71 | assertThat(response.getContentAsString()).isEqualTo("{\"errorMessage\":\"The requested route is not available, please try again later on.\",\"errorCode\":401}"); 72 | } 73 | 74 | @Test 75 | void testHandleDelete() throws Exception { 76 | // Setup 77 | // Run the test 78 | final MockHttpServletResponse response = mockMvc.perform(delete("/error") 79 | .accept(MediaType.APPLICATION_JSON)) 80 | .andReturn().getResponse(); 81 | 82 | // Verify the results 83 | assertThat(response.getStatus()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR.value()); 84 | assertThat(response.getContentAsString()).isEqualTo("{\"errorMessage\":\"The requested route is not available, please try again later on.\",\"errorCode\":401}"); 85 | } 86 | 87 | @Test 88 | void testHandlePatch() throws Exception { 89 | // Setup 90 | // Run the test 91 | final MockHttpServletResponse response = mockMvc.perform(patch("/error") 92 | .content("content").contentType(MediaType.APPLICATION_JSON) 93 | .accept(MediaType.APPLICATION_JSON)) 94 | .andReturn().getResponse(); 95 | 96 | // Verify the results 97 | assertThat(response.getStatus()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR.value()); 98 | assertThat(response.getContentAsString()).isEqualTo("{\"errorMessage\":\"The requested route is not available, please try again later on.\",\"errorCode\":401}"); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/test/java/io/surisoft/capi/controller/ErrorControllerTest.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.controller; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.Test; 5 | import org.junit.jupiter.api.extension.ExtendWith; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.http.MediaType; 10 | import org.springframework.mock.web.MockHttpServletResponse; 11 | import org.springframework.test.context.junit.jupiter.SpringExtension; 12 | import org.springframework.test.web.servlet.MockMvc; 13 | import org.springframework.test.web.servlet.setup.MockMvcBuilders; 14 | import org.springframework.web.context.WebApplicationContext; 15 | 16 | import static org.assertj.core.api.Assertions.assertThat; 17 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; 18 | 19 | @ExtendWith(SpringExtension.class) 20 | @SpringBootTest 21 | class ErrorControllerTest { 22 | 23 | @Autowired 24 | private WebApplicationContext webApplicationContext; 25 | 26 | private MockMvc mockMvc; 27 | 28 | @BeforeEach 29 | public void initialize() { 30 | mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build(); 31 | } 32 | 33 | @Test 34 | void testGet() throws Exception { 35 | // Setup 36 | // Run the test 37 | final MockHttpServletResponse response = mockMvc.perform(get("/capi-error/**") 38 | .accept(MediaType.APPLICATION_JSON)) 39 | .andReturn().getResponse(); 40 | 41 | // Verify the results 42 | assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_GATEWAY.value()); 43 | assertThat(response.getContentAsString()).isEqualTo("{\"routeID\":null,\"errorMessage\":\"There was an exception connecting to the requested service, please try again later on.\",\"errorCode\":502,\"httpUri\":null,\"traceID\":null}"); 44 | } 45 | 46 | @Test 47 | void testPost() throws Exception { 48 | // Setup 49 | // Run the test 50 | final MockHttpServletResponse response = mockMvc.perform(post("/capi-error/**") 51 | .content("content").contentType(MediaType.APPLICATION_JSON) 52 | .accept(MediaType.APPLICATION_JSON)) 53 | .andReturn().getResponse(); 54 | 55 | // Verify the results 56 | assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_GATEWAY.value()); 57 | assertThat(response.getContentAsString()).isEqualTo("{\"routeID\":null,\"errorMessage\":\"There was an exception connecting to the requested service, please try again later on.\",\"errorCode\":502,\"httpUri\":null,\"traceID\":null}"); 58 | } 59 | 60 | @Test 61 | void testPut() throws Exception { 62 | // Setup 63 | // Run the test 64 | final MockHttpServletResponse response = mockMvc.perform(put("/capi-error") 65 | .content("content").contentType(MediaType.APPLICATION_JSON) 66 | .accept(MediaType.APPLICATION_JSON)) 67 | .andReturn().getResponse(); 68 | 69 | // Verify the results 70 | assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_GATEWAY.value()); 71 | assertThat(response.getContentAsString()).isEqualTo("{\"routeID\":null,\"errorMessage\":\"There was an exception connecting to the requested service, please try again later on.\",\"errorCode\":502,\"httpUri\":null,\"traceID\":null}"); 72 | } 73 | 74 | @Test 75 | void testDelete() throws Exception { 76 | // Setup 77 | // Run the test 78 | final MockHttpServletResponse response = mockMvc.perform(delete("/capi-error") 79 | .accept(MediaType.APPLICATION_JSON)) 80 | .andReturn().getResponse(); 81 | 82 | // Verify the results 83 | assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_GATEWAY.value()); 84 | assertThat(response.getContentAsString()).isEqualTo("{\"routeID\":null,\"errorMessage\":\"There was an exception connecting to the requested service, please try again later on.\",\"errorCode\":502,\"httpUri\":null,\"traceID\":null}"); 85 | } 86 | } -------------------------------------------------------------------------------- /src/test/java/io/surisoft/capi/controller/TestCapiConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.controller; 2 | 3 | import io.surisoft.capi.tracer.CapiTracer; 4 | import org.apache.camel.component.http.HttpComponent; 5 | import org.junit.jupiter.api.Assertions; 6 | import org.junit.jupiter.api.Test; 7 | import org.junit.jupiter.api.extension.ExtendWith; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.test.context.SpringBootTest; 10 | import org.springframework.test.context.TestPropertySource; 11 | import org.springframework.test.context.junit.jupiter.SpringExtension; 12 | 13 | @ExtendWith(SpringExtension.class) 14 | @SpringBootTest 15 | @TestPropertySource( 16 | locations = "classpath:test-observability-application.properties" 17 | ) 18 | class TestCapiConfiguration { 19 | 20 | @Autowired 21 | CapiTracer zipkinTracer; 22 | 23 | @Autowired 24 | HttpComponent httpComponent; 25 | 26 | @Test 27 | void testZipkin() { 28 | Assertions.assertNotNull(zipkinTracer); 29 | } 30 | 31 | @Test 32 | void testHttpComponent() { 33 | Assertions.assertNotNull(httpComponent); 34 | } 35 | } -------------------------------------------------------------------------------- /src/test/java/io/surisoft/capi/controller/TestConsulAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.controller; 2 | 3 | import io.surisoft.capi.service.ConsistencyChecker; 4 | import io.surisoft.capi.service.ConsulNodeDiscovery; 5 | import org.apache.camel.builder.RouteBuilder; 6 | import org.junit.jupiter.api.Assertions; 7 | import org.junit.jupiter.api.Test; 8 | import org.junit.jupiter.api.extension.ExtendWith; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | import org.springframework.test.context.TestPropertySource; 12 | import org.springframework.test.context.junit.jupiter.SpringExtension; 13 | 14 | @ExtendWith(SpringExtension.class) 15 | @SpringBootTest 16 | @TestPropertySource( 17 | locations = "classpath:test-consul-application.properties" 18 | ) 19 | class TestConsulAutoConfiguration { 20 | 21 | @Autowired 22 | ConsulNodeDiscovery consulNodeDiscovery; 23 | 24 | @Autowired 25 | ConsistencyChecker consistencyChecker; 26 | 27 | @Test 28 | void testConsulNodeDiscovery() { 29 | Assertions.assertNotNull(consulNodeDiscovery); 30 | } 31 | 32 | @Test 33 | void testConsistencyChecker() { 34 | Assertions.assertNotNull(consistencyChecker); 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /src/test/java/io/surisoft/capi/controller/TestHttpUtils.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.controller; 2 | 3 | import io.surisoft.capi.utils.HttpUtils; 4 | import org.junit.jupiter.api.Assertions; 5 | import org.junit.jupiter.api.Test; 6 | import org.junit.jupiter.api.extension.ExtendWith; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.test.context.junit.jupiter.SpringExtension; 10 | 11 | @ExtendWith(SpringExtension.class) 12 | @SpringBootTest 13 | class TestHttpUtils { 14 | 15 | @Autowired 16 | HttpUtils httpUtils; 17 | 18 | @Test 19 | void testHttpConnectionTimeout() { 20 | String expected1 = "localhost?param=key&connectTimeout=100"; 21 | String expected2 = "localhost?connectTimeout=100"; 22 | Assertions.assertEquals(httpUtils.setHttpConnectTimeout("localhost?param=key", 100), expected1); 23 | Assertions.assertEquals(httpUtils.setHttpConnectTimeout("localhost", 100), expected2); 24 | } 25 | 26 | @Test 27 | void testSocketTimeout() { 28 | String expected1 = "localhost?param=key&socketTimeout=100"; 29 | String expected2 = "localhost?socketTimeout=100"; 30 | Assertions.assertEquals(httpUtils.setHttpSocketTimeout("localhost?param=key", 100), expected1); 31 | Assertions.assertEquals(httpUtils.setHttpSocketTimeout("localhost", 100), expected2); 32 | } 33 | 34 | @Test 35 | void testIngressEndpoint() { 36 | String expected1 = "localhost?param=key&customHostHeader=ingress.domain"; 37 | String expected2 = "localhost?customHostHeader=ingress.domain"; 38 | Assertions.assertEquals(httpUtils.setIngressEndpoint("localhost?param=key", "ingress.domain"), expected1); 39 | Assertions.assertEquals(httpUtils.setIngressEndpoint("localhost", "ingress.domain"), expected2); 40 | } 41 | 42 | @Test 43 | void testCapiContext() { 44 | String expected = "capi"; 45 | Assertions.assertEquals(httpUtils.getCapiContext("capi/*"), expected); 46 | } 47 | 48 | @Test 49 | void testAll() { 50 | String expected = "localhost?param=key&customHostHeader=ingress.domain&socketTimeout=100&connectTimeout=100"; 51 | String endpoint = httpUtils.setIngressEndpoint("localhost?param=key", "ingress.domain"); 52 | endpoint = httpUtils.setHttpSocketTimeout(endpoint, 100); 53 | endpoint = httpUtils.setHttpConnectTimeout(endpoint, 100); 54 | Assertions.assertEquals(endpoint, expected); 55 | } 56 | } -------------------------------------------------------------------------------- /src/test/java/io/surisoft/capi/controller/TestRouteUtils.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.controller; 2 | 3 | import io.surisoft.capi.schema.*; 4 | import io.surisoft.capi.utils.RouteUtils; 5 | import org.apache.camel.model.RouteDefinition; 6 | import org.junit.jupiter.api.Assertions; 7 | import org.junit.jupiter.api.Test; 8 | import org.junit.jupiter.api.extension.ExtendWith; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | import org.springframework.test.context.TestPropertySource; 12 | import org.springframework.test.context.junit.jupiter.SpringExtension; 13 | 14 | import java.util.ArrayList; 15 | import java.util.HashSet; 16 | import java.util.List; 17 | import java.util.Set; 18 | 19 | @ExtendWith(SpringExtension.class) 20 | @SpringBootTest 21 | @TestPropertySource( 22 | locations = "classpath:test-consul-application.properties" 23 | ) 24 | class TestRouteUtils { 25 | 26 | @Autowired 27 | RouteUtils routeUtils; 28 | 29 | @Test 30 | void testBuildException() { 31 | RouteDefinition routeDefinition = new RouteDefinition(); 32 | String routeId = "unit-test:unit-test-context"; 33 | routeUtils.buildOnExceptionDefinition(routeDefinition, false, routeId); 34 | Assertions.assertNotNull(routeDefinition); 35 | } 36 | 37 | @Test 38 | void testBuildEndpoints() { 39 | 40 | List expectedEndpointList = new ArrayList<>(); 41 | expectedEndpointList.add("https://first.domain:8380/?bridgeEndpoint=true&throwExceptionOnFailure=false&soTimeout=180000&connectionRequestTimeout=5000&connectTimeout=5000"); 42 | expectedEndpointList.add("https://second.domain:8381/?bridgeEndpoint=true&throwExceptionOnFailure=false&soTimeout=180000&connectionRequestTimeout=5000&connectTimeout=5000"); 43 | 44 | Service service = new Service(); 45 | ServiceMeta serviceMeta = new ServiceMeta(); 46 | serviceMeta.setSchema("https"); 47 | service.setServiceMeta(serviceMeta); 48 | 49 | service.setName("test"); 50 | Set mappingList = new HashSet<>(); 51 | 52 | Mapping mapping1 = new Mapping(); 53 | mapping1.setHostname("first.domain"); 54 | mapping1.setPort(8380); 55 | mapping1.setRootContext("/"); 56 | 57 | Mapping mapping2 = new Mapping(); 58 | mapping2.setHostname("second.domain"); 59 | mapping2.setPort(8381); 60 | mapping2.setRootContext("/"); 61 | 62 | mappingList.add(mapping1); 63 | mappingList.add(mapping2); 64 | service.setMappingList(mappingList); 65 | 66 | String[] endpoints = routeUtils.buildEndpoints(service); 67 | for(String endpoint : endpoints) { 68 | Assertions.assertTrue(expectedEndpointList.contains(endpoint)); 69 | } 70 | } 71 | 72 | @Test 73 | void testBuildFrom() { 74 | Service service = new Service(); 75 | service.setContext("test"); 76 | String context = routeUtils.buildFrom(service); 77 | Assertions.assertEquals(context, "/test"); 78 | } 79 | 80 | @Test 81 | void testGetMethodFromRouteId() { 82 | String routeId = "unit-test:test:get"; 83 | Assertions.assertEquals("get", routeUtils.getMethodFromRouteId(routeId)); 84 | } 85 | } -------------------------------------------------------------------------------- /src/test/java/io/surisoft/capi/schema/AliasInfoTest.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.schema; 2 | 3 | import org.junit.jupiter.api.*; 4 | 5 | import static org.junit.jupiter.api.Assertions.assertEquals; 6 | 7 | class AliasInfoTest { 8 | 9 | 10 | private AliasInfo aliasInfo; 11 | 12 | @BeforeEach 13 | void createInstance() { 14 | aliasInfo = new AliasInfo(); 15 | } 16 | 17 | @Test 18 | void testAlias() { 19 | aliasInfo.setAlias("alias"); 20 | assertEquals("alias", aliasInfo.getAlias()); 21 | } 22 | 23 | @Test 24 | void testIssuerDN() { 25 | aliasInfo.setIssuerDN("dn"); 26 | assertEquals("dn", aliasInfo.getIssuerDN()); 27 | } 28 | 29 | @Test 30 | void testSubjectDN() { 31 | aliasInfo.setSubjectDN("sub"); 32 | assertEquals("sub", aliasInfo.getSubjectDN()); 33 | } 34 | 35 | @Test 36 | void testAdditionalInfo() { 37 | aliasInfo.setAdditionalInfo("add"); 38 | assertEquals("add", aliasInfo.getAdditionalInfo()); 39 | } 40 | 41 | @Test 42 | void testApiId() { 43 | aliasInfo.setServiceId("api"); 44 | assertEquals("api", aliasInfo.getServiceId()); 45 | } 46 | } -------------------------------------------------------------------------------- /src/test/java/io/surisoft/capi/schema/CapiInfoTest.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.schema; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.util.Calendar; 7 | import java.util.Date; 8 | 9 | import static org.junit.jupiter.api.Assertions.*; 10 | 11 | class CapiInfoTest { 12 | 13 | private CapiInfo capiInfo; 14 | 15 | @BeforeEach 16 | void setUp() { 17 | capiInfo = new CapiInfo(); 18 | } 19 | 20 | @Test 21 | void testCapiVersion() { 22 | capiInfo.setCapiVersion("1"); 23 | assertEquals("1", capiInfo.getCapiVersion()); 24 | } 25 | 26 | @Test 27 | void testCapiSpringVersion() { 28 | capiInfo.setCapiSpringVersion("3"); 29 | assertEquals("3", capiInfo.getCapiSpringVersion()); 30 | } 31 | 32 | @Test 33 | void testCamelVersion() { 34 | capiInfo.setCamelVersion("4"); 35 | assertEquals("4", capiInfo.getCamelVersion()); 36 | } 37 | 38 | @Test 39 | void testStartTimestamp() { 40 | Date date = Calendar.getInstance().getTime(); 41 | capiInfo.setStartTimestamp(date); 42 | assertEquals(date, capiInfo.getStartTimestamp()); 43 | } 44 | 45 | @Test 46 | void testTotalRoutes() { 47 | capiInfo.setTotalRoutes(1); 48 | assertEquals(1, capiInfo.getTotalRoutes()); 49 | } 50 | 51 | @Test 52 | void testExchangesTotal() { 53 | capiInfo.setExchangesTotal(1); 54 | assertEquals(1, capiInfo.getExchangesTotal()); 55 | } 56 | 57 | @Test 58 | void testExchangesCompleted() { 59 | capiInfo.setExchangesCompleted(1); 60 | assertEquals(1, capiInfo.getExchangesCompleted()); 61 | } 62 | 63 | @Test 64 | void testStartedRoutes() { 65 | capiInfo.setStartedRoutes(1); 66 | assertEquals(1, capiInfo.getStartedRoutes()); 67 | } 68 | 69 | @Test 70 | void testUptime() { 71 | capiInfo.setUptime("1"); 72 | assertEquals("1", capiInfo.getUptime()); 73 | } 74 | 75 | @Test 76 | void testStoppedRouteCount() { 77 | capiInfo.setStoppedRouteCount(1); 78 | assertEquals(1, capiInfo.getStoppedRouteCount()); 79 | } 80 | 81 | @Test 82 | void testRemovedRouteCount() { 83 | capiInfo.setRemovedRouteCount(1); 84 | assertEquals(1, capiInfo.getRemovedRouteCount()); 85 | } 86 | 87 | @Test 88 | void testFailedExchangeCount() { 89 | capiInfo.setFailedExchangeCount(1); 90 | assertEquals(1, capiInfo.getFailedExchangeCount()); 91 | } 92 | } -------------------------------------------------------------------------------- /src/test/java/io/surisoft/capi/schema/CapiRestErrorTest.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.schema; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.junit.jupiter.api.Assertions.*; 7 | 8 | class CapiRestErrorTest { 9 | 10 | private CapiRestError capiRestError; 11 | 12 | @BeforeEach 13 | void setUp() { 14 | capiRestError = new CapiRestError(); 15 | } 16 | 17 | @Test 18 | void testRouteID() { 19 | capiRestError.setRouteID("ID"); 20 | assertEquals("ID", capiRestError.getRouteID()); 21 | } 22 | 23 | @Test 24 | void testErrorMessage() { 25 | capiRestError.setErrorMessage("error"); 26 | assertEquals("error", capiRestError.getErrorMessage()); 27 | } 28 | 29 | @Test 30 | void testErrorCode() { 31 | capiRestError.setErrorCode(400); 32 | assertEquals(400, capiRestError.getErrorCode()); 33 | } 34 | 35 | @Test 36 | void testHttpUri() { 37 | capiRestError.setHttpUri("/unit/test"); 38 | assertEquals("/unit/test", capiRestError.getHttpUri()); 39 | } 40 | 41 | @Test 42 | void testZipkinTraceID() { 43 | capiRestError.setTraceID("ID"); 44 | assertEquals("ID", capiRestError.getTraceID()); 45 | } 46 | } -------------------------------------------------------------------------------- /src/test/java/io/surisoft/capi/schema/ConsulObjectTest.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.schema; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.junit.jupiter.api.Assertions.assertEquals; 7 | import static org.junit.jupiter.api.Assertions.assertNotNull; 8 | 9 | class ConsulObjectTest { 10 | 11 | private ConsulObject consulObject; 12 | 13 | @BeforeEach 14 | void setUp() { 15 | consulObject = new ConsulObject(); 16 | } 17 | 18 | @Test 19 | void testID() { 20 | consulObject.setID("ID"); 21 | assertEquals("ID", consulObject.getID()); 22 | } 23 | 24 | @Test 25 | void getServiceName() { 26 | consulObject.setServiceName("name"); 27 | assertEquals("name", consulObject.getServiceName()); 28 | } 29 | 30 | @Test 31 | void getServiceAddress() { 32 | consulObject.setServiceAddress("address"); 33 | assertEquals("address", consulObject.getServiceAddress()); 34 | } 35 | 36 | @Test 37 | void getServicePort() { 38 | consulObject.setServicePort(80); 39 | assertEquals(80, consulObject.getServicePort()); 40 | } 41 | 42 | @Test 43 | void getServiceMeta() { 44 | ServiceMeta serviceMeta = new ServiceMeta(); 45 | serviceMeta.setGroup("dev"); 46 | consulObject.setServiceMeta(serviceMeta); 47 | assertNotNull(consulObject.getServiceMeta()); 48 | assertEquals("dev", consulObject.getServiceMeta().getGroup()); 49 | } 50 | } -------------------------------------------------------------------------------- /src/test/java/io/surisoft/capi/schema/MappingIdTest.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.schema; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.junit.jupiter.api.Assertions.*; 7 | 8 | class MappingIdTest { 9 | 10 | private MappingId mappingId; 11 | 12 | @BeforeEach 13 | void setUp() { 14 | mappingId = new MappingId(); 15 | } 16 | 17 | @Test 18 | void testRootContext() { 19 | mappingId.setRootContext("/"); 20 | assertEquals("/", mappingId.getRootContext()); 21 | } 22 | 23 | @Test 24 | void testHostname() { 25 | mappingId.setHostname("localhost"); 26 | assertEquals("localhost", mappingId.getHostname()); 27 | } 28 | 29 | @Test 30 | void getPort() { 31 | mappingId.setPort(1); 32 | assertEquals(1, mappingId.getPort()); 33 | } 34 | } -------------------------------------------------------------------------------- /src/test/java/io/surisoft/capi/schema/MappingTest.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.schema; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.junit.jupiter.api.Assertions.*; 7 | 8 | class MappingTest { 9 | 10 | Mapping mapping; 11 | 12 | @BeforeEach 13 | void setUp() { 14 | mapping = new Mapping(); 15 | } 16 | 17 | @Test 18 | void testRootContext() { 19 | mapping.setRootContext("/"); 20 | assertEquals("/", mapping.getRootContext()); 21 | } 22 | 23 | @Test 24 | void testHostname() { 25 | mapping.setHostname("localhost"); 26 | assertEquals("localhost", mapping.getHostname()); 27 | } 28 | 29 | @Test 30 | void testPort() { 31 | mapping.setPort(80); 32 | assertEquals(80, mapping.getPort()); 33 | } 34 | 35 | @Test 36 | void testIngress() { 37 | mapping.setIngress(true); 38 | assertTrue(mapping.isIngress()); 39 | } 40 | 41 | @Test 42 | void testEquals() { 43 | mapping.setIngress(true); 44 | mapping.setHostname("localhost"); 45 | mapping.setPort(80); 46 | mapping.setRootContext("/"); 47 | 48 | Mapping mapping2 = new Mapping(); 49 | mapping2.setIngress(true); 50 | mapping2.setHostname("localhost"); 51 | mapping2.setPort(43); 52 | mapping2.setRootContext("/"); 53 | 54 | assertNotEquals(mapping, mapping2); 55 | } 56 | 57 | @Test 58 | void testTenandId() { 59 | mapping.setTenandId("123"); 60 | assertEquals("123", mapping.getTenandId()); 61 | } 62 | } -------------------------------------------------------------------------------- /src/test/java/io/surisoft/capi/schema/OIDCClientTest.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.schema; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.junit.jupiter.api.Assertions.*; 7 | 8 | class OIDCClientTest { 9 | 10 | private OIDCClient oidcClient; 11 | 12 | @BeforeEach 13 | void setUp() { 14 | oidcClient = new OIDCClient(); 15 | } 16 | 17 | @Test 18 | void testName() { 19 | oidcClient.setName("name"); 20 | assertEquals("name", oidcClient.getName()); 21 | } 22 | 23 | @Test 24 | void testClientId() { 25 | oidcClient.setClientId("clientId"); 26 | assertEquals("clientId", oidcClient.getClientId()); 27 | } 28 | 29 | @Test 30 | void testSecret() { 31 | oidcClient.setSecret("secret"); 32 | assertEquals("secret", oidcClient.getSecret()); 33 | } 34 | 35 | @Test 36 | void testServiceAccountsEnabled() { 37 | oidcClient.setServiceAccountsEnabled(true); 38 | assertTrue(oidcClient.isServiceAccountsEnabled()); 39 | } 40 | } -------------------------------------------------------------------------------- /src/test/java/io/surisoft/capi/schema/RouteDetailsEndpointInfoTest.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.schema; 2 | 3 | import org.apache.camel.CamelContext; 4 | import org.apache.camel.Route; 5 | import org.junit.jupiter.api.BeforeEach; 6 | import org.junit.jupiter.api.extension.ExtendWith; 7 | import org.mockito.Mock; 8 | import org.mockito.junit.jupiter.MockitoExtension; 9 | import org.springframework.test.util.ReflectionTestUtils; 10 | 11 | @ExtendWith(MockitoExtension.class) 12 | class RouteDetailsEndpointInfoTest { 13 | 14 | @Mock 15 | private CamelContext mockCamelContext; 16 | @Mock 17 | private Route mockRoute; 18 | @Mock 19 | private RouteDetails mockRouteDetails; 20 | 21 | @BeforeEach 22 | void setUp() throws Exception { 23 | RouteDetailsEndpointInfo routeDetailsEndpointInfoUnderTest = new RouteDetailsEndpointInfo(mockCamelContext, mockRoute); 24 | ReflectionTestUtils.setField(routeDetailsEndpointInfoUnderTest, "routeDetails", mockRouteDetails); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/io/surisoft/capi/schema/RouteDetailsTest.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.schema; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.util.Calendar; 7 | import java.util.Date; 8 | 9 | import static org.junit.jupiter.api.Assertions.assertEquals; 10 | 11 | class RouteDetailsTest { 12 | RouteDetails routeDetails; 13 | 14 | @BeforeEach 15 | void setUp() { 16 | routeDetails = new RouteDetails(); 17 | } 18 | 19 | @Test 20 | void testDeltaProcessingTime() { 21 | routeDetails.setDeltaProcessingTime(1); 22 | assertEquals(1, routeDetails.getDeltaProcessingTime()); 23 | } 24 | 25 | @Test 26 | void testExchangesInflight() { 27 | routeDetails.setExchangesInflight(1); 28 | assertEquals(1, routeDetails.getExchangesInflight()); 29 | } 30 | 31 | @Test 32 | void testExchangesTotal() { 33 | routeDetails.setExchangesTotal(1); 34 | assertEquals(1, routeDetails.getExchangesTotal()); 35 | } 36 | 37 | @Test 38 | void testExternalRedeliveries() { 39 | routeDetails.setExternalRedeliveries(1); 40 | assertEquals(1, routeDetails.getExternalRedeliveries()); 41 | } 42 | 43 | @Test 44 | void testFailuresHandled() { 45 | routeDetails.setFailuresHandled(1); 46 | assertEquals(1, routeDetails.getFailuresHandled()); 47 | } 48 | 49 | @Test 50 | void testFirstExchangeCompletedExchangeId() { 51 | routeDetails.setFirstExchangeCompletedExchangeId("ID"); 52 | assertEquals("ID", routeDetails.getFirstExchangeCompletedExchangeId()); 53 | } 54 | 55 | @Test 56 | void testFirstExchangeCompletedTimestamp() { 57 | Date date = Calendar.getInstance().getTime(); 58 | routeDetails.setFirstExchangeCompletedTimestamp(date); 59 | assertEquals(date, routeDetails.getFirstExchangeCompletedTimestamp()); 60 | } 61 | 62 | @Test 63 | void testFirstExchangeFailureExchangeId() { 64 | routeDetails.setFirstExchangeFailureExchangeId("ID"); 65 | assertEquals("ID", routeDetails.getFirstExchangeFailureExchangeId()); 66 | } 67 | 68 | @Test 69 | void testFirstExchangeFailureTimestamp() { 70 | Date date = Calendar.getInstance().getTime(); 71 | routeDetails.setFirstExchangeFailureTimestamp(date); 72 | assertEquals(date, routeDetails.getFirstExchangeFailureTimestamp()); 73 | } 74 | 75 | @Test 76 | void testLastExchangeCompletedExchangeId() { 77 | routeDetails.setLastExchangeCompletedExchangeId("ID"); 78 | assertEquals("ID", routeDetails.getLastExchangeCompletedExchangeId()); 79 | } 80 | 81 | @Test 82 | void testLastExchangeCompletedTimestamp() { 83 | Date date = Calendar.getInstance().getTime(); 84 | routeDetails.setLastExchangeCompletedTimestamp(date); 85 | assertEquals(date, routeDetails.getLastExchangeCompletedTimestamp()); 86 | } 87 | 88 | @Test 89 | void testLastExchangeFailureExchangeId() { 90 | routeDetails.setLastExchangeFailureExchangeId("ID"); 91 | assertEquals("ID", routeDetails.getLastExchangeFailureExchangeId()); 92 | } 93 | 94 | @Test 95 | void testLastExchangeFailureTimestamp() { 96 | Date date = Calendar.getInstance().getTime(); 97 | routeDetails.setLastExchangeFailureTimestamp(date); 98 | assertEquals(date, routeDetails.getLastExchangeFailureTimestamp()); 99 | } 100 | 101 | @Test 102 | void testLastProcessingTime() { 103 | routeDetails.setLastProcessingTime(1); 104 | assertEquals(1, routeDetails.getLastProcessingTime()); 105 | } 106 | 107 | @Test 108 | void getMaxProcessingTime() { 109 | } 110 | 111 | @Test 112 | void setMaxProcessingTime() { 113 | } 114 | 115 | @Test 116 | void getMeanProcessingTime() { 117 | } 118 | 119 | @Test 120 | void setMeanProcessingTime() { 121 | } 122 | 123 | @Test 124 | void getMinProcessingTime() { 125 | } 126 | 127 | @Test 128 | void setMinProcessingTime() { 129 | } 130 | 131 | @Test 132 | void getOldestInflightDuration() { 133 | } 134 | 135 | @Test 136 | void setOldestInflightDuration() { 137 | } 138 | 139 | @Test 140 | void getOldestInflightExchangeId() { 141 | } 142 | 143 | @Test 144 | void setOldestInflightExchangeId() { 145 | } 146 | 147 | @Test 148 | void getRedeliveries() { 149 | } 150 | 151 | @Test 152 | void setRedeliveries() { 153 | } 154 | 155 | @Test 156 | void getTotalProcessingTime() { 157 | } 158 | 159 | @Test 160 | void setTotalProcessingTime() { 161 | } 162 | 163 | @Test 164 | void isHasRouteController() { 165 | } 166 | 167 | @Test 168 | void setHasRouteController() { 169 | } 170 | } -------------------------------------------------------------------------------- /src/test/java/io/surisoft/capi/schema/RouteEndpointInfoTest.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.schema; 2 | 3 | import org.apache.camel.Route; 4 | import org.junit.jupiter.api.BeforeEach; 5 | import org.junit.jupiter.api.Test; 6 | import org.junit.jupiter.api.extension.ExtendWith; 7 | import org.mockito.Mock; 8 | import org.mockito.junit.jupiter.MockitoExtension; 9 | import org.springframework.test.util.ReflectionTestUtils; 10 | 11 | import java.util.Map; 12 | 13 | import static org.assertj.core.api.Assertions.assertThat; 14 | 15 | @ExtendWith(MockitoExtension.class) 16 | class RouteEndpointInfoTest { 17 | 18 | @Mock 19 | private Route mockRoute; 20 | @Mock 21 | private Map mockProperties; 22 | 23 | private RouteEndpointInfo routeEndpointInfoUnderTest; 24 | 25 | @BeforeEach 26 | void setUp() throws Exception { 27 | routeEndpointInfoUnderTest = new RouteEndpointInfo(mockRoute); 28 | ReflectionTestUtils.setField(routeEndpointInfoUnderTest, "properties", mockProperties); 29 | ReflectionTestUtils.setField(routeEndpointInfoUnderTest, "group", "group"); 30 | ReflectionTestUtils.setField(routeEndpointInfoUnderTest, "description", "description"); 31 | ReflectionTestUtils.setField(routeEndpointInfoUnderTest, "status", "status"); 32 | ReflectionTestUtils.setField(routeEndpointInfoUnderTest, "uptime", "uptime"); 33 | } 34 | 35 | @Test 36 | void testIdGetterAndSetter() { 37 | final String id = "id"; 38 | routeEndpointInfoUnderTest.setId(id); 39 | assertThat(routeEndpointInfoUnderTest.getId()).isEqualTo(id); 40 | } 41 | 42 | @Test 43 | void testGetGroup() { 44 | assertThat(routeEndpointInfoUnderTest.getGroup()).isEqualTo("group"); 45 | } 46 | 47 | @Test 48 | void testGetProperties() { 49 | assertThat(routeEndpointInfoUnderTest.getProperties()).isEqualTo(mockProperties); 50 | } 51 | 52 | @Test 53 | void testGetDescription() { 54 | assertThat(routeEndpointInfoUnderTest.getDescription()).isEqualTo("description"); 55 | } 56 | 57 | @Test 58 | void testGetUptime() { 59 | assertThat(routeEndpointInfoUnderTest.getUptime()).isEqualTo("uptime"); 60 | } 61 | 62 | @Test 63 | void testGetUptimeMillis() { 64 | assertThat(routeEndpointInfoUnderTest.getUptimeMillis()).isEqualTo(0L); 65 | } 66 | 67 | @Test 68 | void testGetStatus() { 69 | assertThat(routeEndpointInfoUnderTest.getStatus()).isEqualTo("status"); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/test/java/io/surisoft/capi/schema/RunningTenantTest.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.schema; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.junit.jupiter.api.Assertions.*; 7 | 8 | class RunningTenantTest { 9 | 10 | private RunningTenant runningTenant; 11 | 12 | @BeforeEach 13 | void setUp() { 14 | runningTenant = new RunningTenant("ten1", 1); 15 | } 16 | 17 | @Test 18 | void testGets() { 19 | assertEquals("ten1", runningTenant.getTenant()); 20 | assertEquals(1, runningTenant.getNodeIndex()); 21 | } 22 | 23 | @Test 24 | void testSets() { 25 | runningTenant.setTenant("ten2"); 26 | runningTenant.setNodeIndex(2); 27 | assertEquals("ten2", runningTenant.getTenant()); 28 | assertEquals(2, runningTenant.getNodeIndex()); 29 | } 30 | } -------------------------------------------------------------------------------- /src/test/java/io/surisoft/capi/schema/ServiceMetaTest.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.schema; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.junit.jupiter.api.Assertions.*; 7 | 8 | class ServiceMetaTest { 9 | 10 | private ServiceMeta serviceMeta; 11 | @BeforeEach 12 | void setUp() { 13 | serviceMeta = new ServiceMeta(); 14 | } 15 | 16 | @Test 17 | void testSecured() { 18 | serviceMeta.setSecured(true); 19 | assertTrue(serviceMeta.isSecured()); 20 | } 21 | 22 | @Test 23 | void testRootContext() { 24 | serviceMeta.setRootContext("/"); 25 | assertEquals("/", serviceMeta.getRootContext()); 26 | } 27 | 28 | @Test 29 | void testSchema() { 30 | serviceMeta.setSchema("http"); 31 | assertEquals("http", serviceMeta.getSchema()); 32 | } 33 | 34 | @Test 35 | void testTenantAware() { 36 | serviceMeta.setTenantAware(true); 37 | assertTrue(serviceMeta.isTenantAware()); 38 | } 39 | 40 | @Test 41 | void testTenantId() { 42 | serviceMeta.setTenantId("ID"); 43 | assertEquals("ID", serviceMeta.getTenantId()); 44 | } 45 | 46 | @Test 47 | void testGroup() { 48 | serviceMeta.setGroup("dev"); 49 | assertEquals("dev", serviceMeta.getGroup()); 50 | } 51 | 52 | @Test 53 | void testB3TraceId() { 54 | serviceMeta.setB3TraceId(true); 55 | assertTrue(serviceMeta.isB3TraceId()); 56 | } 57 | 58 | @Test 59 | void testIngress() { 60 | serviceMeta.setIngress("ingress"); 61 | assertEquals("ingress", serviceMeta.getIngress()); 62 | } 63 | 64 | @Test 65 | void testOpenApiEndpoint() { 66 | serviceMeta.setOpenApiEndpoint("http://openapi.com"); 67 | assertEquals("http://openapi.com", serviceMeta.getOpenApiEndpoint()); 68 | } 69 | 70 | @Test 71 | void testStickySessionEnabled() { 72 | serviceMeta.setStickySession(true); 73 | assertTrue(serviceMeta.isStickySession()); 74 | } 75 | 76 | @Test 77 | void testStickySessionType() { 78 | serviceMeta.setStickySessionType("cookie"); 79 | assertEquals("cookie", serviceMeta.getStickySessionType()); 80 | } 81 | 82 | @Test 83 | void testStickySessionKey() { 84 | serviceMeta.setStickySessionKey("SESSION"); 85 | assertEquals("SESSION", serviceMeta.getStickySessionKey()); 86 | } 87 | 88 | @Test 89 | void testOpaRego() { 90 | serviceMeta.setOpaRego("capi/smk_api/dev"); 91 | assertEquals("capi/smk_api/dev", serviceMeta.getOpaRego()); 92 | } 93 | } -------------------------------------------------------------------------------- /src/test/java/io/surisoft/capi/schema/ServiceTest.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.schema; 2 | 3 | import io.swagger.v3.oas.models.OpenAPI; 4 | import org.junit.jupiter.api.BeforeEach; 5 | import org.junit.jupiter.api.Test; 6 | import org.junit.jupiter.api.extension.ExtendWith; 7 | import org.mockito.Mock; 8 | import org.mockito.junit.jupiter.MockitoExtension; 9 | 10 | import java.util.Set; 11 | 12 | import static org.assertj.core.api.Assertions.assertThat; 13 | 14 | @ExtendWith(MockitoExtension.class) 15 | class ServiceTest { 16 | 17 | @Mock 18 | private Set mockMappingList; 19 | @Mock 20 | private ServiceMeta mockServiceMeta; 21 | @Mock 22 | private OpenAPI mockOpenAPI; 23 | 24 | private Service serviceUnderTest; 25 | 26 | @BeforeEach 27 | void setUp() throws Exception { 28 | serviceUnderTest = new Service(); 29 | serviceUnderTest.setMappingList(mockMappingList); 30 | serviceUnderTest.setServiceMeta(mockServiceMeta); 31 | serviceUnderTest.setOpenAPI(mockOpenAPI); 32 | } 33 | 34 | @Test 35 | void testNameGetterAndSetter() { 36 | final String name = "name"; 37 | serviceUnderTest.setName(name); 38 | assertThat(serviceUnderTest.getName()).isEqualTo(name); 39 | } 40 | 41 | @Test 42 | void testContextGetterAndSetter() { 43 | final String context = "context"; 44 | serviceUnderTest.setContext(context); 45 | assertThat(serviceUnderTest.getContext()).isEqualTo(context); 46 | } 47 | 48 | @Test 49 | void testGetMappingList() { 50 | assertThat(serviceUnderTest.getMappingList()).isEqualTo(mockMappingList); 51 | } 52 | 53 | @Test 54 | void testGetServiceMeta() { 55 | assertThat(serviceUnderTest.getServiceMeta()).isEqualTo(mockServiceMeta); 56 | } 57 | 58 | @Test 59 | void testRoundRobinEnabledGetterAndSetter() { 60 | final boolean roundRobinEnabled = false; 61 | serviceUnderTest.setRoundRobinEnabled(roundRobinEnabled); 62 | assertThat(serviceUnderTest.isRoundRobinEnabled()).isFalse(); 63 | } 64 | 65 | @Test 66 | void testMatchOnUriPrefixGetterAndSetter() { 67 | final boolean matchOnUriPrefix = false; 68 | serviceUnderTest.setMatchOnUriPrefix(matchOnUriPrefix); 69 | assertThat(serviceUnderTest.isMatchOnUriPrefix()).isFalse(); 70 | } 71 | 72 | @Test 73 | void testForwardPrefixGetterAndSetter() { 74 | final boolean forwardPrefix = false; 75 | serviceUnderTest.setForwardPrefix(forwardPrefix); 76 | assertThat(serviceUnderTest.isForwardPrefix()).isFalse(); 77 | } 78 | 79 | @Test 80 | void testIdGetterAndSetter() { 81 | final String id = "id"; 82 | serviceUnderTest.setId(id); 83 | assertThat(serviceUnderTest.getId()).isEqualTo(id); 84 | } 85 | 86 | @Test 87 | void testFailOverEnabledGetterAndSetter() { 88 | final boolean failOverEnabled = false; 89 | serviceUnderTest.setFailOverEnabled(failOverEnabled); 90 | assertThat(serviceUnderTest.isFailOverEnabled()).isFalse(); 91 | } 92 | 93 | @Test 94 | void testRegisteredByGetterAndSetter() { 95 | final String registeredBy = "registeredBy"; 96 | serviceUnderTest.setRegisteredBy(registeredBy); 97 | assertThat(serviceUnderTest.getRegisteredBy()).isEqualTo(registeredBy); 98 | } 99 | 100 | @Test 101 | void testGetOpenAPI() { 102 | assertThat(serviceUnderTest.getOpenAPI()).isEqualTo(mockOpenAPI); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/test/java/io/surisoft/capi/schema/StickySessionTest.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.schema; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.assertj.core.api.Assertions.assertThat; 7 | 8 | class StickySessionTest { 9 | 10 | private StickySession stickySessionUnderTest; 11 | 12 | @BeforeEach 13 | void setUp() throws Exception { 14 | stickySessionUnderTest = new StickySession(); 15 | } 16 | 17 | @Test 18 | void testIdGetterAndSetter() { 19 | final String id = "id"; 20 | stickySessionUnderTest.setId(id); 21 | assertThat(stickySessionUnderTest.getId()).isEqualTo(id); 22 | } 23 | 24 | @Test 25 | void testParamNameGetterAndSetter() { 26 | final String paramName = "paramName"; 27 | stickySessionUnderTest.setParamName(paramName); 28 | assertThat(stickySessionUnderTest.getParamName()).isEqualTo(paramName); 29 | } 30 | 31 | @Test 32 | void testParamValueGetterAndSetter() { 33 | final String paramValue = "paramValue"; 34 | stickySessionUnderTest.setParamValue(paramValue); 35 | assertThat(stickySessionUnderTest.getParamValue()).isEqualTo(paramValue); 36 | } 37 | 38 | @Test 39 | void testNodeIndexGetterAndSetter() { 40 | final int nodeIndex = 0; 41 | stickySessionUnderTest.setNodeIndex(nodeIndex); 42 | assertThat(stickySessionUnderTest.getNodeIndex()).isEqualTo(nodeIndex); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/io/surisoft/capi/schema/WebsocketClientTest.java: -------------------------------------------------------------------------------- 1 | package io.surisoft.capi.schema; 2 | 3 | import io.undertow.server.HttpHandler; 4 | import org.junit.jupiter.api.BeforeEach; 5 | import org.junit.jupiter.api.Test; 6 | import org.junit.jupiter.api.extension.ExtendWith; 7 | import org.mockito.Mock; 8 | import org.mockito.junit.jupiter.MockitoExtension; 9 | 10 | import java.util.Set; 11 | 12 | import static org.assertj.core.api.Assertions.assertThat; 13 | 14 | @ExtendWith(MockitoExtension.class) 15 | class WebsocketClientTest { 16 | 17 | @Mock 18 | private Set mockMappingList; 19 | @Mock 20 | private HttpHandler mockHttpHandler; 21 | 22 | private WebsocketClient websocketClientUnderTest; 23 | 24 | @BeforeEach 25 | void setUp() throws Exception { 26 | websocketClientUnderTest = new WebsocketClient(); 27 | websocketClientUnderTest.setMappingList(mockMappingList); 28 | websocketClientUnderTest.setHttpHandler(mockHttpHandler); 29 | } 30 | 31 | @Test 32 | void testPathGetterAndSetter() { 33 | final String path = "path"; 34 | websocketClientUnderTest.setPath(path); 35 | assertThat(websocketClientUnderTest.getPath()).isEqualTo(path); 36 | } 37 | 38 | @Test 39 | void testGetHttpHandler() { 40 | assertThat(websocketClientUnderTest.getHttpHandler()).isEqualTo(mockHttpHandler); 41 | } 42 | 43 | @Test 44 | void testRequiresSubscriptionGetterAndSetter() { 45 | final boolean requiresSubscription = false; 46 | websocketClientUnderTest.setRequiresSubscription(requiresSubscription); 47 | assertThat(websocketClientUnderTest.requiresSubscription()).isFalse(); 48 | } 49 | 50 | @Test 51 | void testSubscriptionRoleGetterAndSetter() { 52 | final String subscriptionRole = "subscriptionRole"; 53 | websocketClientUnderTest.setSubscriptionRole(subscriptionRole); 54 | assertThat(websocketClientUnderTest.getSubscriptionRole()).isEqualTo(subscriptionRole); 55 | } 56 | 57 | @Test 58 | void testApiIdGetterAndSetter() { 59 | final String apiId = "apiId"; 60 | websocketClientUnderTest.setServiceId(apiId); 61 | assertThat(websocketClientUnderTest.getServiceId()).isEqualTo(apiId); 62 | } 63 | 64 | @Test 65 | void testGetMappingList() { 66 | assertThat(websocketClientUnderTest.getMappingList()).isEqualTo(mockMappingList); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/test/resources/application.yaml: -------------------------------------------------------------------------------- 1 | #################################### 2 | ### Application Server specifics ### 3 | #################################### 4 | server: 5 | undertow: 6 | accesslog: 7 | enabled: false 8 | dir: 9 | rotate: false 10 | port: 8380 11 | ssl: 12 | enabled: false 13 | key-store-type: PKCS12 14 | key-store: 15 | key-store-password: 16 | ######################## 17 | ### Spring specifics ### 18 | ######################## 19 | spring: 20 | servlet: 21 | multipart: 22 | max-file-size: 200MB 23 | max-request-size: 200MB 24 | autoconfigure: 25 | exclude: org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration 26 | profiles: 27 | active: default 28 | # CAPI Banner, keep it, it is beautiful! 29 | banner: 30 | location: classpath:capi.txt 31 | # Allow bean definition overriding (Do not change) 32 | main: 33 | allow-bean-definition-overriding: true 34 | # Actuator Metrics Endpoint Configuration (Do not change) 35 | management: 36 | server: 37 | port: 8381 38 | security: 39 | enabled: false 40 | endpoint: 41 | camelroutes: 42 | access: read_only 43 | endpoints: 44 | web: 45 | base-path: /metrics/ 46 | exposure: 47 | include: 'health,prometheus,routes,capi,openapi,truststore,kv,ws-routes' 48 | ####################### 49 | ### Camel Specifics ### 50 | ####################### 51 | camel: 52 | main: 53 | name: capi-gateway 54 | tracing: false 55 | component: 56 | metrics: 57 | enabled: true 58 | servlet: 59 | mapping: 60 | context-path: /capi/* 61 | ###################### 62 | ### CAPI specifics ### 63 | ###################### 64 | capi: 65 | namespace: local 66 | strict: false 67 | mode: full 68 | throttling: 69 | enabled: false 70 | public-endpoint: http://localhost:8380/capi/ 71 | #### Kafka is still experimental #### 72 | kafka: 73 | enabled: false 74 | host: localhost:9092 75 | topic: capi 76 | group-instance: 77 | group-id: 78 | ssl: 79 | enabled: false 80 | keystore: 81 | location: 82 | password: 83 | truststore: 84 | location: 85 | password: 86 | reverse: 87 | proxy: 88 | enabled: false 89 | host: localhost:8380 90 | # DO NOT CHANGE, unless you know exactly that you CAPI to follow Redirects. 91 | # If you want to disable routes from following redirects 92 | disable: 93 | redirect: true 94 | # Websocket Gateway (Editable Property) 95 | websocket: 96 | enabled: false 97 | server: 98 | port: 8382 99 | sse: 100 | enabled: false 101 | server: 102 | port: 8383 103 | # gRPC Gateway (Editable Property) 104 | grpc: 105 | enabled: false 106 | server: 107 | host: localhost 108 | port: 8384 109 | # Enable Traces (Editable Property) 110 | traces: 111 | enabled: false 112 | endpoint: http://localhost:9411 113 | # Consul Integration (Editable Property) 114 | consul: 115 | kv: 116 | enabled: false 117 | host: http://localhost:8500 118 | token: 119 | timer: 120 | interval: 10000 121 | token: 122 | hosts: 123 | http://localhost:8500 124 | discovery: 125 | enabled: false 126 | timer: 127 | interval: 40000 128 | # Certificate Management (Editable property) 129 | # If you want to enable certificate management, please provide a trust store (JKS). 130 | trust: 131 | store: 132 | enabled: false 133 | path: 134 | password: 135 | encoded: 136 | route: 137 | socket: 138 | timeout: 180000 139 | connection: 140 | request: 141 | #How long does CAPI waits to get a connection from the Connection Pool. 142 | timeout: 5000 143 | request: 144 | # How long does CAPI waits for establishing a connection to the remote server 145 | timeout: 5000 146 | version: ^project.version^ 147 | name: ^project.name^ 148 | spring: 149 | version: ^project.description^ 150 | gateway: 151 | cors: 152 | management: 153 | enabled: false 154 | allowed-headers: 155 | Origin, 156 | Accept, 157 | X-Requested-With, 158 | Content-Type, 159 | Access-Control-Request-Method, 160 | Access-Control-Request-Headers, 161 | x-referrer, 162 | Authorization, 163 | X-Csrf-Request, 164 | Cache-Control, 165 | pragma, 166 | gem-context, 167 | x-syncmode, 168 | X-Total-Count, 169 | Last-Event-ID, 170 | X-B3-Sampled, 171 | X-B3-SpanId, 172 | X-B3-TraceId, 173 | X-B3-ParentSpanId, 174 | Vary 175 | # oauth2 support to protect the routes. (Editable properties) 176 | oauth2: 177 | cookieName: 178 | provider: 179 | enabled: false 180 | keys: http://localhost:8080/realms/capi/protocol/openid-connect/certs 181 | opa: 182 | enabled: false 183 | endpoint: http://localhost:8181 184 | # Time to live for Sticky Sessions feature 185 | sticky: 186 | session: 187 | time: 188 | to: 189 | live: 2 -------------------------------------------------------------------------------- /src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/test/resources/test-cert-application.properties: -------------------------------------------------------------------------------- 1 | capi.trust.store.enabled=true 2 | capi.trust.store.path=classpath:cacerts 3 | capi.trust.store.password=changeit 4 | capi.traces.enabled=false 5 | capi.throttling.enabled=false -------------------------------------------------------------------------------- /src/test/resources/test-consul-application.properties: -------------------------------------------------------------------------------- 1 | spring.main.allow-bean-definition-overriding=true 2 | camel.springboot.name=CAPI Load Balancer 3 | server.port=8380 4 | spring.banner.location=classpath:capi.txt 5 | 6 | capi.version=@project.version@ 7 | capi.name=@project.name@ 8 | capi.throttling.enable=false 9 | 10 | build.version=@project.version@ 11 | 12 | logging.level.root=INFO 13 | logging.level.io.surisoft.capi.lb=TRACE 14 | 15 | management.endpoints.web.base-path=/analytics/ 16 | management.endpoints.web.exposure.include=* 17 | management.security.enabled=false 18 | 19 | camel.component.metrics.enabled=true 20 | camel.servlet.mapping.context-path=/capi/* 21 | 22 | capi.gateway.error.endpoint=localhost:8380/capi-error 23 | 24 | capi.manager.security.enabled=false 25 | capi.manager.security.issuer=http://localhost:8080/auth/realms/master/protocol/openid-connect/certs 26 | 27 | capi.trust.store.enabled=false 28 | capi.trust.store.path=classpath:cacerts 29 | capi.trust.store.password=changeit 30 | 31 | ## If you want CAPI to search consul for available APIs to be deployed, enable this feature. 32 | capi.consul.discovery.enabled=true 33 | capi.consul.discovery.timer.interval=20000 34 | capi.consul.hosts=http://localhost:8500 35 | 36 | capi.traces.enabled=false 37 | capi.traces.endpoint= 38 | 39 | springdoc.swagger-ui.disable-swagger-default-url=true 40 | springdoc.swagger-ui.enabled=true 41 | 42 | capi.persistence.enabled=false 43 | spring.profiles.active=default 44 | capi.disable.redirect=true -------------------------------------------------------------------------------- /src/test/resources/test-consul-kv-application.properties: -------------------------------------------------------------------------------- 1 | spring.main.allow-bean-definition-overriding=true 2 | camel.springboot.name=CAPI Load Balancer 3 | server.port=8380 4 | spring.banner.location=classpath:capi.txt 5 | 6 | capi.version=@project.version@ 7 | capi.name=@project.name@ 8 | capi.throttling.enable=false 9 | 10 | build.version=@project.version@ 11 | 12 | logging.level.root=INFO 13 | logging.level.io.surisoft.capi.lb=TRACE 14 | 15 | management.endpoints.web.base-path=/analytics/ 16 | management.endpoints.web.exposure.include=* 17 | management.security.enabled=false 18 | 19 | camel.component.metrics.enabled=true 20 | camel.servlet.mapping.context-path=/capi/* 21 | 22 | capi.gateway.error.endpoint=localhost:8380/capi-error 23 | 24 | capi.manager.security.enabled=false 25 | capi.manager.security.issuer=http://localhost:8080/auth/realms/master/protocol/openid-connect/certs 26 | 27 | capi.trust.store.enabled=false 28 | capi.trust.store.path=classpath:cacerts 29 | capi.trust.store.password=changeit 30 | 31 | ## If you want CAPI to search consul for available APIs to be deployed, enable this feature. 32 | capi.consul.kv.enabled=true 33 | capi.consul.discovery.timer.interval=20000 34 | capi.consul.hosts=http://localhost:8500 35 | 36 | capi.traces.enabled=false 37 | capi.traces.endpoint= 38 | 39 | springdoc.swagger-ui.disable-swagger-default-url=true 40 | springdoc.swagger-ui.enabled=true 41 | 42 | capi.persistence.enabled=false 43 | spring.profiles.active=default 44 | capi.disable.redirect=true -------------------------------------------------------------------------------- /src/test/resources/test-observability-application.properties: -------------------------------------------------------------------------------- 1 | capi.traces.enabled=true 2 | capi.traces.endpoint=http://localhost:9411 3 | capi.disable.redirect=true 4 | capi.throttling.enabled=false -------------------------------------------------------------------------------- /src/test/resources/test-openapi-application.properties: -------------------------------------------------------------------------------- 1 | spring.main.allow-bean-definition-overriding=true 2 | camel.springboot.name=CAPI Load Balancer 3 | server.port=8380 4 | spring.banner.location=classpath:capi.txt 5 | 6 | capi.version=@project.version@ 7 | capi.name=@project.name@ 8 | capi.throttling.enabled=false 9 | 10 | build.version=@project.version@ 11 | 12 | logging.level.root=INFO 13 | logging.level.io.surisoft.capi.lb=TRACE 14 | 15 | management.endpoints.web.base-path=/metrics/ 16 | management.endpoints.web.exposure.include=* 17 | management.security.enabled=false 18 | 19 | camel.component.metrics.enabled=true 20 | camel.servlet.mapping.context-path=/capi/* 21 | 22 | capi.manager.security.enabled=false 23 | capi.manager.security.issuer=http://localhost:8080/auth/realms/master/protocol/openid-connect/certs 24 | 25 | capi.trust.store.enabled=false 26 | capi.trust.store.path=classpath:cacerts 27 | capi.trust.store.password=changeit 28 | 29 | ## If you want CAPI to search consul for available APIs to be deployed, enable this feature. 30 | capi.consul.discovery.enabled=false 31 | capi.consul.discovery.timer.interval=20 32 | capi.consul.hosts=http://localhost:8500 33 | 34 | capi.traces.enabled=false 35 | capi.traces.endpoint= 36 | 37 | springdoc.swagger-ui.disable-swagger-default-url=true 38 | springdoc.swagger-ui.enabled=true 39 | 40 | capi.persistence.enabled=false 41 | spring.profiles.active=default 42 | capi.disable.redirect=true 43 | 44 | capi.opa.enabled=true 45 | capi.opa.endpoint=http://localhost:9999 --------------------------------------------------------------------------------