diff options
author | Morten Tokle <mortent@verizonmedia.com> | 2021-08-05 15:15:25 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-08-05 15:15:25 +0200 |
commit | 76e45b948ce0cb1d6a20e5c5063229f4e9a2d4de (patch) | |
tree | f008a1431dc3e992436ead40e92ca5f8ba08d425 /controller-server | |
parent | 1738a68e371a70cb9938e2bfc1a99b4c5804f2ad (diff) |
Limit apis for service view (#18686)
Diffstat (limited to 'controller-server')
2 files changed, 36 insertions, 0 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java index 2a06fc76f39..c9d21ea86aa 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java @@ -37,6 +37,8 @@ import com.yahoo.slime.JsonParseException; import com.yahoo.slime.Slime; import com.yahoo.slime.SlimeUtils; import com.yahoo.text.Text; +import com.yahoo.vespa.flags.Flags; +import com.yahoo.vespa.flags.ListFlag; import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.Controller; import com.yahoo.vespa.hosted.controller.Instance; @@ -141,6 +143,7 @@ import java.util.OptionalLong; import java.util.Scanner; import java.util.StringJoiner; import java.util.logging.Level; +import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -166,6 +169,7 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler { private final Controller controller; private final AccessControlRequests accessControlRequests; private final TestConfigSerializer testConfigSerializer; + private final ListFlag<String> allowedServiceViewProxy; @Inject public ApplicationApiHandler(LoggingRequestHandler.Context parentCtx, @@ -175,6 +179,7 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler { this.controller = controller; this.accessControlRequests = accessControlRequests; this.testConfigSerializer = new TestConfigSerializer(controller.system()); + allowedServiceViewProxy = Flags.ALLOWED_SERVICE_VIEW_APIS.bindTo(controller.flagSource()); } @Override @@ -1685,6 +1690,11 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler { return new HtmlResponse(result); } + String normalizedRestPath = URI.create(restPath).normalize().toString(); + if (allowedServiceViewProxy.value().stream().noneMatch(normalizedRestPath::startsWith)) { + return ErrorResponse.forbidden("Access denied"); + } + Map<?,?> result = controller.serviceRegistry().configServer().getServiceApiResponse(deploymentId, serviceName, restPath); ServiceApiResponse response = new ServiceApiResponse(deploymentId.zoneId(), deploymentId.applicationId(), diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java index a01097cfcb6..3348454c6af 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java @@ -1583,6 +1583,32 @@ public class ApplicationApiTest extends ControllerContainerTest { assertEquals(0, activeGrants.size()); } + @Test + public void testServiceView() throws Exception { + createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID); + String serviceApi="/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/instance1/service"; + // Not allowed to request apis not listed in feature flag allowed-service-view-apis. e.g /document/v1 + tester.assertResponse(request(serviceApi + "/storagenode-awe3slno6mmq2fye191y324jl/document/v1/", GET) + .userIdentity(USER_ID) + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), + "{\"error-code\":\"FORBIDDEN\",\"message\":\"Access denied\"}", + 403); + + // Test path traversal + tester.assertResponse(request(serviceApi + "/storagenode-awe3slno6mmq2fye191y324jl/state/v1/../../document/v1/", GET) + .userIdentity(USER_ID) + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), + "{\"error-code\":\"FORBIDDEN\",\"message\":\"Access denied\"}", + 403); + + // Test urlencoded path traversal + tester.assertResponse(request(serviceApi + "/storagenode-awe3slno6mmq2fye191y324jl/state%2Fv1%2F..%2F..%2Fdocument%2Fv1%2F", GET) + .userIdentity(USER_ID) + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), + "{\"error-code\":\"FORBIDDEN\",\"message\":\"Access denied\"}", + 403); + } + private static String serializeInstant(Instant i) { return DateTimeFormatter.ISO_INSTANT.format(i.truncatedTo(ChronoUnit.SECONDS)); } |