Skip to content

Commit

Permalink
ZPE support for certificates (#288)
Browse files Browse the repository at this point in the history
* ZPE support for certificates

* fix test

* test update for mockito 2

* update
  • Loading branch information
charlesk40 authored and havetisyan committed Oct 20, 2017
1 parent a8085d1 commit 867fbb5
Show file tree
Hide file tree
Showing 14 changed files with 201 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public void testAuthorizer() {
ZMSRDLGeneratedClient zmsRdlClient = Mockito.mock(ZMSRDLGeneratedClient.class);
client.setZMSRDLGeneratedClient(zmsRdlClient);
Domain domainMock = Mockito.mock(Domain.class);
Mockito.when(zmsRdlClient.postTopLevelDomain(Mockito.anyString(), Mockito.any(TopLevelDomain.class)))
Mockito.when(zmsRdlClient.postTopLevelDomain(Mockito.<String>any(), Mockito.any(TopLevelDomain.class)))
.thenReturn(domainMock);

setupAccess(client, domain);
Expand Down Expand Up @@ -156,7 +156,7 @@ public void testAddCredentials() {
ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class);
client.setZMSRDLGeneratedClient(c);
Domain domainMock = Mockito.mock(Domain.class);
Mockito.when(c.postTopLevelDomain(Mockito.anyString(), Mockito.any(TopLevelDomain.class)))
Mockito.when(c.postTopLevelDomain(Mockito.<String>any(), Mockito.any(TopLevelDomain.class)))
.thenReturn(domainMock);

setupAccess(client, domain);
Expand Down Expand Up @@ -210,7 +210,7 @@ public void testAuthorizerNoDomain() {
ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class);
client.setZMSRDLGeneratedClient(c);
Domain domainMock = Mockito.mock(Domain.class);
Mockito.when(c.postTopLevelDomain(Mockito.anyString(), Mockito.any(TopLevelDomain.class)))
Mockito.when(c.postTopLevelDomain(Mockito.<String>any(), Mockito.any(TopLevelDomain.class)))
.thenReturn(domainMock);

setupAccess(client, domain);
Expand Down Expand Up @@ -253,7 +253,7 @@ public void testAuthorizerResourceWithDomain() {
ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class);
client.setZMSRDLGeneratedClient(c);
Domain domainMock = Mockito.mock(Domain.class);
Mockito.when(c.postTopLevelDomain(Mockito.anyString(), Mockito.any(TopLevelDomain.class)))
Mockito.when(c.postTopLevelDomain(Mockito.<String>any(), Mockito.any(TopLevelDomain.class)))
.thenReturn(domainMock);
setupAccess(client, domain);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1275,7 +1275,7 @@ public void testCreateTopLevelDomainUserToken() {
"Test Domain1", "testOrg", systemAdminFullUser);
Domain domainMock = Mockito.mock(Domain.class);
dom1.setAuditEnabled(true);
Mockito.when(c.postTopLevelDomain(Mockito.anyString(), Mockito.any(TopLevelDomain.class))).thenReturn(domainMock);
Mockito.when(c.postTopLevelDomain(Mockito.<String>any(), Mockito.any(TopLevelDomain.class))).thenReturn(domainMock);
Mockito.when(c.getDomain("AddTopDom1")).thenReturn(domainMock);
Mockito.when(c.getDomain("AddTopDom3")).thenThrow(new NullPointerException()).thenThrow(new ResourceException(204));;
testCreateTopLevelDomain(client, systemAdminFullUser);
Expand All @@ -1290,7 +1290,7 @@ public void testCreateTopLevelDomainOnceOnlyUserToken() {
"Test Domain1", "testOrg", systemAdminFullUser);
Domain domainMock = Mockito.mock(Domain.class);
dom1.setAuditEnabled(true);
Mockito.when(c.postTopLevelDomain(Mockito.anyString(), Mockito.any(TopLevelDomain.class))).thenReturn(domainMock).thenThrow(new ResourceException(204));
Mockito.when(c.postTopLevelDomain(Mockito.<String>any(), Mockito.any(TopLevelDomain.class))).thenReturn(domainMock).thenThrow(new ResourceException(204));
testCreateTopLevelDomainOnceOnly(client, systemAdminFullUser);
}

Expand Down Expand Up @@ -1882,7 +1882,7 @@ public void testDeletePublicKeyEntryUserToken() {
ZMSRDLGeneratedClient c = Mockito.mock(ZMSRDLGeneratedClient.class);
client.setZMSRDLGeneratedClient(c);
Domain domainMock = Mockito.mock(Domain.class);
Mockito.when(c.postTopLevelDomain(Mockito.anyString(), Mockito.any(TopLevelDomain.class))).thenReturn(domainMock);
Mockito.when(c.postTopLevelDomain(Mockito.<String>any(), Mockito.any(TopLevelDomain.class))).thenReturn(domainMock);
ServiceIdentity serviceMock = Mockito.mock(ServiceIdentity.class);
Mockito.when(c.putServiceIdentity("DelPublicKeyDom1", "Service1", AUDIT_REF, serviceMock)).thenReturn(serviceMock);
PublicKeyEntry entoryMock = Mockito.mock(PublicKeyEntry.class);
Expand Down
128 changes: 121 additions & 7 deletions clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/AuthZpeClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,27 @@
*/
package com.yahoo.athenz.zpe;

import com.yahoo.athenz.auth.impl.RoleAuthority;
import com.yahoo.athenz.auth.token.RoleToken;
import com.yahoo.athenz.zpe.match.ZpeMatch;
import com.yahoo.athenz.zpe.pkey.PublicKeyStore;
import com.yahoo.athenz.zpe.pkey.PublicKeyStoreFactory;
import com.yahoo.rdl.Struct;

import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.security.auth.x500.X500Principal;

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

import com.yahoo.athenz.auth.impl.RoleAuthority;
import com.yahoo.athenz.auth.token.RoleToken;
import com.yahoo.athenz.auth.util.Crypto;
import com.yahoo.athenz.zpe.match.ZpeMatch;
import com.yahoo.athenz.zpe.pkey.PublicKeyStore;
import com.yahoo.athenz.zpe.pkey.PublicKeyStoreFactory;
import com.yahoo.rdl.Struct;

public class AuthZpeClient {

private static final Logger LOG = LoggerFactory.getLogger(AuthZpeClient.class);
Expand Down Expand Up @@ -59,6 +66,10 @@ public class AuthZpeClient {
private static ZpeClient zpeClt = null;
private static PublicKeyStore publicKeyStore = null;

private static final Set<String> X509_ISSUERS = new HashSet<String>();

private static final String ROLE_SEARCH = ":role.";

public enum AccessCheckStatus {
ALLOW {
public String toString() {
Expand Down Expand Up @@ -109,6 +120,26 @@ public String toString() {
public String toString() {
return "Access denied due to invalid/empty action/resource values";
};
},
DENY_CERT_MISMATCH_ISSUER {
public String toString() {
return "Access denied due to certificate mismatch in issuer";
}
},
DENY_CERT_MISSING_SUBJECT {
public String toString() {
return "Access denied due to missing subject in certificate";
}
},
DENY_CERT_MISSING_DOMAIN {
public String toString() {
return "Access denied due to missing domain name in certificate";
}
},
DENY_CERT_MISSING_ROLE_NAME {
public String toString() {
return "Access denied due to missing role name in certificate";
}
}
}

Expand Down Expand Up @@ -147,6 +178,22 @@ public String toString() {
if (allowedOffset < 0) {
allowedOffset = 300;
}

loadX509CAIssuers();
}

private static void loadX509CAIssuers() {
String issuers = System.getProperty(ZpeConsts.ZPE_PROP_X509_CA_ISSUERS);
if (null != issuers && !issuers.isEmpty()) {
issuers = issuers.replaceAll("\\s+" , "");
String[] issuerArray = issuers.split("\\|");
for (String issuer: issuerArray) {
X509_ISSUERS.add(issuer);
if (LOG.isDebugEnabled()) {
LOG.debug("x509 issuer: {}", issuer);
}
}
}
}

public static void init() {
Expand All @@ -163,6 +210,73 @@ public static PublicKey getZmsPublicKey(String keyId) {
return publicKeyStore.getZmsKey(keyId);
}

/**
* Determine if access(action) is allowed against the specified resource by
* a user represented by the X509Certificate
* @param cert - X509Certificate
*
* @param angResource is a domain qualified resource the calling service
* will check access for. ex: my_domain:my_resource
* ex: "angler:pondsKernCounty"
* ex: "sports:service.storage.tenant.Activator.ActionMap"
* @param action is the type of access attempted by a client
* ex: "read"
* ex: "scan"
* @return AccessCheckStatus if the user can access the resource via the specified action
* the result is ALLOW otherwise one of the DENY_* values specifies the exact
* reason why the access was denied
*/
public static AccessCheckStatus allowAccess(X509Certificate cert, String angResource, String action) {
StringBuilder matchRoleName = new StringBuilder(256);
if (LOG.isDebugEnabled()) {
LOG.debug("AUTHZPECLT:allowAccess: action=" + action + " resource=" + angResource);
}
zpeMetric.increment(ZpeConsts.ZPE_METRIC_NAME, DEFAULT_DOMAIN);
// validate the certificate against CAs
X500Principal issuerx500Principal = cert.getIssuerX500Principal();
String issuer = issuerx500Principal.getName();

if (issuer == null || issuer.isEmpty()
|| !X509_ISSUERS.contains(issuer.replaceAll("\\s+" , ""))) {
if (LOG.isDebugEnabled()) {
LOG.debug("AUTHZPECLT:allowAccess: missing or mismatch issuer {}", issuer);
}
return AccessCheckStatus.DENY_CERT_MISMATCH_ISSUER;
}
String subject = Crypto.extractX509CertCommonName(cert);
if (subject == null || subject.isEmpty()) {
if (LOG.isDebugEnabled()) {
LOG.debug("AUTHZPECLT:allowAccess: missing subject");
}
return AccessCheckStatus.DENY_CERT_MISSING_SUBJECT;
}
int idx = subject.indexOf(ROLE_SEARCH);
if (idx == -1) {
if (LOG.isDebugEnabled()) {
LOG.debug("AUTHZPECLT:allowAccess: missing domain or role");
}
return AccessCheckStatus.DENY_CERT_MISSING_ROLE_NAME;
}
String domainName = subject.substring(0, idx);
if (domainName == null || domainName.isEmpty()) {
if (LOG.isDebugEnabled()) {
LOG.debug("AUTHZPECLT:allowAccess: missing domain");
}
return AccessCheckStatus.DENY_CERT_MISSING_DOMAIN;
}
String roleName = subject.substring(idx + ROLE_SEARCH.length(), subject.length());
if (roleName == null || roleName.isEmpty()) {
if (LOG.isDebugEnabled()) {
LOG.debug("AUTHZPECLT:allowAccess: missing role name");
}
return AccessCheckStatus.DENY_CERT_MISSING_ROLE_NAME;
}
List<String> roles = new ArrayList<String>();
roles.add(roleName);
return allowActionZPE(action, domainName, angResource, roles, matchRoleName);

}

/**
* Determine if access(action) is allowed against the specified resource by
* a user represented by the user (cltToken, cltTokenName).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,7 @@ public final class ZpeConsts {
public static final String ZPE_PROP_MON_TIMEOUT = "athenz.zpe.monitor_timeout_secs";
public static final String ZPE_PROP_MON_CLEANUP_TOKENS = "athenz.zpe.cleanup_tokens_secs";
public static final String ZPE_PROP_POLICY_DIR = "athenz.zpe.policy_dir";

public static final String ZPE_PROP_X509_CA_ISSUERS = "athenz.zpe.x509.ca.issuers";

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
package com.yahoo.athenz.zpe;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNull;

import java.io.File;
import java.io.IOException;
Expand All @@ -26,21 +26,23 @@
import java.nio.file.Paths;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.security.auth.x500.X500Principal;

import org.mockito.Mockito;
import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import com.yahoo.athenz.auth.token.RoleToken;
import com.yahoo.athenz.auth.util.Crypto;
import com.yahoo.athenz.common.utils.SignUtils;
import com.yahoo.athenz.zpe.AuthZpeClient;
import com.yahoo.athenz.zpe.ZpeUpdPolLoader;
import com.yahoo.athenz.zpe.AuthZpeClient.AccessCheckStatus;
import com.yahoo.athenz.zts.DomainSignedPolicyData;
import com.yahoo.athenz.zts.SignedPolicyData;
Expand Down Expand Up @@ -164,6 +166,10 @@ public void beforeClass() throws IOException {

renamedFile = new File("./src/test/resources/pol_dir/empty.pol");
file.renameTo(renamedFile);

String issuers = "C=US, ST=CA, O=Athenz, OU=Testing Domain, CN=angler:role.public | C=US, ST=CA, O=Athenz, OU=Testing Domain2, CN=angler:role.public | C=US, ST=CA, O=Athenz, OU=Testing Domain, CN=angler.test:role.public";
System.setProperty(ZpeConsts.ZPE_PROP_X509_CA_ISSUERS, issuers);

}

@BeforeMethod
Expand Down Expand Up @@ -1008,5 +1014,32 @@ public void testgetZtsPublicKeyNull() throws Exception {
PublicKey key = AuthZpeClient.getZtsPublicKey("notexist");
assertNull(key);
}

@DataProvider(name = "x509CertData")
public static Object[][] x509CertData() {
return new Object[][] {
{ "C=US, ST=CA, O=Athenz, OU=Testing Domain, CN=angler:role.public", "C=US, ST=CA, O=Athenz, OU=Testing Domain, CN=angler:role.public", AccessCheckStatus.ALLOW, "angler:stuff" },
{ "C=US, ST=CA, O=Athenz, OU=Testing Domain, CN=angler:role.public", "C=US, ST=CA, O=Athenz, OU=Testing Domain, CN=angler:role.private", AccessCheckStatus.DENY_NO_MATCH, "angler:stuff" },
{ "C=US, ST=CA, O=Athenz, OU=Testing Domain, CN=angler:role.public", "C=US, ST=CA, O=Athenz, OU=Testing Domain, CN=angler:role", AccessCheckStatus.DENY_CERT_MISSING_ROLE_NAME, "angler:stuff" },
{ "C=US, ST=CA, O=Athenz, OU=Testing Domain, CN=angler:role.public", "C=US, ST=CA, O=Athenz, OU=Testing Domain, CN=angler", AccessCheckStatus.DENY_CERT_MISSING_ROLE_NAME, "angler:stuff" },
{ "C=US, ST=CA, O=Athenz, OU=Testing Domain, CN=angler:role.public", "", AccessCheckStatus.DENY_CERT_MISSING_SUBJECT, "angler:stuff" },
{ "", "C=US, ST=CA, O=Athenz, OU=Testing Domain, CN=angler:role.public", AccessCheckStatus.DENY_CERT_MISMATCH_ISSUER, "angler:stuff"},
{ "C=US, ST=CA, O=Athenz, OU=Testing Domain, CN=angler.test:role.public", "C=US, ST=CA, O=Athenz, OU=Testing Domain, CN=angler.test:role.public", AccessCheckStatus.DENY_DOMAIN_NOT_FOUND, "angler.test:stuff"}
};
}

@Test(dataProvider = "x509CertData")
public void testX509CertificateReadAllowed(String issuer, String subject, AccessCheckStatus expectedStatus, String angResource) throws Exception{
String action = "read";
X509Certificate cert = Mockito.mock(X509Certificate.class);
X500Principal x500Principal = Mockito.mock(X500Principal.class);
X500Principal x500PrincipalS = Mockito.mock(X500Principal.class);
Mockito.when(x500Principal.getName()).thenReturn(issuer);
Mockito.when(x500PrincipalS.getName()).thenReturn(subject);
Mockito.when(cert.getIssuerX500Principal()).thenReturn(x500Principal);
Mockito.when(cert.getSubjectX500Principal()).thenReturn(x500PrincipalS);
AccessCheckStatus status = AuthZpeClient.allowAccess(cert, angResource, action);
Assert.assertEquals(status, expectedStatus);
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mock-maker-inline
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

import static org.mockito.Matchers.any;


public class AWSCredentialsProviderImplTest {

Expand All @@ -31,7 +29,7 @@ public void testAWSCredentialsProviderImpl() throws Exception {

//Check that get credentials calls refresh to get credentials from ZTS

Mockito.when(ztsClient.getAWSTemporaryCredentials(any(String.class), any(String.class))).thenReturn(awsTemporaryCredentials, awsTemporaryCredentialsTwo);
Mockito.when(ztsClient.getAWSTemporaryCredentials(Mockito.<String>any(), Mockito.<String>any())).thenReturn(awsTemporaryCredentials, awsTemporaryCredentialsTwo);
AWSCredentialsProviderImpl original = new AWSCredentialsProviderImpl(ztsClient, null, null);

AWSCredentialsProviderImpl awsCredentialsProviderImpl = Mockito.spy(original);
Expand All @@ -43,7 +41,7 @@ public void testAWSCredentialsProviderImpl() throws Exception {
Mockito.verify(awsCredentialsProviderImpl, Mockito.times(2)).refresh();

//null credentials are returned in case of exception
Mockito.when(ztsClient.getAWSTemporaryCredentials(any(String.class), any(String.class))).thenThrow(new ResourceException(400));
Mockito.when(ztsClient.getAWSTemporaryCredentials(Mockito.<String>any(), Mockito.<String>any())).thenThrow(new ResourceException(400));
Assert.assertNull(awsCredentialsProviderImpl.getCredentials());
}
}
Loading

0 comments on commit 867fbb5

Please sign in to comment.