Skip to content

Commit

Permalink
fix(templates): Escape custom inline templates. (#579)
Browse files Browse the repository at this point in the history
fix(templates): Escape custom inline templates.
fix(templates): Escape all custom templates for standalone canary analysis as well.
  • Loading branch information
Matt Duftler authored Jun 28, 2019
1 parent d7fc103 commit 26feb32
Show file tree
Hide file tree
Showing 10 changed files with 105 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import java.util.List;
import java.util.Map;

@Builder
@Builder(toBuilder = true)
@ToString
@NoArgsConstructor
@AllArgsConstructor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,9 @@ default String getCustomFilterTemplate() {
return null;
}

default CanaryMetricSetQueryConfig cloneWithEscapedInlineTemplate() {
return this;
}

@NonNull String getServiceType();
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import com.google.common.annotations.VisibleForTesting;
import com.netflix.kayenta.canary.CanaryConfig;
import com.netflix.kayenta.canary.CanaryMetricConfig;
import com.netflix.kayenta.canary.CanaryMetricSetQueryConfig;
import com.netflix.kayenta.canary.CanaryScope;
import freemarker.template.Configuration;
Expand All @@ -35,7 +36,9 @@
import java.io.IOException;
import java.io.StringReader;
import java.lang.reflect.InvocationTargetException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -147,14 +150,36 @@ private static void populateTemplateBindings(Object bean,

@VisibleForTesting
public static CanaryConfig escapeTemplates(CanaryConfig canaryConfig) {
if (canaryConfig == null) {
return null;
}

Map<String, String> escapedTemplates;

if (!CollectionUtils.isEmpty(canaryConfig.getTemplates())) {
Map<String, String> escapedTemplates =
escapedTemplates =
canaryConfig.getTemplates()
.entrySet()
.stream()
.collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue().replace("${", "$\\{")));
} else {
escapedTemplates = Collections.emptyMap();
}

List<CanaryMetricConfig> escapedMetrics = null;

if (!CollectionUtils.isEmpty(canaryConfig.getMetrics())) {
escapedMetrics =
canaryConfig.getMetrics()
.stream()
.map(m -> m.toBuilder().query(m.getQuery().cloneWithEscapedInlineTemplate()).build())
.collect(Collectors.toList());
} else {
escapedMetrics = Collections.emptyList();
}

canaryConfig = canaryConfig.toBuilder().templates(escapedTemplates).build();
if (escapedTemplates != null || escapedMetrics != null) {
canaryConfig = canaryConfig.toBuilder().templates(escapedTemplates).clearMetrics().metrics(escapedMetrics).build();
}

return canaryConfig;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@
package com.netflix.kayenta.canary.providers

import com.netflix.kayenta.canary.CanaryConfig
import com.netflix.kayenta.canary.CanaryMetricConfig
import com.netflix.kayenta.canary.CanaryMetricSetQueryConfig
import com.netflix.kayenta.canary.providers.metrics.QueryConfigUtils
import org.springframework.util.StringUtils
import spock.lang.Specification
import spock.lang.Unroll

Expand Down Expand Up @@ -53,4 +56,41 @@ class QueryConfigUtilsSpec extends Specification {
'A test: key1=${key1} key2=$\\{key2}.' || 'A test: key1=${key1} key2=${key2}.'
'A test: key1=key1.' || 'A test: key1=key1.'
}

@Unroll
void "Custom inline template #customInlineTemplate is escaped to protect it from premature expression evaluation by orca"() {
expect:
CanaryMetricConfig canaryMetricConfig =
CanaryMetricConfig.builder().query(new TestCanaryMetricSetQueryConfig(customInlineTemplate: customInlineTemplate)).build()
CanaryConfig canaryConfig = CanaryConfig.builder().metric(canaryMetricConfig).build()
QueryConfigUtils.escapeTemplates(canaryConfig).getMetrics()[0].getQuery().getCustomInlineTemplate() ==
expectedEscapedCustomInlineTemplate

where:
customInlineTemplate || expectedEscapedCustomInlineTemplate
'A test: key1=${key1}.' || 'A test: key1=$\\{key1}.'
'A test: key1=${key1} key2=${key2}.' || 'A test: key1=$\\{key1} key2=$\\{key2}.'
'A test: key1=val1.' || 'A test: key1=val1.'
null || null
"" || ""
}
}

class TestCanaryMetricSetQueryConfig implements CanaryMetricSetQueryConfig {

String customInlineTemplate

@Override
CanaryMetricSetQueryConfig cloneWithEscapedInlineTemplate() {
if (StringUtils.isEmpty(customInlineTemplate)) {
return this
} else {
return new TestCanaryMetricSetQueryConfig(customInlineTemplate: customInlineTemplate.replace('${', '$\\{'))
}
}

@Override
String getServiceType() {
"test-service"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public class InfluxdbCanaryMetricSetQueryConfig implements CanaryMetricSetQueryC

@Getter
private List<String> fields;

@Override
public String getServiceType() {
return SERVICE_TYPE;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.netflix.kayenta.canary.CanaryMetricSetQueryConfig;
import lombok.*;
import org.springframework.util.StringUtils;

import javax.validation.constraints.NotNull;
import java.util.List;

@Builder
@Builder(toBuilder = true)
@ToString
@NoArgsConstructor
@AllArgsConstructor
Expand Down Expand Up @@ -58,6 +59,15 @@ public class PrometheusCanaryMetricSetQueryConfig implements CanaryMetricSetQuer
@Getter
private String customFilterTemplate;

@Override
public CanaryMetricSetQueryConfig cloneWithEscapedInlineTemplate() {
if (StringUtils.isEmpty(customInlineTemplate)) {
return this;
} else {
return this.toBuilder().customInlineTemplate(customInlineTemplate.replace("${", "$\\{")).build();
}
}

@Override
public String getServiceType() {
return SERVICE_TYPE;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.netflix.kayenta.canary.CanaryMetricSetQueryConfig;
import lombok.*;
import org.springframework.util.StringUtils;

import javax.validation.constraints.NotNull;
import java.util.List;

@Builder
@Builder(toBuilder = true)
@ToString
@NoArgsConstructor
@AllArgsConstructor
Expand Down Expand Up @@ -62,6 +63,15 @@ public class StackdriverCanaryMetricSetQueryConfig implements CanaryMetricSetQue
@Getter
private String customFilterTemplate;

@Override
public CanaryMetricSetQueryConfig cloneWithEscapedInlineTemplate() {
if (StringUtils.isEmpty(customInlineTemplate)) {
return this;
} else {
return this.toBuilder().customInlineTemplate(customInlineTemplate.replace("${", "$\\{")).build();
}
}

@Override
public String getServiceType() {
return SERVICE_TYPE;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.netflix.kayenta.canaryanalysis.controller;

import com.netflix.kayenta.canary.CanaryConfig;
import com.netflix.kayenta.canary.providers.metrics.QueryConfigUtils;
import com.netflix.kayenta.canaryanalysis.domain.CanaryAnalysisAdhocExecutionRequest;
import com.netflix.kayenta.canaryanalysis.domain.CanaryAnalysisConfig;
import com.netflix.kayenta.canaryanalysis.domain.CanaryAnalysisExecutionRequest;
Expand Down Expand Up @@ -135,7 +136,7 @@ public CanaryAnalysisExecutionResponse initiateCanaryAnalysis(
.metricsAccountName(resolvedMetricsAccountName)
.storageAccountName(resolvedStorageAccountName)
.configurationAccountName(configurationAccountName)
.canaryConfig(canaryConfig)
.canaryConfig(QueryConfigUtils.escapeTemplates(canaryConfig))
.build());
}

Expand Down Expand Up @@ -195,7 +196,7 @@ public CanaryAnalysisExecutionResponse initiateCanaryAnalysisExecutionWithConfig
.executionRequest(canaryAnalysisAdhocExecutionRequest.getExecutionRequest())
.metricsAccountName(resolvedMetricsAccountName)
.storageAccountName(resolvedStorageAccountName)
.canaryConfig(canaryAnalysisAdhocExecutionRequest.getCanaryConfig())
.canaryConfig(QueryConfigUtils.escapeTemplates(canaryAnalysisAdhocExecutionRequest.getCanaryConfig()))
.build());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@
import com.netflix.kayenta.canary.CanaryScope;
import com.netflix.kayenta.canary.CanaryScopePair;
import com.netflix.kayenta.canaryanalysis.domain.CanaryAnalysisConfig;
import com.netflix.kayenta.canaryanalysis.domain.CanaryAnalysisExecutionRequest;
import com.netflix.kayenta.canaryanalysis.domain.RunCanaryContext;
import com.netflix.spinnaker.orca.pipeline.StageDefinitionBuilder;
import com.netflix.spinnaker.orca.pipeline.TaskNode;
import com.netflix.spinnaker.orca.pipeline.WaitStage;
import com.netflix.spinnaker.orca.pipeline.graph.StageGraphBuilder;
import com.netflix.spinnaker.orca.pipeline.model.Stage;
import com.netflix.kayenta.canaryanalysis.domain.CanaryAnalysisExecutionRequest;
import com.netflix.kayenta.canaryanalysis.domain.RunCanaryContext;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,17 @@
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.netflix.kayenta.canaryanalysis.domain.CanaryAnalysisConfig;
import com.netflix.spinnaker.orca.ExecutionStatus;
import com.netflix.spinnaker.orca.pipeline.ExecutionLauncher;
import com.netflix.spinnaker.orca.pipeline.model.Execution;
import com.netflix.spinnaker.orca.pipeline.model.PipelineBuilder;
import com.netflix.spinnaker.orca.pipeline.persistence.ExecutionRepository;
import com.netflix.kayenta.canaryanalysis.domain.CanaryAnalysisExecutionResponse;
import com.netflix.kayenta.canaryanalysis.domain.CanaryAnalysisExecutionResult;
import com.netflix.kayenta.canaryanalysis.domain.CanaryAnalysisExecutionStatusResponse;
import com.netflix.kayenta.canaryanalysis.domain.StageMetadata;
import com.netflix.kayenta.canaryanalysis.orca.stage.GenerateCanaryAnalysisResultStage;
import com.netflix.kayenta.canaryanalysis.orca.stage.SetupAndExecuteCanariesStage;
import com.netflix.spinnaker.orca.ExecutionStatus;
import com.netflix.spinnaker.orca.pipeline.ExecutionLauncher;
import com.netflix.spinnaker.orca.pipeline.model.Execution;
import com.netflix.spinnaker.orca.pipeline.model.PipelineBuilder;
import com.netflix.spinnaker.orca.pipeline.persistence.ExecutionRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
Expand Down

0 comments on commit 26feb32

Please sign in to comment.