{"record":{"id":"73940a750edfe192","repo":"apache/pulsar","slug":"both-s3-id-field-and-s3-secret-field-must-be","errorCode":null,"errorMessage":"Both ${S3_ID_FIELD} and ${S3_SECRET_FIELD} must be set when providing offload credentials in the configuration","messagePattern":"Both (.+?) and (.+?) must be set when providing offload credentials in the configuration","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"tiered-storage/jcloud/src/main/java/org/apache/bookkeeper/mledger/offload/jcloud/provider/JCloudBlobStoreProvider.java","lineNumber":447,"sourceCode":"            throw new IllegalArgumentException(\n                    \"ManagedLedgerOffloadMaxBlockSizeInBytes cannot be less than 5MB for \"\n                            + config.getDriver() + \" offload\");\n        }\n    };\n\n    static final CredentialBuilder S3_CREDENTIAL_BUILDER = (TieredStorageConfiguration config) -> {\n        if (config.getCredentials() != null) {\n            return;\n        }\n        // Credentials provided in the tiered storage configuration take\n        // precedence over the environment variables. Offload policies carry\n        // credentials under the s3-prefixed keys for every S3-compatible\n        // driver (see OffloadPoliciesImpl), so those are the keys accepted.\n        String configId = config.getConfigProperty(S3_ID_FIELD);\n        String configSecret = config.getConfigProperty(S3_SECRET_FIELD);\n        if (StringUtils.isNotBlank(configId) || StringUtils.isNotBlank(configSecret)) {\n            if (StringUtils.isBlank(configId) || StringUtils.isBlank(configSecret)) {\n                throw new IllegalArgumentException(\n                        \"Both \" + S3_ID_FIELD + \" and \" + S3_SECRET_FIELD\n                                + \" must be set when providing offload credentials in the configuration\");\n            }\n            Credentials credentials = new Credentials(configId, configSecret);\n            config.setProviderCredentials(() -> credentials);\n            return;\n        }\n        String accountName = System.getenv().getOrDefault(\"ACCESS_KEY_ID\", \"\");\n        // For forward compatibility\n        if (StringUtils.isEmpty(accountName.trim())) {\n            accountName = System.getenv().getOrDefault(\"ALIYUN_OSS_ACCESS_KEY_ID\", \"\");\n        }\n        if (StringUtils.isEmpty(accountName.trim())) {\n            throw new IllegalArgumentException(\"Couldn't get the access key id.\");\n        }\n        String accountKey = System.getenv().getOrDefault(\"ACCESS_KEY_SECRET\", \"\");\n        if (StringUtils.isEmpty(accountKey.trim())) {\n            accountKey = System.getenv().getOrDefault(\"ALIYUN_OSS_ACCESS_KEY_SECRET\", \"\");","sourceCodeStart":429,"sourceCodeEnd":465,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/tiered-storage/jcloud/src/main/java/org/apache/bookkeeper/mledger/offload/jcloud/provider/JCloudBlobStoreProvider.java#L429-L465","documentation":"Thrown when S3-style offload credentials are supplied via configuration but only one of the two required keys is present: both the access-key-id field (S3_ID_FIELD, e.g. s3ManagedLedgerOffloadRole/s3 id key) and the secret field (S3_SECRET_FIELD) must be provided together. A lone value is treated as a misconfiguration rather than silently ignored.","triggerScenarios":"During S3 credential building: config.getConfigProperty(S3_ID_FIELD) is non-blank while S3_SECRET_FIELD is blank, or vice versa — e.g. only the access key was added to configProperties, only the secret key leaked into broker.conf, or one key was renamed/typoed.","commonSituations":"Rotating credentials and only updating one of the two keys; pasting a credential pair but forgetting the second line; secrets templating that renders one variable empty; copy-pasting only the secret into configuration expecting the id to come from env.","solutions":["Set both the id and secret config properties (e.g. s3ManagedLedgerOffloadAccessKeyId and s3ManagedLedgerOffloadAccessKeySecret) to non-blank values together.","Check configProperties via logs/startup for which of the two keys is missing or blank.","If credentials should come from the environment or instance profile instead, remove BOTH config keys so the env-based credential path is used.","Fix templating/secrets manager so both values render."],"exampleFix":"// before\ns3ManagedLedgerOffloadAccessKeyId=AKIA...\n# secret missing\n// after\ns3ManagedLedgerOffloadAccessKeyId=AKIA...\ns3ManagedLedgerOffloadAccessKeySecret=wJalr...","handlingStrategy":"validation","validationCode":"String id = cfg.getConfigProperty(S3_ID_FIELD);\nString secret = cfg.getConfigProperty(S3_SECRET_FIELD);\nif ((id != null && !id.isBlank()) || (secret != null && !secret.isBlank())) {\n    if (id == null || id.isBlank() || secret == null || secret.isBlank()) {\n        throw new IllegalStateException(\"Provide both S3 access key id and secret, or neither\");\n    }\n}","typeGuard":null,"tryCatchPattern":"try {\n    provider.validateConfig(cfg);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().contains(\"must be set when providing offload credentials\")) {\n        log.error(\"S3 offload credential pair incomplete: set both id and secret, or remove both\", e);\n    }\n}","preventionTips":["Always treat credential id+secret as an atomic pair in config templates.","Prefer IAM instance profiles / env credentials over inline config keys.","Rotate both keys together.","Check rendered config in staging before deploying to production."],"tags":["configuration","credentials","s3","offload"],"backgroundTag":"incomplete-credentials","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}