{"record":{"id":"5b57a942bd6bd854","repo":"floci-io/floci","slug":"invalidargument-5b57a9","errorCode":"InvalidArgument","errorMessage":"The S3 origin access identity is invalid.","messagePattern":"The S3 origin access identity is invalid\\.","errorType":"exception","errorClass":"AwsException","httpStatus":400,"severity":"error","filePath":"src/main/java/io/github/hectorvent/floci/services/cloudfront/CloudFrontServingController.java","lineNumber":369,"sourceCode":"            s3Service.authorizeCloudFrontOaiGetObject(\n                    bucket, key, oaiId, oai.getS3CanonicalUserId());\n            return;\n        }\n        s3Service.authorizeAnonymousGetObject(bucket, key);\n    }\n\n    private static String originAccessIdentityId(Origin origin) {\n        Map<String, String> config = origin.getS3OriginConfig();\n        String value = config != null ? config.get(\"OriginAccessIdentity\") : null;\n        if (value == null || value.isBlank()) {\n            return null;\n        }\n        String normalized = value.startsWith(\"/\") ? value.substring(1) : value;\n        String prefix = \"origin-access-identity/cloudfront/\";\n        if (!normalized.startsWith(prefix)\n                || normalized.length() == prefix.length()\n                || normalized.substring(prefix.length()).contains(\"/\")) {\n            throw new AwsException(\n                    \"InvalidArgument\", \"The S3 origin access identity is invalid.\", 400);\n        }\n        return normalized.substring(prefix.length());\n    }\n\n    /** Fetches from a custom (non-S3) origin. {@code forwardUri} already includes the origin path. */\n    private OriginResponse fetchFromCustomOrigin(Origin origin, String forwardUri, String rawQuery,\n                                                 String viewerScheme, boolean includeBody) {\n        try {\n            URI target = buildCustomOriginUri(\n                    origin, viewerScheme, forwardUri, rawQuery);\n            HttpRequest.Builder rb = HttpRequest.newBuilder()\n                    .uri(target)\n                    .timeout(Duration.ofSeconds(30));\n            if (includeBody) {\n                rb.GET();\n            } else {\n                rb.method(\"HEAD\", HttpRequest.BodyPublishers.noBody());","sourceCodeStart":351,"sourceCodeEnd":387,"githubUrl":"https://github.com/floci-io/floci/blob/62ff490619e7bd3554597c28c704081b4c15add5/src/main/java/io/github/hectorvent/floci/services/cloudfront/CloudFrontServingController.java#L351-L387","documentation":"When Floci's CloudFront serving path forwards a request to an S3 origin, it parses the OriginAccessIdentity field of the S3 origin config. The value must have the exact shape origin-access-identity/cloudfront/<id> (a single non-empty id segment after the prefix, no extra slashes); a leading slash is tolerated. Anything else throws InvalidArgument (HTTP 400).","triggerScenarios":"A distribution whose S3 origin sets OriginAccessIdentity to a bare id (e.g. E2XXXX), an empty segment after the prefix, or a value with an additional slash — and then a request is served through that distribution (the data-plane path, not the management API).","commonSituations":"Hand-written distribution JSON that puts the OAI id alone in the field; copying the cloudfront portion of the ARN incorrectly; Terraform/CloudFormation configs where the interpolation produced an empty id.","solutions":["Set the S3 origin's OriginAccessIdentity to the full form: origin-access-identity/cloudfront/<OAI-id>","Create the OAI with CreateCloudFrontOriginAccessIdentity first and reuse the exact value AWS/Floci returns","Fix the distribution with UpdateDistribution, then retry the serving request"],"exampleFix":"// before\nOrigins.builder().items(List.of(Origin.builder()\n    .id(\"s3-origin\")\n    .domainName(\"my-bucket.s3.amazonaws.com\")\n    .s3OriginConfig(S3OriginConfig.builder()\n        .originAccessIdentity(\"E127EXAMPLE51Z\")\n        .build())\n    .build())).build();\n\n// after\nString oaiId = cloudFrontClient.createCloudFrontOriginAccessIdentity(r -> r\n    .cloudFrontOriginAccessIdentityConfig(\n        CloudFrontOriginAccessIdentityConfig.builder()\n            .callerReference(\"oai-1\")\n            .comment(\"bucket oai\").build()))\n    .cloudFrontOriginAccessIdentity().id();\nOrigins.builder().items(List.of(Origin.builder()\n    .id(\"s3-origin\")\n    .domainName(\"my-bucket.s3.amazonaws.com\")\n    .s3OriginConfig(S3OriginConfig.builder()\n        .originAccessIdentity(\"origin-access-identity/cloudfront/\" + oaiId)\n        .build())\n    .build())).build();","handlingStrategy":"validation","validationCode":"private static final Pattern OAI =\n    Pattern.compile(\"^/?origin-access-identity/cloudfront/[^/]+$\");\n\nstatic boolean isValidOriginAccessIdentity(String v) {\n    return v != null && OAI.matcher(v).matches();\n}\n\n// before creating the distribution:\nif (!isValidOriginAccessIdentity(origin.s3OriginConfig().originAccessIdentity())) {\n    throw new IllegalArgumentException(\"OriginAccessIdentity must be origin-access-identity/cloudfront/<id>\");\n}","typeGuard":null,"tryCatchPattern":"catch (CloudFrontException e) {\n    if (\"InvalidArgument\".equals(e.awsErrorDetails().errorCode())\n            && e.getMessage().contains(\"origin access identity\")) {\n        // fix the S3 origin config to the full prefix form and retry the request\n    } else { throw e; }\n}","preventionTips":["Always compose the value as \"origin-access-identity/cloudfront/\" + oaiId from the create response","Validate the distribution config shape in tests before serving traffic through it"],"tags":["cloudfront","s3-origin","origin-access-identity","data-plane"],"backgroundTag":null,"analyzedSha":"62ff490619e7bd3554597c28c704081b4c15add5","analyzedAt":"2026-08-14T14:25:23.764Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}