From 6f5660bddad952f2734eddbb753b49c7f4c78a22 Mon Sep 17 00:00:00 2001 From: Morten Tokle Date: Fri, 9 Dec 2022 15:40:01 +0100 Subject: Disallow exclusions in access-control --- .../AccessControlFilterExcludeValidator.java | 43 ++++++++ .../AccessControlFilterExcludeValidatorTest.java | 108 +++++++++++++++++++++ 2 files changed, 151 insertions(+) create mode 100644 config-model/src/main/java/com/yahoo/vespa/model/application/validation/AccessControlFilterExcludeValidator.java create mode 100644 config-model/src/test/java/com/yahoo/vespa/model/application/validation/AccessControlFilterExcludeValidatorTest.java 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 = """ + + + + + + + http://*/foo/ + + + + + + """; + + + @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()); + + } +} -- cgit v1.2.3