fix(acm): keep the Export option on RequestCertificate #12

Merged
Zakaria merged 1 commits from fix/acm-request-export-option into main 2026-10-25 17:21:09 +00:00
2 changed files with 74 additions and 2 deletions
@@ -563,8 +563,10 @@ public class AcmJsonHandler {
private CertificateOptions parseOptions(JsonNode optionsNode) { private CertificateOptions parseOptions(JsonNode optionsNode) {
if (optionsNode.isMissingNode()) return null; if (optionsNode.isMissingNode()) return null;
String ctPref = optionsNode.path("CertificateTransparencyLoggingPreference").asText("ENABLED"); String ctPref = parseOptionValue(optionsNode, "CertificateTransparencyLoggingPreference");
return new CertificateOptions(ctPref, "DISABLED"); String export = parseOptionValue(optionsNode, "Export");
// An exportable public certificate is requested with Export ENABLED, and keeps it for life.
return new CertificateOptions(ctPref != null ? ctPref : "ENABLED", export != null ? export : "DISABLED");
} }
private List<CertificateStatus> parseCertificateStatuses(JsonNode node) { private List<CertificateStatus> parseCertificateStatuses(JsonNode node) {
@@ -0,0 +1,70 @@
package io.github.hectorvent.floci.services.acm;
import io.github.hectorvent.floci.testing.RestAssuredJsonUtils;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.notNullValue;
@QuarkusTest
class AcmExportableRequestTest {
private static final String ACM_CONTENT_TYPE = "application/x-amz-json-1.1";
@BeforeAll
static void configureRestAssured() {
RestAssuredJsonUtils.configureAwsContentTypes();
}
@Test
void requestCertificateKeepsTheExportOption() {
String exportable = request("{\"DomainName\":\"exportable.example.com\",\"Options\":{\"Export\":\"ENABLED\"}}");
call("DescribeCertificate", "{\"CertificateArn\":\"" + exportable + "\"}")
.statusCode(200)
.body("Certificate.Options.Export", equalTo("ENABLED"))
.body("Certificate.Options.CertificateTransparencyLoggingPreference", equalTo("ENABLED"));
call("ExportCertificate", "{\"CertificateArn\":\"" + exportable + "\",\"Passphrase\":\"c2VjcmV0\"}")
.statusCode(200)
.body("PrivateKey", notNullValue());
}
@Test
void requestCertificateWithoutExportIsNotExportable() {
String plain = request("{\"DomainName\":\"plain.example.com\",\"Options\":{\"CertificateTransparencyLoggingPreference\":\"DISABLED\"}}");
call("DescribeCertificate", "{\"CertificateArn\":\"" + plain + "\"}")
.statusCode(200)
.body("Certificate.Options.Export", equalTo("DISABLED"))
.body("Certificate.Options.CertificateTransparencyLoggingPreference", equalTo("DISABLED"));
call("ExportCertificate", "{\"CertificateArn\":\"" + plain + "\",\"Passphrase\":\"c2VjcmV0\"}")
.statusCode(400)
.body("__type", equalTo("ValidationException"));
}
@Test
void requestCertificateRejectsAnUnknownExportValue() {
call("RequestCertificate", "{\"DomainName\":\"bad.example.com\",\"Options\":{\"Export\":\"MAYBE\"}}")
.statusCode(400)
.body("__type", equalTo("ValidationException"));
}
private String request(String body) {
return call("RequestCertificate", body).statusCode(200).extract().jsonPath().getString("CertificateArn");
}
private io.restassured.response.ValidatableResponse call(String action, String body) {
return given()
.header("X-Amz-Target", "CertificateManager." + action)
.contentType(ACM_CONTENT_TYPE)
.body(body)
.when()
.post("/")
.then();
}
}