diff --git a/.gitignore b/.gitignore index c2065bc..1dbf6b6 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,6 @@ out/ ### VS Code ### .vscode/ + +### Env ### +.env* diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5ccc76f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,20 @@ +FROM gradle:8.10.2-jdk21 AS build +WORKDIR /app + +COPY build.gradle.kts settings.gradle.kts gradlew ./ +COPY gradle ./gradle + +RUN ./gradlew dependencies --no-daemon || true + +COPY . . +RUN ./gradlew clean bootJar --no-daemon + +FROM eclipse-temurin:21-jre-alpine +WORKDIR /app + +COPY --from=build /app/build/libs/*.jar app.jar + +EXPOSE 8080 + +ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"] + diff --git a/build.gradle.kts b/build.gradle.kts index cb4f4ce..a523931 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -25,55 +25,30 @@ repositories { } extra["springCloudVersion"] = "2025.1.0" -extra["springModulithVersion"] = "2.0.1" + +extra["infisicalVersion"] = "3.0.5" dependencies { implementation("org.springframework.boot:spring-boot-starter-actuator") - implementation("org.springframework.boot:spring-boot-starter-amqp") - implementation("org.springframework.boot:spring-boot-starter-data-jpa") - implementation("org.springframework.boot:spring-boot-starter-liquibase") - implementation("org.springframework.boot:spring-boot-starter-mail") - implementation("org.springframework.boot:spring-boot-starter-quartz") - implementation("org.springframework.boot:spring-boot-starter-restclient") implementation("org.springframework.boot:spring-boot-starter-security") - implementation("org.springframework.boot:spring-boot-starter-thymeleaf") implementation("org.springframework.boot:spring-boot-starter-validation") implementation("org.springframework.boot:spring-boot-starter-webmvc") implementation("org.springframework.cloud:spring-cloud-starter-openfeign") - implementation("org.springframework.modulith:spring-modulith-events-api") - implementation("org.springframework.modulith:spring-modulith-starter-core") - implementation("org.springframework.modulith:spring-modulith-starter-jpa") - implementation("org.thymeleaf.extras:thymeleaf-extras-springsecurity6") compileOnly("org.projectlombok:lombok") - developmentOnly("org.springframework.boot:spring-boot-docker-compose") - runtimeOnly("org.postgresql:postgresql") - runtimeOnly("org.springframework.modulith:spring-modulith-actuator") - runtimeOnly("org.springframework.modulith:spring-modulith-events-amqp") - runtimeOnly("org.springframework.modulith:spring-modulith-observability") annotationProcessor("org.springframework.boot:spring-boot-configuration-processor") annotationProcessor("org.projectlombok:lombok") testImplementation("org.springframework.boot:spring-boot-starter-actuator-test") - testImplementation("org.springframework.boot:spring-boot-starter-amqp-test") - testImplementation("org.springframework.boot:spring-boot-starter-data-jpa-test") - testImplementation("org.springframework.boot:spring-boot-starter-liquibase-test") - testImplementation("org.springframework.boot:spring-boot-starter-mail-test") - testImplementation("org.springframework.boot:spring-boot-starter-quartz-test") - testImplementation("org.springframework.boot:spring-boot-starter-restclient-test") testImplementation("org.springframework.boot:spring-boot-starter-security-test") - testImplementation("org.springframework.boot:spring-boot-starter-thymeleaf-test") testImplementation("org.springframework.boot:spring-boot-starter-validation-test") testImplementation("org.springframework.boot:spring-boot-starter-webmvc-test") - testImplementation("org.springframework.boot:spring-boot-testcontainers") - testImplementation("org.springframework.modulith:spring-modulith-starter-test") - testImplementation("org.testcontainers:testcontainers-junit-jupiter") - testImplementation("org.testcontainers:testcontainers-postgresql") - testImplementation("org.testcontainers:testcontainers-rabbitmq") testRuntimeOnly("org.junit.platform:junit-platform-launcher") + + // https://mvnrepository.com/artifact/com.infisical/sdk + implementation("com.infisical:sdk:${property("infisicalVersion")}") } dependencyManagement { imports { - mavenBom("org.springframework.modulith:spring-modulith-bom:${property("springModulithVersion")}") mavenBom("org.springframework.cloud:spring-cloud-dependencies:${property("springCloudVersion")}") } } diff --git a/compose.yaml b/compose.yaml index 80e8ef1..8a4bd07 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,16 +1,16 @@ services: postgres: - image: 'postgres:latest' + image: "postgres:latest" environment: - - 'POSTGRES_DB=mydatabase' - - 'POSTGRES_PASSWORD=secret' - - 'POSTGRES_USER=myuser' + - "POSTGRES_DB=mydatabase" + - "POSTGRES_PASSWORD=secret" + - "POSTGRES_USER=myuser" ports: - - '5432' - rabbitmq: - image: 'rabbitmq:latest' - environment: - - 'RABBITMQ_DEFAULT_PASS=secret' - - 'RABBITMQ_DEFAULT_USER=myuser' - ports: - - '5672' + - "5432" + # rabbitmq: + # image: 'rabbitmq:latest' + # environment: + # - 'RABBITMQ_DEFAULT_PASS=secret' + # - 'RABBITMQ_DEFAULT_USER=myuser' + # ports: + # - '5672' diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..c33a3d7 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,11 @@ +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} diff --git a/src/main/java/com/abnov/infisicalbridge/InfisicalBridgeApplication.java b/src/main/java/com/abnov/infisicalbridge/InfisicalBridgeApplication.java index c7c68c0..c4ab5fb 100644 --- a/src/main/java/com/abnov/infisicalbridge/InfisicalBridgeApplication.java +++ b/src/main/java/com/abnov/infisicalbridge/InfisicalBridgeApplication.java @@ -2,12 +2,14 @@ package com.abnov.infisicalbridge; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication +@EnableFeignClients public class InfisicalBridgeApplication { - public static void main(String[] args) { - SpringApplication.run(InfisicalBridgeApplication.class, args); - } + public static void main(String[] args) { + SpringApplication.run(InfisicalBridgeApplication.class, args); + } } diff --git a/src/main/java/com/abnov/infisicalbridge/config/AppConfig.java b/src/main/java/com/abnov/infisicalbridge/config/AppConfig.java new file mode 100644 index 0000000..c90c458 --- /dev/null +++ b/src/main/java/com/abnov/infisicalbridge/config/AppConfig.java @@ -0,0 +1,15 @@ +package com.abnov.infisicalbridge.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.fasterxml.jackson.databind.ObjectMapper; + +@Configuration +public class AppConfig { + + @Bean + public ObjectMapper objectMapper() { + return new ObjectMapper(); + } +} diff --git a/src/main/java/com/abnov/infisicalbridge/config/SecurityConfig.java b/src/main/java/com/abnov/infisicalbridge/config/SecurityConfig.java new file mode 100644 index 0000000..46286ca --- /dev/null +++ b/src/main/java/com/abnov/infisicalbridge/config/SecurityConfig.java @@ -0,0 +1,22 @@ +package com.abnov.infisicalbridge.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.web.SecurityFilterChain; + +@Configuration +public class SecurityConfig { + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .csrf(csrf -> csrf.disable()) + .cors(cors -> cors.disable()) + .authorizeHttpRequests(auth -> auth + .requestMatchers("/webhook/**").permitAll() + .anyRequest().authenticated()); + + return http.build(); + } +} diff --git a/src/main/java/com/abnov/infisicalbridge/dokploy/DokployClient.java b/src/main/java/com/abnov/infisicalbridge/dokploy/DokployClient.java new file mode 100644 index 0000000..9a8b768 --- /dev/null +++ b/src/main/java/com/abnov/infisicalbridge/dokploy/DokployClient.java @@ -0,0 +1,14 @@ +package com.abnov.infisicalbridge.dokploy; + +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.DokployComposeUpdateRequest; + +@FeignClient(name = "dokployClient", url = "${dokploy.api-url}", configuration = DokployFeignConfig.class) +public interface DokployClient { + + @PostMapping("/compose.update") + void updateCompose(@RequestBody DokployComposeUpdateRequest request); +} diff --git a/src/main/java/com/abnov/infisicalbridge/dokploy/DokployFeignConfig.java b/src/main/java/com/abnov/infisicalbridge/dokploy/DokployFeignConfig.java new file mode 100644 index 0000000..6ff5f6c --- /dev/null +++ b/src/main/java/com/abnov/infisicalbridge/dokploy/DokployFeignConfig.java @@ -0,0 +1,24 @@ +package com.abnov.infisicalbridge.dokploy; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import feign.RequestInterceptor; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Configuration +@RequiredArgsConstructor +@Slf4j +public class DokployFeignConfig { + + private final DokployProperties properties; + + @Bean + public RequestInterceptor dokployRequestInterceptor() { + return requestTemplate -> { + // Add API key to every request + requestTemplate.header("x-api-key", properties.getApiKey()); + }; + } +} diff --git a/src/main/java/com/abnov/infisicalbridge/dokploy/DokployProperties.java b/src/main/java/com/abnov/infisicalbridge/dokploy/DokployProperties.java new file mode 100644 index 0000000..9fcafae --- /dev/null +++ b/src/main/java/com/abnov/infisicalbridge/dokploy/DokployProperties.java @@ -0,0 +1,21 @@ +package com.abnov.infisicalbridge.dokploy; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.validation.annotation.Validated; + +import jakarta.validation.constraints.NotBlank; + +@Data +@Configuration +@ConfigurationProperties(prefix = "dokploy") +@Validated +public class DokployProperties { + + @NotBlank(message = "Dokploy API URL is required") + private String apiUrl; + + @NotBlank(message = "Dokploy API KEY is required") + private String apiKey; +} diff --git a/src/main/java/com/abnov/infisicalbridge/dto/DokployComposeUpdateRequest.java b/src/main/java/com/abnov/infisicalbridge/dto/DokployComposeUpdateRequest.java new file mode 100644 index 0000000..38eab79 --- /dev/null +++ b/src/main/java/com/abnov/infisicalbridge/dto/DokployComposeUpdateRequest.java @@ -0,0 +1,6 @@ +package com.abnov.infisicalbridge.dto; + +public record DokployComposeUpdateRequest( + String composeId, + String env) { +} diff --git a/src/main/java/com/abnov/infisicalbridge/dto/InfisicalWebhookEventResponse.java b/src/main/java/com/abnov/infisicalbridge/dto/InfisicalWebhookEventResponse.java new file mode 100644 index 0000000..43d8e6d --- /dev/null +++ b/src/main/java/com/abnov/infisicalbridge/dto/InfisicalWebhookEventResponse.java @@ -0,0 +1,7 @@ +package com.abnov.infisicalbridge.dto; + +public record InfisicalWebhookEventResponse( + String event, + ProjectResponse project, + long timestamp) { +} diff --git a/src/main/java/com/abnov/infisicalbridge/dto/ProjectResponse.java b/src/main/java/com/abnov/infisicalbridge/dto/ProjectResponse.java new file mode 100644 index 0000000..b2bb99a --- /dev/null +++ b/src/main/java/com/abnov/infisicalbridge/dto/ProjectResponse.java @@ -0,0 +1,10 @@ +package com.abnov.infisicalbridge.dto; + +public record ProjectResponse( + String workspaceId, + String projectId, + String projectName, + String environment, + String secretPath) { + +} diff --git a/src/main/java/com/abnov/infisicalbridge/infisical/InfisicalProperties.java b/src/main/java/com/abnov/infisicalbridge/infisical/InfisicalProperties.java new file mode 100644 index 0000000..cdead2a --- /dev/null +++ b/src/main/java/com/abnov/infisicalbridge/infisical/InfisicalProperties.java @@ -0,0 +1,27 @@ +package com.abnov.infisicalbridge.infisical; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.validation.annotation.Validated; + +import jakarta.validation.constraints.NotBlank; + +@Data +@Configuration +@ConfigurationProperties(prefix = "infisical") +@Validated +public class InfisicalProperties { + + @NotBlank(message = "Infisical API URL is required") + private String apiUrl; + + @NotBlank(message = "Infisical client ID is required") + private String clientId; + + @NotBlank(message = "Infisical client secret is required") + private String clientSecret; + + @NotBlank(message = "Infisical webhook secret is required") + private String webhookSecret; +} diff --git a/src/main/java/com/abnov/infisicalbridge/infisical/InfisicalService.java b/src/main/java/com/abnov/infisicalbridge/infisical/InfisicalService.java new file mode 100644 index 0000000..1e5a29d --- /dev/null +++ b/src/main/java/com/abnov/infisicalbridge/infisical/InfisicalService.java @@ -0,0 +1,45 @@ +package com.abnov.infisicalbridge.infisical; + +import org.springframework.stereotype.Service; + +import com.infisical.sdk.InfisicalSdk; +import com.infisical.sdk.config.SdkConfig; +import com.infisical.sdk.util.InfisicalException; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +@Service +@Slf4j +public class InfisicalService { + private final InfisicalProperties properties; + + @Getter + private final InfisicalSdk sdk; + + public InfisicalService(InfisicalProperties properties) { + this.properties = properties; + this.sdk = initializeClient(); + } + + private InfisicalSdk initializeClient() { + try { + log.info("Initializing Infisical SDK"); + var sdkInstance = new InfisicalSdk( + new SdkConfig.Builder() + .withSiteUrl(properties.getApiUrl()) + .build()); + + sdkInstance.Auth().UniversalAuthLogin( + properties.getClientId(), + properties.getClientSecret()); + + log.info("Successfully authenticated with Infisical"); + return sdkInstance; + + } catch (InfisicalException e) { + log.error("Failed to initialize Infisical SDK", e); + throw new IllegalStateException("Failed to initialize Infisical SDK", e); + } + } +} diff --git a/src/main/java/com/abnov/infisicalbridge/infisical/InfisicalSignatureVerifier.java b/src/main/java/com/abnov/infisicalbridge/infisical/InfisicalSignatureVerifier.java new file mode 100644 index 0000000..1c17bb6 --- /dev/null +++ b/src/main/java/com/abnov/infisicalbridge/infisical/InfisicalSignatureVerifier.java @@ -0,0 +1,151 @@ +package com.abnov.infisicalbridge.infisical; + +import org.springframework.stereotype.Component; + +import lombok.extern.slf4j.Slf4j; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.time.Instant; + +/** + * Service to verify Infisical webhook signatures + */ +@Component +@Slf4j +public class InfisicalSignatureVerifier { + + private static final String HMAC_SHA256 = "HmacSHA256"; + private static final int DEFAULT_TOLERANCE_SECONDS = 300; // 5 minutes + + /** + * Verifies the signature of an Infisical webhook request + * + * @param payload The raw request body as string + * @param signatureHeader The value of x-infisical-signature header + * @param secretKey Your secret key from Infisical + * @return true if signature is valid, false otherwise + */ + public boolean verifySignature(String payload, String signatureHeader, String secretKey) { + return verifySignature(payload, signatureHeader, secretKey, DEFAULT_TOLERANCE_SECONDS); + } + + /** + * Verifies the signature with custom tolerance + * + * @param payload The raw request body as string + * @param signatureHeader The value of x-infisical-signature header (format: + * t=;) + * @param secretKey Your secret key from Infisical + * @param toleranceInSeconds Time tolerance for replay attacks + * @return true if signature is valid, false otherwise + */ + public boolean verifySignature(String payload, String signatureHeader, String secretKey, int toleranceInSeconds) { + try { + // Parse the signature header: t=; + String[] parts = signatureHeader.split(";"); + if (parts.length != 2) { + log.error("Invalid signature header format"); + return false; + } + + String timestamp = parts[0].substring(2); // Remove "t=" + String receivedSignature = parts[1]; + + // Check timestamp to prevent replay attacks + long webhookTime = Long.parseLong(timestamp); + long currentTime; + + // Detect if timestamp is in milliseconds or seconds + if (timestamp.length() > 10) { + // Timestamp is in milliseconds + currentTime = Instant.now().toEpochMilli(); + long toleranceInMillis = (long) toleranceInSeconds * 1000; + if (currentTime - webhookTime > toleranceInMillis) { + log.error("Webhook timestamp is too old"); + return false; + } + } else { + // Timestamp is in seconds + currentTime = Instant.now().getEpochSecond(); + if (currentTime - webhookTime > toleranceInSeconds) { + log.error("Webhook timestamp is too old"); + return false; + } + } + + // Try different signature formats that Infisical might use + + // Format 1: timestamp.payload (most common) + String signedPayload1 = timestamp + "." + payload; + String expectedSignature1 = generateHmacSHA256(signedPayload1, secretKey); + + if (MessageDigest.isEqual( + receivedSignature.getBytes(StandardCharsets.UTF_8), + expectedSignature1.getBytes(StandardCharsets.UTF_8))) { + return true; + } + + // Format 2: payload only + String expectedSignature2 = generateHmacSHA256(payload, secretKey); + + if (MessageDigest.isEqual( + receivedSignature.getBytes(StandardCharsets.UTF_8), + expectedSignature2.getBytes(StandardCharsets.UTF_8))) { + return true; + } + + // Format 3: t=timestamp.payload (with t= prefix) + String signedPayload3 = signatureHeader.split(";")[0] + "." + payload; + String expectedSignature3 = generateHmacSHA256(signedPayload3, secretKey); + + if (MessageDigest.isEqual( + receivedSignature.getBytes(StandardCharsets.UTF_8), + expectedSignature3.getBytes(StandardCharsets.UTF_8))) { + return true; + } + + // No format matched + log.error("Signature verification failed - no matching format found"); + return false; + + } catch (Exception e) { + log.error("Error verifying signature: {}" + e.getMessage()); + return false; + } + } + + /** + * Generates HMAC SHA256 signature + */ + private String generateHmacSHA256(String data, String key) + throws NoSuchAlgorithmException, InvalidKeyException { + Mac mac = Mac.getInstance(HMAC_SHA256); + SecretKeySpec secretKeySpec = new SecretKeySpec( + key.getBytes(StandardCharsets.UTF_8), + HMAC_SHA256); + mac.init(secretKeySpec); + + byte[] hmacBytes = mac.doFinal(data.getBytes(StandardCharsets.UTF_8)); + return bytesToHex(hmacBytes); + } + + /** + * Converts byte array to hex string + */ + private String bytesToHex(byte[] bytes) { + StringBuilder hexString = new StringBuilder(); + for (byte b : bytes) { + String hex = Integer.toHexString(0xff & b); + if (hex.length() == 1) { + hexString.append('0'); + } + hexString.append(hex); + } + return hexString.toString(); + } +} diff --git a/src/main/java/com/abnov/infisicalbridge/infisical/InfisicalWebhookController.java b/src/main/java/com/abnov/infisicalbridge/infisical/InfisicalWebhookController.java new file mode 100644 index 0000000..b47b6bb --- /dev/null +++ b/src/main/java/com/abnov/infisicalbridge/infisical/InfisicalWebhookController.java @@ -0,0 +1,93 @@ +package com.abnov.infisicalbridge.infisical; + +import java.util.List; +import java.util.stream.Collectors; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +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.DokployComposeUpdateRequest; +import com.abnov.infisicalbridge.dto.InfisicalWebhookEventResponse; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.infisical.sdk.InfisicalSdk; +import com.infisical.sdk.models.Secret; +import com.infisical.sdk.util.InfisicalException; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@RestController +@RequestMapping("/webhook") +@RequiredArgsConstructor +@Slf4j +public class InfisicalWebhookController { + private final InfisicalSignatureVerifier signatureVerifier; + private final InfisicalService service; + private final InfisicalProperties infisicalProperties; + private final ObjectMapper objectMapper; + private final DokployClient dokployClient; + + @PostMapping + public ResponseEntity handleWebhook( + @RequestBody String payload, + @RequestParam String dokployComposeId, + @RequestHeader(value = "X-Infisical-Signature", required = false) String signature) + throws InfisicalException { + + if (signature == null || signature.isEmpty()) { + log.warn("Missing X-Infisical-Signature header"); + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } + + if (!signatureVerifier.verifySignature(payload, signature, infisicalProperties.getWebhookSecret())) { + log.warn("Invalid webhook signature"); + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } + + InfisicalWebhookEventResponse webhookEvent; + try { + webhookEvent = objectMapper.readValue(payload, InfisicalWebhookEventResponse.class); + } catch (JsonProcessingException e) { + log.error("Invalid webhook payload", e); + return ResponseEntity.badRequest().build(); + } + + InfisicalSdk sdk = service.getSdk(); + List secrets = sdk.Secrets().ListSecrets( + webhookEvent.project().projectId(), + webhookEvent.project().environment(), + webhookEvent.project().secretPath(), + false, + false, + false); + + if (secrets.isEmpty()) { + log.warn("No secrets found for project={} env={}", + webhookEvent.project().projectName(), + webhookEvent.project().environment()); + return ResponseEntity.noContent().build(); + } + + String envContent = secrets.stream() + .map(s -> s.getSecretKey() + "=" + s.getSecretValue()) + .collect(Collectors.joining("\n")); + + try { + dokployClient.updateCompose( + new DokployComposeUpdateRequest(dokployComposeId, envContent)); + } catch (Exception e) { + log.error("Failed to update Dokploy compose {}", dokployComposeId, e); + return ResponseEntity.status(HttpStatus.BAD_GATEWAY).build(); + } + + return ResponseEntity.ok().build(); + } +} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 1c129d6..71f1d9c 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -1,3 +1,13 @@ spring: application: name: infisical-bridge + +infisical: + api-url: ${INFISICAL_API_URL} + client-id: ${INFISICAL_CLIENT_ID} + client-secret: ${INFISICAL_CLIENT_SECRET} + webhook-secret: ${INFISICAL_WEBHOOK_SECRET} + +dokploy: + api-url: ${DOKPLOY_API_URL} + api-key: ${DOKPLOY_API_KEY}