summaryrefslogtreecommitdiffstats
path: root/config-model
diff options
context:
space:
mode:
authorMorten Tokle <mortent@yahooinc.com>2022-12-09 15:40:01 +0100
committerMorten Tokle <mortent@yahooinc.com>2022-12-09 15:40:01 +0100
commit6f5660bddad952f2734eddbb753b49c7f4c78a22 (patch)
tree6b5038c83468bf3f0b0a120ea6166451bfe036b1 /config-model
parent0f15b405cad738460a8126bf9ef689ec3edfd094 (diff)
Disallow exclusions in access-control
Diffstat (limited to 'config-model')
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/application/validation/AccessControlFilterExcludeValidator.java43
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/application/validation/AccessControlFilterExcludeValidatorTest.java108
2 files changed, 151 insertions, 0 deletions
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/AccessControlFilterExcludeValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/AccessControlFilterExcludeValidator.java
new file mode 100644
index 00000000000..1a72f0863ee
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/AccessControlFilterExcludeValidator.java
@@ -0,0 +1,43 @@
+package com.yahoo.vespa.model.application.validation;
+
+import com.yahoo.config.model.deploy.DeployState;
+import com.yahoo.config.provision.CloudName;
+import com.yahoo.vespa.model.VespaModel;
+import com.yahoo.vespa.model.container.http.AccessControl;
+import com.yahoo.vespa.model.container.http.Http;
+
+import java.util.logging.Level;
+
+/**
+ * Validates that 'access-control' does not include any exclusions unless explicitly allowed.
+ * Logs in Yahoo clouds and fails in AWS clouds
+ *
+ * @author mortent
+ */
+public class AccessControlFilterExcludeValidator extends Validator {
+
+ @Override
+ public void validate(VespaModel model, DeployState deployState) {
+ if (!deployState.isHosted() || deployState.zone().system().isPublic()) return;
+ if (deployState.getProperties().allowDisableMtls()) return;
+ model.getContainerClusters().forEach((id, cluster) -> {
+ Http http = cluster.getHttp();
+ if (http != null) {
+ if (http.getAccessControl().isPresent()) {
+ verifyNoExclusions(id, http.getAccessControl().get(), deployState);
+ }
+ }
+ });
+ }
+
+ private void verifyNoExclusions(String clusterId, AccessControl accessControl, DeployState deployState) {
+ if (!accessControl.excludedBindings().isEmpty()) {
+ String message = "Application cluster %s excludes paths from access control, this is not allowed and should be removed.".formatted(clusterId);
+ if (deployState.zone().cloud().name() == CloudName.AWS) {
+ throw new IllegalArgumentException(message);
+ } else {
+ deployState.getDeployLogger().log(Level.WARNING, message);
+ }
+ }
+ }
+}
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/AccessControlFilterExcludeValidatorTest.java b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/AccessControlFilterExcludeValidatorTest.java
new file mode 100644
index 00000000000..8754fd499a7
--- /dev/null
+++ b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/AccessControlFilterExcludeValidatorTest.java
@@ -0,0 +1,108 @@
+package com.yahoo.vespa.model.application.validation;
+
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.config.model.MapConfigModelRegistry;
+import com.yahoo.config.model.deploy.DeployState;
+import com.yahoo.config.model.deploy.TestProperties;
+import com.yahoo.config.model.test.MockApplicationPackage;
+import com.yahoo.config.model.test.ModelBuilderAddingAccessControlFilter;
+import com.yahoo.config.provision.AthenzDomain;
+import com.yahoo.config.provision.Cloud;
+import com.yahoo.config.provision.CloudAccount;
+import com.yahoo.config.provision.CloudName;
+import com.yahoo.config.provision.Environment;
+import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.SystemName;
+import com.yahoo.config.provision.Zone;
+import com.yahoo.vespa.model.VespaModel;
+import org.junit.jupiter.api.Test;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class AccessControlFilterExcludeValidatorTest {
+ private static final String SERVICES_XML = """
+ <services version='1.0'>
+ <container id='container-cluster-with-access-control' version='1.0'>
+ <http>
+ <filtering>
+ <access-control>
+ <exclude>
+ <binding>http://*/foo/</binding>
+ </exclude>
+ </access-control>
+ </filtering>
+ </http>
+ </container>
+ </services>""";
+
+
+ @Test
+ public void validator_rejects_excludes_in_cloud() throws IOException, SAXException {
+ DeployState deployState = createDeployState(zone(CloudName.AWS, SystemName.main), new StringBuffer(), false);
+ VespaModel model = new VespaModel(
+ MapConfigModelRegistry.createFromList(new ModelBuilderAddingAccessControlFilter()),
+ deployState);
+
+ IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> new AccessControlFilterExcludeValidator().validate(model, deployState));
+ String expectedMessage = "Application cluster container-cluster-with-access-control excludes paths from access control, this is not allowed and should be removed.";
+ assertEquals(expectedMessage, exception.getMessage());
+ }
+
+ @Test
+ public void validator_warns_excludes_in_cloud() throws IOException, SAXException {
+ StringBuffer logOutput = new StringBuffer();
+ DeployState deployState = createDeployState(zone(CloudName.YAHOO, SystemName.main), logOutput, false);
+ VespaModel model = new VespaModel(
+ MapConfigModelRegistry.createFromList(new ModelBuilderAddingAccessControlFilter()),
+ deployState);
+
+ new AccessControlFilterExcludeValidator().validate(model, deployState);
+ String expectedMessage = "Application cluster container-cluster-with-access-control excludes paths from access control, this is not allowed and should be removed.";
+ assertTrue(logOutput.toString().contains(expectedMessage));
+ }
+
+ @Test
+ public void validator_accepts_when_allowed_to_exclude() throws IOException, SAXException {
+ DeployState deployState = createDeployState(zone(CloudName.AWS, SystemName.main), new StringBuffer(), true);
+ VespaModel model = new VespaModel(
+ MapConfigModelRegistry.createFromList(new ModelBuilderAddingAccessControlFilter()),
+ deployState);
+ new AccessControlFilterExcludeValidator().validate(model, deployState);
+ }
+
+ @Test
+ public void validator_accepts_public_deployments() throws IOException, SAXException {
+ DeployState deployState = createDeployState(zone(CloudName.AWS, SystemName.Public), new StringBuffer(), false);
+ VespaModel model = new VespaModel(
+ MapConfigModelRegistry.createFromList(new ModelBuilderAddingAccessControlFilter()),
+ deployState);
+
+ new AccessControlFilterExcludeValidator().validate(model, deployState);
+ }
+
+ private static DeployState createDeployState(Zone zone, StringBuffer buffer, boolean allowExcludes) {
+ DeployLogger logger = (__, message) -> buffer.append(message).append('\n');
+ return new DeployState.Builder()
+ .applicationPackage(new MockApplicationPackage.Builder().withServices(SERVICES_XML).build())
+ .properties(
+ new TestProperties()
+ .setHostedVespa(true)
+ .setAthenzDomain(AthenzDomain.from("foo.bar"))
+ .allowDisableMtls(allowExcludes))
+ .deployLogger(logger)
+ .zone(zone)
+ .build();
+ }
+
+ private static Zone zone(CloudName cloudName, SystemName systemName) {
+ Cloud.Builder cloudBuilder = Cloud.builder().name(cloudName);
+ if (cloudName == CloudName.AWS) cloudBuilder.account(CloudAccount.from("123456789012"));
+ return new Zone(cloudBuilder.build(), systemName, Environment.prod, RegionName.defaultName());
+
+ }
+}