summaryrefslogtreecommitdiffstats
path: root/controller-server
diff options
context:
space:
mode:
authorBjørn Christian Seime <bjorncs@oath.com>2018-02-20 14:42:46 +0100
committerBjørn Christian Seime <bjorncs@oath.com>2018-02-21 11:15:22 +0100
commitb23445f6489045794a219a087bca2b8af15989cf (patch)
tree0096c9548f2520c8d41a8dfb410210e099c11e86 /controller-server
parentbcaa910f1cdd10815ec7bd30d67332af3751b759 (diff)
Remove Authorizer and ApplicationInstanceAuthorizer
Diffstat (limited to 'controller-server')
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/UserAuthWithAthenzPrincipalFilter.java8
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java122
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationInstanceAuthorizer.java136
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/Authorizer.java160
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java1
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/MockAuthorizer.java80
6 files changed, 54 insertions, 453 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/UserAuthWithAthenzPrincipalFilter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/UserAuthWithAthenzPrincipalFilter.java
index 1dc46ed81ab..f59e0fbce5c 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/UserAuthWithAthenzPrincipalFilter.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/UserAuthWithAthenzPrincipalFilter.java
@@ -2,18 +2,16 @@
package com.yahoo.vespa.hosted.controller.athenz.filter;
import com.google.inject.Inject;
-import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.jdisc.Response;
import com.yahoo.jdisc.handler.ResponseHandler;
import com.yahoo.jdisc.http.filter.DiscFilterRequest;
import com.yahoo.log.LogLevel;
-import com.yahoo.vespa.hosted.controller.api.identifiers.UserId;
import com.yahoo.vespa.athenz.api.AthenzPrincipal;
import com.yahoo.vespa.athenz.api.AthenzUser;
import com.yahoo.vespa.athenz.api.NToken;
+import com.yahoo.vespa.hosted.controller.api.identifiers.UserId;
import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsKeystore;
import com.yahoo.vespa.hosted.controller.athenz.config.AthenzConfig;
-import com.yahoo.vespa.hosted.controller.restapi.application.Authorizer;
import java.security.Principal;
import java.util.Optional;
@@ -81,10 +79,6 @@ public class UserAuthWithAthenzPrincipalFilter extends AthenzPrincipalFilter {
.orElseThrow(() -> new IllegalStateException("Invalid status code: " + statusCode));
}
- /**
- * NOTE: The Bouncer user roles ({@link DiscFilterRequest#roles} are still intact as they are required
- * for {@link Authorizer#isMemberOfVespaBouncerGroup(HttpRequest)}.
- */
private void rewriteUserPrincipalToAthenz(DiscFilterRequest request) {
Principal userPrincipal = request.getUserPrincipal();
log.log(LogLevel.DEBUG, () -> "Original user principal: " + userPrincipal.toString());
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 a99b50f0980..c161374d753 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
@@ -5,6 +5,7 @@ import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Inject;
import com.yahoo.component.Version;
+import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.Environment;
@@ -20,6 +21,7 @@ import com.yahoo.slime.Inspector;
import com.yahoo.slime.Slime;
import com.yahoo.vespa.athenz.api.AthenzDomain;
import com.yahoo.vespa.athenz.api.AthenzIdentity;
+import com.yahoo.vespa.athenz.api.AthenzPrincipal;
import com.yahoo.vespa.athenz.api.AthenzUser;
import com.yahoo.vespa.athenz.api.NToken;
import com.yahoo.vespa.config.SlimeUtils;
@@ -80,6 +82,7 @@ import com.yahoo.yolean.Exceptions;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.ForbiddenException;
+import javax.ws.rs.InternalServerErrorException;
import javax.ws.rs.NotAuthorizedException;
import java.io.IOException;
import java.io.InputStream;
@@ -89,6 +92,7 @@ import java.time.Duration;
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Optional;
import java.util.Scanner;
import java.util.logging.Level;
@@ -104,19 +108,15 @@ import java.util.logging.Level;
public class ApplicationApiHandler extends LoggingRequestHandler {
private final Controller controller;
- private final Authorizer authorizer;
private final AthenzClientFactory athenzClientFactory;
- private final ApplicationInstanceAuthorizer applicationInstanceAuthorizer;
@Inject
public ApplicationApiHandler(LoggingRequestHandler.Context parentCtx,
- Controller controller, Authorizer authorizer,
+ Controller controller,
AthenzClientFactory athenzClientFactory) {
super(parentCtx);
this.controller = controller;
- this.authorizer = authorizer;
this.athenzClientFactory = athenzClientFactory;
- this.applicationInstanceAuthorizer = new ApplicationInstanceAuthorizer(controller.zoneRegistry(), athenzClientFactory);
}
@Override
@@ -240,7 +240,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
private HttpResponse authenticatedUser(HttpRequest request) {
String userIdString = request.getProperty("userOverride");
if (userIdString == null)
- userIdString = authorizer.getUserId(request)
+ userIdString = getUserId(request)
.map(UserId::id)
.orElseThrow(() -> new ForbiddenException("You must be authenticated or specify userOverride"));
UserId userId = new UserId(userIdString);
@@ -501,13 +501,12 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
if (!existingTenant.isPresent())
return ErrorResponse.notFoundError("Tenant '" + tenantName + "' does not exist");
- authorizer.throwIfUnauthorized(existingTenant.get().getId(), request);
// Decode payload (reason) and construct parameter to the configserver
Inspector requestData = toSlime(request.getData()).get();
String reason = mandatory("reason", requestData).asString();
- String agent = authorizer.getIdentity(request).getFullName();
+ String agent = getUserPrincipal(request).getIdentity().getFullName();
long timestamp = controller.clock().instant().getEpochSecond();
EndpointStatus.Status status = inService ? EndpointStatus.Status.in : EndpointStatus.Status.out;
EndpointStatus endPointStatus = new EndpointStatus(status, reason, agent, timestamp);
@@ -596,7 +595,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
}
private HttpResponse createUser(HttpRequest request) {
- Optional<UserId> user = authorizer.getUserId(request);
+ Optional<UserId> user = getUserId(request);
if ( ! user.isPresent() ) throw new ForbiddenException("Not authenticated or not an user.");
try {
@@ -614,7 +613,6 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
Inspector requestData = toSlime(request.getData()).get();
- authorizer.throwIfUnauthorized(existingTenant.get().getId(), request);
Tenant updatedTenant;
switch (existingTenant.get().tenantType()) {
case USER: {
@@ -626,8 +624,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
userGroup,
new Property(mandatory("property", requestData).asString()),
optional("propertyId", requestData).map(PropertyId::new));
- throwIfNotSuperUserOrPartOfOpsDbGroup(userGroup, request);
- controller.tenants().updateTenant(updatedTenant, authorizer.getNToken(request));
+ controller.tenants().updateTenant(updatedTenant, getUserPrincipal(request).getNToken());
break;
}
case ATHENS: {
@@ -637,7 +634,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
new AthenzDomain(mandatory("athensDomain", requestData).asString()),
new Property(mandatory("property", requestData).asString()),
optional("propertyId", requestData).map(PropertyId::new));
- controller.tenants().updateTenant(updatedTenant, authorizer.getNToken(request));
+ controller.tenants().updateTenant(updatedTenant, getUserPrincipal(request).getNToken());
break;
}
default: {
@@ -658,12 +655,10 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
optional("property", requestData).map(Property::new),
optional("athensDomain", requestData).map(AthenzDomain::new),
optional("propertyId", requestData).map(PropertyId::new));
- if (tenant.isOpsDbTenant())
- throwIfNotSuperUserOrPartOfOpsDbGroup(new UserGroup(mandatory("userGroup", requestData).asString()), request);
if (tenant.isAthensTenant())
throwIfNotAthenzDomainAdmin(new AthenzDomain(mandatory("athensDomain", requestData).asString()), request);
- controller.tenants().addTenant(tenant, authorizer.getNToken(request));
+ controller.tenants().addTenant(tenant, getUserPrincipal(request).getNToken());
return tenant(tenant, request, true);
}
@@ -674,9 +669,8 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
Property property = new Property(mandatory("property", requestData).asString());
PropertyId propertyId = new PropertyId(mandatory("propertyId", requestData).asString());
- authorizer.throwIfUnauthorized(tenantid, request);
throwIfNotAthenzDomainAdmin(tenantDomain, request);
- NToken nToken = authorizer.getNToken(request)
+ NToken nToken = getUserPrincipal(request).getNToken()
.orElseThrow(() ->
new BadRequestException("The NToken for a domain admin is required to migrate tenant to Athens"));
Tenant tenant = controller.tenants().migrateTenantToAthenz(tenantid, tenantDomain, propertyId, property, nToken);
@@ -684,10 +678,9 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
}
private HttpResponse createApplication(String tenantName, String applicationName, HttpRequest request) {
- authorizer.throwIfUnauthorized(new TenantId(tenantName), request);
Application application;
try {
- application = controller.applications().createApplication(ApplicationId.from(tenantName, applicationName, "default"), authorizer.getNToken(request));
+ application = controller.applications().createApplication(ApplicationId.from(tenantName, applicationName, "default"), getUserPrincipal(request).getNToken());
}
catch (ZmsException e) { // TODO: Push conversion down
if (e.getCode() == com.yahoo.jdisc.Response.Status.FORBIDDEN)
@@ -702,7 +695,6 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
}
/** Trigger deployment of the last built application package, on a given version */
- // TODO Add authorization
// TODO Consider move to API for maintenance related operations
private HttpResponse deploy(String tenantName, String applicationName, HttpRequest request) {
Version version = decideDeployVersion(request);
@@ -723,7 +715,6 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
}
/** Cancel any ongoing change for given application */
- // TODO Add authorization
// TODO Consider move to API for maintenance related operations
private HttpResponse cancelDeploy(String tenantName, String applicationName) {
ApplicationId id = ApplicationId.from(tenantName, applicationName, "default");
@@ -746,11 +737,6 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
// TODO: Propagate all filters
Optional<Hostname> hostname = Optional.ofNullable(request.getProperty("hostname")).map(Hostname::new);
- applicationInstanceAuthorizer.throwIfUnauthorized(authorizer.getPrincipal(request),
- Environment.from(environment),
- getTenantOrThrow(tenantName),
- deploymentId.applicationId().application());
- controller.applications().restart(deploymentId, hostname);
// TODO: Change to return JSON
return new StringResponse("Requested restart of " + path(TenantResource.API_PATH, tenantName,
@@ -770,10 +756,6 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
DeploymentId deploymentId = new DeploymentId(ApplicationId.from(tenantName, applicationName, instanceName),
ZoneId.from(environment, region));
- applicationInstanceAuthorizer.throwIfUnauthorized(authorizer.getPrincipal(request),
- Environment.from(environment),
- getTenantOrThrow(tenantName),
- deploymentId.applicationId().application());
return new JacksonJsonResponse(controller.grabLog(deploymentId));
}
catch (RuntimeException e) {
@@ -795,11 +777,8 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
Optional<ApplicationPackage> applicationPackage = Optional.ofNullable(dataParts.get("applicationZip"))
.map(ApplicationPackage::new);
- applicationInstanceAuthorizer.throwIfUnauthorizedForDeploy(authorizer.getPrincipal(request),
- Environment.from(environment),
- getTenantOrThrow(tenantName),
- ApplicationName.from(applicationName),
- applicationPackage);
+
+ verifyApplicationIdentityConfiguration(tenantName, applicationPackage);
// TODO: get rid of the json object
DeployOptions deployOptionsJsonClass = new DeployOptions(screwdriverBuildJobFromSlime(deployOptions.field("screwdriverBuildJob")),
@@ -813,22 +792,36 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
return new SlimeJsonResponse(toSlime(result));
}
+ private void verifyApplicationIdentityConfiguration(String tenantName, Optional<ApplicationPackage> applicationPackage) {
+ // Validate that domain in identity configuration (deployment.xml) is same as tenant domain
+ applicationPackage.map(ApplicationPackage::deploymentSpec).flatMap(DeploymentSpec::athenzDomain)
+ .ifPresent(identityDomain -> {
+ Tenant tenant = controller.tenants().tenant(new TenantId(tenantName)).orElseThrow(() -> new IllegalArgumentException("Tenant does not exist"));
+ AthenzDomain tenantDomain = tenant.getAthensDomain().orElseThrow(() -> new IllegalArgumentException("Identity provider only available to Athenz onboarded tenants"));
+ if (! Objects.equals(tenantDomain.getName(), identityDomain.value())) {
+ throw new ForbiddenException(
+ String.format(
+ "Athenz domain in deployment.xml: [%s] must match tenant domain: [%s]",
+ identityDomain.value(),
+ tenantDomain.getName()
+ ));
+ }
+ });
+ }
+
private HttpResponse deleteTenant(String tenantName, HttpRequest request) {
Optional<Tenant> tenant = controller.tenants().tenant(new TenantId(tenantName));
if ( ! tenant.isPresent()) return ErrorResponse.notFoundError("Could not delete tenant '" + tenantName + "': Tenant not found"); // NOTE: The Jersey implementation would silently ignore this
- authorizer.throwIfUnauthorized(new TenantId(tenantName), request);
- controller.tenants().deleteTenant(new TenantId(tenantName), authorizer.getNToken(request));
+ controller.tenants().deleteTenant(new TenantId(tenantName), getUserPrincipal(request).getNToken());
// TODO: Change to a message response saying the tenant was deleted
return tenant(tenant.get(), request, false);
}
private HttpResponse deleteApplication(String tenantName, String applicationName, HttpRequest request) {
- authorizer.throwIfUnauthorized(new TenantId(tenantName), request);
-
ApplicationId id = ApplicationId.from(tenantName, applicationName, "default");
- controller.applications().deleteApplication(id, authorizer.getNToken(request));
+ controller.applications().deleteApplication(id, getUserPrincipal(request).getNToken());
return new EmptyJsonResponse(); // TODO: Replicates current behavior but should return a message response instead
}
@@ -838,11 +831,6 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
ZoneId zone = ZoneId.from(environment, region);
Deployment deployment = application.deployments().get(zone);
- applicationInstanceAuthorizer.throwIfUnauthorized(authorizer.getPrincipal(request),
- Environment.from(environment),
- getTenantOrThrow(tenantName),
- ApplicationName.from(applicationName));
-
if (deployment == null) {
// Attempt to deactivate application even if the deployment is not known by the controller
controller.applications().deactivate(application, zone);
@@ -863,10 +851,6 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
*/
private HttpResponse promoteApplication(String tenantName, String applicationName, HttpRequest request) {
try{
- applicationInstanceAuthorizer.throwIfUnauthorized(authorizer.getPrincipal(request),
- getTenantOrThrow(tenantName),
- ApplicationName.from(applicationName));
-
ApplicationChefEnvironment chefEnvironment = new ApplicationChefEnvironment(controller.system());
String sourceEnvironment = chefEnvironment.systemChefEnvironment();
String targetEnvironment = chefEnvironment.applicationSourceEnvironment(TenantName.from(tenantName), ApplicationName.from(applicationName));
@@ -883,11 +867,6 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
*/
private HttpResponse promoteApplicationDeployment(String tenantName, String applicationName, String environmentName, String regionName, String instanceName, HttpRequest request) {
try {
- applicationInstanceAuthorizer.throwIfUnauthorized(authorizer.getPrincipal(request),
- Environment.from(environmentName),
- getTenantOrThrow(tenantName),
- ApplicationName.from(applicationName));
-
ApplicationChefEnvironment chefEnvironment = new ApplicationChefEnvironment(controller.system());
String sourceEnvironment = chefEnvironment.applicationSourceEnvironment(TenantName.from(tenantName), ApplicationName.from(applicationName));
String targetEnvironment = chefEnvironment.applicationTargetEnvironment(TenantName.from(tenantName), ApplicationName.from(applicationName), Environment.from(environmentName), RegionName.from(regionName));
@@ -1046,26 +1025,31 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
}
}
- private void throwIfNotSuperUserOrPartOfOpsDbGroup(UserGroup userGroup, HttpRequest request) {
- AthenzIdentity identity = authorizer.getIdentity(request);
- if (!(identity instanceof AthenzUser)) {
- throw new ForbiddenException("Identity not an user: " + identity.getFullName());
- }
- AthenzUser user = (AthenzUser) identity;
- if (!authorizer.isSuperUser(request) && !authorizer.isGroupMember(new UserId(user.getName()), userGroup) ) {
- throw new ForbiddenException(String.format("User '%s' is not super user or part of the OpsDB user group '%s'",
- user.getName(), userGroup.id()));
- }
- }
-
private void throwIfNotAthenzDomainAdmin(AthenzDomain tenantDomain, HttpRequest request) {
- AthenzIdentity identity = authorizer.getIdentity(request);
- if ( ! authorizer.isAthenzDomainAdmin(identity, tenantDomain)) {
+ AthenzIdentity identity = getUserPrincipal(request).getIdentity();
+ boolean isDomainAdmin = athenzClientFactory.createZmsClientWithServicePrincipal()
+ .isDomainAdmin(identity, tenantDomain);
+ if ( ! isDomainAdmin) {
throw new ForbiddenException(
String.format("The user '%s' is not admin in Athenz domain '%s'", identity.getFullName(), tenantDomain.getName()));
}
}
+ private static Optional<UserId> getUserId(HttpRequest request) {
+ return Optional.of(getUserPrincipal(request))
+ .map(AthenzPrincipal::getIdentity)
+ .filter(AthenzUser.class::isInstance)
+ .map(AthenzUser.class::cast)
+ .map(AthenzUser::getName)
+ .map(UserId::new);
+ }
+
+ private static AthenzPrincipal getUserPrincipal(HttpRequest request) {
+ return Optional.ofNullable(request.getJDiscRequest().getUserPrincipal())
+ .map(AthenzPrincipal.class::cast)
+ .orElseThrow(() -> new InternalServerErrorException("Expected user principal"));
+ }
+
private Inspector mandatory(String key, Inspector object) {
if ( ! object.field(key).valid())
throw new IllegalArgumentException("'" + key + "' is missing");
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationInstanceAuthorizer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationInstanceAuthorizer.java
deleted file mode 100644
index 2deef474f7c..00000000000
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationInstanceAuthorizer.java
+++ /dev/null
@@ -1,136 +0,0 @@
-// 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.config.application.api.DeploymentSpec;
-import com.yahoo.config.provision.ApplicationName;
-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.AthenzPrincipal;
-import com.yahoo.vespa.hosted.controller.api.Tenant;
-import com.yahoo.vespa.hosted.controller.api.application.v4.model.TenantType;
-import com.yahoo.vespa.hosted.controller.api.integration.athenz.ApplicationAction;
-import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactory;
-import com.yahoo.vespa.hosted.controller.api.integration.athenz.ZmsException;
-import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry;
-import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
-
-import javax.ws.rs.ForbiddenException;
-import javax.ws.rs.NotAuthorizedException;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.logging.Logger;
-
-import static com.yahoo.vespa.hosted.controller.api.integration.athenz.HostedAthenzIdentities.SCREWDRIVER_DOMAIN;
-import static com.yahoo.vespa.hosted.controller.restapi.application.Authorizer.environmentRequiresAuthorization;
-
-/**
- * Validates that principal is allowed to perform a mutating operation on an application instance.
- *
- * @author bjorncs
- * @author gjoranv
- */
-public class ApplicationInstanceAuthorizer {
-
- private static final Logger log = Logger.getLogger(ApplicationInstanceAuthorizer.class.getName());
-
- private final ZoneRegistry zoneRegistry;
- private final AthenzClientFactory athenzClientFactory;
-
- public ApplicationInstanceAuthorizer(ZoneRegistry zoneRegistry, AthenzClientFactory athenzClientFactory) {
- this.zoneRegistry = zoneRegistry;
- this.athenzClientFactory = athenzClientFactory;
- }
-
- public void throwIfUnauthorized(AthenzPrincipal principal,
- Tenant tenant,
- ApplicationName application) {
- AthenzDomain principalDomain = principal.getDomain();
- if (isHostedOperator(principal.getIdentity())) return;
-
- if (!principalDomain.equals(SCREWDRIVER_DOMAIN)) {
- throw loggedForbiddenException(
- "Principal '%s' is not a Screwdriver principal. Excepted principal with Athenz domain '%s', got '%s'.",
- principal.getName(), SCREWDRIVER_DOMAIN.getName(), principalDomain.getName());
- }
-
- if (tenant.tenantType() == TenantType.USER) {
- throw loggedForbiddenException("User tenants are only allowed to deploy to 'dev' and 'perf' environment");
- }
-
- // NOTE: no fine-grained deploy authorization for non-Athenz tenants
- if (tenant.isAthensTenant()) {
- AthenzDomain tenantDomain = tenant.getAthensDomain().get();
- if (!hasDeployAccessToAthenzApplication(principal, tenantDomain, application)) {
- throw loggedForbiddenException(
- "Screwdriver principal '%1$s' does not have deploy access to '%2$s'. " +
- "Either the application has not been created at " + zoneRegistry.getDashboardUri() + " or " +
- "'%1$s' is not added to the application's deployer role in Athenz domain '%3$s'.",
- principal.getIdentity().getFullName(), application.value(), tenantDomain.getName());
- }
- }
- }
-
- public void throwIfUnauthorized(AthenzPrincipal principal,
- Environment environment,
- Tenant tenant,
- ApplicationName application) {
- if (!environmentRequiresAuthorization(environment)) {
- return;
- }
- throwIfUnauthorized(principal, tenant, application);
- }
-
- public void throwIfUnauthorizedForDeploy(AthenzPrincipal principal,
- Environment environment,
- Tenant tenant,
- ApplicationName application,
- Optional<ApplicationPackage> applicationPackage) {
- // Validate that domain in identity configuration (deployment.xml) is same as tenant domain
- applicationPackage.map(ApplicationPackage::deploymentSpec).flatMap(DeploymentSpec::athenzDomain)
- .ifPresent(identityDomain -> {
- AthenzDomain tenantDomain = tenant.getAthensDomain().orElseThrow(() -> new IllegalArgumentException("Identity provider only available to Athenz onboarded tenants"));
- if (! Objects.equals(tenantDomain.getName(), identityDomain.value())) {
- throw new ForbiddenException(
- String.format(
- "Athenz domain in deployment.xml: [%s] must match tenant domain: [%s]",
- identityDomain.value(),
- tenantDomain.getName()
- ));
- }
- });
- throwIfUnauthorized(principal, environment, tenant, application);
- }
-
- private static ForbiddenException loggedForbiddenException(String message, Object... args) {
- String formattedMessage = String.format(message, args);
- log.info(formattedMessage);
- return new ForbiddenException(formattedMessage);
- }
-
- private static NotAuthorizedException loggedUnauthorizedException(String message, Object... args) {
- String formattedMessage = String.format(message, args);
- log.info(formattedMessage);
- return new NotAuthorizedException(formattedMessage);
- }
-
- private boolean isHostedOperator(AthenzIdentity identity) {
- return athenzClientFactory.createZmsClientWithServicePrincipal()
- .hasHostedOperatorAccess(identity);
- }
-
- private boolean hasDeployAccessToAthenzApplication(AthenzPrincipal principal, AthenzDomain domain, ApplicationName application) {
- try {
- return athenzClientFactory.createZmsClientWithServicePrincipal()
- .hasApplicationAccess(
- principal.getIdentity(),
- ApplicationAction.deploy,
- domain,
- new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId(application.value()));
- } catch (ZmsException e) {
- throw loggedForbiddenException(
- "Failed to authorize deployment through Athenz. If this problem persists, " +
- "please create ticket at yo/vespa-support. (" + e.getMessage() + ")");
- }
- }
-}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/Authorizer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/Authorizer.java
deleted file mode 100644
index 6a268ce8fda..00000000000
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/Authorizer.java
+++ /dev/null
@@ -1,160 +0,0 @@
-// 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.config.provision.Environment;
-import com.yahoo.container.jdisc.HttpRequest;
-import com.yahoo.vespa.hosted.controller.Controller;
-import com.yahoo.vespa.hosted.controller.api.Tenant;
-import com.yahoo.vespa.athenz.api.AthenzDomain;
-import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId;
-import com.yahoo.vespa.hosted.controller.api.identifiers.UserGroup;
-import com.yahoo.vespa.hosted.controller.api.identifiers.UserId;
-import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactory;
-import com.yahoo.vespa.athenz.api.AthenzIdentity;
-import com.yahoo.vespa.athenz.api.AthenzPrincipal;
-import com.yahoo.vespa.athenz.api.AthenzUser;
-import com.yahoo.vespa.athenz.api.NToken;
-import com.yahoo.vespa.hosted.controller.api.integration.entity.EntityService;
-import com.yahoo.vespa.hosted.controller.common.ContextAttributes;
-
-import javax.ws.rs.ForbiddenException;
-import javax.ws.rs.HttpMethod;
-import javax.ws.rs.core.SecurityContext;
-import java.util.Optional;
-import java.util.logging.Logger;
-
-
-/**
- * @author Stian Kristoffersen
- * @author Tony Vaagenes
- * @author bjorncs
- */
-// TODO: Make this an interface
-public class Authorizer {
-
- private static final Logger log = Logger.getLogger(Authorizer.class.getName());
-
- // Must be kept in sync with bouncer filter configuration.
- private static final String VESPA_HOSTED_ADMIN_ROLE = "10707.A";
-
- private final Controller controller;
- private final AthenzClientFactory athenzClientFactory;
- private final EntityService entityService;
-
- public Authorizer(Controller controller, EntityService entityService, AthenzClientFactory athenzClientFactory) {
- this.controller = controller;
- this.athenzClientFactory = athenzClientFactory;
- this.entityService = entityService;
- }
-
- public void throwIfUnauthorized(TenantId tenantId, HttpRequest request) throws ForbiddenException {
- if (isReadOnlyMethod(request.getMethod().name())) return;
- if (isSuperUser(request)) return;
-
- Optional<Tenant> tenant = controller.tenants().tenant(tenantId);
- if ( ! tenant.isPresent()) return;
-
- AthenzIdentity identity = getIdentity(request);
- if (isTenantAdmin(identity, tenant.get())) return;
-
- throw loggedForbiddenException("User " + identity.getFullName() + " does not have write access to tenant " + tenantId);
- }
-
- public AthenzIdentity getIdentity(HttpRequest request) {
- return getPrincipal(request).getIdentity();
- }
-
- /** Returns the principal or throws forbidden */ // TODO: Avoid REST exceptions
- public AthenzPrincipal getPrincipal(HttpRequest request) {
- return Optional.ofNullable(request.getJDiscRequest().getUserPrincipal())
- .map(AthenzPrincipal.class::cast)
- .orElseThrow(() -> loggedForbiddenException("User is not authenticated"));
- }
-
- public Optional<NToken> getNToken(HttpRequest request) {
- return getPrincipal(request).getNToken();
- }
-
- public Optional<UserId> getUserId(HttpRequest request) {
- return Optional.of(getPrincipal(request))
- .map(AthenzPrincipal::getIdentity)
- .filter(AthenzUser.class::isInstance)
- .map(AthenzUser.class::cast)
- .map(AthenzUser::getName)
- .map(UserId::new);
- }
-
- public boolean isSuperUser(HttpRequest request) {
- // TODO Replace check with membership of a dedicated 'hosted Vespa super-user' role in Vespa's Athenz domain
- return isMemberOfVespaBouncerGroup(request);
- }
-
- private static ForbiddenException loggedForbiddenException(String message, Object... args) {
- String formattedMessage = String.format(message, args);
- log.info(formattedMessage);
- return new ForbiddenException(formattedMessage);
- }
-
- public boolean isTenantAdmin(AthenzIdentity identity, Tenant tenant) {
- switch (tenant.tenantType()) {
- case ATHENS:
- return isAthenzTenantAdmin(identity, tenant.getAthensDomain().get());
- case OPSDB: {
- if (!(identity instanceof AthenzUser)) {
- return false;
- }
- AthenzUser user = (AthenzUser) identity;
- return isGroupMember(new UserId(user.getName()), tenant.getUserGroup().get());
- }
- case USER: {
- if (!(identity instanceof AthenzUser)) {
- return false;
- }
- AthenzUser user = (AthenzUser) identity;
- return isUserTenantOwner(tenant.getId(), new UserId(user.getName()));
- }
- }
- throw new IllegalArgumentException("Unknown tenant type: " + tenant.tenantType());
- }
-
- private boolean isAthenzTenantAdmin(AthenzIdentity athenzIdentity, AthenzDomain tenantDomain) {
- return athenzClientFactory.createZmsClientWithServicePrincipal()
- .hasTenantAdminAccess(athenzIdentity, tenantDomain);
- }
-
- public boolean isAthenzDomainAdmin(AthenzIdentity identity, AthenzDomain tenantDomain) {
- return athenzClientFactory.createZmsClientWithServicePrincipal()
- .isDomainAdmin(identity, tenantDomain);
- }
-
- public boolean isGroupMember(UserId userId, UserGroup userGroup) {
- return entityService.isGroupMember(userId, userGroup);
- }
-
- private static boolean isUserTenantOwner(TenantId tenantId, UserId userId) {
- return tenantId.equals(userId.toTenantId());
- }
-
- public static boolean environmentRequiresAuthorization(Environment environment) {
- return environment != Environment.dev && environment != Environment.perf;
- }
-
- private static boolean isReadOnlyMethod(String method) {
- return method.equals(HttpMethod.GET) || method.equals(HttpMethod.HEAD) || method.equals(HttpMethod.OPTIONS);
- }
-
- @Deprecated
- // TODO Remove this method. Stop using Bouncer for authorization and use Athenz instead
- private boolean isMemberOfVespaBouncerGroup(HttpRequest request) {
- Optional<SecurityContext> securityContext = securityContextOf(request);
- if ( ! securityContext.isPresent() ) throw Authorizer.loggedForbiddenException("User is not authenticated");
- return securityContext.get().isUserInRole(Authorizer.VESPA_HOSTED_ADMIN_ROLE);
- }
-
- @Deprecated
- // TODO: Remove once Bouncer filter is no longer needed
- protected Optional<SecurityContext> securityContextOf(HttpRequest request) {
- return Optional.ofNullable((SecurityContext)request.getJDiscRequest().context().get(ContextAttributes.SECURITY_CONTEXT_ATTRIBUTE));
- }
-
-}
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 abc5f9f8aa1..1818bf97430 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
@@ -65,7 +65,6 @@ public class ControllerContainerTest {
" <component id='com.yahoo.vespa.hosted.controller.maintenance.ControllerMaintenance'/>\n" +
" <component id='com.yahoo.vespa.hosted.controller.maintenance.JobControl'/>\n" +
" <component id='com.yahoo.vespa.hosted.controller.persistence.MemoryControllerDb'/>\n" +
- " <component id='com.yahoo.vespa.hosted.controller.restapi.application.MockAuthorizer'/>\n" +
" <component id='com.yahoo.vespa.hosted.controller.routing.MockRoutingGenerator'/>\n" +
" <component id='com.yahoo.vespa.hosted.controller.ArtifactRepositoryMock'/>\n" +
" <handler id='com.yahoo.vespa.hosted.controller.restapi.application.ApplicationApiHandler'>\n" +
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
deleted file mode 100644
index f2fc4b12096..00000000000
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/MockAuthorizer.java
+++ /dev/null
@@ -1,80 +0,0 @@
-// 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<NToken> getNToken(HttpRequest request) {
- return Optional.of(TestIdentities.userNToken);
- }
-
-
- @Override
- protected Optional<SecurityContext> 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(); }
-
- }
-
-}