conditionalCert

This commit is contained in:
Anthony Stirling 2025-09-16 14:22:49 +01:00
parent cd71075f79
commit a949019d5d
4 changed files with 57 additions and 29 deletions

View File

@ -18,17 +18,30 @@ import lombok.RequiredArgsConstructor;
import stirling.software.SPDF.config.EndpointConfiguration; import stirling.software.SPDF.config.EndpointConfiguration;
import stirling.software.common.configuration.AppConfig; import stirling.software.common.configuration.AppConfig;
import stirling.software.common.model.ApplicationProperties; import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.service.ServerCertificateServiceInterface;
@RestController @RestController
@Tag(name = "Config", description = "Configuration APIs") @Tag(name = "Config", description = "Configuration APIs")
@RequestMapping("/api/v1/config") @RequestMapping("/api/v1/config")
@RequiredArgsConstructor
@Hidden @Hidden
public class ConfigController { public class ConfigController {
private final ApplicationProperties applicationProperties; private final ApplicationProperties applicationProperties;
private final ApplicationContext applicationContext; private final ApplicationContext applicationContext;
private final EndpointConfiguration endpointConfiguration; private final EndpointConfiguration endpointConfiguration;
private final ServerCertificateServiceInterface serverCertificateService;
public ConfigController(
ApplicationProperties applicationProperties,
ApplicationContext applicationContext,
EndpointConfiguration endpointConfiguration,
@org.springframework.beans.factory.annotation.Autowired(required = false)
ServerCertificateServiceInterface serverCertificateService) {
this.applicationProperties = applicationProperties;
this.applicationContext = applicationContext;
this.endpointConfiguration = endpointConfiguration;
this.serverCertificateService = serverCertificateService;
}
@GetMapping("/app-config") @GetMapping("/app-config")
public ResponseEntity<Map<String, Object>> getAppConfig() { public ResponseEntity<Map<String, Object>> getAppConfig() {
@ -62,6 +75,10 @@ public class ConfigController {
// Premium/Enterprise settings // Premium/Enterprise settings
configData.put("premiumEnabled", applicationProperties.getPremium().isEnabled()); configData.put("premiumEnabled", applicationProperties.getPremium().isEnabled());
// Server certificate settings
configData.put("serverCertificateEnabled",
serverCertificateService != null && serverCertificateService.isEnabled());
// Legal settings // Legal settings
configData.put( configData.put(
"termsAndConditions", applicationProperties.getLegal().getTermsAndConditions()); "termsAndConditions", applicationProperties.getLegal().getTermsAndConditions());

View File

@ -14,7 +14,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.common.service.ServerCertificateService; import stirling.software.common.service.ServerCertificateServiceInterface;
@RestController @RestController
@RequestMapping("/api/v1/admin/server-certificate") @RequestMapping("/api/v1/admin/server-certificate")
@ -26,16 +26,16 @@ import stirling.software.common.service.ServerCertificateService;
@PreAuthorize("hasRole('ADMIN')") @PreAuthorize("hasRole('ADMIN')")
public class ServerCertificateController { public class ServerCertificateController {
private final ServerCertificateService serverCertificateService; private final ServerCertificateServiceInterface serverCertificateService;
@GetMapping("/info") @GetMapping("/info")
@Operation( @Operation(
summary = "Get server certificate information", summary = "Get server certificate information",
description = "Returns information about the current server certificate") description = "Returns information about the current server certificate")
public ResponseEntity<ServerCertificateService.ServerCertificateInfo> public ResponseEntity<ServerCertificateServiceInterface.ServerCertificateInfo>
getServerCertificateInfo() { getServerCertificateInfo() {
try { try {
ServerCertificateService.ServerCertificateInfo info = ServerCertificateServiceInterface.ServerCertificateInfo info =
serverCertificateService.getServerCertificateInfo(); serverCertificateService.getServerCertificateInfo();
return ResponseEntity.ok(info); return ResponseEntity.ok(info);
} catch (Exception e) { } catch (Exception e) {
@ -109,27 +109,27 @@ public class ServerCertificateController {
} }
} }
@GetMapping("/public-key") @GetMapping("/certificate")
@Operation( @Operation(
summary = "Download server certificate public key", summary = "Download server certificate",
description = description =
"Download the public key of the server certificate for validation purposes") "Download the server certificate in DER format for validation purposes")
public ResponseEntity<byte[]> getServerCertificatePublicKey() { public ResponseEntity<byte[]> getServerCertificate() {
try { try {
if (!serverCertificateService.hasServerCertificate()) { if (!serverCertificateService.hasServerCertificate()) {
return ResponseEntity.notFound().build(); return ResponseEntity.notFound().build();
} }
byte[] publicKey = serverCertificateService.getServerCertificatePublicKey(); byte[] certificate = serverCertificateService.getServerCertificatePublicKey();
return ResponseEntity.ok() return ResponseEntity.ok()
.header( .header(
HttpHeaders.CONTENT_DISPOSITION, HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"server-cert.crt\"") "attachment; filename=\"server-cert.cer\"")
.contentType(MediaType.APPLICATION_OCTET_STREAM) .contentType(MediaType.valueOf("application/pkix-cert"))
.body(publicKey); .body(certificate);
} catch (Exception e) { } catch (Exception e) {
log.error("Failed to get server certificate public key", e); log.error("Failed to get server certificate", e);
return ResponseEntity.internalServerError().build(); return ResponseEntity.internalServerError().build();
} }
} }

View File

@ -1,5 +1,6 @@
import { Stack, Button } from "@mantine/core"; import { Stack, Button } from "@mantine/core";
import { ManageSignaturesParameters } from "../../../hooks/tools/manageSignatures/useManageSignaturesParameters"; import { ManageSignaturesParameters } from "../../../hooks/tools/manageSignatures/useManageSignaturesParameters";
import { useAppConfig } from "../../../hooks/useAppConfig";
interface CertificateTypeSettingsProps { interface CertificateTypeSettingsProps {
parameters: ManageSignaturesParameters; parameters: ManageSignaturesParameters;
@ -8,6 +9,13 @@ interface CertificateTypeSettingsProps {
} }
const CertificateTypeSettings = ({ parameters, onParameterChange, disabled = false }: CertificateTypeSettingsProps) => { const CertificateTypeSettings = ({ parameters, onParameterChange, disabled = false }: CertificateTypeSettingsProps) => {
const { config } = useAppConfig();
const isServerCertificateEnabled = config?.serverCertificateEnabled ?? false;
// Reset to MANUAL if AUTO is selected but feature is disabled
if (parameters.signMode === 'AUTO' && !isServerCertificateEnabled) {
onParameterChange('signMode', 'MANUAL');
}
return ( return (
<Stack gap="md"> <Stack gap="md">
@ -29,6 +37,7 @@ const CertificateTypeSettings = ({ parameters, onParameterChange, disabled = fal
Manual Manual
</div> </div>
</Button> </Button>
{isServerCertificateEnabled && (
<Button <Button
variant={parameters.signMode === 'AUTO' ? 'filled' : 'outline'} variant={parameters.signMode === 'AUTO' ? 'filled' : 'outline'}
color={parameters.signMode === 'AUTO' ? 'green' : 'var(--text-muted)'} color={parameters.signMode === 'AUTO' ? 'green' : 'var(--text-muted)'}
@ -41,9 +50,10 @@ const CertificateTypeSettings = ({ parameters, onParameterChange, disabled = fal
style={{ flex: 1, height: 'auto', minHeight: '40px', fontSize: '11px' }} style={{ flex: 1, height: 'auto', minHeight: '40px', fontSize: '11px' }}
> >
<div style={{ textAlign: 'center', lineHeight: '1.1', fontSize: '11px' }}> <div style={{ textAlign: 'center', lineHeight: '1.1', fontSize: '11px' }}>
Auto Auto (server)
</div> </div>
</Button> </Button>
)}
</div> </div>
</Stack> </Stack>
); );

View File

@ -23,6 +23,7 @@ export interface AppConfig {
license?: string; license?: string;
GoogleDriveEnabled?: boolean; GoogleDriveEnabled?: boolean;
SSOAutoLogin?: boolean; SSOAutoLogin?: boolean;
serverCertificateEnabled?: boolean;
error?: string; error?: string;
} }