diff --git a/jobs/otel-collector-windows/templates/config.yml.erb b/jobs/otel-collector-windows/templates/config.yml.erb index c590c0c..3822cea 100644 --- a/jobs/otel-collector-windows/templates/config.yml.erb +++ b/jobs/otel-collector-windows/templates/config.yml.erb @@ -1,35 +1,46 @@ <%= def config @config ||= begin - cfg = retrieve_config + cfg = retrieve_property('config') + secrets = retrieve_property('secrets').to_h { |x| [x['name'], { 'cert' => x['cert'], 'key' => x['key'], 'ca' => x['ca'], 'secret' => x['secret'] }] } + used_secrets = {} + interpolate_secrets!(cfg, secrets, used_secrets) + check_for_unused_secrets!(secrets, used_secrets) cfg = handle_old_properties(cfg) if cfg.empty? cfg end end -def retrieve_config - cfg = p('config') +def interpolate_secrets!(cfg, secrets, used_secrets) + if cfg.is_a? String + match = cfg.match(/{{[^\S\r\n]*\.(\w+)\.(\w+)[^\S\r\n]*}}/) + unless match.nil? + secret_name = match[1] + secret_type = match[2] + secret_value = secrets.dig(secret_name, secret_type) + unless secret_value.nil? || secret_value.empty? + cfg = secret_value + used_secrets["#{secret_name}.#{secret_type}"] = true + end + end + elsif cfg.is_a? Array + cfg.map! { |elem| interpolate_secrets!(elem, secrets, used_secrets) } + elsif cfg.is_a? Hash + cfg.keys.each { |key| cfg[key] = interpolate_secrets!(cfg[key], secrets, used_secrets) } + end - return cfg if cfg.respond_to?(:each) + cfg +end +def check_for_unused_secrets!(secrets, used_secrets) unused_secrets = [] - p('secrets').each do |secret| - %w[cert key ca secret].each do |type| - next if secret[type].nil? || secret[type].empty? - old_cfg = cfg.dup - cfg.gsub!(/{{[^\S\r\n]*\.#{secret["name"]}\.#{type}[^\S\r\n]*}}/, secret[type].gsub("\n", '\n')) - unused_secrets.push "#{secret['name']}.#{type}" if old_cfg.eql? cfg + secrets.each do |name, hash| + hash.each do |type, value| + search_str = "#{name}.#{type}" + unused_secrets.push search_str unless value.nil? || value.empty? || !used_secrets[search_str].nil? end end - raise "The following secrets are unused: ['#{unused_secrets.join("', '")}']" if unused_secrets.any? - - missing_secrets = [] - cfg.scan(/{{[^\S\r\n]*\.\w+\.\w+[^\S\r\n]*}}/) do |m| - missing_secrets.push m - end - raise "The following template variables are missing secrets: ['#{missing_secrets.join("', '")}']" if missing_secrets.any? - - YAML.safe_load(cfg) + raise "The following secrets are unused: ['#{unused_secrets.join("', '")}']" unless unused_secrets.empty? end def check_for_new_and_old_properties! diff --git a/jobs/otel-collector/templates/config.yml.erb b/jobs/otel-collector/templates/config.yml.erb index 6d13431..0411058 100644 --- a/jobs/otel-collector/templates/config.yml.erb +++ b/jobs/otel-collector/templates/config.yml.erb @@ -1,35 +1,46 @@ <%= def config @config ||= begin - cfg = retrieve_config + cfg = retrieve_property('config') + secrets = retrieve_property('secrets').to_h { |x| [x['name'], { 'cert' => x['cert'], 'key' => x['key'], 'ca' => x['ca'], 'secret' => x['secret'] }] } + used_secrets = {} + interpolate_secrets!(cfg, secrets, used_secrets) + check_for_unused_secrets!(secrets, used_secrets) cfg = handle_old_properties(cfg) if cfg.empty? cfg end end -def retrieve_config - cfg = p('config') +def interpolate_secrets!(cfg, secrets, used_secrets) + if cfg.is_a? String + match = cfg.match(/{{[^\S\r\n]*\.(\w+)\.(\w+)[^\S\r\n]*}}/) + unless match.nil? + secret_name = match[1] + secret_type = match[2] + secret_value = secrets.dig(secret_name, secret_type) + unless secret_value.nil? || secret_value.empty? + cfg = secret_value + used_secrets["#{secret_name}.#{secret_type}"] = true + end + end + elsif cfg.is_a? Array + cfg.map! { |elem| interpolate_secrets!(elem, secrets, used_secrets) } + elsif cfg.is_a? Hash + cfg.keys.each { |key| cfg[key] = interpolate_secrets!(cfg[key], secrets, used_secrets) } + end - return cfg if cfg.respond_to?(:each) + cfg +end +def check_for_unused_secrets!(secrets, used_secrets) unused_secrets = [] - p('secrets').each do |secret| - %w[cert key ca secret].each do |type| - next if secret[type].nil? || secret[type].empty? - old_cfg = cfg.dup - cfg.gsub!(/{{[^\S\r\n]*\.#{secret["name"]}\.#{type}[^\S\r\n]*}}/, secret[type].gsub("\n", '\n')) - unused_secrets.push "#{secret['name']}.#{type}" if old_cfg.eql? cfg + secrets.each do |name, hash| + hash.each do |type, value| + search_str = "#{name}.#{type}" + unused_secrets.push search_str unless value.nil? || value.empty? || !used_secrets[search_str].nil? end end - raise "The following secrets are unused: ['#{unused_secrets.join("', '")}']" if unused_secrets.any? - - missing_secrets = [] - cfg.scan(/{{[^\S\r\n]*\.\w+\.\w+[^\S\r\n]*}}/) do |m| - missing_secrets.push m - end - raise "The following template variables are missing secrets: ['#{missing_secrets.join("', '")}']" if missing_secrets.any? - - YAML.safe_load(cfg) + raise "The following secrets are unused: ['#{unused_secrets.join("', '")}']" unless unused_secrets.empty? end def check_for_new_and_old_properties! diff --git a/spec/support/shared_examples_for_otel_collector.rb b/spec/support/shared_examples_for_otel_collector.rb index 1746067..8818613 100644 --- a/spec/support/shared_examples_for_otel_collector.rb +++ b/spec/support/shared_examples_for_otel_collector.rb @@ -584,6 +584,11 @@ def without_internal_telemetry(cfg) 'headers' => { 'auth' => '{{ .anothersecret.secret }}' } + }, + 'prometheus/test' => { + 'tags' => [ + '{{ .anothersecret.secret }}' + ] } }, 'service' => { @@ -649,6 +654,7 @@ def without_internal_telemetry(cfg) expect(rendered['exporters']['otlp']['tls']['key_pem']).to eq('bar') expect(rendered['exporters']['otlp']['tls']['ca_pem']).to eq('baz') expect(rendered['exporters']['otlp']['headers']['auth']).to eq('foobarbaz') + expect(rendered['exporters']['prometheus/test']['tags'][0]).to eq('foobarbaz') end context 'when no secrets exist for template variables' do @@ -658,14 +664,19 @@ def without_internal_telemetry(cfg) properties['secrets'].delete_at(1) end - it 'raises an error' do - expect { rendered }.to raise_error(/The following template variables are missing secrets: \['{{ .testsecret.key }}', '{{ .testsecret.ca }}', '{{ .anothersecret.secret }}'\]/) + it 'does not interpolate those template variables' do + expect(rendered['exporters']['otlp']['tls']['cert_pem']).to eq("-----BEGIN CERTIFICATE-----\nMIIE4jCCAsqgAwIBAgIUO/DRqVeXUmewgpy33MkQpe0ME7YwDQYJKoZIhvcNAQEL\nBQAwgZkxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH\nDA1TYW4gRnJhbmNpc2NvMQwwCgYDVQQKDANNQVAxDzANBgNVBAsMBlZNd2FyZTEV\nMBMGA1UEAwwMVG9vbHNtaXRoc0NBMScwJQYJKoZIhvcNAQkBFhhjZi10b29sc21p\ndGhzQHdtd2FyZS5jb20wHhcNMjQwODI3MjEzMDU3WhcNMjYwODI4MjEzMDU3WjAy\nMQswCQYDVQQGEwJVUzEQMA4GA1UECgwHUGl2b3RhbDERMA8GA1UEAwwIYmxhaC5j\nb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDT0qMGluiM2jrZ0k/3\nYjSy6/55NJttugG+RjfWXIPTti3ySHBgf5oOhgE1w/TMH8vQC1QBXSi3erw+WlZV\nGW7pSs1AwPiTDJWlCmsyabY3En5+V+yFTI7CtA5uxC8Yo6szfHxk+RlZUcE8S7vd\n0Lty0hahK0q+cNLqDfWDJ4jgJWKkoT9yGKSF+LLoUpJXqzI7d0soevzAolXEGb6X\nO8ORQDYbT/onCwq9MKb4jRVE+KYT2+ajdKI0MPR4/3JA8/o2O4BNTf6MOnSFKWLe\nCYXdtcqaDE2GqK3OUnlH2Tv2lS+1KCGq9800MfXJ/ln7kuetPBz7MelR6Ph9SWqk\nEv3NAgMBAAGjgYcwgYQwHQYDVR0OBBYEFPF+Zo5VBV/ZCDQk02HBER1j5WDtMB8G\nA1UdIwQYMBaAFMGM2idsRltlr/D2KjmlZE2sdFgVMB0GA1UdJQQWMBQGCCsGAQUF\nBwMCBggrBgEFBQcDATAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0RBAwwCoIIYmxhaC5j\nb20wDQYJKoZIhvcNAQELBQADggIBALkKkStBbqSJmhAgXsfxMyX+ksuf0iKchP14\n/PIq9srwy6S6urc+9ajp7qNDvM+xaj8w2poUF4CPPVS7RqiRf5wJr2ZJDq0lcXbU\nM+qqKth+6VkOPUsOP+5b6j/aUoo1zTxqiP6q2bJ2igujHfSJ4H3JenD2VogqzrDS\nhNU0m4vupB79dlqPUWkkhkyQ+83GMLWzgwatmjj11jBeOPHNXZJikUODxvwVqscZ\niYYdVzzSqVJCxinwk1eGvGXeGsSR4EBsLpF9g18L57PPT8OfDHM7KnBdwhSFkLuU\ngtd7i3u9NSScr7g3beQIBEi+ho/FR/pPcU453ilECsza3esMKAubr1nE6Be3tlhL\nEZpwAdkj3lZVnAMcXyNo20mgYK7yVoVa+rS4E9oyTcldjqBUvFnFtqbB70h5ZZ/v\n71uRB07WqE6zdvslcHtgWls5mM4APKhxjuszmY4GgEEQ7SJObQSzC53avPhlu+TB\n3EWIdIjpvyNSEsC6yIVQrKJ6ejcqV9+OVPFQyHQ2yzyBDVSVVU6EqYFUJy3zmHp+\nmm95ZMr9Q04nwi5//MNW7Yuw7XmjFtTlN6ybHrc82jNWDJx5GvZkHj0Qmg6TMYu2\nhqmaUsNEA27fgk2HRuHUOJ+2EFFlCVZMLR7vN/JVE/LhZ2CdzoyMOkH0vtKophTg\nHqBTRxft\n-----END CERTIFICATE-----") + expect(rendered['exporters']['otlp']['tls']['key_pem']).to eq('{{ .testsecret.key }}') + expect(rendered['exporters']['otlp']['tls']['ca_pem']).to eq('{{ .testsecret.ca }}') + expect(rendered['exporters']['otlp']['headers']['auth']).to eq('{{ .anothersecret.secret }}') + expect(rendered['exporters']['prometheus/test']['tags'][0]).to eq('{{ .anothersecret.secret }}') end end context 'when no template variables exist for a secret' do before do config['exporters']['otlp'].delete('headers') + config['exporters'].delete('prometheus/test') end it 'raises an error' do @@ -673,7 +684,7 @@ def without_internal_telemetry(cfg) end end - context 'when a template variable uses differing amounts of space separation' do + context 'when template variables uses differing amounts of space separation' do before do config['exporters']['otlp']['tls']['cert_pem'] = '{{.testsecret.cert}}' config['exporters']['otlp']['tls']['key_pem'] = '{{ .testsecret.key}}' @@ -685,6 +696,7 @@ def without_internal_telemetry(cfg) expect(rendered['exporters']['otlp']['tls']['key_pem']).to eq('bar') expect(rendered['exporters']['otlp']['tls']['ca_pem']).to eq('baz') expect(rendered['exporters']['otlp']['headers']['auth']).to eq('foobarbaz') + expect(rendered['exporters']['prometheus/test']['tags'][0]).to eq('foobarbaz') end end @@ -699,6 +711,22 @@ def without_internal_telemetry(cfg) expect { rendered }.to raise_error(/The following secrets are unused: \['testsecret.cert', 'testsecret.key', 'testsecret.ca'\]/) end end + + context 'when template variables are not quoted' do + before do + properties['config'] = ' +exporters: + otlp/other: + endpoint: otelcol:4317 + headers: + auth: {{ .test.secret }}' + properties['secrets'] = [{'name' => 'test', 'secret' => 'mysecret'}] + end + + it 'does not match secrets to those variables' do + expect { rendered }.to raise_error(/The following secrets are unused: \['test.secret'\]/) + end + end end end end