Output configuration fields marked isEncrypted are stored in plain text when installed from a content pack
Expected Behavior
MessageOutput configuration fields declared with isEncrypted=true should be encrypted before being persisted, no matter which code path creates the output. Content pack installation should behave like the REST API.
Current Behavior
OutputFacade.decode passes the raw configuration map straight into outputService.create, with no conversion of encrypted fields:
final CreateOutputRequest createOutputRequest = CreateOutputRequest.create(
outputEntity.title().asString(parameters),
outputEntity.type().asString(parameters),
toValueMap(outputEntity.configuration(), parameters),
null // Outputs are assigned to streams in StreamFacade
);InputFacade.decode does the equivalent conversion, routing the configuration through ConfigurationWrapper so the specialized deserializer turns plain strings into EncryptedValue objects:
// Incoming encrypted fields from a content pack will be plain strings in the input configuration at this
// point.
// We use the object mapper to convert them into proper EncryptedValue objects. This works, because classes
// implementing the WithInputConfiguration interface will be converted using a specialized deserializer.
final var configuration = objectMapper.convertValue(
new ConfigurationWrapper(type, rawConfiguration), ConfigurationWrapper.class).configuration();Result for outputs: the value lands in MongoDB as a plain String. Configuration only routes EncryptedValue instances into its encrypted map, so Configuration.getEncryptedValue(key) returns unset and the running output gets no secret.
Two concrete consequences:
- A content pack that parameterizes the field stores the operator supplied secret in plain text in the
outputscollection. - A content pack exported from a live system carries the placeholder that
ValueReference.of(EncryptedValue)substitutes (<Encrypted value was replaced with this text for content pack export. ...>). Installing it stores that literal placeholder string as the field value rather than leaving the field unset.
The export side itself is fine. ValueReference.of(EncryptedValue) already prevents secrets from leaking into an exported content pack.
Steps to Reproduce
- Build a plugin output with a
TextFielddeclaredisEncrypted=true, as in #26626. - Create a content pack containing that output, with the encrypted field exposed as a parameter.
- Install the content pack and supply a value for the parameter.
- Inspect the document in the
outputscollection. The field holds the plain string, not{ "encrypted_value": "...", "salt": "..." }.
Context
Follow-up to #26626 and PR #27104, which fixed the REST API create and update paths (OutputResource) and the load path (OutputServiceImpl). The content pack path was left out of scope there.
No output shipped by core or enterprise declares isEncrypted today, so this only affects third-party plugin outputs. It is the same class of bug as #26626 and the fix is to mirror what InputFacade.decode already does.
Related
- #26626
- #27104
Source: Graylog2/graylog2-server