From fa1957146225558d5e191c0349ca273a140ffb86 Mon Sep 17 00:00:00 2001 From: Harald Musum Date: Wed, 21 Feb 2018 19:58:42 +0100 Subject: Revert "Bjorncs/controller authorization" --- .../vespa/hosted/controller/AthenzFilterMock.java | 42 ----- ...plicationRequestToDiscFilterRequestWrapper.java | 182 --------------------- .../hosted/controller/restapi/ContainerTester.java | 40 +---- .../restapi/ControllerContainerTest.java | 46 +----- ...esponseHandlerToApplicationResponseWrapper.java | 77 --------- .../restapi/application/ApplicationApiTest.java | 162 +++++++----------- .../restapi/application/MockAuthorizer.java | 80 +++++++++ .../restapi/controller/ControllerApiTest.java | 57 +++---- .../restapi/deployment/DeploymentApiTest.java | 6 +- .../filter/ControllerAuthorizationFilterTest.java | 5 +- .../restapi/screwdriver/ScrewdriverApiTest.java | 21 +-- .../controller/restapi/zone/v1/ZoneApiTest.java | 11 +- .../controller/restapi/zone/v2/ZoneApiTest.java | 28 ++-- 13 files changed, 195 insertions(+), 562 deletions(-) delete mode 100644 controller-server/src/test/java/com/yahoo/vespa/hosted/controller/AthenzFilterMock.java delete mode 100644 controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ApplicationRequestToDiscFilterRequestWrapper.java delete mode 100644 controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ResponseHandlerToApplicationResponseWrapper.java create mode 100644 controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/MockAuthorizer.java (limited to 'controller-server/src/test') diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/AthenzFilterMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/AthenzFilterMock.java deleted file mode 100644 index 02a7f63fbb8..00000000000 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/AthenzFilterMock.java +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller; - -import com.yahoo.jdisc.handler.ResponseHandler; -import com.yahoo.jdisc.http.HttpResponse; -import com.yahoo.jdisc.http.filter.DiscFilterRequest; -import com.yahoo.jdisc.http.filter.SecurityRequestFilter; -import com.yahoo.vespa.athenz.api.AthenzIdentity; -import com.yahoo.vespa.athenz.api.AthenzPrincipal; -import com.yahoo.vespa.athenz.api.NToken; -import com.yahoo.vespa.athenz.utils.AthenzIdentities; -import com.yahoo.yolean.chain.Before; - -import static com.yahoo.vespa.hosted.controller.restapi.filter.SecurityFilterUtils.sendErrorResponse; - -/** - * @author bjorncs - */ -@Before("com.yahoo.vespa.hosted.controller.restapi.filter.ControllerAuthorizationFilter") -public class AthenzFilterMock implements SecurityRequestFilter { - - public static final String IDENTITY_HEADER_NAME = "Athenz-Identity"; - public static final String ATHENZ_NTOKEN_HEADER_NAME = "Athenz-NToken"; - - @Override - public void filter(DiscFilterRequest request, ResponseHandler handler) { - if (request.getMethod().equalsIgnoreCase("OPTIONS")) return; - String identityName = request.getHeader(IDENTITY_HEADER_NAME); - String nToken = request.getHeader(ATHENZ_NTOKEN_HEADER_NAME); - if (identityName == null) { - sendErrorResponse(handler, HttpResponse.Status.UNAUTHORIZED, "Not authenticated"); - } else { - AthenzIdentity identity = AthenzIdentities.from(identityName); - AthenzPrincipal principal = - nToken == null ? - new AthenzPrincipal(identity) : - new AthenzPrincipal(identity, new NToken(nToken)); - request.setUserPrincipal(principal); - } - } - -} diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ApplicationRequestToDiscFilterRequestWrapper.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ApplicationRequestToDiscFilterRequestWrapper.java deleted file mode 100644 index da9dd1f0786..00000000000 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ApplicationRequestToDiscFilterRequestWrapper.java +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.restapi; - -import com.yahoo.application.container.handler.Request; -import com.yahoo.jdisc.HeaderFields; -import com.yahoo.jdisc.http.Cookie; -import com.yahoo.jdisc.http.HttpRequest; -import com.yahoo.jdisc.http.filter.DiscFilterRequest; -import com.yahoo.jdisc.http.servlet.ServletOrJdiscHttpRequest; - -import java.net.SocketAddress; -import java.net.URI; -import java.security.Principal; -import java.util.Collections; -import java.util.Enumeration; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; - -/** - * Wraps an {@link Request} into a {@link DiscFilterRequest}. Only a few methods are supported. - * Changes are not propagated; updated request instance must be retrieved through {@link #getUpdatedRequest()}. - * - * @author bjorncs - */ -public class ApplicationRequestToDiscFilterRequestWrapper extends DiscFilterRequest { - - private final Request request; - private Principal userPrincipal; - - public ApplicationRequestToDiscFilterRequestWrapper(Request request) { - super(new ServletOrJdiscHttpRequest() { - @Override - public void copyHeaders(HeaderFields target) { - request.getHeaders().forEach(target::add); - } - - @Override - public Map> parameters() { - return Collections.emptyMap(); - } - - @Override - public URI getUri() { - return URI.create(request.getUri()); - } - - @Override - public HttpRequest.Version getVersion() { - throw new UnsupportedOperationException(); - } - - @Override - public String getRemoteHostAddress() { - throw new UnsupportedOperationException(); - } - - @Override - public String getRemoteHostName() { - throw new UnsupportedOperationException(); - } - - @Override - public int getRemotePort() { - throw new UnsupportedOperationException(); - } - - @Override - public void setRemoteAddress(SocketAddress remoteAddress) { - throw new UnsupportedOperationException(); - } - - @Override - public Map context() { - throw new UnsupportedOperationException(); - } - - @Override - public List decodeCookieHeader() { - throw new UnsupportedOperationException(); - } - - @Override - public void encodeCookieHeader(List cookies) { - throw new UnsupportedOperationException(); - } - - @Override - public long getConnectedAt(TimeUnit unit) { - throw new UnsupportedOperationException(); - } - }); - this.request = request; - this.userPrincipal = request.getUserPrincipal().orElse(null); - } - - public Request getUpdatedRequest() { - Request updatedRequest = new Request(this.request.getUri(), this.request.getBody(), this.request.getMethod(), this.userPrincipal); - this.request.getHeaders().forEach(updatedRequest.getHeaders()::put); - return updatedRequest; - } - - @Override - public String getMethod() { - return request.getMethod().name(); - } - - @Override - public void setUri(URI uri) { - throw new UnsupportedOperationException(); - } - - @Override - public String getParameter(String name) { - throw new UnsupportedOperationException(); - } - - @Override - public Enumeration getParameterNames() { - throw new UnsupportedOperationException(); - } - - @Override - public void addHeader(String name, String value) { - throw new UnsupportedOperationException(); - } - - @Override - public String getHeader(String name) { - return request.getHeaders().getFirst(name); - } - - @Override - public Enumeration getHeaderNames() { - throw new UnsupportedOperationException(); - } - - @Override - public List getHeaderNamesAsList() { - throw new UnsupportedOperationException(); - } - - @Override - public Enumeration getHeaders(String name) { - throw new UnsupportedOperationException(); - } - - @Override - public List getHeadersAsList(String name) { - throw new UnsupportedOperationException(); - } - - @Override - public void removeHeaders(String name) { - throw new UnsupportedOperationException(); - } - - @Override - public void setHeaders(String name, String value) { - throw new UnsupportedOperationException(); - } - - @Override - public void setHeaders(String name, List values) { - throw new UnsupportedOperationException(); - } - - @Override - public Principal getUserPrincipal() { - return this.userPrincipal; - } - - @Override - public void setUserPrincipal(Principal principal) { - this.userPrincipal = principal; - } - - @Override - public void clearCookies() { - throw new UnsupportedOperationException(); - } -} diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java index be987e84cd8..95810e90cdb 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java @@ -5,12 +5,8 @@ import com.yahoo.application.container.JDisc; import com.yahoo.application.container.handler.Request; import com.yahoo.application.container.handler.Response; import com.yahoo.collections.Pair; -import com.yahoo.component.ComponentSpecification; import com.yahoo.component.Version; -import com.yahoo.container.http.filter.FilterChainRepository; import com.yahoo.io.IOUtils; -import com.yahoo.jdisc.http.filter.SecurityRequestFilter; -import com.yahoo.jdisc.http.filter.SecurityRequestFilterChain; import com.yahoo.slime.ArrayTraverser; import com.yahoo.slime.Inspector; import com.yahoo.slime.Slime; @@ -25,7 +21,6 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashSet; import java.util.List; -import java.util.Optional; import java.util.Set; import java.util.function.Supplier; @@ -75,9 +70,7 @@ public class ContainerTester { public void assertResponse(Request request, File responseFile, int expectedStatusCode) throws IOException { String expectedResponse = IOUtils.readFile(new File(responseFilePath + responseFile.toString())); expectedResponse = include(expectedResponse); - FilterResult filterResult = invokeSecurityFilters(request); - request = filterResult.request; - Response response = filterResult.response != null ? filterResult.response : container.handleRequest(request); + Response response = container.handleRequest(request); Slime expectedSlime = SlimeUtils.jsonToSlime(expectedResponse.getBytes(StandardCharsets.UTF_8)); Set fieldsToCensor = fieldsToCensor(null, expectedSlime.get(), new HashSet<>()); Slime responseSlime = SlimeUtils.jsonToSlime(response.getBody()); @@ -103,31 +96,11 @@ public class ContainerTester { } public void assertResponse(Request request, String expectedResponse, int expectedStatusCode) throws IOException { - FilterResult filterResult = invokeSecurityFilters(request); - request = filterResult.request; - Response response = filterResult.response != null ? filterResult.response : container.handleRequest(request); + Response response = container.handleRequest(request); assertEquals(expectedResponse, response.getBodyAsString()); assertEquals("Status code", expectedStatusCode, response.getStatus()); } - // Hack to run request filters as part of the request processing chain. - // Limitation: Bindings ignored, disc filter request wrapper only support limited set of methods. - private FilterResult invokeSecurityFilters(Request request) { - FilterChainRepository filterChainRepository = (FilterChainRepository) container.components().getComponent(FilterChainRepository.class.getName()); - SecurityRequestFilterChain chain = (SecurityRequestFilterChain) filterChainRepository.getFilter(ComponentSpecification.fromString("default")); - for (SecurityRequestFilter securityRequestFilter : chain.getFilters()) { - ApplicationRequestToDiscFilterRequestWrapper discFilterRequest = new ApplicationRequestToDiscFilterRequestWrapper(request); - ResponseHandlerToApplicationResponseWrapper responseHandlerWrapper = new ResponseHandlerToApplicationResponseWrapper(); - securityRequestFilter.filter(discFilterRequest, responseHandlerWrapper); - request = discFilterRequest.getUpdatedRequest(); - Optional filterResponse = responseHandlerWrapper.toResponse(); - if (filterResponse.isPresent()) { - return new FilterResult(request, filterResponse.get()); - } - } - return new FilterResult(request, null); - } - private Set fieldsToCensor(String fieldNameOrNull, Inspector value, Set fieldsToCensor) { switch (value.type()) { case ARRAY: value.traverse((ArrayTraverser)(int index, Inspector element) -> fieldsToCensor(null, element, fieldsToCensor)); break; @@ -184,14 +157,5 @@ public class ContainerTester { return prefix + includedContent + postFix; } - static class FilterResult { - final Request request; - final Response response; - - FilterResult(Request request, Response response) { - this.request = request; - this.response = response; - } - } } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java index 32053cce7d2..abc5f9f8aa1 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java @@ -5,17 +5,11 @@ import com.yahoo.application.Networking; import com.yahoo.application.container.JDisc; import com.yahoo.application.container.handler.Request; import com.yahoo.application.container.handler.Response; -import com.yahoo.vespa.athenz.api.AthenzIdentity; -import com.yahoo.vespa.athenz.api.AthenzUser; -import com.yahoo.vespa.athenz.api.NToken; -import com.yahoo.vespa.hosted.controller.athenz.mock.AthenzClientFactoryMock; import org.junit.After; import org.junit.Before; import java.io.IOException; -import static com.yahoo.vespa.hosted.controller.AthenzFilterMock.ATHENZ_NTOKEN_HEADER_NAME; -import static com.yahoo.vespa.hosted.controller.AthenzFilterMock.IDENTITY_HEADER_NAME; import static org.junit.Assert.assertEquals; /** @@ -30,7 +24,6 @@ import static org.junit.Assert.assertEquals; */ public class ControllerContainerTest { - public static final AthenzUser USER = AthenzUser.fromUserId("bob"); protected JDisc container; @Before @@ -72,6 +65,7 @@ public class ControllerContainerTest { " \n" + " \n" + " \n" + + " \n" + " \n" + " \n" + " \n" + @@ -94,16 +88,6 @@ public class ControllerContainerTest { " http://*/zone/v2\n" + " http://*/zone/v2/*\n" + " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " http://*/*\n" + - " \n" + - " \n" + - " \n" + ""; protected void assertResponse(Request request, int responseStatus, String responseMessage) throws IOException { @@ -113,32 +97,4 @@ public class ControllerContainerTest { "status: " + response.getStatus() + "\nmessage: " + response.getBodyAsString()); } - protected static Request authenticatedRequest(String uri) { - return addIdentityToRequest(new Request(uri), USER); - } - - protected static Request authenticatedRequest(String uri, String body, Request.Method method) { - return addIdentityToRequest(new Request(uri, body, method), USER); - } - - protected static Request authenticatedRequest(String uri, byte[] body, Request.Method method) { - return addIdentityToRequest(new Request(uri, body, method), USER); - } - - protected static Request addIdentityToRequest(Request request, AthenzIdentity identity) { - request.getHeaders().put(IDENTITY_HEADER_NAME, identity.getFullName()); - return request; - } - - protected static Request addNTokenToRequest(Request request, NToken nToken) { - request.getHeaders().put(ATHENZ_NTOKEN_HEADER_NAME, nToken.getRawToken()); - return request; - } - - protected void addUserToHostedOperatorRole(AthenzIdentity athenzIdentity) { - AthenzClientFactoryMock mock = (AthenzClientFactoryMock) container.components() - .getComponent(AthenzClientFactoryMock.class.getName()); - mock.getSetup().addHostedOperator(athenzIdentity); - } - } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ResponseHandlerToApplicationResponseWrapper.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ResponseHandlerToApplicationResponseWrapper.java deleted file mode 100644 index 55cfc075c7c..00000000000 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ResponseHandlerToApplicationResponseWrapper.java +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.restapi; - -import com.yahoo.jdisc.Response; -import com.yahoo.jdisc.handler.CompletionHandler; -import com.yahoo.jdisc.handler.ContentChannel; -import com.yahoo.jdisc.handler.ResponseHandler; - -import java.nio.ByteBuffer; -import java.util.Optional; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * A {@link ResponseHandler} that caches the response content and - * converts a {@link Response} to {@link com.yahoo.application.container.handler.Response}. - * - * @author bjorncs - */ -public class ResponseHandlerToApplicationResponseWrapper implements ResponseHandler { - - private Response response; - private SimpleContentChannel contentChannel; - - @Override - public ContentChannel handleResponse(Response response) { - this.response = response; - SimpleContentChannel contentChannel = new SimpleContentChannel(); - this.contentChannel = contentChannel; - return contentChannel; - } - - public Optional toResponse() { - return Optional.ofNullable(this.response) - .map(r -> { - byte[] bytes = contentChannel.toByteArray(); - return new com.yahoo.application.container.handler.Response(response.getStatus(), bytes); - }); - } - - private class SimpleContentChannel implements ContentChannel { - - private final Queue buffers = new ConcurrentLinkedQueue<>(); - private final AtomicBoolean closed = new AtomicBoolean(false); - - @Override - public void write(ByteBuffer buf, CompletionHandler handler) { - buffers.add(buf); - handler.completed(); - } - - @Override - public void close(CompletionHandler handler) { - handler.completed(); - if (closed.getAndSet(true)) { - throw new IllegalStateException("Already closed"); - } - } - - byte[] toByteArray() { - if (!closed.get()) { - throw new IllegalStateException("Content channel not closed yet"); - } - int totalSize = 0; - for (ByteBuffer responseBuffer : buffers) { - totalSize += responseBuffer.remaining(); - } - ByteBuffer totalBuffer = ByteBuffer.allocate(totalSize); - for (ByteBuffer responseBuffer : buffers) { - totalBuffer.put(responseBuffer); - } - return totalBuffer.array(); - } - } - -} 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 9cd28e92228..e6fe7531fdc 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 @@ -8,7 +8,6 @@ import com.yahoo.config.provision.Environment; import com.yahoo.vespa.athenz.api.AthenzDomain; import com.yahoo.vespa.athenz.api.AthenzIdentity; import com.yahoo.vespa.athenz.api.AthenzUser; -import com.yahoo.vespa.athenz.api.NToken; import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.ConfigServerClientMock; import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId; @@ -78,8 +77,6 @@ public class ApplicationApiTest extends ControllerContainerTest { private static final AthenzDomain ATHENZ_TENANT_DOMAIN = new AthenzDomain("domain1"); private static final ScrewdriverId SCREWDRIVER_ID = new ScrewdriverId("12345"); private static final UserId USER_ID = new UserId("myuser"); - private static final UserId HOSTED_VESPA_OPERATOR = new UserId("johnoperator"); - private static final NToken N_TOKEN = new NToken("dummy"); @Test public void testApplicationApi() throws Exception { @@ -90,34 +87,32 @@ public class ApplicationApiTest extends ControllerContainerTest { createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID); // (Necessary but not provided in this API) // GET API root - tester.assertResponse(request("/application/v4/", GET).userIdentity(USER_ID), + tester.assertResponse(request("/application/v4/", GET), new File("root.json")); // GET athens domains - tester.assertResponse(request("/application/v4/athensDomain/", GET).userIdentity(USER_ID), + tester.assertResponse(request("/application/v4/athensDomain/", GET), new File("athensDomain-list.json")); // GET OpsDB properties - tester.assertResponse(request("/application/v4/property/", GET).userIdentity(USER_ID), + tester.assertResponse(request("/application/v4/property/", GET), new File("property-list.json")); // GET cookie freshness - tester.assertResponse(request("/application/v4/cookiefreshness/", GET).userIdentity(USER_ID), + tester.assertResponse(request("/application/v4/cookiefreshness/", GET), new File("cookiefreshness.json")); // POST (add) a tenant without property ID tester.assertResponse(request("/application/v4/tenant/tenant1", POST) .userIdentity(USER_ID) - .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}") - .nToken(N_TOKEN), + .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}"), new File("tenant-without-applications.json")); // PUT (modify) a tenant tester.assertResponse(request("/application/v4/tenant/tenant1", PUT) .userIdentity(USER_ID) - .nToken(N_TOKEN) .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}"), new File("tenant-without-applications.json")); // GET the authenticated user (with associated tenants) tester.assertResponse(request("/application/v4/user", GET).userIdentity(USER_ID), new File("user.json")); // GET all tenants - tester.assertResponse(request("/application/v4/tenant/", GET).userIdentity(USER_ID), + tester.assertResponse(request("/application/v4/tenant/", GET), new File("tenant-list.json")); @@ -128,17 +123,15 @@ public class ApplicationApiTest extends ControllerContainerTest { // POST (add) a tenant with property ID tester.assertResponse(request("/application/v4/tenant/tenant2", POST) .userIdentity(USER_ID) - .nToken(N_TOKEN) .data("{\"athensDomain\":\"domain2\", \"property\":\"property2\", \"propertyId\":\"1234\"}"), new File("tenant-without-applications-with-id.json")); // PUT (modify) a tenant with property ID tester.assertResponse(request("/application/v4/tenant/tenant2", PUT) .userIdentity(USER_ID) - .nToken(N_TOKEN) .data("{\"athensDomain\":\"domain2\", \"property\":\"property2\", \"propertyId\":\"1234\"}"), new File("tenant-without-applications-with-id.json")); // GET a tenant with property ID - tester.assertResponse(request("/application/v4/tenant/tenant2", GET).userIdentity(USER_ID), + tester.assertResponse(request("/application/v4/tenant/tenant2", GET), new File("tenant-without-applications-with-id.json")); // Test legacy OpsDB tenants @@ -155,33 +148,28 @@ public class ApplicationApiTest extends ControllerContainerTest { // POST (create) an application tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST) - .userIdentity(USER_ID) - .nToken(N_TOKEN), + .userIdentity(USER_ID), new File("application-reference.json")); // GET a tenant - tester.assertResponse(request("/application/v4/tenant/tenant1", GET).userIdentity(USER_ID), + tester.assertResponse(request("/application/v4/tenant/tenant1", GET), new File("tenant-with-application.json")); // GET tenant applications - tester.assertResponse(request("/application/v4/tenant/tenant1/application/", GET).userIdentity(USER_ID), + tester.assertResponse(request("/application/v4/tenant/tenant1/application/", GET), new File("application-list.json")); - - addUserToHostedOperatorRole(HostedAthenzIdentities.from(HOSTED_VESPA_OPERATOR)); - // POST triggering of a full deployment to an application (if version is omitted, current system version is used) tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/deploying", POST) - .userIdentity(HOSTED_VESPA_OPERATOR) + .userIdentity(USER_ID) .data("6.1.0"), new File("application-deployment.json")); // DELETE (cancel) ongoing change tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/deploying", DELETE) - .userIdentity(HOSTED_VESPA_OPERATOR), + .userIdentity(USER_ID), new File("application-deployment-cancelled.json")); // DELETE (cancel) again is a no-op - tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/deploying", DELETE) - .userIdentity(HOSTED_VESPA_OPERATOR), + tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/deploying", DELETE), new File("application-deployment-cancelled-no-op.json")); // POST (deploy) an application to a zone - manual user deployment @@ -201,7 +189,6 @@ public class ApplicationApiTest extends ControllerContainerTest { // Trigger deployment tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/deploying", POST) - .userIdentity(HOSTED_VESPA_OPERATOR) .data("6.1.0"), new File("application-deployment.json")); @@ -233,17 +220,14 @@ public class ApplicationApiTest extends ControllerContainerTest { controllerTester.notifyJobCompletion(id, screwdriverProjectId, false, DeploymentJobs.JobType.productionCorpUsEast1); // GET tenant screwdriver projects - tester.assertResponse(request("/application/v4/tenant-pipeline/", GET) - .userIdentity(USER_ID), + tester.assertResponse(request("/application/v4/tenant-pipeline/", GET), new File("tenant-pipelines.json")); setDeploymentMaintainedInfo(controllerTester); // GET tenant application deployments - tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", GET) - .userIdentity(USER_ID), + tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", GET), new File("application.json")); // GET an application deployment - tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/corp-us-east-1/instance/default", GET) - .userIdentity(USER_ID), + tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/corp-us-east-1/instance/default", GET), new File("deployment.json")); addIssues(controllerTester, ApplicationId.from("tenant1", "application1", "default")); @@ -278,20 +262,16 @@ public class ApplicationApiTest extends ControllerContainerTest { .screwdriverIdentity(SCREWDRIVER_ID), "Requested restart of tenant/tenant1/application/application1/environment/prod/region/corp-us-east-1/instance/default"); // POST a 'log' command - tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/corp-us-east-1/instance/default/log", POST) - .screwdriverIdentity(SCREWDRIVER_ID), + tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/corp-us-east-1/instance/default/log", POST), new File("log-response.json")); // Proxied to config server, not sure about the expected return format // GET (wait for) convergence - tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/corp-us-east-1/instance/default/converge", GET) - .userIdentity(USER_ID), + tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/corp-us-east-1/instance/default/converge", GET), new File("convergence.json")); // GET services - tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/corp-us-east-1/instance/default/service", GET) - .userIdentity(USER_ID), + tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/corp-us-east-1/instance/default/service", GET), new File("services.json")); // GET service - tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/corp-us-east-1/instance/default/service/storagenode-awe3slno6mmq2fye191y324jl/state/v1/", GET) - .userIdentity(USER_ID), + tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/corp-us-east-1/instance/default/service/storagenode-awe3slno6mmq2fye191y324jl/state/v1/", GET), new File("service.json")); // DELETE application with active deployments fails @@ -300,7 +280,7 @@ public class ApplicationApiTest extends ControllerContainerTest { // DELETE (deactivate) a deployment - dev tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/dev/region/us-west-1/instance/default", DELETE) - .userIdentity(USER_ID), + .screwdriverIdentity(SCREWDRIVER_ID), "Deactivated tenant/tenant1/application/application1/environment/dev/region/us-west-1/instance/default"); // DELETE (deactivate) a deployment - prod tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/corp-us-east-1/instance/default", DELETE) @@ -323,13 +303,11 @@ public class ApplicationApiTest extends ControllerContainerTest { ""); // GET global rotation status - tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-west-1/instance/default/global-rotation", GET) - .userIdentity(USER_ID), + tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-west-1/instance/default/global-rotation", GET), new File("global-rotation.json")); // GET global rotation override status - tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-west-1/instance/default/global-rotation/override", GET) - .userIdentity(USER_ID), + tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-west-1/instance/default/global-rotation/override", GET), new File("global-rotation-get.json")); // SET global rotation override status @@ -352,12 +330,10 @@ public class ApplicationApiTest extends ControllerContainerTest { "{\"message\":\"Successfully copied environment hosted-instance_tenant1_application1_placeholder_component_default to hosted-instance_tenant1_application1_us-west-1_prod_default\"}"); // DELETE an application - tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", DELETE).userIdentity(USER_ID) - .nToken(N_TOKEN), + tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", DELETE).userIdentity(USER_ID), ""); // DELETE a tenant - tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE).userIdentity(USER_ID) - .nToken(N_TOKEN), + tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE).userIdentity(USER_ID), new File("tenant-without-applications.json")); controllerTester.controller().deconstruct(); @@ -380,14 +356,12 @@ public class ApplicationApiTest extends ControllerContainerTest { // Create tenant tester.assertResponse(request("/application/v4/tenant/tenant1", POST).userIdentity(USER_ID) - .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}") - .nToken(N_TOKEN), + .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}"), new File("tenant-without-applications.json")); // Create application tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST) - .userIdentity(USER_ID) - .nToken(N_TOKEN), + .userIdentity(USER_ID), new File("application-reference.json")); // Grant deploy access @@ -414,14 +388,12 @@ public class ApplicationApiTest extends ControllerContainerTest { // Create tenant tester.assertResponse(request("/application/v4/tenant/tenant1", POST) .userIdentity(USER_ID) - .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}") - .nToken(N_TOKEN), + .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}"), new File("tenant-without-applications.json")); // Create application tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST) - .userIdentity(USER_ID) - .nToken(N_TOKEN), + .userIdentity(USER_ID), new File("application-reference.json")); // Give Screwdriver project deploy access @@ -468,8 +440,7 @@ public class ApplicationApiTest extends ControllerContainerTest { controllerTester.notifyJobCompletion(id, projectId, true, DeploymentJobs.JobType.productionUsEast3); setDeploymentMaintainedInfo(controllerTester); - tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", GET) - .userIdentity(USER_ID), + tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", GET), new File("application-without-change-multiple-deployments.json")); } @@ -481,41 +452,35 @@ public class ApplicationApiTest extends ControllerContainerTest { // PUT (update) non-existing tenant tester.assertResponse(request("/application/v4/tenant/tenant1", PUT) - .userIdentity(USER_ID) .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}"), "{\"error-code\":\"NOT_FOUND\",\"message\":\"Tenant 'tenant1' does not exist\"}", 404); // GET non-existing tenant - tester.assertResponse(request("/application/v4/tenant/tenant1", GET) - .userIdentity(USER_ID), + tester.assertResponse(request("/application/v4/tenant/tenant1", GET), "{\"error-code\":\"NOT_FOUND\",\"message\":\"Tenant 'tenant1' does not exist\"}", 404); // GET non-existing application - tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", GET) - .userIdentity(USER_ID), + tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", GET), "{\"error-code\":\"NOT_FOUND\",\"message\":\"tenant1.application1 not found\"}", 404); // GET non-existing deployment - tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-east/instance/default", GET) - .userIdentity(USER_ID), + tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-east/instance/default", GET), "{\"error-code\":\"NOT_FOUND\",\"message\":\"tenant1.application1 not found\"}", 404); // POST (add) a tenant tester.assertResponse(request("/application/v4/tenant/tenant1", POST) .userIdentity(USER_ID) - .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}") - .nToken(N_TOKEN), + .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}"), new File("tenant-without-applications.json")); // POST (add) another tenant under the same domain tester.assertResponse(request("/application/v4/tenant/tenant2", POST) .userIdentity(USER_ID) - .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}") - .nToken(N_TOKEN), + .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}"), "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Could not create tenant 'tenant2': The Athens domain 'domain1' is already connected to tenant 'tenant1'\"}", 400); @@ -528,8 +493,7 @@ public class ApplicationApiTest extends ControllerContainerTest { // POST (create) an (empty) application tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST) - .userIdentity(USER_ID) - .nToken(N_TOKEN), + .userIdentity(USER_ID), new File("application-reference.json")); // Create the same application again @@ -577,8 +541,7 @@ public class ApplicationApiTest extends ControllerContainerTest { // DELETE application tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", DELETE) - .userIdentity(USER_ID) - .nToken(N_TOKEN), + .userIdentity(USER_ID), ""); // DELETE application again - should produce 404 tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", DELETE) @@ -587,18 +550,15 @@ public class ApplicationApiTest extends ControllerContainerTest { 404); // DELETE tenant tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE) - .userIdentity(USER_ID) - .nToken(N_TOKEN), + .userIdentity(USER_ID), new File("tenant-without-applications.json")); // DELETE tenant again - should produce 404 - tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE) - .userIdentity(USER_ID), + tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE), "{\"error-code\":\"NOT_FOUND\",\"message\":\"Could not delete tenant 'tenant1': Tenant not found\"}", 404); // Promote application chef env for nonexistent tenant/application - tester.assertResponse(request("/application/v4/tenant/dontexist/application/dontexist/environment/prod/region/us-west-1/instance/default/promote", POST) - .userIdentity(USER_ID), + tester.assertResponse(request("/application/v4/tenant/dontexist/application/dontexist/environment/prod/region/us-west-1/instance/default/promote", POST), "{\"error-code\":\"INTERNAL_SERVER_ERROR\",\"message\":\"Unable to promote Chef environments for application\"}", 500); } @@ -609,15 +569,14 @@ public class ApplicationApiTest extends ControllerContainerTest { UserId authorizedUser = USER_ID; UserId unauthorizedUser = new UserId("othertenant"); - // Mutation without an user is disallowed + // Mutation without an authorized user is disallowed tester.assertResponse(request("/application/v4/tenant/tenant1", POST) .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}"), - "{\n \"message\" : \"Not authenticated\"\n}", - 401); + "{\"error-code\":\"FORBIDDEN\",\"message\":\"User is not authenticated\"}", + 403); - // ... but read methods are allowed for authenticated user + // ... but read methods are allowed tester.assertResponse(request("/application/v4/tenant/", GET) - .userIdentity(USER_ID) .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}"), "[]", 200); @@ -634,22 +593,19 @@ public class ApplicationApiTest extends ControllerContainerTest { // (Create it with the right tenant id) tester.assertResponse(request("/application/v4/tenant/tenant1", POST) .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}") - .userIdentity(authorizedUser) - .nToken(N_TOKEN), + .userIdentity(authorizedUser), new File("tenant-without-applications.json"), 200); // Creating an application for an Athens domain the user is not admin for is disallowed tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST) - .userIdentity(unauthorizedUser) - .nToken(N_TOKEN), - "{\n \"message\" : \"Tenant admin or Vespa operator role required\"\n}", + .userIdentity(unauthorizedUser), + "{\"error-code\":\"FORBIDDEN\",\"message\":\"User user.othertenant does not have write access to tenant tenant1\"}", 403); // (Create it with the right tenant id) tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", POST) - .userIdentity(authorizedUser) - .nToken(N_TOKEN), + .userIdentity(authorizedUser), new File("application-reference.json"), 200); @@ -658,19 +614,18 @@ public class ApplicationApiTest extends ControllerContainerTest { tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-west-1/instance/default/deploy", POST) .data(entity) .userIdentity(USER_ID), - "{\n \"message\" : \"'user.myuser' is not a Screwdriver identity. Only Screwdriver is allowed to deploy to this environment.\"\n}", + "{\"error-code\":\"FORBIDDEN\",\"message\":\"Principal 'user.myuser' is not a Screwdriver principal. Excepted principal with Athenz domain 'cd.screwdriver.project', got 'user'.\"}", 403); // Deleting an application for an Athens domain the user is not admin for is disallowed tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", DELETE) .userIdentity(unauthorizedUser), - "{\n \"message\" : \"Tenant admin or Vespa operator role required\"\n}", + "{\"error-code\":\"FORBIDDEN\",\"message\":\"User user.othertenant does not have write access to tenant tenant1\"}", 403); // (Deleting it with the right tenant id) tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1", DELETE) - .userIdentity(authorizedUser) - .nToken(N_TOKEN), + .userIdentity(authorizedUser), "", 200); @@ -678,22 +633,21 @@ public class ApplicationApiTest extends ControllerContainerTest { tester.assertResponse(request("/application/v4/tenant/tenant1", PUT) .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}") .userIdentity(unauthorizedUser), - "{\n \"message\" : \"Tenant admin or Vespa operator role required\"\n}", + "{\"error-code\":\"FORBIDDEN\",\"message\":\"User user.othertenant does not have write access to tenant tenant1\"}", 403); // Change Athens domain createAthenzDomainWithAdmin(new AthenzDomain("domain2"), USER_ID); tester.assertResponse(request("/application/v4/tenant/tenant1", PUT) .data("{\"athensDomain\":\"domain2\", \"property\":\"property1\"}") - .userIdentity(authorizedUser) - .nToken(N_TOKEN), + .userIdentity(authorizedUser), "{\"tenant\":\"tenant1\",\"type\":\"ATHENS\",\"athensDomain\":\"domain2\",\"property\":\"property1\",\"applications\":[]}", 200); // Deleting a tenant for an Athens domain the user is not admin for is disallowed tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE) .userIdentity(unauthorizedUser), - "{\n \"message\" : \"Tenant admin or Vespa operator role required\"\n}", + "{\"error-code\":\"FORBIDDEN\",\"message\":\"User user.othertenant does not have write access to tenant tenant1\"}", 403); } @@ -786,7 +740,6 @@ public class ApplicationApiTest extends ControllerContainerTest { private final Request.Method method; private byte[] data = new byte[0]; private AthenzIdentity identity; - private NToken nToken; private String contentType = "application/json"; private String recursive; @@ -808,7 +761,6 @@ public class ApplicationApiTest extends ControllerContainerTest { } private RequestBuilder userIdentity(UserId userId) { this.identity = HostedAthenzIdentities.from(userId); return this; } private RequestBuilder screwdriverIdentity(ScrewdriverId screwdriverId) { this.identity = HostedAthenzIdentities.from(screwdriverId); return this; } - private RequestBuilder nToken(NToken nToken) { this.nToken = nToken; return this; } private RequestBuilder contentType(String contentType) { this.contentType = contentType; return this; } private RequestBuilder recursive(String recursive) { this.recursive = recursive; return this; } @@ -820,10 +772,8 @@ public class ApplicationApiTest extends ControllerContainerTest { data, method); request.getHeaders().put("Content-Type", contentType); if (identity != null) { - addIdentityToRequest(request, identity); - } - if (nToken != null) { - addNTokenToRequest(request, nToken); + request.getHeaders().put("Athenz-Identity-Domain", identity.getDomain().getName()); + request.getHeaders().put("Athenz-Identity-Name", identity.getName()); } return request; } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/MockAuthorizer.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/MockAuthorizer.java new file mode 100644 index 00000000000..f2fc4b12096 --- /dev/null +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/MockAuthorizer.java @@ -0,0 +1,80 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.restapi.application; + +import com.yahoo.container.jdisc.HttpRequest; +import com.yahoo.vespa.athenz.api.AthenzDomain; +import com.yahoo.vespa.athenz.api.AthenzPrincipal; +import com.yahoo.vespa.athenz.api.NToken; +import com.yahoo.vespa.athenz.utils.AthenzIdentities; +import com.yahoo.vespa.hosted.controller.Controller; +import com.yahoo.vespa.hosted.controller.TestIdentities; +import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactory; +import com.yahoo.vespa.hosted.controller.api.integration.entity.EntityService; + +import javax.ws.rs.ForbiddenException; +import javax.ws.rs.core.SecurityContext; +import java.security.Principal; +import java.util.Optional; + +/** + * This overrides methods in Authorizer which relies on properties set by jdisc HTTP filters. + * This is necessary because filters are not currently executed when executing requests with Application. + * + * @author bratseth + * @author bjorncs + */ +@SuppressWarnings("unused") // injected +public class MockAuthorizer extends Authorizer { + + public MockAuthorizer(Controller controller, EntityService entityService, AthenzClientFactory athenzClientFactory) { + super(controller, entityService, athenzClientFactory); + } + + /** Returns a principal given by the request parameters 'domain' and 'user' */ + @Override + public AthenzPrincipal getPrincipal(HttpRequest request) { + String domain = request.getHeader("Athenz-Identity-Domain"); + String name = request.getHeader("Athenz-Identity-Name"); + if (domain == null || name == null) { + throw new ForbiddenException("User is not authenticated"); + } + return new AthenzPrincipal( + AthenzIdentities.from(new AthenzDomain(domain), name), + new NToken("dummy")); + } + + /** Returns the hardcoded NToken of {@link TestIdentities#userId} */ + @Override + public Optional getNToken(HttpRequest request) { + return Optional.of(TestIdentities.userNToken); + } + + + @Override + protected Optional securityContextOf(HttpRequest request) { + return Optional.of(new MockSecurityContext(getPrincipal(request))); + } + + private static final class MockSecurityContext implements SecurityContext { + + private final Principal principal; + + private MockSecurityContext(Principal principal) { + this.principal = principal; + } + + @Override + public Principal getUserPrincipal() { return principal; } + + @Override + public boolean isUserInRole(String role) { return false; } + + @Override + public boolean isSecure() { return true; } + + @Override + public String getAuthenticationScheme() { throw new UnsupportedOperationException(); } + + } + +} diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiTest.java index c7128bb4cfc..8047b0d48c9 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiTest.java @@ -2,13 +2,12 @@ package com.yahoo.vespa.hosted.controller.restapi.controller; import com.yahoo.application.container.handler.Request; -import com.yahoo.vespa.athenz.api.AthenzIdentity; -import com.yahoo.vespa.athenz.api.AthenzUser; import com.yahoo.vespa.hosted.controller.restapi.ContainerControllerTester; import com.yahoo.vespa.hosted.controller.restapi.ControllerContainerTest; import org.junit.Test; import java.io.File; +import java.io.IOException; /** * @author bratseth @@ -16,68 +15,60 @@ import java.io.File; public class ControllerApiTest extends ControllerContainerTest { private final static String responseFiles = "src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/"; - private static final AthenzIdentity HOSTED_VESPA_OPERATOR = AthenzUser.fromUserId("johnoperator"); @Test public void testControllerApi() throws Exception { ContainerControllerTester tester = new ContainerControllerTester(container, responseFiles); - tester.assertResponse(authenticatedRequest("http://localhost:8080/controller/v1/", new byte[0], Request.Method.GET), new File("root.json")); + tester.assertResponse(new Request("http://localhost:8080/controller/v1/"), new File("root.json")); // POST deactivation of a maintenance job - assertResponse(authenticatedRequest("http://localhost:8080/controller/v1/maintenance/inactive/DeploymentExpirer", - new byte[0], Request.Method.POST), + assertResponse(new Request("http://localhost:8080/controller/v1/maintenance/inactive/DeploymentExpirer", + new byte[0], Request.Method.POST), 200, "{\"message\":\"Deactivated job 'DeploymentExpirer'\"}"); // GET a list of all maintenance jobs - tester.assertResponse(authenticatedRequest("http://localhost:8080/controller/v1/maintenance/", new byte[0], Request.Method.GET), + tester.assertResponse(new Request("http://localhost:8080/controller/v1/maintenance/"), new File("maintenance.json")); // DELETE deactivation of a maintenance job - assertResponse(authenticatedRequest("http://localhost:8080/controller/v1/maintenance/inactive/DeploymentExpirer", - new byte[0], Request.Method.DELETE), + assertResponse(new Request("http://localhost:8080/controller/v1/maintenance/inactive/DeploymentExpirer", + new byte[0], Request.Method.DELETE), 200, "{\"message\":\"Re-activated job 'DeploymentExpirer'\"}"); } @Test public void testUpgraderApi() throws Exception { - addUserToHostedOperatorRole(HOSTED_VESPA_OPERATOR); - ContainerControllerTester tester = new ContainerControllerTester(container, responseFiles); // Get current configuration - tester.assertResponse(authenticatedRequest("http://localhost:8080/controller/v1/jobs/upgrader", new byte[0], Request.Method.GET), + tester.assertResponse(new Request("http://localhost:8080/controller/v1/jobs/upgrader"), "{\"upgradesPerMinute\":0.5,\"ignoreConfidence\":false}", 200); // Set invalid configuration - ; - tester.assertResponse( - hostedOperatorRequest("http://localhost:8080/controller/v1/jobs/upgrader", "{\"upgradesPerMinute\":-1}", Request.Method.PATCH), - "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Upgrades per minute must be >= 0\"}", - 400); + tester.assertResponse(new Request("http://localhost:8080/controller/v1/jobs/upgrader", + "{\"upgradesPerMinute\":-1}", Request.Method.PATCH), + "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Upgrades per minute must be >= 0\"}", + 400); // Unrecognized field - tester.assertResponse( - hostedOperatorRequest("http://localhost:8080/controller/v1/jobs/upgrader","{\"foo\":bar}", Request.Method.PATCH), - "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Unable to configure upgrader with data in request: '{\\\"foo\\\":bar}'\"}", - 400); + tester.assertResponse(new Request("http://localhost:8080/controller/v1/jobs/upgrader", + "{\"foo\":bar}", Request.Method.PATCH), + "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Unable to configure upgrader with data in request: '{\\\"foo\\\":bar}'\"}", + + 400); // Patch configuration - tester.assertResponse( - hostedOperatorRequest("http://localhost:8080/controller/v1/jobs/upgrader", "{\"upgradesPerMinute\":42.0}", Request.Method.PATCH), - "{\"upgradesPerMinute\":42.0,\"ignoreConfidence\":false}", - 200); + tester.assertResponse(new Request("http://localhost:8080/controller/v1/jobs/upgrader", + "{\"upgradesPerMinute\":42.0}", Request.Method.PATCH), + "{\"upgradesPerMinute\":42.0,\"ignoreConfidence\":false}", + 200); // Patch configuration - tester.assertResponse( - hostedOperatorRequest("http://localhost:8080/controller/v1/jobs/upgrader", "{\"ignoreConfidence\":true}", Request.Method.PATCH), - "{\"upgradesPerMinute\":42.0,\"ignoreConfidence\":true}", - 200); - } - - private static Request hostedOperatorRequest(String uri, String body, Request.Method method) { - return addIdentityToRequest(new Request(uri, body, method), HOSTED_VESPA_OPERATOR); + tester.assertResponse(new Request("http://localhost:8080/controller/v1/jobs/upgrader", + "{\"ignoreConfidence\":true}", Request.Method.PATCH), + "{\"upgradesPerMinute\":42.0,\"ignoreConfidence\":true}", + 200); } } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiTest.java index f70da6583ed..f3c8e57b1b5 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiTest.java @@ -2,12 +2,13 @@ package com.yahoo.vespa.hosted.controller.restapi.deployment; import com.google.common.collect.ImmutableSet; +import com.yahoo.application.container.handler.Request; import com.yahoo.component.Version; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.RegionName; +import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneId; import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.Controller; -import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneId; import com.yahoo.vespa.hosted.controller.application.ApplicationPackage; import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder; import com.yahoo.vespa.hosted.controller.restapi.ContainerControllerTester; @@ -78,7 +79,8 @@ public class DeploymentApiTest extends ControllerContainerTest { tester.controller().updateVersionStatus(censorConfigServers(VersionStatus.compute(tester.controller()), tester.controller())); - tester.assertResponse(authenticatedRequest("http://localhost:8080/deployment/v1/"), new File("root.json")); + tester.assertResponse(new Request("http://localhost:8080/deployment/v1/"), + new File("root.json")); } private VersionStatus censorConfigServers(VersionStatus versionStatus, Controller controller) { diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilterTest.java index 497f865a2a5..ff4ceae1c8e 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilterTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilterTest.java @@ -17,6 +17,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.athenz.ApplicationActio import com.yahoo.vespa.hosted.controller.api.integration.athenz.HostedAthenzIdentities; import com.yahoo.vespa.hosted.controller.athenz.mock.AthenzClientFactoryMock; import com.yahoo.vespa.hosted.controller.athenz.mock.AthenzDbMock; +import com.yahoo.vespa.hosted.controller.restapi.filter.ControllerAuthorizationFilter.DefaultAuthorizationResponseHandler; import org.junit.Test; import java.io.IOException; @@ -146,7 +147,9 @@ public class ControllerAuthorizationFilterTest { private static ControllerAuthorizationFilter createFilter(ControllerTester controllerTester) { return new ControllerAuthorizationFilter(new AthenzClientFactoryMock(controllerTester.athenzDb()), controllerTester.controller(), - controllerTester.entityService()); + controllerTester.entityService(), + controllerTester.zoneRegistry(), + new DefaultAuthorizationResponseHandler()); } private static Optional invokeFilter(ControllerAuthorizationFilter filter, diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/screwdriver/ScrewdriverApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/screwdriver/ScrewdriverApiTest.java index 9bca164cb9c..679b0114721 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/screwdriver/ScrewdriverApiTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/screwdriver/ScrewdriverApiTest.java @@ -7,8 +7,6 @@ import com.yahoo.component.Version; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.RegionName; -import com.yahoo.vespa.athenz.api.AthenzIdentity; -import com.yahoo.vespa.athenz.api.AthenzUser; import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneId; import com.yahoo.vespa.config.SlimeUtils; import com.yahoo.vespa.hosted.controller.Application; @@ -44,24 +42,22 @@ public class ScrewdriverApiTest extends ControllerContainerTest { private static final String responseFiles = "src/test/java/com/yahoo/vespa/hosted/controller/restapi/screwdriver/responses/"; private static final ZoneId testZone = ZoneId.from(Environment.test, RegionName.from("us-east-1")); private static final ZoneId stagingZone = ZoneId.from(Environment.staging, RegionName.from("us-east-3")); - private static final AthenzIdentity HOSTED_VESPA_OPERATOR = AthenzUser.fromUserId("johnoperator"); @Test public void testGetReleaseStatus() throws Exception { ContainerControllerTester tester = new ContainerControllerTester(container, responseFiles); - tester.containerTester().assertResponse(authenticatedRequest("http://localhost:8080/screwdriver/v1/release/vespa"), + tester.containerTester().assertResponse(new Request("http://localhost:8080/screwdriver/v1/release/vespa"), "{\"error-code\":\"NOT_FOUND\",\"message\":\"Information about the current system version is not available at this time\"}", 404); tester.controller().updateVersionStatus(VersionStatus.compute(tester.controller())); - tester.containerTester().assertResponse(authenticatedRequest("http://localhost:8080/screwdriver/v1/release/vespa"), + tester.containerTester().assertResponse(new Request("http://localhost:8080/screwdriver/v1/release/vespa"), new File("release-response.json"), 200); } @Test public void testJobStatusReporting() throws Exception { ContainerControllerTester tester = new ContainerControllerTester(container, responseFiles); - addUserToHostedOperatorRole(HOSTED_VESPA_OPERATOR); tester.containerTester().updateSystemVersion(); long projectId = 1; Application app = tester.createApplication(); @@ -78,13 +74,12 @@ public class ScrewdriverApiTest extends ControllerContainerTest { notifyCompletion(app.id(), projectId, JobType.systemTest, Optional.empty()); // Notifying about unknown job fails - Request request = new Request("http://localhost:8080/application/v4/tenant/tenant1/application/application1/jobreport", - jsonReport(app.id(), JobType.productionUsEast3, projectId, 1L, - Optional.empty()) - .getBytes(StandardCharsets.UTF_8), - Request.Method.POST); - addIdentityToRequest(request, HOSTED_VESPA_OPERATOR); - tester.containerTester().assertResponse(request, new File("unexpected-completion.json"), 400); + tester.containerTester().assertResponse(new Request("http://localhost:8080/application/v4/tenant/tenant1/application/application1/jobreport", + jsonReport(app.id(), JobType.productionUsEast3, projectId, 1L, + Optional.empty()) + .getBytes(StandardCharsets.UTF_8), + Request.Method.POST), + new File("unexpected-completion.json"), 400); // ... and assert it was recorded JobStatus recordedStatus = diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiTest.java index 054e71465ad..6e87304774a 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiTest.java @@ -1,10 +1,11 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller.restapi.zone.v1; +import com.yahoo.application.container.handler.Request; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.RegionName; -import com.yahoo.vespa.hosted.controller.ZoneRegistryMock; import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneId; +import com.yahoo.vespa.hosted.controller.ZoneRegistryMock; import com.yahoo.vespa.hosted.controller.restapi.ContainerControllerTester; import com.yahoo.vespa.hosted.controller.restapi.ControllerContainerTest; import org.junit.Before; @@ -41,22 +42,22 @@ public class ZoneApiTest extends ControllerContainerTest { @Test public void test_requests() throws Exception { // GET /zone/v1 - tester.containerTester().assertResponse(authenticatedRequest("http://localhost:8080/zone/v1"), + tester.containerTester().assertResponse(new Request("http://localhost:8080/zone/v1"), new File("root.json")); // GET /zone/v1/environment/prod - tester.containerTester().assertResponse(authenticatedRequest("http://localhost:8080/zone/v1/environment/prod"), + tester.containerTester().assertResponse(new Request("http://localhost:8080/zone/v1/environment/prod"), new File("prod.json")); // GET /zone/v1/environment/dev/default - tester.containerTester().assertResponse(authenticatedRequest("http://localhost:8080/zone/v1/environment/dev/default"), + tester.containerTester().assertResponse(new Request("http://localhost:8080/zone/v1/environment/dev/default"), new File("default-for-region.json")); } @Test public void test_invalid_requests() throws Exception { // GET /zone/v1/environment/prod/default: No default region - tester.containerTester().assertResponse(authenticatedRequest("http://localhost:8080/zone/v1/environment/prod/default"), + tester.containerTester().assertResponse(new Request("http://localhost:8080/zone/v1/environment/prod/default"), new File("no-default-region.json"), 400); } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiTest.java index 5e9de74fe1b..c52266dfacc 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiTest.java @@ -5,12 +5,10 @@ import com.yahoo.application.container.handler.Request; import com.yahoo.application.container.handler.Request.Method; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.RegionName; +import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneId; import com.yahoo.text.Utf8; -import com.yahoo.vespa.athenz.api.AthenzIdentity; -import com.yahoo.vespa.athenz.api.AthenzUser; import com.yahoo.vespa.hosted.controller.ConfigServerProxyMock; import com.yahoo.vespa.hosted.controller.ZoneRegistryMock; -import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneId; import com.yahoo.vespa.hosted.controller.restapi.ContainerControllerTester; import com.yahoo.vespa.hosted.controller.restapi.ControllerContainerTest; import org.junit.Before; @@ -28,7 +26,6 @@ import static org.junit.Assert.assertFalse; */ public class ZoneApiTest extends ControllerContainerTest { - private static final AthenzIdentity HOSTED_VESPA_OPERATOR = AthenzUser.fromUserId("johnoperator"); private static final String responseFiles = "src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/responses/"; private static final List zones = Arrays.asList( ZoneId.from(Environment.prod, RegionName.from("us-north-1")), @@ -48,17 +45,16 @@ public class ZoneApiTest extends ControllerContainerTest { .setZones(zones); this.tester = new ContainerControllerTester(container, responseFiles); this.proxy = (ConfigServerProxyMock) container.components().getComponent(ConfigServerProxyMock.class.getName()); - addUserToHostedOperatorRole(HOSTED_VESPA_OPERATOR); } @Test public void test_requests() throws Exception { // GET /zone/v2 - tester.containerTester().assertResponse(authenticatedRequest("http://localhost:8080/zone/v2"), + tester.containerTester().assertResponse(new Request("http://localhost:8080/zone/v2"), new File("root.json")); // GET /zone/v2/prod/us-north-1 - tester.containerTester().assertResponse(authenticatedRequest("http://localhost:8080/zone/v2/prod/us-north-1"), + tester.containerTester().assertResponse(new Request("http://localhost:8080/zone/v2/prod/us-north-1"), "ok"); assertEquals("prod", proxy.lastReceived().get().getEnvironment()); assertEquals("us-north-1", proxy.lastReceived().get().getRegion()); @@ -66,7 +62,7 @@ public class ZoneApiTest extends ControllerContainerTest { assertEquals("GET", proxy.lastReceived().get().getMethod()); // GET /zone/v2/nodes/v2/node/?recursive=true - tester.containerTester().assertResponse(authenticatedRequest("http://localhost:8080/zone/v2/prod/us-north-1/nodes/v2/node/?recursive=true"), + tester.containerTester().assertResponse(new Request("http://localhost:8080/zone/v2/prod/us-north-1/nodes/v2/node/?recursive=true"), "ok"); assertEquals("prod", proxy.lastReceived().get().getEnvironment()); @@ -75,7 +71,7 @@ public class ZoneApiTest extends ControllerContainerTest { assertEquals("GET", proxy.lastReceived().get().getMethod()); // POST /zone/v2/dev/us-north-2/nodes/v2/command/restart?hostname=node1 - tester.containerTester().assertResponse(hostedOperatorRequest("http://localhost:8080/zone/v2/dev/us-north-2/nodes/v2/command/restart?hostname=node1", + tester.containerTester().assertResponse(new Request("http://localhost:8080/zone/v2/dev/us-north-2/nodes/v2/command/restart?hostname=node1", new byte[0], Method.POST), "ok"); assertEquals("dev", proxy.lastReceived().get().getEnvironment()); @@ -84,7 +80,7 @@ public class ZoneApiTest extends ControllerContainerTest { assertEquals("POST", proxy.lastReceived().get().getMethod()); // PUT /zone/v2/prod/us-north-1/nodes/v2/state/dirty/node1 - tester.containerTester().assertResponse(hostedOperatorRequest("http://localhost:8080/zone/v2/prod/us-north-1/nodes/v2/state/dirty/node1", + tester.containerTester().assertResponse(new Request("http://localhost:8080/zone/v2/prod/us-north-1/nodes/v2/state/dirty/node1", new byte[0], Method.PUT), "ok"); assertEquals("prod", proxy.lastReceived().get().getEnvironment()); assertEquals("us-north-1", proxy.lastReceived().get().getRegion()); @@ -92,7 +88,7 @@ public class ZoneApiTest extends ControllerContainerTest { assertEquals("PUT", proxy.lastReceived().get().getMethod()); // DELETE /zone/v2/prod/us-north-1/nodes/v2/node/node1 - tester.containerTester().assertResponse(hostedOperatorRequest("http://localhost:8080/zone/v2/prod/us-north-1/nodes/v2/node/node1", + tester.containerTester().assertResponse(new Request("http://localhost:8080/zone/v2/prod/us-north-1/nodes/v2/node/node1", new byte[0], Method.DELETE), "ok"); assertEquals("prod", proxy.lastReceived().get().getEnvironment()); assertEquals("us-north-1", proxy.lastReceived().get().getRegion()); @@ -100,7 +96,7 @@ public class ZoneApiTest extends ControllerContainerTest { assertEquals("DELETE", proxy.lastReceived().get().getMethod()); // PATCH /zone/v2/prod/us-north-1/nodes/v2/node/node1 - tester.containerTester().assertResponse(hostedOperatorRequest("http://localhost:8080/zone/v2/prod/us-north-1/nodes/v2/node/node1", + tester.containerTester().assertResponse(new Request("http://localhost:8080/zone/v2/prod/us-north-1/nodes/v2/node/node1", Utf8.toBytes("{\"currentRestartGeneration\": 1}"), Method.PATCH), "ok"); assertEquals("prod", proxy.lastReceived().get().getEnvironment()); @@ -112,15 +108,11 @@ public class ZoneApiTest extends ControllerContainerTest { @Test public void test_invalid_requests() throws Exception { - // POST /zone/v2/prod/us-north-34/nodes/v2 - tester.containerTester().assertResponse(hostedOperatorRequest("http://localhost:8080/zone/v2/prod/us-north-42/nodes/v2", + // GET /zone/v2/prod/us-north-34/nodes/v2 + tester.containerTester().assertResponse(new Request("http://localhost:8080/zone/v2/prod/us-north-42/nodes/v2", new byte[0], Method.POST), new File("unknown-zone.json"), 400); assertFalse(proxy.lastReceived().isPresent()); } - private static Request hostedOperatorRequest(String uri, byte[] body, Request.Method method) { - return addIdentityToRequest(new Request(uri, body, method), HOSTED_VESPA_OPERATOR); - } - } -- cgit v1.2.3