{"record":{"id":"c0151f0d5f1c7d80","repo":"apereo/cas","slug":"located-s3-object-s-from-bucket","errorCode":null,"errorMessage":"Located [{}] S3 object(s) from bucket [{}]","messagePattern":"Located \\[(.+?)\\] S3 object\\(s\\) from bucket \\[(.+?)\\]","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"support/cas-server-support-saml-idp-metadata-aws-s3/src/main/java/org/apereo/cas/support/saml/idp/metadata/AmazonS3SamlIdPMetadataLocator.java","lineNumber":60,"sourceCode":"    @Override\n    public SamlIdPMetadataDocument fetchInternal(final Optional<SamlRegisteredService> registeredService) throws Exception {\n        val bucketToUse = AmazonS3SamlIdPMetadataUtils.determineBucketNameFor(registeredService, this.bucketName, s3Client);\n        LOGGER.debug(\"Locating S3 object(s) from bucket [{}]...\", bucketToUse);\n        if (s3Client.listBuckets(ListBucketsRequest.builder().build())\n            .buckets().stream().noneMatch(b -> b.name().equalsIgnoreCase(bucketToUse))) {\n            LOGGER.debug(\"S3 bucket [{}] does not exist\", bucketToUse);\n            return null;\n        }\n\n        val result = s3Client.listObjectsV2(ListObjectsV2Request.builder().bucket(bucketToUse).build());\n        val objects = result.contents();\n        LOGGER.debug(\"Located [{}] S3 object(s) from bucket [{}]\", objects.size(), bucketToUse);\n\n        if (objects.isEmpty()) {\n            return null;\n        }\n        if (objects.size() > 1) {\n            LOGGER.warn(\"Located [{}] S3 object(s) from bucket [{}]\", objects.size(), bucketToUse);\n            objects.forEach(obj -> {\n                LOGGER.debug(\"Deleting object [{}] from bucket [{}]\", obj.key(), bucketToUse);\n                val deleteRequest = DeleteObjectRequest.builder().bucket(bucketToUse).key(obj.key()).build();\n                s3Client.deleteObject(deleteRequest);\n            });\n            throw new IllegalArgumentException(\"Multiple S3 objects where found in bucket \" + bucketToUse);\n        }\n\n        val firstMetadataObject = objects.getFirst();\n        LOGGER.debug(\"Fetching object [{}] from bucket [{}]\", firstMetadataObject.key(), bucketToUse);\n        val metadataEntry = s3Client.getObject(GetObjectRequest.builder().bucket(bucketToUse).key(firstMetadataObject.key()).build());\n        return AmazonS3SamlIdPMetadataUtils.readMetadataDocumentFromBucket(metadataEntry, bucketToUse);\n    }\n}\n\n","sourceCodeStart":42,"sourceCodeEnd":76,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/support/cas-server-support-saml-idp-metadata-aws-s3/src/main/java/org/apereo/cas/support/saml/idp/metadata/AmazonS3SamlIdPMetadataLocator.java#L42-L76","documentation":"AmazonS3SamlIdPMetadataLocator.fetchInternal() lists objects in the configured S3 bucket expecting exactly the IdP signing/encryption metadata objects, but found more than one match. It logs a warning, deletes every matching object from the bucket, and throws IllegalArgumentException, because multiple candidate objects make it ambiguous which key material is authoritative.","triggerScenarios":"Calling fetchInternal() (via locate/fetch of the AWS S3 metadata locator) when the ListObjectsV2 response for the bucket/prefix returns 2+ objects — e.g. duplicate uploads of signing/encryption keys or leftover objects from prior writes under the same prefix.","commonSituations":"Manual re-upload of metadata objects without deleting old ones; concurrent writers (multiple CAS nodes or scripts) each pushing key objects; a bucket prefix broad enough to catch unrelated objects; failed prior runs leaving orphans, then a retry adding another copy; bucket reuse across environments (staging objects left in prod bucket).","solutions":["Inspect the bucket/prefix and delete duplicate objects so exactly one signing and one encryption object remain, then retry.","Upload metadata via the CAS-supported single-writer path and ensure old objects are replaced (same key) rather than added under new keys.","Narrow the configured prefix/filter so the locator only matches the intended metadata objects.","Note the locator already deletes the found duplicates before throwing — verify the bucket after this error, as the next attempt may succeed with an empty-then-repopulated state.","Restrict IAM write access to the metadata bucket to prevent uncoordinated uploads from other systems."],"exampleFix":"// before: uploading with timestamped keys -> multiple objects\n// s3: key = cas-idp-signing-2024-01-01.pem, cas-idp-signing-2026-01-01.pem\n// after: stable keys, overwrite in place\n// s3: key = cas-idp-signing.pem (PutObject overwrites, one object total)","handlingStrategy":"fallback","validationCode":"// Before locating metadata, assert the bucket holds exactly the expected objects\nvar list = s3Client.listObjectsV2(ListObjectsV2Request.builder().bucket(bucket).prefix(prefix).build());\nif (list.contents().size() > 2) { // signing + encryption expected\n    throw new IllegalStateException(\"Bucket \" + bucket + \"/\" + prefix + \" has \"\n        + list.contents().size() + \" objects; clean duplicates before proceeding\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    return s3Locator.fetchInternal(...);\n} catch (IllegalArgumentException e) {\n    // locator already deleted duplicates; re-fetch once from the now-clean bucket\n    return s3Locator.fetchInternal(...);\n}","preventionTips":["Use fixed, well-known object keys (e.g. cas-idp-signing.pem) so uploads overwrite instead of accumulating.","Restrict write IAM permissions on the metadata bucket to the CAS service account only.","Don't reuse one bucket across environments; separate staging and production buckets/prefixes.","Alert/monitor on object counts in the metadata bucket exceeding expected (2 for signing+encryption)."],"tags":["aws","s3","duplicate-objects","metadata","illegalargument"],"backgroundTag":"multiple-s3-objects-found","analyzedSha":"e7288fc434b4f4505b8452e1a57e8fb3111bb863","analyzedAt":"2026-09-08T15:39:16.015Z","contentChangedAt":"2026-09-08T15:39:16.015Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}