Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import * as yaml from 'yaml';

import type { FullAgentConfigMap } from '../types/models/agent_cm';

import { fullAgentConfigMapToYaml } from './agent_cm_to_yaml';

function makeConfigMap(overrides?: Partial<FullAgentConfigMap>): FullAgentConfigMap {
return {
apiVersion: 'v1',
kind: 'ConfigMap',
metadata: {
name: 'agent-node-datastreams',
namespace: 'kube-system',
labels: { 'k8s-app': 'elastic-agent' },
},
data: {
'agent.yml': {} as any,
},
...overrides,
};
}

describe('fullAgentConfigMapToYaml', () => {
it('serializes a ConfigMap to valid YAML', () => {
const cm = makeConfigMap();
const result = fullAgentConfigMapToYaml(cm, yaml);

const parsed = yaml.parse(result);
expect(parsed.apiVersion).toBe('v1');
expect(parsed.kind).toBe('ConfigMap');
expect(parsed.metadata.name).toBe('agent-node-datastreams');
expect(parsed.metadata.namespace).toBe('kube-system');
expect(parsed.metadata.labels['k8s-app']).toBe('elastic-agent');
});

it('orders top-level keys as apiVersion, kind, metadata, data', () => {
// Supply keys in reverse order to confirm sorting is applied
const cm = {
data: { 'agent.yml': {} as any },
metadata: {
name: 'agent-node-datastreams',
namespace: 'kube-system',
labels: { 'k8s-app': 'elastic-agent' },
},
kind: 'ConfigMap',
apiVersion: 'v1',
} as FullAgentConfigMap;

const result = fullAgentConfigMapToYaml(cm, yaml);

const lines = result.split('\n').filter((l) => /^\w/.test(l));
expect(lines[0]).toMatch(/^apiVersion/);
expect(lines[1]).toMatch(/^kind/);
expect(lines[2]).toMatch(/^metadata/);
expect(lines[3]).toMatch(/^data/);
});

it('quotes date-only strings to prevent YAML 1.1 timestamp interpretation by the agent', () => {
// The Elastic Agent parses policy YAML with a YAML 1.1 parser, which treats unquoted
// YYYY-MM-DD values as timestamps and converts them to RFC3339 (2021-06-01T00:00:00Z).
const cm = makeConfigMap({
data: {
'agent.yml': {
state: {
assessment_api_version: '2021-06-01',
sub_assessment_api_version: '2019-01-01-preview',
},
} as any,
},
});

const result = fullAgentConfigMapToYaml(cm, yaml);

expect(result).toContain('assessment_api_version: "2021-06-01"');
expect(result).toContain('sub_assessment_api_version: 2019-01-01-preview');
});

it('preserves nested agent policy data unchanged', () => {
const cm = makeConfigMap({
data: {
'agent.yml': {
id: 'policy-id',
outputs: {
default: { type: 'elasticsearch', hosts: ['http://localhost:9200'] },
},
inputs: [{ id: 'input-1', type: 'logfile', streams: [] }],
} as any,
},
});

const result = fullAgentConfigMapToYaml(cm, yaml);
const parsed = yaml.parse(result);

expect(parsed.data['agent.yml'].id).toBe('policy-id');
expect(parsed.data['agent.yml'].outputs.default.type).toBe('elasticsearch');
expect(parsed.data['agent.yml'].inputs[0].id).toBe('input-1');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@ const CM_KEYS_ORDER = ['apiVersion', 'kind', 'metadata', 'data'];

export const fullAgentConfigMapToYaml = (policy: FullAgentConfigMap, yaml: YamlModule): string => {
const sortCmKeys = createYamlKeysSorter(CM_KEYS_ORDER, yaml);
return toYaml(policy, { sortMapEntries: sortCmKeys, strict: false }, yaml);
return toYaml(policy, { sortMapEntries: sortCmKeys, strict: false, schema: 'yaml-1.1' }, yaml);
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* 2.0.
*/

import * as yaml from 'yaml';

import type { FullAgentPolicy } from '../types';

import { fullAgentPolicyToYaml } from './full_agent_policy_to_yaml';
Expand Down Expand Up @@ -65,4 +67,37 @@ describe('fullAgentPolicyToYaml', () => {
`"{\\"id\\":\\"1234\\",\\"outputs\\":{\\"default\\":{\\"type\\":\\"elasticsearch\\",\\"hosts\\":[\\"http://localhost:9200\\"]}},\\"inputs\\":[{\\"id\\":\\"test_input-secrets-abcd1234\\",\\"revision\\":1,\\"name\\":\\"secrets-1\\",\\"type\\":\\"test_input\\",\\"data_stream\\":{\\"namespace\\":\\"default\\"},\\"use_output\\":\\"default\\",\\"package_policy_id\\":\\"abcd1234\\",\\"package_var_secret\\":\\"\${SECRET_0}\\",\\"input_var_secret\\":\\"\${SECRET_1}\\",\\"streams\\":[{\\"id\\":\\"test_input-secrets.log-abcd1234\\",\\"data_stream\\":{\\"type\\":\\"logs\\",\\"dataset\\":\\"secrets.log\\"},\\"package_var_secret\\":\\"\${SECRET_0}\\",\\"input_var_secret\\":\\"\${SECRET_1}\\",\\"stream_var_secret\\":\\"\${SECRET_2}\\"}],\\"meta\\":{\\"package\\":{\\"name\\":\\"secrets\\",\\"version\\":\\"1.0.0\\"}}}],\\"secret_references\\":[{\\"id\\":\\"secret-id-1\\"},{\\"id\\":\\"secret-id-2\\"},{\\"id\\":\\"secret-id-3\\"}],\\"revision\\":2,\\"agent\\":{},\\"signed\\":{},\\"output_permissions\\":{},\\"fleet\\":{}}"`
);
});

it('should quote date-only strings to prevent YAML 1.1 timestamp interpretation by the agent', () => {
// Integrations like microsoft_defender_cloud use date-only API versions (e.g. 2021-06-01).
// The Elastic Agent parses policy YAML with a YAML 1.1 parser, which treats unquoted
// YYYY-MM-DD values as timestamps and converts them to RFC3339 (2021-06-01T00:00:00Z).
// The yaml-1.1 schema in Document options forces these values to be quoted.
const policy = {
id: 'test-policy',
outputs: {},
inputs: [
{
id: 'test-input',
type: 'cel',
streams: [
{
id: 'test-stream',
state: {
assessment_api_version: '2021-06-01',
sub_assessment_api_version: '2019-01-01-preview',
},
},
],
},
],
revision: 1,
agent: {},
} as unknown as FullAgentPolicy;

const result = fullAgentPolicyToYaml(policy, yaml);

expect(result).toContain('assessment_api_version: "2021-06-01"');
expect(result).toContain('sub_assessment_api_version: 2019-01-01-preview');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ export const fullAgentPolicyToYaml = (
apiKey?: string
): string => {
const sortYamlKeys = createYamlKeysSorter(POLICY_KEYS_ORDER, yaml);
const yamlText = toYaml(policy, { sortMapEntries: sortYamlKeys, strict: false }, yaml);
const yamlText = toYaml(
policy,
{ sortMapEntries: sortYamlKeys, strict: false, schema: 'yaml-1.1' },
yaml
);
const formattedYml = apiKey ? replaceApiKey(yamlText, apiKey) : yamlText;

if (!policy?.secret_references?.length) return formattedYml;
Expand Down
Loading