diff --git a/deploy-service/common/src/main/java/com/pinterest/deployservice/bean/AgentBean.java b/deploy-service/common/src/main/java/com/pinterest/deployservice/bean/AgentBean.java index 4f11b6723d..ab749648da 100644 --- a/deploy-service/common/src/main/java/com/pinterest/deployservice/bean/AgentBean.java +++ b/deploy-service/common/src/main/java/com/pinterest/deployservice/bean/AgentBean.java @@ -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; @@ -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() { diff --git a/deploy-service/common/src/main/java/com/pinterest/deployservice/bean/BaseBean.java b/deploy-service/common/src/main/java/com/pinterest/deployservice/bean/BaseBean.java new file mode 100644 index 0000000000..586518d726 --- /dev/null +++ b/deploy-service/common/src/main/java/com/pinterest/deployservice/bean/BaseBean.java @@ -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; + } +} diff --git a/deploy-service/common/src/main/java/com/pinterest/deployservice/bean/ConfigHistoryBean.java b/deploy-service/common/src/main/java/com/pinterest/deployservice/bean/ConfigHistoryBean.java index 0c7bcfcca4..d14e0faba6 100644 --- a/deploy-service/common/src/main/java/com/pinterest/deployservice/bean/ConfigHistoryBean.java +++ b/deploy-service/common/src/main/java/com/pinterest/deployservice/bean/ConfigHistoryBean.java @@ -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; @@ -62,7 +62,7 @@ public Long getCreation_time() { } public void setOperator(String operator) { - this.operator = operator; + this.operator = getStringWithinSizeLimit(operator, 64); } public String getOperator() { diff --git a/deploy-service/common/src/main/java/com/pinterest/deployservice/bean/DataBean.java b/deploy-service/common/src/main/java/com/pinterest/deployservice/bean/DataBean.java index 99518a48ac..7045593550 100644 --- a/deploy-service/common/src/main/java/com/pinterest/deployservice/bean/DataBean.java +++ b/deploy-service/common/src/main/java/com/pinterest/deployservice/bean/DataBean.java @@ -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; @@ -46,7 +46,7 @@ public String getOperator() { } public void setOperator(String operator) { - this.operator = operator; + this.operator = getStringWithinSizeLimit(operator, 64); } public String getData() { diff --git a/deploy-service/common/src/main/java/com/pinterest/deployservice/bean/DeployBean.java b/deploy-service/common/src/main/java/com/pinterest/deployservice/bean/DeployBean.java index afd41c1cbc..228e3e6e2c 100644 --- a/deploy-service/common/src/main/java/com/pinterest/deployservice/bean/DeployBean.java +++ b/deploy-service/common/src/main/java/com/pinterest/deployservice/bean/DeployBean.java @@ -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; @@ -107,7 +107,7 @@ public String getOperator() { } public void setOperator(String operator) { - this.operator = operator; + this.operator = getStringWithinSizeLimit(operator, 64); } public Long getLast_update() { diff --git a/deploy-service/common/src/main/java/com/pinterest/deployservice/bean/EnvironBean.java b/deploy-service/common/src/main/java/com/pinterest/deployservice/bean/EnvironBean.java index 8e2c4cc9c7..465fede074 100644 --- a/deploy-service/common/src/main/java/com/pinterest/deployservice/bean/EnvironBean.java +++ b/deploy-service/common/src/main/java/com/pinterest/deployservice/bean/EnvironBean.java @@ -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; @@ -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() { diff --git a/deploy-service/common/src/main/java/com/pinterest/deployservice/bean/HotfixBean.java b/deploy-service/common/src/main/java/com/pinterest/deployservice/bean/HotfixBean.java index 30290d33d3..eeed75f8b3 100644 --- a/deploy-service/common/src/main/java/com/pinterest/deployservice/bean/HotfixBean.java +++ b/deploy-service/common/src/main/java/com/pinterest/deployservice/bean/HotfixBean.java @@ -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 @@ -145,7 +145,7 @@ public String getOperator() { } public void setOperator(String operator) { - this.operator = operator; + this.operator = getStringWithinSizeLimit(operator, 32); } public Long getStart_time() { diff --git a/deploy-service/common/src/main/java/com/pinterest/deployservice/bean/PromoteBean.java b/deploy-service/common/src/main/java/com/pinterest/deployservice/bean/PromoteBean.java index a1b21f5a92..59c879af25 100644 --- a/deploy-service/common/src/main/java/com/pinterest/deployservice/bean/PromoteBean.java +++ b/deploy-service/common/src/main/java/com/pinterest/deployservice/bean/PromoteBean.java @@ -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; @@ -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() { diff --git a/deploy-service/common/src/main/java/com/pinterest/deployservice/bean/TagBean.java b/deploy-service/common/src/main/java/com/pinterest/deployservice/bean/TagBean.java index b93068c387..557d2366d6 100644 --- a/deploy-service/common/src/main/java/com/pinterest/deployservice/bean/TagBean.java +++ b/deploy-service/common/src/main/java/com/pinterest/deployservice/bean/TagBean.java @@ -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(); diff --git a/deploy-service/common/src/test/java/com/pinterest/deployservice/bean/BaseBeanTest.java b/deploy-service/common/src/test/java/com/pinterest/deployservice/bean/BaseBeanTest.java new file mode 100644 index 0000000000..e9b2fe1281 --- /dev/null +++ b/deploy-service/common/src/test/java/com/pinterest/deployservice/bean/BaseBeanTest.java @@ -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); + } +} diff --git a/deploy-service/teletraanservice/bin/server.yaml b/deploy-service/teletraanservice/bin/server.yaml index 9b9bbfdcbe..b18620b13e 100644 --- a/deploy-service/teletraanservice/bin/server.yaml +++ b/deploy-service/teletraanservice/bin/server.yaml @@ -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. # @@ -218,4 +226,4 @@ health: checkInterval: 5s downtimeInterval: 10s failureAttempts: 2 - successAttempts: 1 \ No newline at end of file + successAttempts: 1 diff --git a/deploy-service/teletraanservice/src/main/java/com/pinterest/teletraan/config/CompositeAuthenticationFactory.java b/deploy-service/teletraanservice/src/main/java/com/pinterest/teletraan/config/CompositeAuthenticationFactory.java index e7f1b8d29b..961bc20835 100644 --- a/deploy-service/teletraanservice/src/main/java/com/pinterest/teletraan/config/CompositeAuthenticationFactory.java +++ b/deploy-service/teletraanservice/src/main/java/com/pinterest/teletraan/config/CompositeAuthenticationFactory.java @@ -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; @@ -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 pinDeploySpiffeIds; + @SuppressWarnings({"rawtypes", "unchecked"}) @Override public ContainerRequestFilter create(TeletraanServiceContext context) throws Exception { - Authenticator authenticator = - new EnvoyAuthenticator(); + Authenticator 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(); diff --git a/deploy-service/universal/src/main/java/com/pinterest/teletraan/universal/security/Constants.java b/deploy-service/universal/src/main/java/com/pinterest/teletraan/universal/security/Constants.java index 6a8a495359..946906f83f 100644 --- a/deploy-service/universal/src/main/java/com/pinterest/teletraan/universal/security/Constants.java +++ b/deploy-service/universal/src/main/java/com/pinterest/teletraan/universal/security/Constants.java @@ -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"; } diff --git a/deploy-service/universal/src/main/java/com/pinterest/teletraan/universal/security/EnvoyAuthFilter.java b/deploy-service/universal/src/main/java/com/pinterest/teletraan/universal/security/EnvoyAuthFilter.java index e2fb064c1f..e29b5a01f6 100644 --- a/deploy-service/universal/src/main/java/com/pinterest/teletraan/universal/security/EnvoyAuthFilter.java +++ b/deploy-service/universal/src/main/java/com/pinterest/teletraan/universal/security/EnvoyAuthFilter.java @@ -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; @@ -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 groups = - getGroups(requestContext.getHeaders().getFirst(Constants.GROUPS_HEADER)); + MultivaluedMap headers = requestContext.getHeaders(); + String user = headers.getFirst(Constants.USER_HEADER); + String spiffeId = getSpiffeId(headers.getFirst(Constants.CLIENT_CERT_HEADER)); + List 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); } /** diff --git a/deploy-service/universal/src/main/java/com/pinterest/teletraan/universal/security/EnvoyAuthenticator.java b/deploy-service/universal/src/main/java/com/pinterest/teletraan/universal/security/EnvoyAuthenticator.java index abf31bdde9..33bf09935b 100644 --- a/deploy-service/universal/src/main/java/com/pinterest/teletraan/universal/security/EnvoyAuthenticator.java +++ b/deploy-service/universal/src/main/java/com/pinterest/teletraan/universal/security/EnvoyAuthenticator.java @@ -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; @@ -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 { - 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. + * + *

The default value is an empty list. Note that the order of the replacers matters. + */ + private ImmutableList principalReplacers = ImmutableList.of(); @Override public Optional 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); } } diff --git a/deploy-service/universal/src/main/java/com/pinterest/teletraan/universal/security/PinDeployPipelinePrincipalReplacer.java b/deploy-service/universal/src/main/java/com/pinterest/teletraan/universal/security/PinDeployPipelinePrincipalReplacer.java new file mode 100644 index 0000000000..ebbcfb28a3 --- /dev/null +++ b/deploy-service/universal/src/main/java/com/pinterest/teletraan/universal/security/PinDeployPipelinePrincipalReplacer.java @@ -0,0 +1,41 @@ +/** + * 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.google.common.collect.ImmutableList; +import com.pinterest.teletraan.universal.security.bean.EnvoyCredentials; +import com.pinterest.teletraan.universal.security.bean.ServicePrincipal; +import com.pinterest.teletraan.universal.security.bean.TeletraanPrincipal; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; + +@RequiredArgsConstructor +public class PinDeployPipelinePrincipalReplacer implements PrincipalReplacer { + private final ImmutableList pinDeploySpiffeIds; + + @Override + public TeletraanPrincipal replace(TeletraanPrincipal principal, EnvoyCredentials credentials) { + if (principal != null && ServicePrincipal.class.isAssignableFrom(principal.getClass())) { + ServicePrincipal servicePrincipal = (ServicePrincipal) principal; + if (pinDeploySpiffeIds.contains(servicePrincipal.getName()) + && StringUtils.isNotEmpty(credentials.getPipelineId())) { + return new ServicePrincipal( + String.join("/", servicePrincipal.getName(), credentials.getPipelineId())); + } + } + return principal; + } +} diff --git a/deploy-service/universal/src/main/java/com/pinterest/teletraan/universal/security/PrincipalReplacer.java b/deploy-service/universal/src/main/java/com/pinterest/teletraan/universal/security/PrincipalReplacer.java new file mode 100644 index 0000000000..8b539459c4 --- /dev/null +++ b/deploy-service/universal/src/main/java/com/pinterest/teletraan/universal/security/PrincipalReplacer.java @@ -0,0 +1,23 @@ +/** + * 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.EnvoyCredentials; +import com.pinterest.teletraan.universal.security.bean.TeletraanPrincipal; + +public interface PrincipalReplacer { + TeletraanPrincipal replace(TeletraanPrincipal principal, EnvoyCredentials credentials); +} diff --git a/deploy-service/universal/src/main/java/com/pinterest/teletraan/universal/security/bean/EnvoyCredentials.java b/deploy-service/universal/src/main/java/com/pinterest/teletraan/universal/security/bean/EnvoyCredentials.java index f8208ed2fd..c2c26939da 100644 --- a/deploy-service/universal/src/main/java/com/pinterest/teletraan/universal/security/bean/EnvoyCredentials.java +++ b/deploy-service/universal/src/main/java/com/pinterest/teletraan/universal/security/bean/EnvoyCredentials.java @@ -25,4 +25,12 @@ public class EnvoyCredentials { String user; String spiffeId; List groups; + String pipelineId; + + public EnvoyCredentials(String user, String spiffeId, List groups) { + this.user = user; + this.spiffeId = spiffeId; + this.groups = groups; + this.pipelineId = null; // Default value for backward compatibility + } } diff --git a/deploy-service/universal/src/test/java/com/pinterest/teletraan/universal/security/EnvoyAuthenticatorTest.java b/deploy-service/universal/src/test/java/com/pinterest/teletraan/universal/security/EnvoyAuthenticatorTest.java index 44191dbc9a..97fc2cc6a3 100644 --- a/deploy-service/universal/src/test/java/com/pinterest/teletraan/universal/security/EnvoyAuthenticatorTest.java +++ b/deploy-service/universal/src/test/java/com/pinterest/teletraan/universal/security/EnvoyAuthenticatorTest.java @@ -18,7 +18,12 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +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; @@ -93,6 +98,25 @@ void testAuthenticate_withEmptyCredentials() throws AuthenticationException { assertCounterValue(false, 1.0, PrincipalType.NA); } + @Test + void testAuthenticate_withPrincipalReplacers() throws AuthenticationException { + EnvoyCredentials credentials = new EnvoyCredentials(USER_NAME, null, null); + PrincipalReplacer replacer = mock(PrincipalReplacer.class); + when(replacer.replace(any(), eq(credentials))) + .thenReturn(new UserPrincipal(USER_NAME + "_replaced", null)); + EnvoyAuthenticator authenticator = new EnvoyAuthenticator(ImmutableList.of(replacer)); + + Optional result = authenticator.authenticate(credentials); + + assertTrue(result.isPresent()); + assertTrue(result.get() instanceof UserPrincipal); + + UserPrincipal userPrincipal = (UserPrincipal) result.get(); + assertEquals(USER_NAME + "_replaced", userPrincipal.getName()); + + assertCounterValue(true, 1.0, PrincipalType.USER); + } + private void assertCounterValue(Boolean success, double expected, PrincipalType type) { Counter counter = registry.find(COUNTER_NAME) diff --git a/deploy-service/universal/src/test/java/com/pinterest/teletraan/universal/security/PinDeployPipelinePrincipalReplacerTest.java b/deploy-service/universal/src/test/java/com/pinterest/teletraan/universal/security/PinDeployPipelinePrincipalReplacerTest.java new file mode 100644 index 0000000000..c020c30d54 --- /dev/null +++ b/deploy-service/universal/src/test/java/com/pinterest/teletraan/universal/security/PinDeployPipelinePrincipalReplacerTest.java @@ -0,0 +1,98 @@ +/** + * 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 static org.junit.Assert.assertNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertSame; + +import com.google.common.collect.ImmutableList; +import com.pinterest.teletraan.universal.security.bean.EnvoyCredentials; +import com.pinterest.teletraan.universal.security.bean.ServicePrincipal; +import com.pinterest.teletraan.universal.security.bean.TeletraanPrincipal; +import com.pinterest.teletraan.universal.security.bean.UserPrincipal; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class PinDeployPipelinePrincipalReplacerTest { + private static final String PIPELINE_ID_1 = "pipeline123"; + private static final String PINDEPLOY_SPIFFE_1 = "spiffe://example.org/service1"; + private static final String PINDEPLOY_SPIFFE_2 = "spiffe://example.org/service2"; + private static final ImmutableList pinDeploySpiffeIds = + ImmutableList.of(PINDEPLOY_SPIFFE_1, PINDEPLOY_SPIFFE_2); + private PinDeployPipelinePrincipalReplacer sut; + + @BeforeEach + void setUp() { + sut = new PinDeployPipelinePrincipalReplacer(pinDeploySpiffeIds); + } + + @ParameterizedTest + @ValueSource(strings = {PINDEPLOY_SPIFFE_1, PINDEPLOY_SPIFFE_2}) + void replaceShouldReturnModifiedPrincipalWhenConditionsMet(String spiffe) { + ServicePrincipal principal = new ServicePrincipal(spiffe); + EnvoyCredentials credentials = new EnvoyCredentials(null, spiffe, null, PIPELINE_ID_1); + + TeletraanPrincipal result = sut.replace(principal, credentials); + + assertInstanceOf(ServicePrincipal.class, result); + assertEquals(spiffe + "/" + PIPELINE_ID_1, ((ServicePrincipal) result).getName()); + } + + @Test + void replaceShouldReturnOriginalPrincipalWhenNotServicePrincipal() { + TeletraanPrincipal principal = new UserPrincipal("user", null); + EnvoyCredentials credentials = new EnvoyCredentials("user", null, null); + + TeletraanPrincipal result = sut.replace(principal, credentials); + + assertSame(principal, result); + } + + @Test + void replaceShouldReturnOriginalPrincipalWhenSpiffeIdNotInList() { + String unknownSpiffe = "spiffe://example.org/unknown"; + ServicePrincipal principal = new ServicePrincipal(unknownSpiffe); + EnvoyCredentials credentials = + new EnvoyCredentials(null, unknownSpiffe, null, PIPELINE_ID_1); + + TeletraanPrincipal result = sut.replace(principal, credentials); + + assertSame(principal, result); + } + + @ParameterizedTest + @ValueSource(strings = {PINDEPLOY_SPIFFE_1, PINDEPLOY_SPIFFE_2}) + void replaceShouldReturnOriginalPrincipalWhenPipelineIdIsEmpty(String spiffe) { + ServicePrincipal principal = new ServicePrincipal(spiffe); + EnvoyCredentials credentials = new EnvoyCredentials(null, spiffe, null, null); + + TeletraanPrincipal result = sut.replace(principal, credentials); + + assertSame(principal, result); + } + + @Test + void replaceShouldReturnOriginalPrincipalWhenPrincipalIsNull() { + EnvoyCredentials credentials = new EnvoyCredentials(null, PINDEPLOY_SPIFFE_1, null, null); + TeletraanPrincipal result = sut.replace(null, credentials); + + assertNull(result); + } +}