summaryrefslogtreecommitdiffstats
path: root/controller-server
diff options
context:
space:
mode:
authorMorten Tokle <mortent@verizonmedia.com>2021-06-02 12:01:13 +0200
committerMorten Tokle <mortent@verizonmedia.com>2021-06-02 12:01:13 +0200
commit1c3c58567c71251c37206cc1a4ac1fab67ebae14 (patch)
treebd6d23dbd4926bbfda2fb59d15c7a2397ed1b2a6 /controller-server
parent23314e77219262b263c42f1dd037591e22001d85 (diff)
Register operator grants
Diffstat (limited to 'controller-server')
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java23
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiHandler.java11
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/support/access/SupportAccessControl.java15
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java12
4 files changed, 57 insertions, 4 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 b72a6d2f820..30f5f6462ae 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
@@ -31,6 +31,7 @@ import com.yahoo.restapi.Path;
import com.yahoo.restapi.ResourceResponse;
import com.yahoo.restapi.SlimeJsonResponse;
import com.yahoo.security.KeyUtils;
+import com.yahoo.security.X509CertificateUtils;
import com.yahoo.slime.Cursor;
import com.yahoo.slime.Inspector;
import com.yahoo.slime.JsonParseException;
@@ -122,6 +123,7 @@ import java.net.URISyntaxException;
import java.security.DigestInputStream;
import java.security.Principal;
import java.security.PublicKey;
+import java.security.cert.X509Certificate;
import java.time.DayOfWeek;
import java.time.Duration;
import java.time.Instant;
@@ -307,6 +309,7 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/restart")) return restart(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/suspend")) return suspend(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), true);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/access/support")) return allowSupportAccess(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/access/support/grant")) return grantAccess(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}")) return deploy(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/deploy")) return deploy(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request); // legacy synonym of the above
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/restart")) return restart(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request);
@@ -992,6 +995,26 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
return new SlimeJsonResponse(SupportAccessSerializer.toSlime(allowed, false, Optional.of(now)));
}
+ private HttpResponse grantAccess(String tenantName, String applicationName, String instanceName, String environment, String region, HttpRequest request) {
+ DeploymentId deployment = new DeploymentId(ApplicationId.from(tenantName, applicationName, instanceName), requireZone(environment, region));
+ Principal principal = requireUserPrincipal(request);
+ Instant now = controller.clock().instant();
+
+ Inspector requestObject = toSlime(request.getData()).get();
+ X509Certificate certificate = X509CertificateUtils.fromPem(requestObject.field("certificate").asString());
+
+ // Register grant
+ SupportAccess supportAccess = controller.supportAccess().registerGrant(deployment, principal.getName(), certificate);
+
+ // Trigger deployment to include operator cert
+ JobType jobType = JobType.from(controller.system(), deployment.zoneId())
+ .orElseThrow(() -> new IllegalStateException("No job found to trigger for " + deployment.toUserFriendlyString()));
+
+ String jobName = controller.applications().deploymentTrigger()
+ .reTrigger(deployment.applicationId(), jobType).type().jobName();
+ return new MessageResponse(String.format("Operator %s granted access and job %s triggered", principal.getName(), jobName));
+ }
+
private HttpResponse disallowSupportAccess(String tenantName, String applicationName, String instanceName, String environment, String region, HttpRequest request) {
DeploymentId deployment = new DeploymentId(ApplicationId.from(tenantName, applicationName, instanceName), requireZone(environment, region));
Principal principal = requireUserPrincipal(request);
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiHandler.java
index 1a1118a127f..2b2087fccf4 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiHandler.java
@@ -2,6 +2,9 @@
package com.yahoo.vespa.hosted.controller.restapi.controller;
import com.yahoo.component.Version;
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.Zone;
+import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.container.jdisc.LoggingRequestHandler;
@@ -12,8 +15,8 @@ import com.yahoo.restapi.ResourceResponse;
import com.yahoo.slime.Inspector;
import com.yahoo.slime.SlimeUtils;
import com.yahoo.vespa.athenz.api.AthenzUser;
-import com.yahoo.vespa.athenz.utils.AthenzIdentities;
import com.yahoo.vespa.hosted.controller.Controller;
+import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
import com.yahoo.vespa.hosted.controller.auditlog.AuditLoggingRequestHandler;
import com.yahoo.vespa.hosted.controller.maintenance.ControllerMaintenance;
import com.yahoo.vespa.hosted.controller.maintenance.Upgrader;
@@ -85,7 +88,11 @@ public class ControllerApiHandler extends AuditLoggingRequestHandler {
private HttpResponse approveMembership(HttpRequest request, String user) {
AthenzUser athenzUser = AthenzUser.fromUserId(user);
- boolean approved = controller.serviceRegistry().accessControlService().approveDataPlaneAccess(athenzUser);
+ byte[] jsonBytes = toJsonBytes(request.getData());
+ Inspector inspector = SlimeUtils.jsonToSlime(jsonBytes).get();
+ ApplicationId applicationId = ApplicationId.fromSerializedForm(inspector.field("applicationId").asString());
+ ZoneId zone = ZoneId.from(inspector.field("zone").asString());
+ controller.supportAccess().allowDataplaneMembership(athenzUser, new DeploymentId(applicationId, zone));
return new AccessRequestResponse(controller.serviceRegistry().accessControlService().listMembers());
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/support/access/SupportAccessControl.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/support/access/SupportAccessControl.java
index 4a550ad3379..585cd609f53 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/support/access/SupportAccessControl.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/support/access/SupportAccessControl.java
@@ -1,16 +1,20 @@
// Copyright 2021 Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.support.access;
+import com.yahoo.vespa.athenz.api.AthenzIdentity;
+import com.yahoo.vespa.athenz.api.AthenzUser;
import com.yahoo.vespa.curator.Lock;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
+import java.security.Principal;
import java.security.cert.X509Certificate;
import java.time.Instant;
import java.time.Period;
import java.util.List;
import java.util.stream.Collectors;
+import static com.yahoo.vespa.hosted.controller.support.access.SupportAccess.State.ALLOWED;
import static com.yahoo.vespa.hosted.controller.support.access.SupportAccess.State.NOT_ALLOWED;
/**
@@ -87,4 +91,15 @@ public class SupportAccessControl {
.filter(grant -> !grant.certificate().getNotAfter().toInstant().isAfter(now))
.collect(Collectors.toUnmodifiableList());
}
+
+ public void allowDataplaneMembership(AthenzUser identity, DeploymentId deploymentId) {
+ Instant instant = controller.clock().instant();
+ SupportAccess supportAccess = forDeployment(deploymentId);
+ SupportAccess.CurrentStatus currentStatus = supportAccess.currentStatus(instant);
+ if(currentStatus.state() == ALLOWED) {
+ controller.serviceRegistry().accessControlService().approveDataPlaneAccess(identity, currentStatus.allowedUntil().orElse(instant.plus(MAX_SUPPORT_ACCESS_TIME)));
+ } else {
+ throw new IllegalStateException("Not allowed");
+ }
+ }
}
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 47aa3e6b9d4..f1e54033792 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
@@ -18,6 +18,7 @@ import com.yahoo.security.KeyAlgorithm;
import com.yahoo.security.KeyUtils;
import com.yahoo.security.SignatureAlgorithm;
import com.yahoo.security.X509CertificateBuilder;
+import com.yahoo.security.X509CertificateUtils;
import com.yahoo.vespa.athenz.api.AthenzDomain;
import com.yahoo.vespa.athenz.api.AthenzIdentity;
import com.yahoo.vespa.athenz.api.AthenzPrincipal;
@@ -82,6 +83,7 @@ import java.io.File;
import java.math.BigInteger;
import java.net.URI;
import java.security.cert.X509Certificate;
+import java.time.Duration;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
@@ -1501,6 +1503,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
var zone = ZoneId.from(Environment.prod, RegionName.from("us-west-1"));
deploymentTester.controllerTester().zoneRegistry().setRoutingMethod(ZoneApiMock.from(zone),
List.of(RoutingMethod.exclusive, RoutingMethod.shared));
+ addUserToHostedOperatorRole(HostedAthenzIdentities.from(HOSTED_VESPA_OPERATOR));
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
.athenzIdentity(com.yahoo.config.provision.AthenzDomain.from("domain"), AthenzService.from("service"))
.compileVersion(RoutingController.DIRECT_ROUTING_MIN_VERSION)
@@ -1528,11 +1531,16 @@ public class ApplicationApiTest extends ControllerContainerTest {
// Grant access to support user
X509Certificate support_cert = grantCertificate(now, now.plusSeconds(3600));
- tester.controller().supportAccess().registerGrant(app.deploymentIdIn(zone), "user.andreer", support_cert);
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/environment/prod/region/us-west-1/access/support/grant", POST)
+ .data("{\"certificate\":\""+X509CertificateUtils.toPem(support_cert)+"\"}")
+ .userIdentity(HOSTED_VESPA_OPERATOR),
+ "{\"message\":\"Operator user.johnoperator granted access and job production-us-west-1 triggered\"}");
+
+ //tester.controller().supportAccess().registerGrant(app.deploymentIdIn(zone), "user.andreer", support_cert);
// GET shows grant
String grantResponse = allowedResponse.replaceAll("\"grants\":\\[]",
- "\"grants\":[{\"requestor\":\"user.andreer\",\"notBefore\":\"" + serializeInstant(now) + "\",\"notAfter\":\"" + serializeInstant(now.plusSeconds(3600)) + "\"}]");
+ "\"grants\":[{\"requestor\":\"user.johnoperator\",\"notBefore\":\"" + serializeInstant(now) + "\",\"notAfter\":\"" + serializeInstant(now.plusSeconds(3600)) + "\"}]");
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/environment/prod/region/us-west-1/access/support", GET)
.userIdentity(USER_ID),
grantResponse, 200