fix(cloudfront): reject a function runtime that CloudFront does not support (#5445)

Closes #5425

Adapted: kept only the runtime check; the duplicate-name check that surrounds it
upstream comes from a later CloudFront Functions change not taken here.

(cherry picked from commit 8deac0a4244449470d6bd4bfa8769f8921d1533d)
This commit is contained in:
Aymen JAMAAOUI
2026-10-11 20:52:01 +02:00
committed by Zakaria
parent ec4664589b
commit 13e91cbb5a
3 changed files with 90 additions and 0 deletions
@@ -59,6 +59,7 @@ public class CloudFrontService {
Set.of("s3", "mediastore", "mediapackagev2", "lambda");
private static final Set<String> OAC_SIGNING_BEHAVIORS =
Set.of("always", "never", "no-override");
private static final Set<String> FUNCTION_RUNTIMES = Set.of("cloudfront-js-1.0", "cloudfront-js-2.0");
private static final int MAX_ORIGIN_CUSTOM_HEADERS = 30;
private static final int MAX_CUSTOM_HEADER_NAME_LENGTH = 256;
private static final int MAX_CUSTOM_HEADER_VALUE_LENGTH = 1_783;
@@ -1227,6 +1228,7 @@ public class CloudFrontService {
// ── CloudFront Functions ──────────────────────────────────────────────────
public synchronized CloudFrontFunction createFunction(CloudFrontFunction fn) {
validateFunctionRuntime(fn.getRuntime());
fn.setStage("DEVELOPMENT");
fn.setStatus("UNPUBLISHED");
fn.setEtag(UUID.randomUUID().toString());
@@ -1251,6 +1253,7 @@ public class CloudFrontService {
public synchronized CloudFrontFunction updateFunction(String name, String ifMatch,
CloudFrontFunction updated) {
validateFunctionRuntime(updated.getRuntime());
CloudFrontFunction existing = describeFunction(name, null);
if (!existing.getEtag().equals(ifMatch)) {
throw new AwsException("InvalidIfMatchVersion",
@@ -1323,6 +1326,13 @@ public class CloudFrontService {
return effectiveStage;
}
private static void validateFunctionRuntime(String runtime) {
if (runtime != null && !FUNCTION_RUNTIMES.contains(runtime)) {
throw new AwsException("InvalidArgument",
"The parameter Runtime must be cloudfront-js-1.0 or cloudfront-js-2.0.", 400);
}
}
private static AwsException invalidFunctionStage(String stage) {
return new AwsException("InvalidArgument",
"The parameter Stage must be DEVELOPMENT or LIVE: " + stage, 400);
@@ -109,6 +109,38 @@ class CloudFrontManagementProtocolTest {
equalTo("InvalidArgument")));
}
@Test
void createFunctionRejectsAnUnknownRuntime() {
String name = "runtime-test-" + UUID.randomUUID();
String createBody = """
<CreateFunctionRequest xmlns="http://cloudfront.amazonaws.com/doc/2020-05-31/">
<Name>%s</Name>
<FunctionConfig>
<Comment>routing function</Comment>
<Runtime>python3</Runtime>
</FunctionConfig>
<FunctionCode>function handler(event) { return event.request; }</FunctionCode>
</CreateFunctionRequest>
""".formatted(name);
given()
.contentType("application/xml")
.body(createBody)
.when()
.post(API + "function")
.then()
.statusCode(400)
.body(hasXPath(
"//*[local-name()='Code']/text()",
equalTo("InvalidArgument")));
given()
.when()
.get(API + "function/" + name + "/describe")
.then()
.statusCode(404);
}
@Test
void describeAndPublishFunctionPreserveBothStages() {
String name = "describe-test-" + UUID.randomUUID();
@@ -164,6 +164,54 @@ class CloudFrontServiceTest {
assertNotEquals(development.getEtag(), live.getEtag());
}
@Test
void createFunction_unknownRuntime_isRejected() {
CloudFrontService service = serviceWithDomainSuffix("cloudfront.net");
CloudFrontFunction function = new CloudFrontFunction();
function.setName("routing");
function.setRuntime("python3");
AwsException error = assertThrows(AwsException.class, () -> service.createFunction(function));
assertEquals("InvalidArgument", error.getErrorCode());
assertEquals(400, error.getHttpStatus());
assertThrows(AwsException.class, () -> service.describeFunction("routing", null));
}
@Test
void createFunction_supportedRuntimes_areAccepted() {
CloudFrontService service = serviceWithDomainSuffix("cloudfront.net");
for (String runtime : List.of("cloudfront-js-1.0", "cloudfront-js-2.0")) {
CloudFrontFunction function = new CloudFrontFunction();
function.setName("routing-" + runtime);
function.setRuntime(runtime);
assertEquals(runtime, service.createFunction(function).getRuntime());
}
}
@Test
void updateFunction_unknownRuntime_isRejectedAndKeepsTheFunction() {
CloudFrontService service = serviceWithDomainSuffix("cloudfront.net");
CloudFrontFunction function = new CloudFrontFunction();
function.setName("routing");
function.setFunctionCode("first");
function.setRuntime("cloudfront-js-2.0");
String etag = service.createFunction(function).getEtag();
CloudFrontFunction update = new CloudFrontFunction();
update.setFunctionCode("second");
update.setRuntime("python3");
AwsException error = assertThrows(AwsException.class,
() -> service.updateFunction("routing", etag, update));
assertEquals("InvalidArgument", error.getErrorCode());
CloudFrontFunction kept = service.describeFunction("routing", null);
assertEquals("first", kept.getFunctionCode());
assertEquals("cloudfront-js-2.0", kept.getRuntime());
assertEquals(etag, kept.getEtag());
}
@Test
void listFunctionsWithoutStageReturnsDevelopmentAndLive() {
CloudFrontService service = serviceWithDomainSuffix("cloudfront.net");