Skip to content

Commit

Permalink
Support attributes in AuthZResource (#1656)
Browse files Browse the repository at this point in the history
Include attributes in the authorization request payload so PDP can implement Attribute-based access control for more complex authorization logic.
The first usage is the BUILD resource type. In Rodimus we will use this change to indicate if the authorization request is for a SOX environment.
  • Loading branch information
tylerwowen authored Jul 9, 2024
1 parent c9ed0fb commit 33288ad
Show file tree
Hide file tree
Showing 15 changed files with 365 additions and 65 deletions.
8 changes: 4 additions & 4 deletions deploy-service/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
<dependency>
<groupId>com.pinterest.teletraan</groupId>
<artifactId>universal</artifactId>
<version>2.0-SNAPSHOT</version>
<version>2.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.pinterest.teletraan</groupId>
Expand Down Expand Up @@ -98,8 +98,8 @@
<plugins>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
Expand Down Expand Up @@ -209,7 +209,7 @@
<!-- For non-Pinterest deployments, remove this block and use the maven-deploy-plugin instead. -->
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.0.0</version>
<version>3.0.0</version>
<executions>
<execution>
<id>artifactory-push-deploy</id>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,14 @@
*/
package com.pinterest.teletraan.security;

import java.io.InputStream;

import javax.ws.rs.container.ContainerRequestContext;

import org.glassfish.jersey.server.ContainerRequest;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.pinterest.deployservice.bean.BuildBean;
import com.pinterest.teletraan.universal.security.AuthZResourceExtractor;
import com.pinterest.teletraan.universal.security.bean.AuthZResource;
import java.io.InputStream;
import java.util.HashMap;
import javax.ws.rs.container.ContainerRequestContext;
import org.glassfish.jersey.server.ContainerRequest;

public class BuildBodyExtractor implements AuthZResourceExtractor {
@Override
Expand All @@ -35,7 +33,12 @@ public AuthZResource extractResource(ContainerRequestContext requestContext)
InputStream inputStream = request.getEntityStream();
try {
BuildBean buildBean = new ObjectMapper().readValue(inputStream, BuildBean.class);
return new AuthZResource(buildBean.getBuild_name(), AuthZResource.Type.BUILD);
HashMap<String, String> attributes = new HashMap<>();
attributes.put(
AuthZResource.AttributeKeys.BUILD_ARTIFACT_URL.name(),
buildBean.getArtifact_url());
return new AuthZResource(
buildBean.getBuild_name(), AuthZResource.Type.BUILD, attributes);
} catch (Exception e) {
throw new BeanClassExtractionException(BuildBean.class, e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@
*/
package com.pinterest.teletraan.security;

import javax.ws.rs.NotFoundException;
import javax.ws.rs.container.ContainerRequestContext;

import com.pinterest.deployservice.ServiceContext;
import com.pinterest.deployservice.bean.BuildBean;
import com.pinterest.deployservice.dao.BuildDAO;
import com.pinterest.teletraan.universal.security.AuthZResourceExtractor;
import com.pinterest.teletraan.universal.security.bean.AuthZResource;
import java.util.HashMap;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.container.ContainerRequestContext;

public class BuildPathExtractor implements AuthZResourceExtractor {
private static final String BUILD_ID = "id";
Expand Down Expand Up @@ -50,6 +50,10 @@ public AuthZResource extractResource(ContainerRequestContext requestContext)
if (buildBean == null) {
throw new NotFoundException(String.format("Build %s not found", buildId));
}
return new AuthZResource(buildBean.getBuild_name(), AuthZResource.Type.BUILD);

HashMap<String, String> attributes = new HashMap<>();
attributes.put(
AuthZResource.AttributeKeys.BUILD_ARTIFACT_URL.name(), buildBean.getArtifact_url());
return new AuthZResource(buildBean.getBuild_name(), AuthZResource.Type.BUILD, attributes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,19 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.pinterest.deployservice.bean.BuildBean;
import com.pinterest.teletraan.universal.security.AuthZResourceExtractor.ExtractionException;
import com.pinterest.teletraan.universal.security.bean.AuthZResource;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

import javax.ws.rs.container.ContainerRequestContext;

import org.glassfish.jersey.server.ContainerRequest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.pinterest.deployservice.bean.BuildBean;
import com.pinterest.teletraan.universal.security.AuthZResourceExtractor.ExtractionException;
import com.pinterest.teletraan.universal.security.bean.AuthZResource;

class BuildBodyExtractorTest {
private BuildBodyExtractor sut;
private ContainerRequestContext requestContext;
Expand All @@ -51,6 +48,7 @@ void setUp() {
void testExtractResource() throws ExtractionException, IOException {
BuildBean buildBean = new BuildBean();
buildBean.setBuild_name("test-build");
buildBean.setArtifact_url("testURL");

ByteArrayOutputStream out = new ByteArrayOutputStream();
objectMapper.writeValue(out, buildBean);
Expand All @@ -62,15 +60,19 @@ void testExtractResource() throws ExtractionException, IOException {

assertEquals("test-build", resource.getName());
assertEquals(AuthZResource.Type.BUILD, resource.getType());
assertEquals(
"testURL",
resource.getAttributes()
.get(AuthZResource.AttributeKeys.BUILD_ARTIFACT_URL.name()));
}

@Test
void testExtractResourceWithInvalidInput() throws IOException {
void testExtractResourceWithInvalidInput() {
String invalidJson = "{ xyz }";
InputStream inputStream = new ByteArrayInputStream(invalidJson.getBytes());

when(requestContext.getEntityStream()).thenReturn(inputStream);

assertThrows(ExtractionException.class, () -> sut.extractResource(requestContext));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,17 @@ void testExtractResource() throws Exception {

BuildBean buildBean = new BuildBean();
buildBean.setBuild_name("Test Build");
buildBean.setArtifact_url("testURL");
when(buildDAO.getById(buildId)).thenReturn(buildBean);

AuthZResource result = sut.extractResource(context);

assertNotNull(result);
assertEquals("Test Build", result.getName());
assertEquals(AuthZResource.Type.BUILD, result.getType());
assertEquals(
"testURL",
result.getAttributes().get(AuthZResource.AttributeKeys.BUILD_ARTIFACT_URL.name()));
}

@Test
Expand Down
4 changes: 2 additions & 2 deletions deploy-service/universal/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

<groupId>com.pinterest.teletraan</groupId>
<artifactId>universal</artifactId>
<version>2.0-SNAPSHOT</version>
<version>2.1-SNAPSHOT</version>

<name>Teletraan platform universal components</name>
<url>https://github.com/pinterest/teletraan/</url>
Expand Down Expand Up @@ -141,7 +141,7 @@
<!-- For non-Pinterest deployments, remove this block and use the maven-deploy-plugin instead. -->
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.0.0</version>
<version>3.0.0</version>
<executions>
<execution>
<id>artifactory-push-deploy</id>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* 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.universal.security;

import com.pinterest.teletraan.universal.security.bean.AuthZResource;
import java.util.HashMap;
import java.util.Map;
import javax.ws.rs.container.ContainerRequestContext;
import lombok.NoArgsConstructor;

@NoArgsConstructor(access = lombok.AccessLevel.PRIVATE)
public class AuthZResourceAttributesUtils {
@SuppressWarnings("unchecked")
public static Map<String, String> insertAuthZResourceAttributes(
ContainerRequestContext requestContext, AuthZResource.AttributeKeys key, String value) {
if (requestContext == null) {
return new HashMap<>();
}

Object attributes = requestContext.getProperty(Constants.AUTHZ_ATTR_REQ_CXT_KEY);
Map<String, String> attributesMap;
if (attributes instanceof Map<?, ?>) {
attributesMap = (Map<String, String>) attributes;
attributesMap.put(key.name(), value);
} else {
attributesMap = new HashMap<>();
attributesMap.put(key.name(), value);
requestContext.setProperty(Constants.AUTHZ_ATTR_REQ_CXT_KEY, attributesMap);
}
return attributesMap;
}

@SuppressWarnings("unchecked")
public static Map<String, String> getAuthZResourceAttributes(
ContainerRequestContext requestContext) {
if (requestContext == null) {
return new HashMap<>();
}
Object attributes = requestContext.getProperty(Constants.AUTHZ_ATTR_REQ_CXT_KEY);
if (attributes instanceof Map<?, ?>) {
return (Map<String, String>) attributes;
}
return new HashMap<>();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ public final class Constants {
public static final String USER_HEADER = "x-forwarded-user";
public static final String GROUPS_HEADER = "x-forwarded-groups";
public static final String CLIENT_CERT_HEADER = "x-forwarded-client-cert";
public static final String AUTHZ_ATTR_REQ_CXT_KEY = "AuthZAttributes";
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,36 @@

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import java.util.Map;
import javax.annotation.Nonnull;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Value;

/** AuthZResource represents a resource for authorization purposes. */
@Data
@Value
@AllArgsConstructor
public class AuthZResource {
private @Nonnull String name;
private final Type type;
@Nonnull String name;
@Nonnull Type type;

@JsonInclude(JsonInclude.Include.NON_NULL)
private String accountId;
@JsonInclude(JsonInclude.Include.NON_EMPTY)
String accountId;

@JsonInclude(JsonInclude.Include.NON_EMPTY)
Map<String, String> attributes;

public static final String ALL = "*";
public static final String NA = "NA";
public static final AuthZResource SYSTEM_RESOURCE = new AuthZResource(ALL, Type.SYSTEM);
public static final AuthZResource UNSPECIFIED_RESOURCE = new AuthZResource(NA, Type.UNSPECIFIED);
public static final AuthZResource UNSPECIFIED_RESOURCE =
new AuthZResource(NA, Type.UNSPECIFIED);

public AuthZResource(@Nonnull String name, Type type) {
this(name, type, null);
this(name, type, null, null);
}

public AuthZResource(@Nonnull String name, Type type, Map<String, String> attributes) {
this(name, type, null, attributes);
}

/**
Expand All @@ -47,11 +56,24 @@ public AuthZResource(@Nonnull String name, Type type) {
* @param stageName the name of the stage
*/
public AuthZResource(String envName, String stageName) {
this(envName, stageName, null);
}

/**
* Convenient constructor for creating an ENV_STAGE resource, the most common resource type.
*
* @param envName the name of the environment
* @param stageName the name of the stage
* @param attributes the attributes of the resource
*/
public AuthZResource(String envName, String stageName, Map<String, String> attributes) {
if (envName == null) {
throw new IllegalArgumentException("envName cannot be null");
}
this.name = String.format("%s/%s", envName, stageName);
this.type = Type.ENV_STAGE;
this.attributes = attributes;
this.accountId = null;
}

/**
Expand Down Expand Up @@ -99,12 +121,8 @@ public enum Type {
UNSPECIFIED,
}

/**
* @deprecated Use getName() instead this. It is needed for converting DB records to
* AuthZResource objects.
*/
@Deprecated
public void setId(@Nonnull String id) {
this.name = id;
public enum AttributeKeys {
ENV_STAGE_IS_SOX,
BUILD_ARTIFACT_URL,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@

import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Value;

@Data
@Value
@AllArgsConstructor
public class EnvoyCredentials {
String user;
Expand Down
Loading

0 comments on commit 33288ad

Please sign in to comment.