Skip to content

Commit

Permalink
Provide interface to allow customization of SPIFFE URI format
Browse files Browse the repository at this point in the history
Signed-off-by: Henry Avetisyan <[email protected]>
  • Loading branch information
havetisyan committed Jan 7, 2025
1 parent baf7c7c commit 2e1ca9b
Show file tree
Hide file tree
Showing 19 changed files with 641 additions and 160 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
*
* * Copyright The Athenz Authors
* *
* * 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.yahoo.athenz.common.server.spiffe;

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

import java.util.ArrayList;
import java.util.List;

public class SpiffeUriManager {

private static final Logger LOGGER = LoggerFactory.getLogger(SpiffeUriManager.class);

public static final String ZTS_PROP_SPIFFE_URI_VALIDATOR_CLASSES = "athenz.zts.spiffe_uri_validator_classes";
public static final String ZTS_DEFAULT_SPIFFE_URI_VALIDATOR_CLASSES = "com.yahoo.athenz.common.server.spiffe.impl.SpiffeUriTrustDomain,com.yahoo.athenz.common.server.spiffe.impl.SpiffeUriBasic";

private final List<SpiffeUriValidator> validators;

public SpiffeUriManager() {

final String validatorClasses = System.getProperty(ZTS_PROP_SPIFFE_URI_VALIDATOR_CLASSES,
ZTS_DEFAULT_SPIFFE_URI_VALIDATOR_CLASSES);

validators = new ArrayList<>();
String[] validatorClassList = validatorClasses.split(",");
for (String validatorClass : validatorClassList) {
SpiffeUriValidator validator = getValidator(validatorClass.trim());
if (validator == null) {
throw new IllegalArgumentException("Invalid spiffe uri validator: " + validatorClass);
}
validators.add(validator);
}
}

SpiffeUriValidator getValidator(String className) {

LOGGER.debug("Loading spiffe uri validator {}...", className);

SpiffeUriValidator validator;
try {
validator = (SpiffeUriValidator) Class.forName(className).getDeclaredConstructor().newInstance();
} catch (Exception ex) {
LOGGER.error("Invalid validator class: {}", className, ex);
return null;
}
return validator;
}

public boolean validateServiceCertUri(final String spiffeUri, final String domainName, final String serviceName,
final String namespace) {

for (SpiffeUriValidator validator : validators) {
if (validator.validateServiceCertUri(spiffeUri, domainName, serviceName, namespace)) {
return true;
}
}
LOGGER.error("unable to validate service spiffe uri: {}, domainName: {}, serviceName: {}, namespace: {}",
spiffeUri, domainName, serviceName, namespace);
return false;
}

public boolean validateRoleCertUri(final String spiffeUri, final String domainName, final String roleName) {
for (SpiffeUriValidator validator : validators) {
if (validator.validateRoleCertUri(spiffeUri, domainName, roleName)) {
return true;
}
}
LOGGER.error("unable to validate role spiffe uri: {}, domainName: {}, roleName: {}",
spiffeUri, domainName, roleName);
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
*
* * Copyright The Athenz Authors
* *
* * 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.yahoo.athenz.common.server.spiffe;

/**
* An interface that allows system administrators to validate SPIFFE URIs
* based on their own requirements.
*/
public interface SpiffeUriValidator {

/**
* Validate the SPIFFE URI for service identity certificates based on the system requirements.
* @param spiffeUri the SPIFFE URI to be validated (e.g. spiffe://athenz.domain/sa/service)
* @param domainName the domain name of the service
* @param serviceName the service name
* @param namespace the namespace of the service (typically a Kubernetes namespace)
* @return true if the SPIFFE URI is valid, false otherwise
*/
boolean validateServiceCertUri(final String spiffeUri, final String domainName, final String serviceName, final String namespace);

/**
* Validate the SPIFFE URI for rike certificates based on the system requirements.
* @param spiffeUri the SPIFFE URI to be validated (e.g. spiffe://athenz.domain/ra/writers)
* @param domainName the domain name of the service
* @param roleName the role name
* @return true if the SPIFFE URI is valid, false otherwise
*/
boolean validateRoleCertUri(final String spiffeUri, final String domainName, final String roleName);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
*
* * Copyright The Athenz Authors
* *
* * 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.yahoo.athenz.common.server.spiffe.impl;

import com.yahoo.athenz.common.server.spiffe.SpiffeUriValidator;

/**
* Basic implementation of SpiffeUriValidator interface. This class validates the SPIFFE URI
* with the following formats:
* Service Cert URI: spiffe://<domainName>/sa/<serviceName>
* Example: spiffe://athenz/sa/api
* Role Cert URI: spiffe://<domainName>/ra/<roleName>
* Example: spiffe://athenz/ra/readers
*/
public class SpiffeUriBasic implements SpiffeUriValidator {

/**
* Supported Service Cert URI: spiffe://<domainName>/sa/<serviceName>
* Example: spiffe://athenz/sa/api
*/
@Override
public boolean validateServiceCertUri(String spiffeUri, String domainName, String serviceName, String namespace) {
final String reqUri = String.format("spiffe://%s/sa/%s", domainName, serviceName);
return reqUri.equalsIgnoreCase(spiffeUri);
}

/**
* Supported Role Cert URI: spiffe://<domainName>/ra/<roleName>
* Example: spiffe://athenz/ra/readers
*/
@Override
public boolean validateRoleCertUri(String spiffeUri, String domainName, String roleName) {
final String reqUri = String.format("spiffe://%s/ra/%s", domainName, roleName);
return reqUri.equalsIgnoreCase(spiffeUri);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
*
* * Copyright The Athenz Authors
* *
* * 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.yahoo.athenz.common.server.spiffe.impl;

import com.yahoo.athenz.common.server.spiffe.SpiffeUriValidator;
import org.eclipse.jetty.util.StringUtil;

/**
* Trust Domain implementation of SpiffeUriValidator interface. This class validates the SPIFFE URI
* with the following formats:
* Service Cert URI: spiffe://<trustDomain>/ns/<namespace>/sa/<domainName>.<serviceName>
* Example: spiffe://athenz.io/ns/prod/sa/athenz.api
* Role Cert URI: spiffe://<trustDomain>/ns/<domainName>/ra/<roleName>
* Example: spiffe://athenz.io/ns/athenz/ra/readers
*/
public class SpiffeUriTrustDomain implements SpiffeUriValidator {

private static final String SPIFFE_DEFAULT_NAMESPACE = "default";

private static final String SPIFFE_PROP_TRUST_DOMAIN = "athenz.zts.spiffe_trust_domain";
private static final String SPIFFE_TRUST_DOMAIN = System.getProperty(SPIFFE_PROP_TRUST_DOMAIN, "athenz.io");

/**
* Service Cert URI: spiffe://<trustDomain>/ns/<namespace>/sa/<domainName>.<serviceName>
* Example: spiffe://athenz.io/ns/prod/sa/athenz.api
*/
@Override
public boolean validateServiceCertUri(String spiffeUri, String domainName, String serviceName, String namespace) {
final String ns = StringUtil.isEmpty(namespace) ? SPIFFE_DEFAULT_NAMESPACE : namespace;
final String reqUri = String.format("spiffe://%s/ns/%s/sa/%s.%s", SPIFFE_TRUST_DOMAIN,
ns, domainName, serviceName);
return reqUri.equalsIgnoreCase(spiffeUri);
}

/**
* Role Cert URI: spiffe://<trustDomain>/ns/<domainName>/ra/<roleName>
* Example: spiffe://athenz.io/ns/athenz/ra/readers
*/
@Override
public boolean validateRoleCertUri(String spiffeUri, String domainName, String roleName) {
final String reqUri = String.format("spiffe://%s/ns/%s/ra/%s", SPIFFE_TRUST_DOMAIN,
domainName, roleName);
return reqUri.equalsIgnoreCase(spiffeUri);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright The Athenz Authors
*
* 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.yahoo.athenz.common.server.spiffe;

import com.yahoo.athenz.common.server.spiffe.impl.SpiffeUriTrustDomain;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

import static org.testng.Assert.*;

public class SpiffeUriManagerTest {

@BeforeClass
public void setup() {
System.setProperty("athenz.zts.spiffe_trust_domain", "spiffe.athenz.io");
}

@Test
public void testValidateServiceCertUriDefaultClasses() {

System.clearProperty("athenz.zts.spiffe_uri_validator_classes");
SpiffeUriManager manager = new SpiffeUriManager();

assertTrue(manager.validateServiceCertUri("spiffe://athenz/sa/api", "athenz", "api", null));
assertTrue(manager.validateServiceCertUri("spiffe://athenz/sa/api", "athenz", "api", "default"));

assertFalse(manager.validateServiceCertUri("spiffe://athenz/sa/api", "athenz.prod", "api", "default"));
assertFalse(manager.validateServiceCertUri("spiffe://athenz/sa/api", "athenz", "backend", "default"));

assertTrue(manager.validateServiceCertUri("spiffe://spiffe.athenz.io/ns/default/sa/athenz.api", "athenz", "api", null));
assertTrue(manager.validateServiceCertUri("spiffe://spiffe.athenz.io/ns/default/sa/athenz.api", "athenz", "api", "default"));
assertTrue(manager.validateServiceCertUri("spiffe://spiffe.athenz.io/ns/prod/sa/athenz.api", "athenz", "api", "prod"));

assertFalse(manager.validateServiceCertUri("spiffe://spiffe.athenz.io/ns/default/sa/athenz.api", "athenz", "api", "prod"));
assertFalse(manager.validateServiceCertUri("spiffe://spiffe.athenz.io/ns/default/sa/athenz.backend", "athenz", "api", "default"));
assertFalse(manager.validateServiceCertUri("spiffe://spiffe.athenz.io/ns/prod/sa/athenz.api", "athenz.prod", "api", "prod"));

assertFalse(manager.validateServiceCertUri("spiffe://athenz.io/ns/prod/sa/athenz.api", "athenz", "api", "prod"));
}

@Test
public void testValidateRoleCertUriDefaultClasses() {

System.clearProperty("athenz.zts.spiffe_uri_validator_classes");
SpiffeUriManager manager = new SpiffeUriManager();

assertTrue(manager.validateRoleCertUri("spiffe://athenz/ra/readers", "athenz", "readers"));
assertTrue(manager.validateRoleCertUri("spiffe://athenz/ra/writers", "athenz", "writers"));

assertFalse(manager.validateRoleCertUri("spiffe://athenz/ra/readers", "athenz.prod", "readers"));
assertFalse(manager.validateRoleCertUri("spiffe://athenz/ra/readers", "athenz", "writers"));

assertTrue(manager.validateRoleCertUri("spiffe://spiffe.athenz.io/ns/athenz/ra/readers", "athenz", "readers"));

assertFalse(manager.validateRoleCertUri("spiffe://spiffe.athenz.io/ns/athenz/ra/readers", "athenz", "writers"));
assertFalse(manager.validateRoleCertUri("spiffe://spiffe.athenz.io/ns/athenz/ra/readers", "athenz.prod", "readers"));

assertFalse(manager.validateRoleCertUri("spiffe://athenz.io/ns/athenz/ra/readers", "athenz", "readers"));
}

@Test
public void testValidateInvalidClass() {

System.setProperty("athenz.zts.spiffe_uri_validator_classes", "com.yahoo.athenz.common.server.spiffe.impl.InvalidClass");
try {
new SpiffeUriManager();
fail();
} catch (IllegalArgumentException ex) {
assertTrue(ex.getMessage().contains("Invalid spiffe uri validator: com.yahoo.athenz.common.server.spiffe.impl.InvalidClass"));
}
System.clearProperty("athenz.zts.spiffe_uri_validator_classes");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright The Athenz Authors
*
* 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.yahoo.athenz.common.server.spiffe;

import org.testng.annotations.Test;

import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;

public class SpiffeUriValidatorTest {

@Test
public void testValidate() {

final String trustDomain = "athenz.io";

SpiffeUriValidator validator = new SpiffeUriValidator() {
@Override
public boolean validateServiceCertUri(String spiffeUri, String domainName, String serviceName, String namespace) {
final String expectedUri = String.format("spiffe://%s/ns/%s/sa/%s.%s", trustDomain, namespace,
domainName, serviceName);
return spiffeUri.equals(expectedUri);
}

@Override
public boolean validateRoleCertUri(String spiffeUri, String domainName, String roleName) {
final String expectedUri = String.format("spiffe://%s/ns/%s/ra/%s", trustDomain,
domainName, roleName);
return spiffeUri.equals(expectedUri);
}
};

assertTrue(validator.validateServiceCertUri("spiffe://athenz.io/ns/prod/sa/athenz.api", "athenz", "api", "prod"));
assertFalse(validator.validateServiceCertUri("spiffe://athenz.io/ns/prod/sa/athenz.api", "athenz", "api", "dev"));

assertTrue(validator.validateRoleCertUri("spiffe://athenz.io/ns/athenz/ra/readers", "athenz", "readers"));
assertFalse(validator.validateRoleCertUri("spiffe://athenz.io/ns/athenz/ra/readers", "athenz", "writers"));
}
}
Loading

0 comments on commit 2e1ca9b

Please sign in to comment.