Compare commits
	
		
			21 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 4075eb78c8 | |||
| 68792e2cbf | |||
| 316dd6b01e | |||
| a1275ec06c | |||
| 6794e56748 | |||
| 25ef5660fa | |||
| 0d576eb9a7 | |||
| f99ed01a54 | |||
| 18e3535a57 | |||
| 4d63eb2e2c | |||
| e9e6bd69f9 | |||
| c8e8817e25 | |||
| 057d0120b7 | |||
| b804cc978f | |||
| 729d0ddcfc | |||
| bbadd41ec4 | |||
| c567cf766d | |||
| fb4ff60729 | |||
| 6b6ef23108 | |||
| 0f04461a92 | |||
| b98dad9c4b | 
| @ -34,13 +34,6 @@ jobs: | ||||
|         uses: actions/checkout@v4 | ||||
|         with: | ||||
|           fetch-depth: 0 | ||||
|       - name: JDK setup | ||||
|         uses: actions/setup-java@v4 | ||||
|         with: | ||||
|           distribution: 'zulu' | ||||
|           java-version: '17' | ||||
|       - name: Validate Gradle Wrapper | ||||
|         uses: gradle/actions/wrapper-validation@v3 | ||||
|       - name: Create and push tag | ||||
|         run: | | ||||
|           echo "NEW_VERSION=${{ needs.tag.outputs.new_version }}" | ||||
|  | ||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1,6 +1,8 @@ | ||||
| HELP.md | ||||
| .gradle | ||||
| .run | ||||
| build/ | ||||
| .env* | ||||
| !gradle/wrapper/gradle-wrapper.jar | ||||
| !**/src/main/**/build/ | ||||
| !**/src/test/**/build/ | ||||
|  | ||||
| @ -24,9 +24,15 @@ repositories { | ||||
| dependencies { | ||||
| 	implementation group: 'org.springdoc', name: 'springdoc-openapi-starter-webmvc-ui', version: '2.8.5' | ||||
| 	implementation group: 'org.springframework.boot', name:'spring-boot-starter-oauth2-client', version: '3.5.5' | ||||
| 	compileOnly 'org.projectlombok:lombok:1.18.30' | ||||
| 	annotationProcessor 'org.projectlombok:lombok:1.18.30' | ||||
| 	implementation 'org.springframework.boot:spring-boot-starter-web' | ||||
| 	testImplementation 'org.springframework.boot:spring-boot-starter-test' | ||||
| 	testImplementation 'org.springframework.security:spring-security-test' | ||||
| 	testRuntimeOnly 'org.junit.platform:junit-platform-launcher' | ||||
| 	implementation 'org.springframework.boot:spring-boot-starter-data-jpa' | ||||
| 	runtimeOnly 'org.postgresql:postgresql' | ||||
| 	implementation 'org.hibernate.validator:hibernate-validator' | ||||
| } | ||||
| 
 | ||||
| tasks.named('test') { | ||||
|  | ||||
| @ -3,7 +3,7 @@ services: | ||||
|     image: gitea.hithomelabs.com/hithomelabs/cftunnels:${ENV} | ||||
|     container_name: cftunnels_${ENV} | ||||
|     ports: | ||||
|       - "${HOST_PORT:-5002}:8080 | ||||
|       - ${HOST_PORT}:8080 | ||||
|     environment: | ||||
|       - CLOUDFLARE_ACCOUNT_ID=${CLOUDFLARE_ACCOUNT_ID} | ||||
|       - CLOUDFLARE_API_KEY=${CLOUDFLARE_API_KEY} | ||||
| @ -12,4 +12,20 @@ services: | ||||
|       - OAUTH_CLIENT_ID=${OAUTH_CLIENT_ID} | ||||
|       - OAUTH_CLIENT_SECRET=${OAUTH_CLIENT_SECRET} | ||||
|       - HOST_PORT=${HOST_PORT} | ||||
|       - POSTGRES_USER=${POSTGRES_USERNAME} | ||||
|       - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} | ||||
|     env_file: | ||||
|       - stack.env | ||||
|     restart: unless-stopped | ||||
|   postgres: | ||||
|     image: postgres:15-alpine | ||||
|     container_name: cftunnel-db-${ENV} | ||||
|     environment: | ||||
|       POSTGRES_DB: cftunnel | ||||
|       POSTGRES_USER: ${POSTGRES_USERNAME} | ||||
|       POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} | ||||
|     restart: unless-stopped | ||||
|     ports: | ||||
|       - "${DB_PORT}:5432" | ||||
|     volumes: | ||||
|       - ${DB_PATH}:/var/lib/postgresql/data | ||||
| @ -11,12 +11,12 @@ import java.util.ArrayList; | ||||
| @Configuration | ||||
| public class OpenApiConfig { | ||||
| 
 | ||||
|     @Value("${api.corsResolveUrl}") | ||||
|     private String corsResolveUrl; | ||||
|     @Value("${api.baseUrl}") | ||||
|     private String baseUrl; | ||||
| 
 | ||||
|     @Bean | ||||
|     public OpenAPI openAPI(){ | ||||
|         Server httpsServer = new Server().url(corsResolveUrl); | ||||
|         Server httpsServer = new Server().url(baseUrl); | ||||
|         OpenAPI openApi = new OpenAPI(); | ||||
|         ArrayList<Server> servers = new ArrayList<>(); | ||||
|         servers.add(httpsServer); | ||||
|  | ||||
| @ -29,8 +29,9 @@ public class SecuirtyConfig { | ||||
|         http | ||||
|                 .authorizeHttpRequests(auth -> auth | ||||
|                         .anyRequest().authenticated() | ||||
|                 ) | ||||
|                 .with(new OAuth2LoginConfigurer<>(), oauth2 -> oauth2.userInfoEndpoint(u -> u.oidcUserService(customOidcUserConfiguration))); | ||||
|                 ).csrf(csrf -> csrf.disable()) | ||||
|                 .with(new OAuth2LoginConfigurer<>(), | ||||
|                         oauth2 -> oauth2.userInfoEndpoint(u -> u.oidcUserService(customOidcUserConfiguration))); | ||||
| 
 | ||||
| 
 | ||||
|         return http.build(); | ||||
|  | ||||
| @ -8,6 +8,7 @@ import com.hithomelabs.CFTunnels.Headers.AuthKeyEmailHeader; | ||||
| import com.hithomelabs.CFTunnels.Models.Config; | ||||
| import com.hithomelabs.CFTunnels.Models.Ingress; | ||||
| import com.hithomelabs.CFTunnels.Models.TunnelResponse; | ||||
| import com.hithomelabs.CFTunnels.Services.CloudflareAPIService; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.boot.web.servlet.error.ErrorController; | ||||
| import org.springframework.http.*; | ||||
| @ -40,6 +41,9 @@ public class TunnelController implements ErrorController { | ||||
|     @Autowired | ||||
|     private RestTemplateConfig restTemplateConfig; | ||||
| 
 | ||||
|     @Autowired | ||||
|     CloudflareAPIService cloudflareAPIService; | ||||
| 
 | ||||
|     @PreAuthorize("hasAnyRole('USER')") | ||||
|     @GetMapping("/whoami") | ||||
|     public Map<String,Object> whoAmI(@AuthenticationPrincipal OidcUser oidcUser) { | ||||
| @ -57,12 +61,7 @@ public class TunnelController implements ErrorController { | ||||
|     @GetMapping("/tunnels") | ||||
|     public ResponseEntity<Map<String,Object>> getTunnels(){ | ||||
| 
 | ||||
|         // * * Resource URL to hit get request at | ||||
|         String url = "https://api.cloudflare.com/client/v4/accounts/" + cloudflareConfig.getAccountId() + "/cfd_tunnel"; | ||||
| 
 | ||||
|         HttpEntity<String> httpEntity = new HttpEntity<>("",authKeyEmailHeader.getHttpHeaders()); | ||||
|         ResponseEntity<Map> responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, Map.class); | ||||
| 
 | ||||
|         ResponseEntity<Map> responseEntity = cloudflareAPIService.getCloudflareTunnels(); | ||||
|         Map<String, Object> jsonResponse = new HashMap<>(); | ||||
|         jsonResponse.put("status", "success"); | ||||
|         jsonResponse.put("data", responseEntity.getBody()); | ||||
| @ -72,14 +71,9 @@ public class TunnelController implements ErrorController { | ||||
| 
 | ||||
|     @PreAuthorize("hasAnyRole('DEVELOPER')") | ||||
|     @GetMapping("/tunnel/{tunnelId}") | ||||
|     public ResponseEntity<Map<String,Object>> getTunnelConfigurations(@PathVariable String tunnelId) throws JsonProcessingException { | ||||
| 
 | ||||
|         // * * Resource URL to hit get request at | ||||
|         String url = "https://api.cloudflare.com/client/v4/accounts/" + cloudflareConfig.getAccountId() + "/cfd_tunnel/" + tunnelId + "/configurations"; | ||||
| 
 | ||||
|         HttpEntity<String> httpEntity = new HttpEntity<>("",authKeyEmailHeader.getHttpHeaders()); | ||||
|         ResponseEntity<Map> responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, Map.class); | ||||
|     public ResponseEntity<Map<String,Object>> getTunnelConfigurations(@PathVariable String tunnelId) { | ||||
| 
 | ||||
|         ResponseEntity<Map> responseEntity = cloudflareAPIService.getCloudflareTunnelConfigurations(tunnelId, restTemplate, Map.class); | ||||
|         Map<String, Object> jsonResponse = new HashMap<>(); | ||||
|         jsonResponse.put("status", "success"); | ||||
|         jsonResponse.put("data", responseEntity.getBody()); | ||||
| @ -87,17 +81,12 @@ public class TunnelController implements ErrorController { | ||||
|         return ResponseEntity.ok(jsonResponse); | ||||
|     } | ||||
| 
 | ||||
| // 50df9101-f625-4618-b7c5-100338a57124 | ||||
|     // 50df9101-f625-4618-b7c5-100338a57124 | ||||
|     @PreAuthorize("hasAnyRole('ADMIN')") | ||||
|     @PutMapping("/tunnel/{tunnelId}/add") | ||||
|     public ResponseEntity<Map<String, Object>> addTunnelconfiguration(@PathVariable String tunnelId, @RequestBody Ingress ingress) throws JsonProcessingException { | ||||
| 
 | ||||
|         String url = "https://api.cloudflare.com/client/v4/accounts/" + cloudflareConfig.getAccountId() + "/cfd_tunnel/" + tunnelId + "/configurations"; | ||||
| 
 | ||||
|         // * * Getting existing public hostname mappings | ||||
|         HttpHeaders httpHeaders = authKeyEmailHeader.getHttpHeaders(); | ||||
|         HttpEntity<String> httpEntity = new HttpEntity<>("",httpHeaders); | ||||
|         ResponseEntity<TunnelResponse> responseEntity = restTemplateConfig.restTemplate().exchange(url, HttpMethod.GET, httpEntity, TunnelResponse.class); | ||||
|         ResponseEntity<TunnelResponse> responseEntity = cloudflareAPIService.getCloudflareTunnelConfigurations(tunnelId, restTemplateConfig.restTemplate(), TunnelResponse.class); | ||||
| 
 | ||||
|         // * * Inserting new ingress value at second-to last position in list | ||||
|         Config config = responseEntity.getBody().getResult().getConfig(); | ||||
| @ -105,9 +94,7 @@ public class TunnelController implements ErrorController { | ||||
|         response_ingress.add(response_ingress.size()-1, ingress); | ||||
| 
 | ||||
|         // * * Hitting put endpoint | ||||
|         httpHeaders.setContentType(MediaType.APPLICATION_JSON); | ||||
|         HttpEntity<Config> entity = new HttpEntity<>(config, httpHeaders); | ||||
|         ResponseEntity<Map> response = restTemplateConfig.restTemplate().exchange(url, HttpMethod.PUT, entity, Map.class); | ||||
|         ResponseEntity<TunnelResponse> response = cloudflareAPIService.putCloudflareTunnelConfigurations(tunnelId, restTemplateConfig.restTemplate(), TunnelResponse.class, config); | ||||
| 
 | ||||
|         // * * Displaying response | ||||
|         Map<String, Object> jsonResponse = new HashMap<>(); | ||||
| @ -121,12 +108,7 @@ public class TunnelController implements ErrorController { | ||||
|     @PutMapping("/tunnel/{tunnelId}/delete") | ||||
|     public ResponseEntity<Map<String, Object>> deleteTunnelConfiguration(@PathVariable String tunnelId, @RequestBody Ingress ingress) throws JsonProcessingException { | ||||
| 
 | ||||
|         String url = "https://api.cloudflare.com/client/v4/accounts/" + cloudflareConfig.getAccountId() + "/cfd_tunnel/" + tunnelId + "/configurations"; | ||||
| 
 | ||||
|         // * * Getting existing public hostname mappings | ||||
|         HttpHeaders httpHeaders = authKeyEmailHeader.getHttpHeaders(); | ||||
|         HttpEntity<String> httpEntity = new HttpEntity<>("",httpHeaders); | ||||
|         ResponseEntity<TunnelResponse> responseEntity = restTemplateConfig.restTemplate().exchange(url, HttpMethod.GET, httpEntity, TunnelResponse.class); | ||||
|         ResponseEntity<TunnelResponse> responseEntity = cloudflareAPIService.getCloudflareTunnelConfigurations(tunnelId, restTemplateConfig.restTemplate(), TunnelResponse.class); | ||||
| 
 | ||||
|         // * * Deleting the selected ingress value | ||||
|         Config config = responseEntity.getBody().getResult().getConfig(); | ||||
| @ -134,9 +116,7 @@ public class TunnelController implements ErrorController { | ||||
|         Boolean result = Ingress.deleteByHostName(response_ingress, ingress.getHostname()); | ||||
| 
 | ||||
|         // * * Hitting put endpoint | ||||
|         httpHeaders.setContentType(MediaType.APPLICATION_JSON); | ||||
|         HttpEntity<Config> entity = new HttpEntity<>(config, httpHeaders); | ||||
|         ResponseEntity<Map> response = restTemplateConfig.restTemplate().exchange(url, HttpMethod.PUT, entity, Map.class); | ||||
|         ResponseEntity<TunnelResponse> response = cloudflareAPIService.putCloudflareTunnelConfigurations(tunnelId, restTemplateConfig.restTemplate(), TunnelResponse.class, config); | ||||
| 
 | ||||
|         // * * Displaying response | ||||
|         Map<String, Object> jsonResponse = new HashMap<>(); | ||||
|  | ||||
| @ -1,8 +1,17 @@ | ||||
| package com.hithomelabs.CFTunnels.Models; | ||||
| 
 | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Getter; | ||||
| import lombok.NoArgsConstructor; | ||||
| import lombok.Setter; | ||||
| 
 | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| 
 | ||||
| @Getter | ||||
| @Setter | ||||
| @NoArgsConstructor | ||||
| @AllArgsConstructor | ||||
| public class TunnelResponse { | ||||
| 
 | ||||
|     private List<Map<String, Object>> errors; | ||||
| @ -12,45 +21,4 @@ public class TunnelResponse { | ||||
|     private Boolean success; | ||||
| 
 | ||||
|     private Result result; | ||||
| 
 | ||||
|     public Result getResult() { | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     public void setResult(Result result) { | ||||
|         this.result = result; | ||||
|     } | ||||
| 
 | ||||
|     public List<Map<String, Object>> getErrors() { | ||||
|         return errors; | ||||
|     } | ||||
| 
 | ||||
|     public void setErrors(List<Map<String, Object>> errors) { | ||||
|         this.errors = errors; | ||||
|     } | ||||
| 
 | ||||
|     public List<Map<String, Object>> getMessages() { | ||||
|         return messages; | ||||
|     } | ||||
| 
 | ||||
|     public void setMessages(List<Map<String, Object>> messages) { | ||||
|         this.messages = messages; | ||||
|     } | ||||
| 
 | ||||
|     public boolean isSuccess() { | ||||
|         return success; | ||||
|     } | ||||
| 
 | ||||
|     public void setSuccess(boolean success) { | ||||
|         this.success = success; | ||||
|     } | ||||
| 
 | ||||
|     public Boolean getSuccess() { | ||||
|         return success; | ||||
|     } | ||||
| 
 | ||||
|     public void setSuccess(Boolean success) { | ||||
|         this.success = success; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -0,0 +1,61 @@ | ||||
| package com.hithomelabs.CFTunnels.Services; | ||||
| 
 | ||||
| import com.hithomelabs.CFTunnels.Config.CloudflareConfig; | ||||
| import com.hithomelabs.CFTunnels.Headers.AuthKeyEmailHeader; | ||||
| import com.hithomelabs.CFTunnels.Models.Config; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.http.*; | ||||
| import org.springframework.stereotype.Service; | ||||
| import org.springframework.web.client.RestTemplate; | ||||
| 
 | ||||
| import java.util.Map; | ||||
| 
 | ||||
| @Service | ||||
| public class CloudflareAPIService { | ||||
| 
 | ||||
|     @Autowired | ||||
|     CloudflareConfig cloudflareConfig; | ||||
| 
 | ||||
|     @Autowired | ||||
|     AuthKeyEmailHeader authKeyEmailHeader; | ||||
| 
 | ||||
|     @Autowired | ||||
|     RestTemplate restTemplate; | ||||
| 
 | ||||
|     public ResponseEntity<Map> getCloudflareTunnels() { | ||||
| 
 | ||||
|         // * * Resource URL to hit get request at | ||||
|         String url = "https://api.cloudflare.com/client/v4/accounts/" + cloudflareConfig.getAccountId() + "/cfd_tunnel"; | ||||
| 
 | ||||
|         HttpEntity<String> httpEntity = new HttpEntity<>("", authKeyEmailHeader.getHttpHeaders()); | ||||
|         ResponseEntity<Map> responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, Map.class); | ||||
|         return responseEntity; | ||||
|     } | ||||
| 
 | ||||
|     public <T> ResponseEntity<T> getCloudflareTunnelConfigurations(String tunnelId, RestTemplate restTemplate, Class<T> responseType) { | ||||
| 
 | ||||
|         // * * Resource URL to hit get request at | ||||
|         String url = "https://api.cloudflare.com/client/v4/accounts/" + cloudflareConfig.getAccountId() + "/cfd_tunnel/" + tunnelId + "/configurations"; | ||||
| 
 | ||||
|         HttpEntity<String> httpEntity = new HttpEntity<>("",authKeyEmailHeader.getHttpHeaders()); | ||||
|         ResponseEntity<T> responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, responseType); | ||||
|         return responseEntity; | ||||
|     } | ||||
| 
 | ||||
|     public <T> ResponseEntity<T> putCloudflareTunnelConfigurations(String tunnelId, RestTemplate restTemplate, Class<T> responseType, Config config) { | ||||
| 
 | ||||
|         // * * Resource URL to hit get request at | ||||
|         String url = "https://api.cloudflare.com/client/v4/accounts/" + cloudflareConfig.getAccountId() + "/cfd_tunnel/" + tunnelId + "/configurations"; | ||||
| 
 | ||||
|         HttpHeaders httpHeaders = authKeyEmailHeader.getHttpHeaders(); | ||||
|         httpHeaders.setContentType(MediaType.APPLICATION_JSON); | ||||
|         HttpEntity<Config> entity = new HttpEntity<>(config, httpHeaders); | ||||
|         ResponseEntity<T> responseEntity = restTemplate.exchange(url, HttpMethod.PUT, entity, responseType); | ||||
|         return responseEntity; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| @ -1 +1,10 @@ | ||||
| api.corsResolveUrl=http://localhost:8080 | ||||
| api.baseUrl=http://localhost:8080 | ||||
| 
 | ||||
| management.health.db.enabled=true | ||||
| management.endpoints.web.exposure.include=health | ||||
| management.endpoint.health.show-details=always | ||||
| 
 | ||||
| logging.level.org.hibernate.SQL=DEBUG | ||||
| debug=true | ||||
| 
 | ||||
| spring.datasource.url=jdbc:postgresql://localhost:5432/cftunnel | ||||
|  | ||||
| @ -1 +1 @@ | ||||
| api.corsResolveUrl=https://cftunnels.hithomelabs.com | ||||
| api.baseUrl=https://cftunnels.hithomelabs.com | ||||
| @ -1 +1 @@ | ||||
| api.corsResolveUrl=https://testcf.hithomelabs.com | ||||
| api.baseUrl=https://testcf.hithomelabs.com | ||||
| @ -4,6 +4,12 @@ cloudflare.apiKey=${CLOUDFLARE_API_KEY} | ||||
| cloudflare.email=${CLOUDFLARE_EMAIL} | ||||
| spring.profiles.active=${ENV} | ||||
| 
 | ||||
| # set root level | ||||
| logging.level.root=INFO | ||||
| # package-specific | ||||
| logging.level.org.springframework=TRACE | ||||
| logging.level.com.myapp=INFO | ||||
| 
 | ||||
| / * * Masking sure app works behind a reverse proxy | ||||
| server.forward-headers-strategy=framework | ||||
| 
 | ||||
| @ -17,3 +23,15 @@ spring.security.oauth2.client.provider.cftunnels.token-uri=https://auth.hithomel | ||||
| spring.security.oauth2.client.provider.cftunnels.user-info-uri=https://auth.hithomelabs.com/application/o/userinfo/ | ||||
| spring.security.oauth2.client.provider.cftunnels.jwk-set-uri=https://auth.hithomelabs.com/application/o/cftunnels/jwks/ | ||||
| spring.security.oauth2.client.provider.cftunnels.issuer-uri=https://auth.hithomelabs.com/application/o/cftunnels/ | ||||
| 
 | ||||
| spring.datasource.url=jdbc:postgresql://192.168.0.100:5432/cftunnel | ||||
| spring.datasource.username=${POSTGRES_USERNAME} | ||||
| spring.datasource.password=${POSTGRES_PASSWORD} | ||||
| spring.datasource.driver-class-name=org.postgresql.Driver | ||||
| spring.sql.init.mode=never | ||||
| 
 | ||||
| spring.jpa.hibernate.ddl-auto=update | ||||
| spring.jpa.show-sql=true | ||||
| spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect | ||||
| 
 | ||||
| spring.jpa.open-in-view=false | ||||
							
								
								
									
										37
									
								
								src/main/resources/schema.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/main/resources/schema.sql
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,37 @@ | ||||
| -- schema.sql | ||||
| 
 | ||||
| -- Roles table | ||||
| CREATE TABLE IF NOT EXISTS roles ( | ||||
|     role_id SERIAL PRIMARY KEY, | ||||
|     role_name VARCHAR(50) UNIQUE NOT NULL | ||||
| ); | ||||
| 
 | ||||
| -- Users table | ||||
| CREATE TABLE IF NOT EXISTS users ( | ||||
|     user_id SERIAL PRIMARY KEY, | ||||
|     user_name VARCHAR(100) NOT NULL, | ||||
|     password VARCHAR(255) NOT NULL | ||||
| ); | ||||
| 
 | ||||
| -- User-Role Mapping table (many-to-many relationship) | ||||
| CREATE TABLE IF NOT EXISTS user_role_mapping ( | ||||
|     mapping_id SERIAL PRIMARY KEY, | ||||
|     user_id INTEGER NOT NULL REFERENCES users(user_id) ON DELETE CASCADE, | ||||
|     role_id INTEGER NOT NULL REFERENCES roles(role_id) ON DELETE CASCADE | ||||
| ); | ||||
| 
 | ||||
| -- Tunnels table | ||||
| CREATE TABLE IF NOT EXISTS tunnels ( | ||||
|     tunnel_id SERIAL PRIMARY KEY, | ||||
|     tunnel_name VARCHAR(100) NOT NULL, | ||||
|     tunnel_type VARCHAR(50) NOT NULL | ||||
| ); | ||||
| 
 | ||||
| -- Mapping Requests table | ||||
| CREATE TABLE IF NOT EXISTS mapping_requests ( | ||||
|     request_id SERIAL PRIMARY KEY, | ||||
|     request_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, | ||||
|     status VARCHAR(20) NOT NULL, | ||||
|     user_id INTEGER REFERENCES users(user_id) ON DELETE SET NULL, | ||||
|     tunnel_id INTEGER REFERENCES tunnels(tunnel_id) ON DELETE SET NULL | ||||
| ); | ||||
| @ -0,0 +1,219 @@ | ||||
| package com.hithomelabs.CFTunnels.Controllers; | ||||
| 
 | ||||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||||
| import com.hithomelabs.CFTunnels.Config.AuthoritiesToGroupMapping; | ||||
| import com.hithomelabs.CFTunnels.Config.CloudflareConfig; | ||||
| import com.hithomelabs.CFTunnels.Config.RestTemplateConfig; | ||||
| import com.hithomelabs.CFTunnels.Headers.AuthKeyEmailHeader; | ||||
| import com.hithomelabs.CFTunnels.Models.Authorities; | ||||
| import com.hithomelabs.CFTunnels.Models.Config; | ||||
| import com.hithomelabs.CFTunnels.Models.Groups; | ||||
| import com.hithomelabs.CFTunnels.Models.TunnelResponse; | ||||
| import com.hithomelabs.CFTunnels.Services.CloudflareAPIService; | ||||
| import org.junit.jupiter.api.DisplayName; | ||||
| import org.junit.jupiter.api.Test; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; | ||||
| import org.springframework.http.*; | ||||
| import org.springframework.security.core.GrantedAuthority; | ||||
| import org.springframework.security.core.authority.SimpleGrantedAuthority; | ||||
| import org.springframework.security.oauth2.core.oidc.OidcIdToken; | ||||
| import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser; | ||||
| import org.springframework.test.context.bean.override.mockito.MockitoBean; | ||||
| import org.springframework.test.web.servlet.MockMvc; | ||||
| import org.springframework.test.web.servlet.result.MockMvcResultMatchers; | ||||
| import org.springframework.web.client.RestTemplate; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.time.Instant; | ||||
| import java.util.*; | ||||
| 
 | ||||
| import static com.hithomelabs.CFTunnels.TestUtils.Util.getClassPathDataResource; | ||||
| import static org.hamcrest.core.IsIterableContaining.hasItem; | ||||
| import static org.mockito.ArgumentMatchers.any; | ||||
| import static org.mockito.ArgumentMatchers.eq; | ||||
| import static org.mockito.Mockito.when; | ||||
| import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; | ||||
| import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.oauth2Login; | ||||
| import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; | ||||
| import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; | ||||
| import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; | ||||
| import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; | ||||
| import static org.hamcrest.Matchers.not; | ||||
| 
 | ||||
| 
 | ||||
| @WebMvcTest(TunnelController.class) | ||||
| class TunnelControllerTest { | ||||
| 
 | ||||
|     @Autowired | ||||
|     MockMvc mockMvc; | ||||
| 
 | ||||
|     @MockitoBean | ||||
|     AuthoritiesToGroupMapping authoritiesToGroupMapping; | ||||
| 
 | ||||
|     @MockitoBean | ||||
|     CloudflareConfig cloudflareConfig; | ||||
| 
 | ||||
|     @MockitoBean | ||||
|     AuthKeyEmailHeader authKeyEmailHeader; | ||||
| 
 | ||||
|     @MockitoBean | ||||
|     RestTemplate restTemplate; | ||||
| 
 | ||||
|     @MockitoBean | ||||
|     CloudflareAPIService cloudflareAPIService; | ||||
| 
 | ||||
|     @MockitoBean | ||||
|     RestTemplateConfig restTemplateConfig; | ||||
| 
 | ||||
|     private static final String tunnelResponseSmallIngressFile = "tunnelResponseSmallIngress.json"; | ||||
| 
 | ||||
|     private static final String tunnelResponseLargeIngressFile = "tunnelResponseLargeIngress.json"; | ||||
| 
 | ||||
|     private static final String withAdditionalIngress; | ||||
| 
 | ||||
|     static { | ||||
|         try { | ||||
|             withAdditionalIngress = getClassPathDataResource(tunnelResponseLargeIngressFile); | ||||
|         } catch (IOException e) { | ||||
|             throw new RuntimeException(e); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static final String withoutAdditionalIngress; | ||||
|     static { | ||||
|         try { | ||||
|             withoutAdditionalIngress = getClassPathDataResource(tunnelResponseSmallIngressFile); | ||||
|         } catch (IOException e) { | ||||
|             throw new RuntimeException(e); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private static final String ingressJson = """ | ||||
|             { | ||||
|                 "service": "http://192.168.0.100:3457", | ||||
|                 "hostname": "random.hithomelabs.com", | ||||
|                 "originRequest": {} | ||||
|             } | ||||
|             """; | ||||
| 
 | ||||
|     private DefaultOidcUser buildOidcUser(String username, String role) { | ||||
| 
 | ||||
|         when(authoritiesToGroupMapping.getAuthorityForGroup()).thenReturn(Map.of(Groups.GITEA_USER, new HashSet<>(Set.of(new SimpleGrantedAuthority(Authorities.ROLE_USER))), | ||||
|                 Groups.POWER_USER, new HashSet<>(Set.of(new SimpleGrantedAuthority(Authorities.ROLE_USER))), | ||||
|                 Groups.HOMELAB_DEVELOPER, new HashSet<>(Set.of(new SimpleGrantedAuthority(Authorities.ROLE_DEVELOPER))), | ||||
|                 Groups.SYSTEM_ADMIN, new HashSet<>(Set.of(new SimpleGrantedAuthority(Authorities.ROLE_APPROVER), new SimpleGrantedAuthority(Authorities.ROLE_ADMIN))))); | ||||
| 
 | ||||
|         Map<String, Set<GrantedAuthority>> roleAuthorityMapping = authoritiesToGroupMapping.getAuthorityForGroup(); | ||||
|         List<GrantedAuthority> authorities = roleAuthorityMapping.get(role).stream().toList(); | ||||
| 
 | ||||
|         OidcIdToken idToken = new OidcIdToken( | ||||
|                 "mock-token", | ||||
|                 Instant.now(), | ||||
|                 Instant.now().plusSeconds(3600), | ||||
|                 Map.of("preferred_username", username, "sub", username) | ||||
|         ); | ||||
| 
 | ||||
|         return new DefaultOidcUser(authorities, idToken, "preferred_username"); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     @DisplayName("should return appropriate user roles when use belongs to group GITEA_USER") | ||||
|     public void testWhoAmI_user() throws Exception { | ||||
|         mockMvc.perform(get("/cloudflare/whoami") | ||||
|                         .with(oauth2Login().oauth2User(buildOidcUser("username", Groups.GITEA_USER)))) | ||||
|                 .andExpect(status().isOk()) | ||||
|                 .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)) | ||||
|                 .andExpect(jsonPath("$.username").value("username")) | ||||
|                 .andExpect(jsonPath("$.roles", hasItem("ROLE_USER"))); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     @DisplayName("should hit tunnels endpoint successfully with ROLE_USER") | ||||
|     public void testGetTunnelsForRoleUser() throws Exception { | ||||
| 
 | ||||
|         when(cloudflareConfig.getAccountId()).thenReturn("abc123"); | ||||
|         HttpHeaders headers = new HttpHeaders(); | ||||
|         headers.set("X-Auth-Email", "me@example.com"); | ||||
|         when(authKeyEmailHeader.getHttpHeaders()).thenReturn(headers); | ||||
| 
 | ||||
|         Map<String, Object> tunnelData = Map.of("tunnels", List.of(Map.of("id", "50df9101-f625-4618-b7c5-100338a57124"))); | ||||
|         ResponseEntity<Map> mockResponse = new ResponseEntity<>(tunnelData, HttpStatus.OK); | ||||
| 
 | ||||
|         when(cloudflareAPIService.getCloudflareTunnels()).thenReturn(mockResponse); | ||||
| 
 | ||||
|         mockMvc.perform(get("/cloudflare/tunnels") | ||||
|                         .with(oauth2Login().oauth2User(buildOidcUser("username", Groups.GITEA_USER)))) | ||||
|                 .andExpect(status().isOk()) | ||||
|                 .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)) | ||||
|                 .andExpect(jsonPath("$.data.tunnels[0].id").value("50df9101-f625-4618-b7c5-100338a57124")); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     void getTunnelConfigurations() throws Exception { | ||||
| 
 | ||||
|         Map<String, Object> tunnelData = Map.of("config", Map.of("result", "success", "ingress", "sample ingress object")); | ||||
|         ResponseEntity<Map> mockResponse = new ResponseEntity<>(tunnelData, HttpStatus.OK); | ||||
| 
 | ||||
|         when(cloudflareAPIService.getCloudflareTunnelConfigurations(eq("sampleTunnelId"), any(RestTemplate.class), eq(Map.class))).thenReturn(mockResponse); | ||||
| 
 | ||||
|         mockMvc.perform(get("/cloudflare/tunnel/{tunnelId}", "sampleTunnelId") | ||||
|                         .with(oauth2Login().oauth2User(buildOidcUser("username", Groups.HOMELAB_DEVELOPER)))) | ||||
|                 .andExpect(status().isOk()) | ||||
|                 .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)) | ||||
|                 .andExpect(jsonPath("$.data.config.ingress").value("sample ingress object")); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     void addTunnelconfiguration() throws Exception { | ||||
| 
 | ||||
|         when(restTemplateConfig.restTemplate()).thenReturn(new RestTemplate()); | ||||
| 
 | ||||
|         ObjectMapper mapper = new ObjectMapper(); | ||||
|         TunnelResponse tunnelStateBefore = mapper.readValue(withoutAdditionalIngress, TunnelResponse.class); | ||||
|         ResponseEntity<TunnelResponse> tunnelResponseBefore = new ResponseEntity<>(tunnelStateBefore, HttpStatus.OK); | ||||
| 
 | ||||
|         when(cloudflareAPIService.getCloudflareTunnelConfigurations(eq("sampleTunnelId"), any(RestTemplate.class), eq(TunnelResponse.class))).thenReturn(tunnelResponseBefore); | ||||
| 
 | ||||
|         TunnelResponse expectedTunnelConfig = mapper.readValue(withAdditionalIngress, TunnelResponse.class); | ||||
|         ResponseEntity<TunnelResponse> expectedHttpTunnelResponse = new ResponseEntity<>(expectedTunnelConfig, HttpStatus.OK); | ||||
|         when(cloudflareAPIService.putCloudflareTunnelConfigurations(eq("sampleTunnelId"), any(RestTemplate.class), eq(TunnelResponse.class), any(Config.class))).thenReturn(expectedHttpTunnelResponse); | ||||
| 
 | ||||
|         mockMvc.perform(put("/cloudflare/tunnel/{tunnelId}/add", "sampleTunnelId") | ||||
|                         .with(oauth2Login().oauth2User(buildOidcUser("admin", Groups.SYSTEM_ADMIN))) | ||||
|                         .with(csrf()) | ||||
|                         .contentType(MediaType.APPLICATION_JSON) | ||||
|                         .content(ingressJson)) | ||||
|                 .andExpect(status().isOk()) | ||||
|                 .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)) | ||||
|                 .andExpect(jsonPath("$.data.result.config.ingress[*].hostname", hasItem("random.hithomelabs.com"))); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     void deleteTunnelConfiguration() throws Exception { | ||||
| 
 | ||||
|         when(restTemplateConfig.restTemplate()).thenReturn(new RestTemplate()); | ||||
| 
 | ||||
|         ObjectMapper mapper = new ObjectMapper(); | ||||
|         TunnelResponse tunnelStateBefore = mapper.readValue(withAdditionalIngress, TunnelResponse.class); | ||||
|         ResponseEntity<TunnelResponse> tunnelResponseBefore = new ResponseEntity<>(tunnelStateBefore, HttpStatus.OK); | ||||
| 
 | ||||
|         when(cloudflareAPIService.getCloudflareTunnelConfigurations(eq("sampleTunnelId"), any(RestTemplate.class), eq(TunnelResponse.class))).thenReturn(tunnelResponseBefore); | ||||
| 
 | ||||
|         TunnelResponse expectedTunnelConfig = mapper.readValue(withoutAdditionalIngress, TunnelResponse.class); | ||||
|         ResponseEntity<TunnelResponse> expectedHttpTunnelResponse = new ResponseEntity<>(expectedTunnelConfig, HttpStatus.OK); | ||||
|         when(cloudflareAPIService.putCloudflareTunnelConfigurations(eq("sampleTunnelId"), any(RestTemplate.class), eq(TunnelResponse.class), any(Config.class))).thenReturn(expectedHttpTunnelResponse); | ||||
| 
 | ||||
|         mockMvc.perform(put("/cloudflare/tunnel/{tunnelId}/delete", "sampleTunnelId") | ||||
|                         .with(oauth2Login().oauth2User(buildOidcUser("admin", Groups.SYSTEM_ADMIN))) | ||||
|                         .with(csrf()) | ||||
|                         .contentType(MediaType.APPLICATION_JSON) | ||||
|                         .content(ingressJson)) | ||||
|                 .andExpect(status().isOk()) | ||||
|                 .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)) | ||||
|                 .andExpect(jsonPath("$.data.result.config.ingress[*].hostname", not(hasItem("random.hithomelabs.com")))); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,117 @@ | ||||
| package com.hithomelabs.CFTunnels.Services; | ||||
| 
 | ||||
| import com.fasterxml.jackson.core.JsonProcessingException; | ||||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||||
| import com.hithomelabs.CFTunnels.Config.CloudflareConfig; | ||||
| import com.hithomelabs.CFTunnels.Headers.AuthKeyEmailHeader; | ||||
| import com.hithomelabs.CFTunnels.Models.Config; | ||||
| import com.hithomelabs.CFTunnels.Models.TunnelResponse; | ||||
| import org.junit.jupiter.api.Test; | ||||
| import org.junit.jupiter.api.extension.ExtendWith; | ||||
| import org.mockito.InjectMocks; | ||||
| import org.mockito.Mock; | ||||
| import org.mockito.junit.jupiter.MockitoExtension; | ||||
| import org.springframework.http.*; | ||||
| import org.springframework.web.client.RestTemplate; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| 
 | ||||
| import static com.hithomelabs.CFTunnels.TestUtils.Util.getClassPathDataResource; | ||||
| import static org.junit.jupiter.api.Assertions.assertEquals; | ||||
| import static org.mockito.ArgumentMatchers.any; | ||||
| import static org.mockito.ArgumentMatchers.eq; | ||||
| import static org.mockito.Mockito.when; | ||||
| 
 | ||||
| @ExtendWith(MockitoExtension.class) | ||||
| class CloudflareAPIServiceTest { | ||||
| 
 | ||||
|     @InjectMocks | ||||
|     private CloudflareAPIService cloudflareAPIService; | ||||
| 
 | ||||
|     @Mock | ||||
|     AuthKeyEmailHeader authKeyEmailHeader; | ||||
| 
 | ||||
|     @Mock | ||||
|     private RestTemplate restTemplate; | ||||
| 
 | ||||
|     @Mock | ||||
|     CloudflareConfig cloudflareConfig; | ||||
| 
 | ||||
|     private static final String tunnelResponseLargeIngressFile = "tunnelResponseLargeIngress.json"; | ||||
| 
 | ||||
|     private static final String bigTunnelResponse; | ||||
| 
 | ||||
|     static { | ||||
|         try { | ||||
|             bigTunnelResponse = getClassPathDataResource(tunnelResponseLargeIngressFile); | ||||
|         } catch (IOException e) { | ||||
|             throw new RuntimeException(e); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     void testGetCloudflareTunnels() { | ||||
| 
 | ||||
|         when(cloudflareConfig.getAccountId()).thenReturn("account-123"); | ||||
|         when(authKeyEmailHeader.getHttpHeaders()).thenReturn(new HttpHeaders()); | ||||
|         Map<String, Object> mockBody = Map.of("tunnels", List.of(Map.of("id", "t1"))); | ||||
|         ResponseEntity<Map> mockResponse = new ResponseEntity<>(mockBody, HttpStatus.OK); | ||||
| 
 | ||||
|         when(restTemplate.exchange( | ||||
|                 any(String.class), | ||||
|                 eq(HttpMethod.GET), | ||||
|                 any(HttpEntity.class), | ||||
|                 eq(Map.class) | ||||
|         )).thenReturn(mockResponse); | ||||
| 
 | ||||
|         ResponseEntity<Map> response = cloudflareAPIService.getCloudflareTunnels(); | ||||
|         assertEquals(HttpStatus.OK, response.getStatusCode()); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     @Test | ||||
|     void getCloudflareTunnelConfigurations() throws JsonProcessingException { | ||||
| 
 | ||||
|         when(cloudflareConfig.getAccountId()).thenReturn("account-123"); | ||||
|         when(authKeyEmailHeader.getHttpHeaders()).thenReturn(new HttpHeaders()); | ||||
| 
 | ||||
|         TunnelResponse tunnelResponse = new ObjectMapper().readValue(bigTunnelResponse, TunnelResponse.class); | ||||
|         ResponseEntity<TunnelResponse> tunnelResponseResponseEntity = new ResponseEntity<>(tunnelResponse, HttpStatus.OK); | ||||
| 
 | ||||
|         when(restTemplate.exchange( | ||||
|                 any(String.class), | ||||
|                 eq(HttpMethod.GET), | ||||
|                 any(HttpEntity.class), | ||||
|                 eq(TunnelResponse.class) | ||||
|         )).thenReturn(tunnelResponseResponseEntity); | ||||
| 
 | ||||
|         ResponseEntity<TunnelResponse> response = cloudflareAPIService.getCloudflareTunnelConfigurations("sampleTunnelID", restTemplate, TunnelResponse.class); | ||||
|         assertEquals(HttpStatus.OK, response.getStatusCode()); | ||||
|         assertEquals(response.getBody().getResult().getConfig().getIngress().get(0).getHostname(), "giteabkp.hithomelabs.com"); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     void putCloudflareTunnelConfigurations() throws JsonProcessingException { | ||||
| 
 | ||||
|         when(cloudflareConfig.getAccountId()).thenReturn("account-123"); | ||||
|         when(authKeyEmailHeader.getHttpHeaders()).thenReturn(new HttpHeaders()); | ||||
| 
 | ||||
|         TunnelResponse tunnelResponse = new ObjectMapper().readValue(bigTunnelResponse, TunnelResponse.class); | ||||
|         ResponseEntity<TunnelResponse> tunnelResponseResponseEntity = new ResponseEntity<>(tunnelResponse, HttpStatus.OK); | ||||
| 
 | ||||
|         Config config = tunnelResponse.getResult().getConfig(); | ||||
| 
 | ||||
|         when(restTemplate.exchange( | ||||
|                 any(String.class), | ||||
|                 eq(HttpMethod.PUT), | ||||
|                 any(HttpEntity.class), | ||||
|                 eq(TunnelResponse.class) | ||||
|         )).thenReturn(tunnelResponseResponseEntity); | ||||
| 
 | ||||
|         ResponseEntity<TunnelResponse> response = cloudflareAPIService.putCloudflareTunnelConfigurations("sampleTunnelID", restTemplate, TunnelResponse.class, config); | ||||
|         assertEquals(HttpStatus.OK, response.getStatusCode()); | ||||
|         assertEquals(response.getBody().getResult().getConfig().getIngress().get(2).getHostname(), "random.hithomelabs.com"); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										17
									
								
								src/test/java/com/hithomelabs/CFTunnels/TestUtils/Util.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/test/java/com/hithomelabs/CFTunnels/TestUtils/Util.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | ||||
| package com.hithomelabs.CFTunnels.TestUtils; | ||||
| 
 | ||||
| import org.springframework.core.io.ClassPathResource; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.nio.charset.StandardCharsets; | ||||
| import java.nio.file.Files; | ||||
| 
 | ||||
| public class Util { | ||||
| 
 | ||||
|     public static String getClassPathDataResource(String filename) throws IOException { | ||||
|         return Files.readString( | ||||
|                 new ClassPathResource(String.format("data/%s", filename)).getFile().toPath(), | ||||
|                 StandardCharsets.UTF_8); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										38
									
								
								src/test/resources/data/tunnelResponseLargeIngress.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/test/resources/data/tunnelResponseLargeIngress.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,38 @@ | ||||
| { | ||||
|   "success": true, | ||||
|   "errors": [], | ||||
|   "messages": [], | ||||
|   "result": { | ||||
|     "tunnel_id": "50df9101-f625-4618-b7c5-100338a57124", | ||||
|     "version": 63, | ||||
|     "config": { | ||||
|       "ingress": [ | ||||
|         { | ||||
|           "service": "http://192.168.0.100:8928", | ||||
|           "hostname": "giteabkp.hithomelabs.com", | ||||
|           "originRequest": {} | ||||
|         }, | ||||
|         { | ||||
|           "service": "https://192.168.0.100:9442", | ||||
|           "hostname": "devdocker.hithomelabs.com", | ||||
|           "originRequest": { | ||||
|             "noTLSVerify": true | ||||
|           } | ||||
|         }, | ||||
|         { | ||||
|           "service": "http://192.168.0.100:3457", | ||||
|           "hostname": "random.hithomelabs.com", | ||||
|           "originRequest": {} | ||||
|         }, | ||||
|         { | ||||
|           "service": "http_status:404" | ||||
|         } | ||||
|       ], | ||||
|       "warp-routing": { | ||||
|         "enabled": false | ||||
|       } | ||||
|     }, | ||||
|     "source": "cloudflare", | ||||
|     "created_at": "2025-10-24T18:17:26.914217Z" | ||||
|   } | ||||
| } | ||||
							
								
								
									
										33
									
								
								src/test/resources/data/tunnelResponseSmallIngress.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/test/resources/data/tunnelResponseSmallIngress.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,33 @@ | ||||
| { | ||||
|   "success": true, | ||||
|   "errors": [], | ||||
|   "messages": [], | ||||
|   "result": { | ||||
|     "tunnel_id": "50df9101-f625-4618-b7c5-100338a57124", | ||||
|     "version": 63, | ||||
|     "config": { | ||||
|       "ingress": [ | ||||
|         { | ||||
|           "service": "http://192.168.0.100:8928", | ||||
|           "hostname": "giteabkp.hithomelabs.com", | ||||
|           "originRequest": {} | ||||
|         }, | ||||
|         { | ||||
|           "service": "https://192.168.0.100:9442", | ||||
|           "hostname": "devdocker.hithomelabs.com", | ||||
|           "originRequest": { | ||||
|             "noTLSVerify": true | ||||
|           } | ||||
|         }, | ||||
|         { | ||||
|           "service": "http_status:404" | ||||
|         } | ||||
|       ], | ||||
|       "warp-routing": { | ||||
|         "enabled": false | ||||
|       } | ||||
|     }, | ||||
|     "source": "cloudflare", | ||||
|     "created_at": "2025-10-24T18:17:26.914217Z" | ||||
|   } | ||||
| } | ||||
							
								
								
									
										13
									
								
								src/test/resources/docker-compose.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/test/resources/docker-compose.yaml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | ||||
| services: | ||||
|   postgres: | ||||
|     image: postgres:15-alpine | ||||
|     container_name: cftunnel-db-${ENV} | ||||
|     environment: | ||||
|       POSTGRES_DB: cftunnel | ||||
|       POSTGRES_USER: ${POSTGRES_USERNAME} | ||||
|       POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} | ||||
|     restart: unless-stopped | ||||
|     ports: | ||||
|       - "${DB_PORT}:5432" | ||||
|     volumes: | ||||
|       - ${DB_PATH}:/var/lib/postgresql/data | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user