Compare commits
15 Commits
668ebfcc84
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 1b1c6e54b1 | |||
| c4568a2f23 | |||
| 6000582d55 | |||
| 0a2937ad55 | |||
| 22a244832e | |||
| ef2b9667fc | |||
| c98900a9ff | |||
| 1684099f74 | |||
| 32cb5b57d7 | |||
| a65b2dbeb0 | |||
| 6037788dbd | |||
| aef24c809b | |||
| 706631c57b | |||
| 1ee7979091 | |||
| 12263fe0cb |
@@ -1,4 +1,4 @@
|
||||
FROM gradle:8.10.2-jdk17 AS build
|
||||
FROM gradle:8.10.2-jdk21 AS build
|
||||
WORKDIR /app
|
||||
|
||||
COPY build.gradle.kts settings.gradle.kts gradlew ./
|
||||
@@ -9,7 +9,7 @@ RUN ./gradlew dependencies --no-daemon || true
|
||||
COPY . .
|
||||
RUN ./gradlew clean bootJar --no-daemon
|
||||
|
||||
FROM eclipse-temurin:17-jdk-alpine
|
||||
FROM eclipse-temurin:21-jre-alpine
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=build /app/build/libs/*.jar app.jar
|
||||
@@ -17,3 +17,4 @@ COPY --from=build /app/build/libs/*.jar app.jar
|
||||
EXPOSE 8080
|
||||
|
||||
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
|
||||
|
||||
|
||||
143
README.md
Normal file
143
README.md
Normal file
@@ -0,0 +1,143 @@
|
||||
# Infisical ↔ Dokploy Bridge
|
||||
|
||||
A Spring Boot (Java 21) application acting as a secure bridge between Infisical and Dokploy, enabling automated synchronization and deployment of secrets through APIs and webhooks.
|
||||
|
||||
## Features
|
||||
|
||||
- Secure integration with Infisical
|
||||
- Automated updates via Dokploy API
|
||||
- Webhook-driven synchronization
|
||||
- Docker and Docker Compose ready
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
Infisical
|
||||
↓ (Webhook / API)
|
||||
Infisical–Dokploy Bridge (Spring Boot)
|
||||
↓ (Dokploy API)
|
||||
Dokploy
|
||||
|
||||
## Requirements
|
||||
|
||||
- Java 21
|
||||
- Docker and Docker Compose
|
||||
- Infisical account
|
||||
- Dokploy instance with API access
|
||||
|
||||
## Environment Variables
|
||||
|
||||
### Infisical
|
||||
|
||||
- INFISICAL_API_URL: Base URL of Infisical API
|
||||
- INFISICAL_CLIENT_ID: Infisical service client ID
|
||||
- INFISICAL_CLIENT_SECRET: Infisical service client secret
|
||||
- INFISICAL_WEBHOOK_SECRET: Webhook signature validation secret
|
||||
|
||||
### Dokploy
|
||||
|
||||
- DOKPLOY_API_URL: Base URL of Dokploy API
|
||||
- DOKPLOY_API_KEY: Dokploy API key
|
||||
|
||||
## Docker Compose
|
||||
|
||||
```txt
|
||||
services:
|
||||
infisical-bridge:
|
||||
build: .
|
||||
restart: always
|
||||
environment:
|
||||
INFISICAL_API_URL: ${INFISICAL_API_URL}
|
||||
INFISICAL_CLIENT_ID: ${INFISICAL_CLIENT_ID}
|
||||
INFISICAL_CLIENT_SECRET: ${INFISICAL_CLIENT_SECRET}
|
||||
INFISICAL_WEBHOOK_SECRET: ${INFISICAL_WEBHOOK_SECRET}
|
||||
DOKPLOY_API_URL: ${DOKPLOY_API_URL}
|
||||
DOKPLOY_API_KEY: ${DOKPLOY_API_KEY}
|
||||
```
|
||||
|
||||
## Running
|
||||
|
||||
With Docker Compose:
|
||||
|
||||
```sh
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
Local development:
|
||||
|
||||
```sh
|
||||
./gradlew bootRun
|
||||
```
|
||||
|
||||
Application runs on http://localhost:8080
|
||||
|
||||
Use a service like ngrok.
|
||||
|
||||
## Infisical Webhook Configuration
|
||||
|
||||
When creating a webhook in Infisical, the following rules must be respected.
|
||||
|
||||
### Webhook URL Formats
|
||||
|
||||
Infisical bridge supports two webhook URL formats, depending on the Dokploy resource you want to update.
|
||||
|
||||
#### Dokploy Compose Webhook
|
||||
|
||||
`${INFISICAL_API_URL}/webhook?dokployComposeId=${DOKPLOY_COMPOSE_ID}`
|
||||
|
||||
Parameters:
|
||||
- dokployComposeId (required):
|
||||
The identifier of the target Dokploy Compose.
|
||||
This value is used to determine which Dokploy compose service should be updated when the webhook is triggered.
|
||||
|
||||
#### Dokploy Application Webhook
|
||||
|
||||
`${INFISICAL_API_URL}/webhook?dokployApplicationId=${DOKPLOY_APPLICATION_ID}`
|
||||
|
||||
Parameters:
|
||||
- dokployApplicationId (required):
|
||||
The identifier of the target Dokploy Application.
|
||||
This value is used to determine which Dokploy application should be updated when the webhook is triggered.
|
||||
|
||||
#### Notes
|
||||
- Exactly one identifier must be provided per webhook URL.
|
||||
- If no identifier or multiple identifiers are provided, the webhook request will be rejected.
|
||||
- Ensure the provided ID matches an existing Dokploy resource.
|
||||
|
||||
### Webhook Secret
|
||||
|
||||
The webhook secret **must exactly match**:
|
||||
|
||||
`${INFISICAL_WEBHOOK_SECRET}`
|
||||
|
||||
Requests with an invalid or missing secret will be rejected.
|
||||
|
||||
## Webhooks Behavior
|
||||
|
||||
- Incoming webhook signatures are validated
|
||||
- Secrets are fetched from Infisical
|
||||
- Dokploy is updated using its API
|
||||
- Invalid or unsigned requests are ignored
|
||||
|
||||
## Security Notes
|
||||
|
||||
- Secrets are never persisted
|
||||
- Configuration is environment-driven
|
||||
- HTTPS is recommended in production
|
||||
- Restrict network access to trusted sources only
|
||||
|
||||
## Testing
|
||||
|
||||
```sh
|
||||
./gradlew test
|
||||
```
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- Java 21
|
||||
- Spring Boot
|
||||
- Gradle (Kotlin DSL)
|
||||
- Docker / Docker Compose
|
||||
|
||||
## License
|
||||
|
||||
MIT License
|
||||
@@ -4,6 +4,7 @@ import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
|
||||
import com.abnov.infisicalbridge.dto.DokployApplicationUpdateRequest;
|
||||
import com.abnov.infisicalbridge.dto.DokployComposeUpdateRequest;
|
||||
|
||||
@FeignClient(name = "dokployClient", url = "${dokploy.api-url}", configuration = DokployFeignConfig.class)
|
||||
@@ -11,4 +12,7 @@ public interface DokployClient {
|
||||
|
||||
@PostMapping("/compose.update")
|
||||
void updateCompose(@RequestBody DokployComposeUpdateRequest request);
|
||||
|
||||
@PostMapping("/application.update")
|
||||
void updateApplication(@RequestBody DokployApplicationUpdateRequest request);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.abnov.infisicalbridge.dto;
|
||||
|
||||
public record DokployApplicationUpdateRequest(
|
||||
String applicationId,
|
||||
String env) {
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.abnov.infisicalbridge.dokploy.DokployClient;
|
||||
import com.abnov.infisicalbridge.dto.DokployApplicationUpdateRequest;
|
||||
import com.abnov.infisicalbridge.dto.DokployComposeUpdateRequest;
|
||||
import com.abnov.infisicalbridge.dto.InfisicalWebhookEventResponse;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
@@ -38,7 +39,8 @@ public class InfisicalWebhookController {
|
||||
@PostMapping
|
||||
public ResponseEntity<Void> handleWebhook(
|
||||
@RequestBody String payload,
|
||||
@RequestParam String dokployComposeId,
|
||||
@RequestParam(required = false) String dokployComposeId,
|
||||
@RequestParam(required = false) String dokployApplicationId,
|
||||
@RequestHeader(value = "X-Infisical-Signature", required = false) String signature)
|
||||
throws InfisicalException {
|
||||
|
||||
@@ -80,6 +82,7 @@ public class InfisicalWebhookController {
|
||||
.map(s -> s.getSecretKey() + "=" + s.getSecretValue())
|
||||
.collect(Collectors.joining("\n"));
|
||||
|
||||
if (dokployComposeId != null) {
|
||||
try {
|
||||
dokployClient.updateCompose(
|
||||
new DokployComposeUpdateRequest(dokployComposeId, envContent));
|
||||
@@ -87,6 +90,17 @@ public class InfisicalWebhookController {
|
||||
log.error("Failed to update Dokploy compose {}", dokployComposeId, e);
|
||||
return ResponseEntity.status(HttpStatus.BAD_GATEWAY).build();
|
||||
}
|
||||
}
|
||||
|
||||
if (dokployApplicationId != null) {
|
||||
try {
|
||||
dokployClient.updateApplication(
|
||||
new DokployApplicationUpdateRequest(dokployApplicationId, envContent));
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to update Dokploy application {}", dokployApplicationId, e);
|
||||
return ResponseEntity.status(HttpStatus.BAD_GATEWAY).build();
|
||||
}
|
||||
}
|
||||
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user