11/*
22 * Copyright (C) 2016-2024 the original author or authors.
33 *
4- * Licensed to the Apache Software Foundation (ASF) under one
5- * or more contributor license agreements. See the NOTICE file
6- * distributed with this work for additional information
7- * regarding copyright ownership. The ASF licenses this file
8- * to you under the Apache License, Version 2.0 (the
9- * "License"); you may not use this file except in compliance
10- * with the License. You may obtain a copy of the License at
4+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
5+ * agreements. See the NOTICE file distributed with this work for additional information regarding
6+ * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the
7+ * "License"); you may not use this file except in compliance with the License. You may obtain a
8+ * copy of the License at
119 *
12- * http://www.apache.org/licenses/LICENSE-2.0
10+ * http://www.apache.org/licenses/LICENSE-2.0
1311 *
14- * Unless required by applicable law or agreed to in writing,
15- * software distributed under the License is distributed on an
16- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17- * KIND, either express or implied. See the License for the
18- * specific language governing permissions and limitations
19- * under the License.
12+ * Unless required by applicable law or agreed to in writing, software distributed under the License
13+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
14+ * or implied. See the License for the specific language governing permissions and limitations under
15+ * the License.
2016 */
2117package com .viglet .turing .api .integration ;
2218
2319import java .io .OutputStream ;
2420import java .net .HttpURLConnection ;
2521import java .net .URI ;
26-
22+ import java . nio . file . Paths ;
2723import org .apache .http .HttpHeaders ;
2824import org .springframework .http .MediaType ;
29- import java .nio .file .Paths ;
3025import org .springframework .web .bind .annotation .PathVariable ;
3126import org .springframework .web .bind .annotation .RequestMapping ;
3227import org .springframework .web .bind .annotation .RequestMethod ;
3328import org .springframework .web .bind .annotation .RestController ;
34-
3529import com .google .common .io .ByteStreams ;
3630import com .google .common .io .CharStreams ;
3731import com .viglet .turing .persistence .model .integration .TurIntegrationInstance ;
3832import com .viglet .turing .persistence .repository .integration .TurIntegrationInstanceRepository ;
39-
4033import io .swagger .v3 .oas .annotations .tags .Tag ;
4134import jakarta .servlet .http .HttpServletRequest ;
4235import jakarta .servlet .http .HttpServletResponse ;
@@ -55,43 +48,45 @@ public class TurIntegrationAPI {
5548 this .turIntegrationInstanceRepository = turIntegrationInstanceRepository ;
5649 }
5750
58- @ RequestMapping (value = "**" , method = { RequestMethod .GET , RequestMethod .POST , RequestMethod . PUT ,
59- RequestMethod .DELETE }, produces = { MediaType .APPLICATION_JSON_VALUE })
51+ @ RequestMapping (value = "**" , method = {RequestMethod .GET , RequestMethod .POST ,
52+ RequestMethod .PUT , RequestMethod . DELETE }, produces = {MediaType .APPLICATION_JSON_VALUE })
6053 public void indexAnyRequest (HttpServletRequest request , HttpServletResponse response ,
6154 @ PathVariable String integrationId ) {
62- turIntegrationInstanceRepository .findById (integrationId )
63- . ifPresent ( turIntegrationInstance -> proxy (turIntegrationInstance , request , response ));
55+ turIntegrationInstanceRepository .findById (integrationId ). ifPresent (
56+ turIntegrationInstance -> proxy (turIntegrationInstance , request , response ));
6457 }
6558
6659 public void proxy (TurIntegrationInstance turIntegrationInstance , HttpServletRequest request ,
6760 HttpServletResponse response ) {
6861 try {
69- String endpoint = turIntegrationInstance .getEndpoint () +
70- request .getRequestURI ()
71- .replace ("/api/v2/integration/" + turIntegrationInstance .getId (), "/api/v2" );
62+ String endpoint = turIntegrationInstance .getEndpoint () + request .getRequestURI ()
63+ .replace ("/api/v2/integration/" + turIntegrationInstance .getId (), "/api/v2" );
7264 log .debug ("Executing: {}" , endpoint );
7365 URI baseUri = URI .create (turIntegrationInstance .getEndpoint ());
7466 URI fullUri = URI .create (endpoint );
75- // SSRF Mitigation: Only allow requests to the same host and scheme as the registered endpoint
76- if (!baseUri .getHost ().equalsIgnoreCase (fullUri .getHost ()) ||
77- !baseUri .getScheme ().equalsIgnoreCase (fullUri .getScheme ())) {
78- log .warn ("Blocked SSRF attempt: attempted host={}, scheme={}" , fullUri .getHost (), fullUri .getScheme ());
67+ // SSRF Mitigation: Only allow requests to the same host and scheme as the registered
68+ // endpoint
69+ if (!baseUri .getHost ().equalsIgnoreCase (fullUri .getHost ())
70+ || !baseUri .getScheme ().equalsIgnoreCase (fullUri .getScheme ())) {
71+ log .warn ("Blocked SSRF attempt: attempted host={}, scheme={}" , fullUri .getHost (),
72+ fullUri .getScheme ());
7973 response .setStatus (HttpServletResponse .SC_FORBIDDEN );
8074 response .getWriter ().write ("{\" error\" : \" Forbidden proxy target\" }" );
8175 return ;
8276 }
8377 // Validate that the path is safe and does not contain traversal or forbidden segments
8478 if (!isValidProxyPath (fullUri .getPath ())) {
85- log .warn ("Blocked SSRF attempt: invalid or unauthorized path: {}" , fullUri .getPath ());
79+ log .warn ("Blocked SSRF attempt: invalid or unauthorized path: {}" ,
80+ fullUri .getPath ());
8681 response .setStatus (HttpServletResponse .SC_FORBIDDEN );
8782 response .getWriter ().write ("{\" error\" : \" Forbidden proxy path\" }" );
8883 return ;
8984 }
90- HttpURLConnection connectorEnpoint = ( HttpURLConnection ) fullUri
91- .toURL ().openConnection ();
85+ HttpURLConnection connectorEnpoint =
86+ ( HttpURLConnection ) fullUri .toURL ().openConnection ();
9287 connectorEnpoint .setRequestMethod (request .getMethod ());
93- request .getHeaderNames ().asIterator ().forEachRemaining (
94- headerName -> connectorEnpoint .setRequestProperty (headerName , request .getHeader (headerName )));
88+ request .getHeaderNames ().asIterator ().forEachRemaining (headerName -> connectorEnpoint
89+ .setRequestProperty (headerName , request .getHeader (headerName )));
9590 String method = request .getMethod ();
9691 if (method .equals (PUT ) || method .equals (POST )) {
9792 connectorEnpoint .setDoOutput (true );
@@ -102,29 +97,29 @@ public void proxy(TurIntegrationInstance turIntegrationInstance, HttpServletRequ
10297 }
10398 response .setStatus (connectorEnpoint .getResponseCode ());
10499 ByteStreams .copy (connectorEnpoint .getInputStream (), response .getOutputStream ());
105- connectorEnpoint .getHeaderFields ()
106- .forEach ((header , values ) -> values .forEach (value -> {
107- if (header != null && !header .equals (HttpHeaders .TRANSFER_ENCODING )) {
108- log .debug ("Header: {} = {}" , header , value );
109- response .setHeader (header , value );
110- }
111- }));
100+ connectorEnpoint .getHeaderFields ().forEach ((header , values ) -> values .forEach (value -> {
101+ if (header != null && !header .equals (HttpHeaders .TRANSFER_ENCODING )) {
102+ log .debug ("Header: {} = {}" , header , value );
103+ response .setHeader (header , value );
104+ }
105+ }));
112106 } catch (Exception e ) {
113107 log .error (e .getMessage (), e );
114108 }
115109 }
116110
117111 /**
118- * Validates that the proxy path is safe: normalized, does not contain directory traversal,
119- * and starts with the expected API prefix.
112+ * Validates that the proxy path is safe: normalized, does not contain directory traversal, and
113+ * starts with the expected API prefix.
120114 */
121115 private boolean isValidProxyPath (String path ) {
122- if (path == null ) return false ;
123- String normalized = java . nio . file . Paths . get ( path ). normalize (). toString () ;
116+ if (path == null )
117+ return false ;
124118 // Must start with allowed prefix after normalization (prevent access to internal endpoints)
125- if (!normalized .startsWith ("/api/v2 /" )) {
119+ if (!path .startsWith ("/api/" )) {
126120 return false ;
127121 }
122+ String normalized = Paths .get (path ).normalize ().toString ();
128123 // Disallow any attempts at directory traversal
129124 if (normalized .contains (".." )) {
130125 return false ;
0 commit comments