Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an auth filter for PinDeploy pipeline #1719

Merged
merged 8 commits into from
Oct 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;

public class AgentBean implements Updatable {
public class AgentBean extends BaseBean implements Updatable {

@JsonProperty("hostId")
private String host_id;
Expand Down Expand Up @@ -161,7 +161,7 @@ public String getLast_operator() {
}

public void setLast_operator(String last_operator) {
this.last_operator = last_operator;
this.last_operator = getStringWithinSizeLimit(last_operator, 64);
}

public Boolean getFirst_deploy() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.pinterest.deployservice.bean;

public class BaseBean {

/**
* Trims the input string to the specified size limit. If the input string's length
* exceeds the limit, the method returns the substring from the end of the string
* with the specified limit. Otherwise returns the original string.
*
* @param value the input string to be trimmed
* @param limit the maximum length of the returned string
* @return the trimmed string if the input string's length exceeds the limit,
* otherwise the original string
*/
protected String getStringWithinSizeLimit(String value, int limit) {
if (value != null && value.length() > limit) {
return value.substring(value.length() - limit, value.length());
}
return value;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;

public class ConfigHistoryBean implements Updatable {
public class ConfigHistoryBean extends BaseBean implements Updatable {
@JsonProperty("id")
private String config_id;

Expand Down Expand Up @@ -62,7 +62,7 @@ public Long getCreation_time() {
}

public void setOperator(String operator) {
this.operator = operator;
this.operator = getStringWithinSizeLimit(operator, 64);
}

public String getOperator() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

import org.apache.commons.lang3.builder.ReflectionToStringBuilder;

public class DataBean implements Updatable {
public class DataBean extends BaseBean implements Updatable {
private String data_id;
// TODO deprecate data_kind, we should use json all the time
private String data_kind;
Expand Down Expand Up @@ -46,7 +46,7 @@ public String getOperator() {
}

public void setOperator(String operator) {
this.operator = operator;
this.operator = getStringWithinSizeLimit(operator, 64);
}

public String getData() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;

public class DeployBean implements Updatable {
public class DeployBean extends BaseBean implements Updatable {
@JsonProperty("id")
private String deploy_id;

Expand Down Expand Up @@ -107,7 +107,7 @@ public String getOperator() {
}

public void setOperator(String operator) {
this.operator = operator;
this.operator = getStringWithinSizeLimit(operator, 64);
}

public Long getLast_update() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import org.apache.commons.text.StringEscapeUtils;
import org.hibernate.validator.constraints.Range;

public class EnvironBean implements Updatable, Serializable {
public class EnvironBean extends BaseBean implements Updatable, Serializable {
@JsonProperty("id")
private String env_id;

Expand Down Expand Up @@ -309,7 +309,7 @@ public String getLast_operator() {
}

public void setLast_operator(String last_operator) {
this.last_operator = last_operator;
this.last_operator = getStringWithinSizeLimit(last_operator, 64);
}

public Long getLast_update() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import javax.validation.constraints.NotEmpty;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;

public class HotfixBean implements Updatable {
public class HotfixBean extends BaseBean implements Updatable {
private String id;

@NotEmpty
Expand Down Expand Up @@ -145,7 +145,7 @@ public String getOperator() {
}

public void setOperator(String operator) {
this.operator = operator;
this.operator = getStringWithinSizeLimit(operator, 32);
}

public Long getStart_time() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.quartz.CronExpression;

public class PromoteBean implements Updatable, Serializable {
public class PromoteBean extends BaseBean implements Updatable, Serializable {
@JsonProperty("envId")
private String env_id;

Expand Down Expand Up @@ -71,7 +71,7 @@ public String getLast_operator() {
}

public void setLast_operator(String last_operator) {
this.last_operator = last_operator;
this.last_operator = getStringWithinSizeLimit(last_operator, 64);
}

public Long getLast_update() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import java.beans.Transient;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;

public class TagBean implements Updatable {
public class TagBean extends BaseBean implements Updatable {

private static final ObjectMapper mapper = new ObjectMapper();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.pinterest.deployservice.bean;


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

import org.junit.jupiter.api.Test;

public class BaseBeanTest {
@Test
void testGetStringWithinSizeLimitInputNull() {
BaseBean baseBean = new BaseBean();
String result = baseBean.getStringWithinSizeLimit(null, 10);
assertNull(result);
}

@Test
void testGetStringWithinSizeLimitInputWithinLimit() {
BaseBean baseBean = new BaseBean();
String input = "test";
String result = baseBean.getStringWithinSizeLimit(input, 10);
assertSame(input, result);
}
@Test
void testGetStringWithinSizeLimitInputExceedsLimit() {
BaseBean baseBean = new BaseBean();
String result = baseBean.getStringWithinSizeLimit("0123456789", 5);
assertEquals("56789", result);
}
}
10 changes: 9 additions & 1 deletion deploy-service/teletraanservice/bin/server.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,14 @@ db:
# (Optional)OAuth token cache
#tokenCacheSpec: maximumSize=1000,expireAfterWrite=10m

# pinDeploySpiffeIds:
# - spiffe://pin220.com/teletraan/spin-orca/dev
# - spiffe://pin220.com/teletraan/spin-orca/staging
# - spiffe://pin220.com/teletraan/spin-orca/prod
# - spiffe://pin220.com/teletraan/spin-keel/dev
# - spiffe://pin220.com/teletraan/spin-keel/staging
# - spiffe://pin220.com/teletraan/spin-keel/prod

#
# Token based Authorization: by default is disabled.
#
Expand Down Expand Up @@ -218,4 +226,4 @@ health:
checkInterval: 5s
downtimeInterval: 10s
failureAttempts: 2
successAttempts: 1
successAttempts: 1
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@

import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.SharedMetricRegistries;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.collect.ImmutableList;
import com.pinterest.teletraan.TeletraanServiceContext;
import com.pinterest.teletraan.universal.security.EnvoyAuthFilter;
import com.pinterest.teletraan.universal.security.EnvoyAuthenticator;
import com.pinterest.teletraan.universal.security.PinDeployPipelinePrincipalReplacer;
import com.pinterest.teletraan.universal.security.bean.EnvoyCredentials;
import com.pinterest.teletraan.universal.security.bean.TeletraanPrincipal;
import io.dropwizard.auth.AuthFilter;
Expand All @@ -30,16 +33,27 @@
import io.dropwizard.auth.JSONUnauthorizedHandler;
import io.dropwizard.auth.chained.ChainedAuthFilter;
import java.util.Arrays;
import java.util.List;
import javax.ws.rs.container.ContainerRequestFilter;
import org.apache.commons.lang3.StringUtils;

@JsonTypeName("composite")
public class CompositeAuthenticationFactory extends TokenAuthenticationFactory {
@JsonProperty private List<String> pinDeploySpiffeIds;

@SuppressWarnings({"rawtypes", "unchecked"})
@Override
public ContainerRequestFilter create(TeletraanServiceContext context) throws Exception {
Authenticator<EnvoyCredentials, TeletraanPrincipal> authenticator =
new EnvoyAuthenticator();
Authenticator<EnvoyCredentials, TeletraanPrincipal> authenticator;
if (pinDeploySpiffeIds != null && !pinDeploySpiffeIds.isEmpty()) {
authenticator =
new EnvoyAuthenticator(
ImmutableList.of(
new PinDeployPipelinePrincipalReplacer(
ImmutableList.copyOf(pinDeploySpiffeIds))));
} else {
authenticator = new EnvoyAuthenticator();
}

if (StringUtils.isNotBlank(getTokenCacheSpec())) {
MetricRegistry registry = SharedMetricRegistries.getDefault();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,7 @@ 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 PINDEPLOY_PIPELINE_HEADER = "Pindeploy-Pipeline-Identifier";

public static final String AUTHZ_ATTR_REQ_CXT_KEY = "AuthZAttributes";
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import javax.annotation.Priority;
import javax.ws.rs.Priorities;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.SecurityContext;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;
Expand Down Expand Up @@ -58,17 +59,17 @@ public void filter(final ContainerRequestContext requestContext) throws IOExcept
*/
@Nullable
private EnvoyCredentials getCredentials(ContainerRequestContext requestContext) {
String user = requestContext.getHeaders().getFirst(Constants.USER_HEADER);
String spiffeId =
getSpiffeId(requestContext.getHeaders().getFirst(Constants.CLIENT_CERT_HEADER));
List<String> groups =
getGroups(requestContext.getHeaders().getFirst(Constants.GROUPS_HEADER));
MultivaluedMap<String, String> headers = requestContext.getHeaders();
String user = headers.getFirst(Constants.USER_HEADER);
String spiffeId = getSpiffeId(headers.getFirst(Constants.CLIENT_CERT_HEADER));
List<String> groups = getGroups(headers.getFirst(Constants.GROUPS_HEADER));
String pipelineId = headers.getFirst(Constants.PINDEPLOY_PIPELINE_HEADER);

if (StringUtils.isBlank(spiffeId) && StringUtils.isBlank(user)) {
return null;
}

return new EnvoyCredentials(user, spiffeId, groups);
return new EnvoyCredentials(user, spiffeId, groups, pipelineId);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package com.pinterest.teletraan.universal.security;

import com.google.common.collect.ImmutableList;
import com.pinterest.teletraan.universal.security.AuthMetricsFactory.PrincipalType;
import com.pinterest.teletraan.universal.security.bean.EnvoyCredentials;
import com.pinterest.teletraan.universal.security.bean.ServicePrincipal;
Expand All @@ -24,38 +25,48 @@
import io.dropwizard.auth.Authenticator;
import io.micrometer.core.instrument.Counter;
import java.util.Optional;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;

/** An authenticator for Envoy credentials. */
@NoArgsConstructor
@AllArgsConstructor
public class EnvoyAuthenticator implements Authenticator<EnvoyCredentials, TeletraanPrincipal> {
private final Counter envoyAuthUserSuccessCounter;
private final Counter envoyAuthServiceSuccessCounter;
private final Counter envoyAuthFailureCounter;

public EnvoyAuthenticator() {
envoyAuthUserSuccessCounter =
AuthMetricsFactory.createAuthNCounter(
EnvoyAuthenticator.class, true, PrincipalType.USER);
envoyAuthServiceSuccessCounter =
AuthMetricsFactory.createAuthNCounter(
EnvoyAuthenticator.class, true, PrincipalType.SERVICE);
envoyAuthFailureCounter =
AuthMetricsFactory.createAuthNCounter(
EnvoyAuthenticator.class, false, PrincipalType.NA);
}
private final Counter envoyAuthUserSuccessCounter =
AuthMetricsFactory.createAuthNCounter(
EnvoyAuthenticator.class, true, PrincipalType.USER);
private final Counter envoyAuthServiceSuccessCounter =
AuthMetricsFactory.createAuthNCounter(
EnvoyAuthenticator.class, true, PrincipalType.SERVICE);
private final Counter envoyAuthFailureCounter =
AuthMetricsFactory.createAuthNCounter(
EnvoyAuthenticator.class, false, PrincipalType.NA);
/**
* List of principal replacers to be applied to the authenticated principal.
*
* <p>The default value is an empty list. Note that the order of the replacers matters.
*/
private ImmutableList<PrincipalReplacer> principalReplacers = ImmutableList.of();

@Override
public Optional<TeletraanPrincipal> authenticate(EnvoyCredentials credentials)
throws AuthenticationException {
TeletraanPrincipal principal = null;
if (StringUtils.isNotBlank(credentials.getUser())) {
envoyAuthUserSuccessCounter.increment();
return Optional.of(new UserPrincipal(credentials.getUser(), credentials.getGroups()));
}
if (StringUtils.isNotBlank(credentials.getSpiffeId())) {
principal = new UserPrincipal(credentials.getUser(), credentials.getGroups());
} else if (StringUtils.isNotBlank(credentials.getSpiffeId())) {
envoyAuthServiceSuccessCounter.increment();
return Optional.of(new ServicePrincipal(credentials.getSpiffeId()));
principal = new ServicePrincipal(credentials.getSpiffeId());
} else {
envoyAuthFailureCounter.increment();
}
envoyAuthFailureCounter.increment();
return Optional.empty();

for (PrincipalReplacer replacer : principalReplacers) {
principal = replacer.replace(principal, credentials);
}

return Optional.ofNullable(principal);
}
}
Loading
Loading