Skip to content

Commit

Permalink
Add authz to hosts endpoints
Browse files Browse the repository at this point in the history
commit-id:ef160c01
  • Loading branch information
tylerwowen committed May 15, 2024
1 parent c842028 commit 3dead2a
Show file tree
Hide file tree
Showing 10 changed files with 298 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2020 Pinterest, Inc.
/**
* Copyright (c) 2020-2024 Pinterest, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -15,14 +15,12 @@
*/
package com.pinterest.deployservice.dao;

import com.pinterest.deployservice.bean.EnvironBean;
import com.pinterest.deployservice.bean.HostAgentBean;
import java.sql.SQLException;
import java.util.List;

import com.pinterest.deployservice.bean.HostAgentBean;

/**
* A collection of methods to help hosts and groups mapping
*/
/** A collection of methods to help hosts and groups mapping */
public interface HostAgentDAO {
void insert(HostAgentBean hostAgentBean) throws Exception;

Expand All @@ -36,11 +34,24 @@ public interface HostAgentDAO {

List<HostAgentBean> getStaleHosts(long lastUpdateBefore) throws SQLException;

List<HostAgentBean> getStaleHosts(long lastUpdateAfter, long lastUpdateBefore) throws SQLException;
List<HostAgentBean> getStaleHosts(long lastUpdateAfter, long lastUpdateBefore)
throws SQLException;

List<HostAgentBean> getStaleEnvHosts(long lastUpdateBefore) throws Exception;

List<HostAgentBean> getHostsByAgent(String agentVersion, long pageIndex, int pageSize) throws Exception;
List<HostAgentBean> getHostsByAgent(String agentVersion, long pageIndex, int pageSize)
throws Exception;

long getDistinctHostsCount() throws SQLException;

/**
* Retrieves the main environment ID for the specified host ID.
*
* <p>The main environment is where the cluster that the host belongs to is created.
*
* @param hostId The ID of the host.
* @return The bean represents the main environment for the specified host ID.
* @throws SQLException if an error occurs while retrieving the main environment ID.
*/
EnvironBean getMainEnvByHostId(String hostId) throws SQLException;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2020 Pinterest, Inc.
/**
* Copyright (c) 2020-2024 Pinterest, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -24,6 +24,7 @@
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;

import com.pinterest.deployservice.bean.EnvironBean;
import com.pinterest.deployservice.bean.HostAgentBean;
import com.pinterest.deployservice.bean.SetClause;
import com.pinterest.deployservice.dao.HostAgentDAO;
Expand All @@ -39,6 +40,8 @@ public class DBHostAgentDAOImpl implements HostAgentDAO {
private static final String GET_STALE_ENV_HOST = "SELECT DISTINCT hosts_and_agents.* FROM hosts_and_agents INNER JOIN hosts_and_envs ON hosts_and_agents.host_name=hosts_and_envs.host_name WHERE hosts_and_agents.last_update<?";
private static final String GET_HOSTS_BY_AGENT = "SELECT * FROM hosts_statuses WHERE agent_version=? ORDER BY host_id LIMIT ?,?";
private static final String GET_DISTINCT_HOSTS_COUNT = "SELECT COUNT(DISTINCT host_id) FROM hosts_and_agents";
private static final String GET_MAIN_ENV_BY_HOST_ID =
"SELECT e.* FROM hosts_and_agents ha JOIN environs e ON ha.auto_scaling_group = e.cluster_name WHERE ha.host_id = ?";

private BasicDataSource dataSource;

Expand Down Expand Up @@ -107,4 +110,10 @@ public long getDistinctHostsCount() throws SQLException {
SingleResultSetHandlerFactory.<Long>newObjectHandler());
return n == null ? 0 : n;
}

@Override
public EnvironBean getMainEnvByHostId(String hostId) throws SQLException {
ResultSetHandler<EnvironBean> h = new BeanHandler<>(EnvironBean.class);
return new QueryRunner(dataSource).query(GET_MAIN_ENV_BY_HOST_ID, h, hostId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/**
* Copyright (c) 2024 Pinterest, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pinterest.deployservice.db;

import static org.junit.Assert.assertNull;
import static org.junit.jupiter.api.Assertions.assertEquals;

import com.pinterest.deployservice.bean.EnvironBean;
import com.pinterest.deployservice.bean.HostAgentBean;
import com.pinterest.deployservice.dao.EnvironDAO;
import com.pinterest.deployservice.dao.HostAgentDAO;
import com.pinterest.deployservice.fixture.EnvironBeanFixture;
import org.apache.commons.dbcp.BasicDataSource;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class DBHostAgentDAOImplTest {
private static final String HOST_ID = "host123";
private static final String TEST_CLUSTER = "test-cluster";
private static BasicDataSource dataSource;
private HostAgentDAO sut;

@BeforeAll
static void setUpAll() throws Exception {
dataSource = DBUtils.createTestDataSource();
}

@BeforeEach
void setUp() {
sut = new DBHostAgentDAOImpl(dataSource);
}

@AfterEach
void tearDown() throws Exception {
DBUtils.truncateAllTables(dataSource);
}

@Test
void testGetMainEnvByHostId_happyPath() throws Exception {
EnvironDAO environDAO = new DBEnvironDAOImpl(dataSource);
EnvironBean expectedEnvBean = EnvironBeanFixture.createRandomEnvironBean();
expectedEnvBean.setCluster_name(TEST_CLUSTER);
environDAO.insert(expectedEnvBean);

HostAgentBean hostAgentBean = new HostAgentBean();
hostAgentBean.setHost_id(HOST_ID);
hostAgentBean.setAuto_scaling_group(TEST_CLUSTER);
sut.insert(hostAgentBean);

EnvironBean actualEnvironBean = sut.getMainEnvByHostId(HOST_ID);
assertEquals(expectedEnvBean.getEnv_name(), actualEnvironBean.getEnv_name());
assertEquals(expectedEnvBean.getStage_name(), actualEnvironBean.getStage_name());

EnvironBean nullEnvironBean = sut.getMainEnvByHostId("random-host-id");
assertNull(nullEnvironBean);
}

@Test
void testGetMainEnvByHostId_noHost() throws Exception {
EnvironBean actualEnvironBean = sut.getMainEnvByHostId(HOST_ID);
assertNull(actualEnvironBean);
}

@Test
void testGetMainEnvByHostId_noEnv() throws Exception {
HostAgentBean hostAgentBean = new HostAgentBean();
hostAgentBean.setHost_id(HOST_ID);
hostAgentBean.setAuto_scaling_group(TEST_CLUSTER);
sut.insert(hostAgentBean);

EnvironBean actualEnvironBean = sut.getMainEnvByHostId(HOST_ID);
assertNull(actualEnvironBean);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public AgentErrorBean getAgentError(
value = "Update host agent",
notes = "Updates host agent specified by given environment name, stage name, and host id with given " +
"agent object")
@RolesAllowed(TeletraanPrincipalRole.Names.WRITE)
@RolesAllowed(TeletraanPrincipalRole.Names.EXECUTE)
@ResourceAuthZInfo(type = AuthZResource.Type.ENV_STAGE, idLocation = ResourceAuthZInfo.Location.PATH)
public void update(
@Context SecurityContext sc,
Expand All @@ -118,7 +118,7 @@ public void update(
@ApiOperation(
value = "Reset failed deploys",
notes = "Resets failing deploys given an environment name, stage name, and deploy id")
@RolesAllowed(TeletraanPrincipalRole.Names.WRITE)
@RolesAllowed(TeletraanPrincipalRole.Names.EXECUTE)
@ResourceAuthZInfo(type = AuthZResource.Type.ENV_STAGE, idLocation = ResourceAuthZInfo.Location.PATH)
public void resetFailedDeploys(
@Context SecurityContext sc,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,20 @@
import com.google.common.base.Optional;
import com.pinterest.deployservice.bean.HostBean;
import com.pinterest.deployservice.bean.HostState;
import com.pinterest.deployservice.bean.TeletraanPrincipalRole;
import com.pinterest.deployservice.dao.HostDAO;
import com.pinterest.deployservice.handler.EnvironHandler;
import com.pinterest.teletraan.TeletraanServiceContext;
import com.pinterest.teletraan.universal.security.ResourceAuthZInfo;
import com.pinterest.teletraan.universal.security.bean.AuthZResource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.swagger.annotations.*;

import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import javax.validation.Valid;
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
Expand All @@ -47,7 +51,6 @@
)
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
// TODO: CDP-7701 Add authorization to hosts endpoints
public class Hosts {
private static final Logger LOG = LoggerFactory.getLogger(Hosts.class);
private HostDAO hostDAO;
Expand All @@ -59,6 +62,11 @@ public Hosts(@Context TeletraanServiceContext context) {
}

@POST
@RolesAllowed(TeletraanPrincipalRole.Names.EXECUTE)
@ResourceAuthZInfo(type = AuthZResource.Type.SYSTEM)
@ApiOperation(
value = "Add a host",
notes = "Add a host to the system. Should be only called by Rodimus that's why it requires SYSTEM permission.")
public void addHost(@Context SecurityContext sc,
@Valid HostBean hostBean) throws Exception {
String operator = sc.getUserPrincipal().getName();
Expand All @@ -75,6 +83,8 @@ public void addHost(@Context SecurityContext sc,

@PUT
@Path("/{hostId : [a-zA-Z0-9\\-_]+}")
@RolesAllowed(TeletraanPrincipalRole.Names.EXECUTE)
@ResourceAuthZInfo(type = AuthZResource.Type.HOST, idLocation = ResourceAuthZInfo.Location.PATH)
public void updateHost(@Context SecurityContext sc,
@PathParam("hostId") String hostId,
@Valid HostBean hostBean) throws Exception {
Expand All @@ -87,6 +97,8 @@ public void updateHost(@Context SecurityContext sc,

@DELETE
@Path("/{hostId : [a-zA-Z0-9\\-_]+}")
@RolesAllowed(TeletraanPrincipalRole.Names.EXECUTE)
@ResourceAuthZInfo(type = AuthZResource.Type.HOST, idLocation = ResourceAuthZInfo.Location.PATH)
public void stopHost(@Context SecurityContext sc,
@PathParam("hostId") String hostId,
@ApiParam(value = "Replace the host or not") @QueryParam("replaceHost") Optional<Boolean> replaceHost)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* Copyright (c) 2024 Pinterest, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pinterest.teletraan.security;

import com.pinterest.deployservice.ServiceContext;
import com.pinterest.deployservice.bean.EnvironBean;
import com.pinterest.deployservice.dao.HostAgentDAO;
import com.pinterest.teletraan.universal.security.AuthZResourceExtractor;
import com.pinterest.teletraan.universal.security.bean.AuthZResource;
import java.sql.SQLException;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.container.ContainerRequestContext;

public class HostPathExtractor implements AuthZResourceExtractor {
private static final String HOST_ID = "hostId";
private final HostAgentDAO hostAgentDAO;

public HostPathExtractor(ServiceContext context) {
this.hostAgentDAO = context.getHostAgentDAO();
}

@Override
public AuthZResource extractResource(ContainerRequestContext requestContext)
throws ExtractionException {
String hostId = requestContext.getUriInfo().getPathParameters().getFirst(HOST_ID);
if (hostId == null) {
throw new ExtractionException("Failed to extract host id");
}

EnvironBean envBean;
try {
envBean = hostAgentDAO.getMainEnvByHostId(hostId);
} catch (SQLException e) {
throw new ExtractionException(
"Failed to get the main environment with host ID: " + hostId, e);
}

if (envBean == null) {
throw new NotFoundException(
"Failed to get the main environment with host ID: " + hostId);
}
return new AuthZResource(envBean.getEnv_name(), envBean.getStage_name());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,14 @@ public class TeletraanAuthZResourceExtractorFactory implements AuthZResourceExtr
private static final AuthZResourceExtractor HOTFIX_BODY_EXTRACTOR = new HotfixBodyExtractor();
private final AuthZResourceExtractor buildPathExtractor;
private final AuthZResourceExtractor deployPathExtractor;
private final AuthZResourceExtractor hostPathExtractor;
private final AuthZResourceExtractor hotfixPathExtractor;

public TeletraanAuthZResourceExtractorFactory(ServiceContext serviceContext) {
buildPathExtractor = new BuildPathExtractor(serviceContext);
deployPathExtractor = new DeployPathExtractor(serviceContext);
hotfixPathExtractor = new HotfixPathExtractor(serviceContext);
hostPathExtractor = new HostPathExtractor(serviceContext);
}

@Override
Expand All @@ -53,6 +55,8 @@ public AuthZResourceExtractor create(ResourceAuthZInfo authZInfo) {
return deployPathExtractor;
case HOTFIX:
return hotfixPathExtractor;
case HOST:
return hostPathExtractor;
default:
throw new UnsupportedResourceInfoException(authZInfo);
}
Expand Down
Loading

0 comments on commit 3dead2a

Please sign in to comment.