Skip to content

Commit

Permalink
explicit launch authorization for k8s provider multi-tenancy use cases (
Browse files Browse the repository at this point in the history
#2601)

Signed-off-by: Abhijeet V <31417623+abvaidya@users.noreply.github.com>
  • Loading branch information
abvaidya authored May 2, 2024
1 parent 897a287 commit 2177d8a
Show file tree
Hide file tree
Showing 10 changed files with 279 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.yahoo.athenz.instance.provider;

import com.yahoo.athenz.auth.Authorizer;
import com.yahoo.athenz.instance.provider.impl.IdTokenAttestationData;

import javax.net.ssl.SSLContext;
Expand All @@ -28,7 +29,7 @@ public interface KubernetesDistributionValidator {
/**
* Optionally initialize the validator with the given region
*/
void initialize(SSLContext sslContext);
void initialize(SSLContext sslContext, Authorizer authorizer);

/**
* Retrieves issuer from id_token in attestation data and validates it
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package com.yahoo.athenz.instance.provider.impl;

import com.yahoo.athenz.auth.Authorizer;
import com.yahoo.athenz.auth.token.IdToken;
import com.yahoo.athenz.auth.token.jwts.JwtsHelper;
import com.yahoo.athenz.auth.token.jwts.JwtsSigningKeyResolver;
Expand Down Expand Up @@ -42,10 +43,13 @@ public abstract class CommonKubernetesDistributionValidator implements Kubernete

Map<String, JwtsSigningKeyResolver> issuersMap = new ConcurrentHashMap<>();
JwtsHelper jwtsHelper = new JwtsHelper();
Authorizer authorizer;
static final String ACTION_LAUNCH = "launch";

@Override
public void initialize(final SSLContext sslContext) {
public void initialize(final SSLContext sslContext, final Authorizer authorizer) {
k8sAttestationExpectedAudience = System.getProperty(ZTS_PROP_K8S_ATTESTATION_EXPECTED_AUDIENCE, "");
this.authorizer = authorizer;
}

String getIssuerFromToken(IdTokenAttestationData attestationData, StringBuilder errMsg) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceClientBuilder;
import com.amazonaws.services.securitytoken.model.AssumeRoleRequest;
import com.amazonaws.services.securitytoken.model.AssumeRoleResult;
import com.yahoo.athenz.auth.Authorizer;
import com.yahoo.athenz.auth.Principal;
import com.yahoo.athenz.auth.impl.SimplePrincipal;
import com.yahoo.athenz.common.server.util.config.dynamic.DynamicConfigBoolean;
import com.yahoo.athenz.common.server.util.config.dynamic.DynamicConfigCsv;
import com.yahoo.athenz.instance.provider.AttrValidator;
Expand Down Expand Up @@ -77,7 +80,7 @@ private DefaultAWSElasticKubernetesServiceValidator() {

static AttrValidator newAttrValidator(final SSLContext sslContext) {
final String factoryClass = System.getProperty(ZTS_PROP_K8S_PROVIDER_AWS_ATTR_VALIDATOR_FACTORY_CLASS);
LOGGER.info("AttributeValidatorFactory class: {}", factoryClass);
LOGGER.info("AWS K8S AttributeValidatorFactory class: {}", factoryClass);
if (factoryClass == null) {
return null;
}
Expand All @@ -94,8 +97,8 @@ static AttrValidator newAttrValidator(final SSLContext sslContext) {
}

@Override
public void initialize(final SSLContext sslContext) {
super.initialize(sslContext);
public void initialize(final SSLContext sslContext, Authorizer authorizer) {
super.initialize(sslContext, authorizer);
serverRegion = System.getProperty(AWS_PROP_REGION_NAME);

useIamRoleForIssuerAttestation = new DynamicConfigBoolean(CONFIG_MANAGER, ZTS_PROP_K8S_PROVIDER_AWS_ATTESTATION_USING_IAM_ROLE, true);
Expand Down Expand Up @@ -150,6 +153,19 @@ public String validateIssuer(InstanceConfirmation confirmation, IdTokenAttestati
}
}

final String domainName = confirmation.getDomain();
final String serviceName = confirmation.getService();
final String resource = String.format("%s:%s:%s", domainName, serviceName,
confirmation.getAttributes().get(ZTS_INSTANCE_AWS_ACCOUNT));

Principal principal = SimplePrincipal.create(domainName, serviceName, (String) null);
boolean accessCheck = authorizer.access(ACTION_LAUNCH, resource, principal, null);
if (!accessCheck) {
errMsg.append("eks launch authorization check failed for action: ").append(ACTION_LAUNCH)
.append(" resource: ").append(resource);
return null;
}

return issuer;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,35 +15,64 @@
*/
package com.yahoo.athenz.instance.provider.impl;

import com.yahoo.athenz.auth.Authorizer;
import com.yahoo.athenz.auth.Principal;
import com.yahoo.athenz.auth.impl.SimplePrincipal;
import com.yahoo.athenz.common.server.util.config.dynamic.DynamicConfigCsv;
import com.yahoo.athenz.instance.provider.AttrValidator;
import com.yahoo.athenz.instance.provider.AttrValidatorFactory;
import com.yahoo.athenz.instance.provider.InstanceConfirmation;
import com.yahoo.athenz.instance.provider.InstanceProvider;
import javax.net.ssl.SSLContext;
import org.eclipse.jetty.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.invoke.MethodHandles;
import java.util.*;

import static com.yahoo.athenz.common.server.util.config.ConfigManagerSingleton.CONFIG_MANAGER;
import static com.yahoo.athenz.instance.provider.impl.InstanceGCPProvider.*;

public class DefaultGCPGoogleKubernetesEngineValidator extends CommonKubernetesDistributionValidator {

private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
Set<String> gcpDNSSuffixes = new HashSet<>();
List<String> gkeDnsSuffixes;
DynamicConfigCsv gkeClusterNames;

private static final DefaultGCPGoogleKubernetesEngineValidator INSTANCE = new DefaultGCPGoogleKubernetesEngineValidator();
static final String GCP_OIDC_ISSUER_PREFIX = "https://container.googleapis.com/v1/projects/";
AttrValidator attrValidator;
static final String ZTS_PROP_K8S_PROVIDER_GCP_ATTR_VALIDATOR_FACTORY_CLASS = "athenz.zts.k8s_provider_gcp_attr_validator_factory_class";

public static DefaultGCPGoogleKubernetesEngineValidator getInstance() {
return INSTANCE;
}
private DefaultGCPGoogleKubernetesEngineValidator() {
}

static AttrValidator newAttrValidator(final SSLContext sslContext) {
final String factoryClass = System.getProperty(ZTS_PROP_K8S_PROVIDER_GCP_ATTR_VALIDATOR_FACTORY_CLASS);
LOGGER.info("GCP K8S AttributeValidatorFactory class: {}", factoryClass);
if (factoryClass == null) {
return null;
}

AttrValidatorFactory attrValidatorFactory;
try {
attrValidatorFactory = (AttrValidatorFactory) Class.forName(factoryClass).getConstructor().newInstance();
} catch (Exception e) {
LOGGER.error("Invalid AttributeValidatorFactory class: {}", factoryClass, e);
throw new IllegalArgumentException("Invalid AttributeValidatorFactory class");
}

return attrValidatorFactory.create(sslContext);
}

@Override
public void initialize(final SSLContext sslContext) {
super.initialize(sslContext);
public void initialize(final SSLContext sslContext, Authorizer authorizer) {
super.initialize(sslContext, authorizer);
final String dnsSuffix = System.getProperty(GCP_PROP_DNS_SUFFIX);
if (!StringUtil.isEmpty(dnsSuffix)) {
gcpDNSSuffixes.addAll(Arrays.asList(dnsSuffix.split(",")));
Expand All @@ -52,6 +81,7 @@ public void initialize(final SSLContext sslContext) {
gkeDnsSuffixes = InstanceUtils.processK8SDnsSuffixList(GCP_PROP_GKE_DNS_SUFFIX);
// get our dynamic list of gke cluster names
gkeClusterNames = new DynamicConfigCsv(CONFIG_MANAGER, GCP_PROP_GKE_CLUSTER_NAMES, null);
this.attrValidator = newAttrValidator(sslContext);
}

@Override
Expand All @@ -62,7 +92,28 @@ public String validateIssuer(InstanceConfirmation confirmation, IdTokenAttestati
}
String gcpProject = confirmation.getAttributes().get(InstanceProvider.ZTS_INSTANCE_GCP_PROJECT);
if (!issuer.startsWith(GCP_OIDC_ISSUER_PREFIX + gcpProject)) {
errMsg.append("Issuer is not present in the GCP project associated with the domain");
// could be a multi-tenant setup where the issuer is not present in the identity's GCP project
if (attrValidator != null) {
confirmation.getAttributes().put(ZTS_INSTANCE_UNATTESTED_ISSUER, issuer);
// Confirm the issuer as per the attribute validator
if (!attrValidator.confirm(confirmation)) {
return null;
}
} else {
errMsg.append("Issuer is not present in the GCP project associated with the domain");
return null;
}
}

final String domainName = confirmation.getDomain();
final String serviceName = confirmation.getService();
final String resource = String.format("%s:%s:%s", domainName, serviceName, gcpProject);

Principal principal = SimplePrincipal.create(domainName, serviceName, (String) null);
boolean accessCheck = authorizer.access(ACTION_LAUNCH, resource, principal, null);
if (!accessCheck) {
errMsg.append("gke launch authorization check failed for action: ").append(ACTION_LAUNCH)
.append(" resource: ").append(resource);
return null;
}
return issuer;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package com.yahoo.athenz.instance.provider.impl;

import com.yahoo.athenz.auth.Authorizer;
import com.yahoo.athenz.auth.KeyStore;
import com.yahoo.athenz.instance.provider.*;
import com.yahoo.rdl.JSON;
Expand All @@ -35,12 +36,18 @@ public class InstanceK8SProvider implements InstanceProvider {
long certValidityTime;
KubernetesDistributionValidatorFactory kubernetesDistributionValidatorFactory;
Map<String, KubernetesDistributionValidator> kubernetesDistributionValidatorMap;
Authorizer authorizer = null;

@Override
public Scheme getProviderScheme() {
return Scheme.CLASS;
}

@Override
public void setAuthorizer(Authorizer authorizer) {
this.authorizer = authorizer;
}

public ResourceException error(String message) {
return error(ResourceException.FORBIDDEN, message);
}
Expand All @@ -58,7 +65,7 @@ public void initialize(String provider, String endpoint, SSLContext sslContext,
if (kubernetesDistributionValidatorFactory != null) {
kubernetesDistributionValidatorFactory.initialize();
kubernetesDistributionValidatorMap = kubernetesDistributionValidatorFactory.getSupportedDistributions();
kubernetesDistributionValidatorMap.forEach((key, value) -> value.initialize(sslContext));
kubernetesDistributionValidatorMap.forEach((key, value) -> value.initialize(sslContext, authorizer));
}
}

Expand Down
Loading

0 comments on commit 2177d8a

Please sign in to comment.