Skip to content

Commit c148e6e

Browse files
author
adamglt
authored
Merge pull request #2 from Traiana/unitests
Unit testing
2 parents a7f43de + 2ebfcf9 commit c148e6e

File tree

6 files changed

+233
-38
lines changed

6 files changed

+233
-38
lines changed

pom.xml

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,34 @@
2020
</plugins>
2121
</build>
2222

23-
2423
<dependencies>
2524
<dependency>
2625
<groupId>org.apache.kafka</groupId>
2726
<artifactId>kafka_2.11</artifactId>
2827
<version>1.1.1</version>
2928
</dependency>
3029
<dependency>
31-
<groupId>org.apache.commons</groupId>
32-
<artifactId>commons-lang3</artifactId>
33-
<version>3.7</version>
30+
<groupId>com.google.code.findbugs</groupId>
31+
<artifactId>jsr305</artifactId>
32+
<version>3.0.2</version>
33+
</dependency>
34+
<dependency>
35+
<groupId>junit</groupId>
36+
<artifactId>junit</artifactId>
37+
<version>4.12</version>
38+
<scope>test</scope>
39+
</dependency>
40+
<dependency>
41+
<groupId>org.mockito</groupId>
42+
<artifactId>mockito-core</artifactId>
43+
<version>2.20.1</version>
44+
<scope>test</scope>
45+
</dependency>
46+
<dependency>
47+
<groupId>org.slf4j</groupId>
48+
<artifactId>slf4j-simple</artifactId>
49+
<version>1.6.4</version>
50+
<scope>test</scope>
3451
</dependency>
3552
</dependencies>
3653

Lines changed: 48 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
package io.okro.kafka;
22

3-
import org.apache.kafka.common.security.auth.*;
3+
import org.apache.kafka.common.security.auth.AuthenticationContext;
4+
import org.apache.kafka.common.security.auth.KafkaPrincipal;
5+
import org.apache.kafka.common.security.auth.KafkaPrincipalBuilder;
6+
import org.apache.kafka.common.security.auth.SslAuthenticationContext;
7+
import org.slf4j.Logger;
8+
import org.slf4j.LoggerFactory;
49

10+
import javax.annotation.Nullable;
511
import javax.net.ssl.SSLPeerUnverifiedException;
612
import javax.net.ssl.SSLSession;
713
import java.security.cert.Certificate;
@@ -10,56 +16,64 @@
1016
import java.util.Collection;
1117
import java.util.List;
1218

13-
import org.slf4j.Logger;
14-
import org.slf4j.LoggerFactory;
15-
16-
import static org.apache.commons.lang3.StringUtils.startsWith;
17-
1819
public class SpiffePrincipalBuilder implements KafkaPrincipalBuilder {
1920
private static final Logger LOG = LoggerFactory.getLogger(SpiffePrincipalBuilder.class);
2021

2122
private static final String SPIFFE_TYPE = "SPIFFE";
2223

2324
public KafkaPrincipal build(AuthenticationContext context) {
24-
if (context instanceof PlaintextAuthenticationContext) {
25+
if (!(context instanceof SslAuthenticationContext)) {
26+
LOG.trace("non-SSL connection coerced to ANONYMOUS");
2527
return KafkaPrincipal.ANONYMOUS;
2628
}
2729

28-
if (!(context instanceof SslAuthenticationContext)) {
29-
throw new IllegalArgumentException("Unhandled authentication context type: " + context.getClass().getName());
30+
SSLSession session = ((SslAuthenticationContext) context).session();
31+
X509Certificate cert = firstX509(session);
32+
if (cert == null) {
33+
LOG.trace("first peer certificate missing / not x509");
34+
return KafkaPrincipal.ANONYMOUS;
3035
}
3136

32-
SSLSession sslSession = ((SslAuthenticationContext) context).session();
37+
String spiffeId = spiffeId(cert);
38+
if (spiffeId == null) {
39+
return new KafkaPrincipal(KafkaPrincipal.USER_TYPE, cert.getSubjectX500Principal().getName());
40+
}
41+
42+
return new KafkaPrincipal(SPIFFE_TYPE, spiffeId);
43+
}
44+
45+
private @Nullable X509Certificate firstX509(SSLSession session) {
3346
try {
34-
Certificate[] peerCerts = sslSession.getPeerCertificates();
35-
if (peerCerts == null || peerCerts.length == 0) {
36-
return KafkaPrincipal.ANONYMOUS;
47+
Certificate[] peerCerts = session.getPeerCertificates();
48+
if (peerCerts.length == 0) {
49+
return null;
3750
}
38-
if (!(peerCerts[0] instanceof X509Certificate)) {
39-
return KafkaPrincipal.ANONYMOUS;
51+
Certificate first = peerCerts[0];
52+
if (!(first instanceof X509Certificate)) {
53+
return null;
4054
}
41-
X509Certificate cert = (X509Certificate) peerCerts[0];
42-
43-
Collection<List<?>> sanCollection = cert.getSubjectAlternativeNames();
44-
KafkaPrincipal principal;
55+
return (X509Certificate) first;
56+
} catch (SSLPeerUnverifiedException e) {
57+
LOG.warn("failed to extract certificate", e);
58+
return null;
59+
}
60+
}
4561

46-
if (sanCollection != null) {
47-
principal = sanCollection.stream()
48-
.map(san -> (String) san.get(1))
49-
.filter(uri -> startsWith(uri, "spiffe://"))
50-
.findFirst()
51-
.map(s -> new KafkaPrincipal(SPIFFE_TYPE, s))
52-
.orElse(new KafkaPrincipal(KafkaPrincipal.USER_TYPE, cert.getSubjectX500Principal().getName()));
53-
} else {
54-
principal = new KafkaPrincipal(KafkaPrincipal.USER_TYPE, cert.getSubjectX500Principal().getName());
62+
private @Nullable String spiffeId(X509Certificate cert) {
63+
try {
64+
Collection<List<?>> sans = cert.getSubjectAlternativeNames();
65+
if (sans == null) {
66+
return null;
5567
}
5668

57-
LOG.debug("PrincipalBuilder found principal: {}", principal.toString());
58-
59-
return principal;
60-
} catch (SSLPeerUnverifiedException | CertificateParsingException se) {
61-
LOG.warn("Unhandled exception: " + se.toString());
62-
return KafkaPrincipal.ANONYMOUS;
69+
return sans.stream()
70+
.map(san -> (String) san.get(1))
71+
.filter(uri -> uri.startsWith("spiffe://"))
72+
.findFirst()
73+
.orElse(null);
74+
} catch (CertificateParsingException e) {
75+
LOG.warn("failed to parse SAN", e);
76+
return null;
6377
}
6478
}
6579
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package io.okro.kafka;
2+
3+
import org.apache.kafka.common.security.auth.KafkaPrincipal;
4+
import org.apache.kafka.common.security.auth.PlaintextAuthenticationContext;
5+
import org.apache.kafka.common.security.auth.SslAuthenticationContext;
6+
import org.junit.Test;
7+
8+
import javax.net.ssl.SSLPeerUnverifiedException;
9+
import javax.net.ssl.SSLSession;
10+
import java.io.InputStream;
11+
import java.net.InetAddress;
12+
import java.net.UnknownHostException;
13+
import java.security.cert.Certificate;
14+
import java.security.cert.CertificateException;
15+
import java.security.cert.CertificateFactory;
16+
import java.security.cert.X509Certificate;
17+
18+
import static org.junit.Assert.assertEquals;
19+
import static org.mockito.Mockito.mock;
20+
import static org.mockito.Mockito.when;
21+
22+
public class SpiffePrincipalBuilderTest {
23+
24+
private SslAuthenticationContext mockedSslContext(String certPath) throws CertificateException, SSLPeerUnverifiedException, UnknownHostException {
25+
// load cert
26+
ClassLoader classLoader = getClass().getClassLoader();
27+
InputStream in = classLoader.getResourceAsStream(certPath);
28+
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
29+
X509Certificate cert = (X509Certificate) certificateFactory.generateCertificate(in);
30+
31+
// mock ssl session
32+
SSLSession session = mock(SSLSession.class);
33+
when(session.getPeerCertificates()).thenReturn(new Certificate[]{cert});
34+
return new SslAuthenticationContext(session, InetAddress.getLocalHost());
35+
}
36+
37+
/**
38+
* X509 V3 with a SPIFFE-based SAN extension.
39+
* Should result in 'SPIFFE:[spiffe://uri]'
40+
*/
41+
@Test
42+
public void TestSpiffeCert() throws CertificateException, SSLPeerUnverifiedException, UnknownHostException {
43+
SslAuthenticationContext context = mockedSslContext("spiffe-cert.pem");
44+
KafkaPrincipal principal = new SpiffePrincipalBuilder().build(context);
45+
46+
assertEquals("SPIFFE", principal.getPrincipalType());
47+
assertEquals(principal.getName(), "spiffe://srv1.okro.io");
48+
}
49+
50+
/**
51+
* X509 V1 certificate with no SAN extension.
52+
* Should fall back to 'User:CN=[CN]'
53+
*/
54+
@Test
55+
public void TestSubjectOnlyCert() throws CertificateException, SSLPeerUnverifiedException, UnknownHostException {
56+
SslAuthenticationContext context = mockedSslContext("subject-only-cert.pem");
57+
KafkaPrincipal principal = new SpiffePrincipalBuilder().build(context);
58+
59+
assertEquals(KafkaPrincipal.USER_TYPE, principal.getPrincipalType());
60+
assertEquals(principal.getName(), "CN=srv2,OU=architects,O=okro.io,L=Tel-Aviv,ST=Tel-Aviv,C=IL");
61+
}
62+
63+
/**
64+
* X509 V3 with a non-SPIFFE SAN extension.
65+
* Should fall back to 'User:CN=[CN]'
66+
*/
67+
@Test
68+
public void TestSanNoSpiffeCert() throws CertificateException, SSLPeerUnverifiedException, UnknownHostException {
69+
SslAuthenticationContext context = mockedSslContext("san-no-spiffe-cert.pem");
70+
KafkaPrincipal principal = new SpiffePrincipalBuilder().build(context);
71+
72+
assertEquals(KafkaPrincipal.USER_TYPE, principal.getPrincipalType());
73+
assertEquals(principal.getName(), "CN=srv3,OU=architects,O=okro.io,L=Tel-Aviv,ST=Tel-Aviv,C=IL");
74+
}
75+
76+
/**
77+
* Non-SSL context.
78+
* Should be unauthenticated.
79+
*/
80+
@Test
81+
public void TestNoSSLContext() throws java.net.UnknownHostException {
82+
PlaintextAuthenticationContext context = new PlaintextAuthenticationContext(InetAddress.getLocalHost());
83+
KafkaPrincipal principal = new SpiffePrincipalBuilder().build(context);
84+
85+
assertEquals(KafkaPrincipal.ANONYMOUS, principal);
86+
}
87+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIEYTCCAkmgAwIBAgIJAMGzBF0pPMySMA0GCSqGSIb3DQEBCwUAMFoxCzAJBgNV
3+
BAYTAklMMREwDwYDVQQIDAhUZWwtQXZpdjERMA8GA1UEBwwIVGVsLUF2aXYxEDAO
4+
BgNVBAoMB29rcm8uaW8xEzARBgNVBAMMCm9rcm8uaW8gQ0EwHhcNMTgwNzMwMjAy
5+
NjA0WhcNMjgwNzI3MjAyNjA0WjBpMQswCQYDVQQGEwJJTDERMA8GA1UECAwIVGVs
6+
LUF2aXYxETAPBgNVBAcMCFRlbC1Bdml2MRAwDgYDVQQKDAdva3JvLmlvMRMwEQYD
7+
VQQLDAphcmNoaXRlY3RzMQ0wCwYDVQQDDARzcnYzMIIBIjANBgkqhkiG9w0BAQEF
8+
AAOCAQ8AMIIBCgKCAQEA1lWqlsmeDKL1yR4aR1Swvb3nW3VGmQs9VejpVWh1Yh/g
9+
paj51zuSHVXYIhfC26mtOdsJkDq5sNyemxGGKDyhnsJQ6w3RVhtI1t89myut0wjZ
10+
qY/mXNkpfZhPKRI31N/UFx5LMr9PZY51t4+5Q362H3YwRiP497xeJ0SUbG+Hm5z5
11+
nrsY5ENecHFF4KATL6/topYd0rns4IEjvbaocOIv+yhyOPAXEEyD7KIozGuPf09h
12+
3M50ne5oMjjTlFk7+lJ6gMSZdnyKUMc01sP9Jvu80Pbq6prXqV0akSAWo8BGUX8+
13+
0RQMktivMyjhyTD/LNUzEdlOPuKrYxODjrtAxTCLpwIDAQABoxswGTAXBgNVHREE
14+
EDAOggxzcnYzLm9rcm8uaW8wDQYJKoZIhvcNAQELBQADggIBAJl/MXI7JVccfaQB
15+
VnnYiLLjX/auW0BWh06Pg0vNWfyfXBbHWJWKps7k1SBtkSBsuqd6ecB4LJAYvbWf
16+
51+j/aw0I71xXw3rXdhuh7mOwi0rg3t/RRNs5EJz/GPk734MecuJcNLrWS/YRq0a
17+
TojJFW22rmlem9vL7OkHPoNDh4LbHN9Wyxgdjt+VmVK+o7Gx4DSHZxo/Dmz6Gp0F
18+
dmKNCUG6yYcxdgDvr0mz4rJeLlqXwb/gjLFmYfnyVWZRB3WOUQuejY7W17W16LYP
19+
11fqlOJSuhyj7E5NekikxO9asOclTlw7qDG7i0PpBTLuLgqDppOo9xZOHrK+DUrf
20+
r3CP9d7P86g2oTv4d6btKUGez3wIU5qfrb4mAZDp6/nngR9hpcFaeGqtjtgJj7Bo
21+
elbJEPy2f3Uwocbatr7nI1szNDJuEoXi0nnAT/h1K4m3EuTRuUsEn8ySJJYwB2fc
22+
ExBhHSrljm111VN/a5TLhT5sZJYKDiVYQ0SJEZRo9rJg8P+G6Xa30JyFa1r6iYOp
23+
UUE8nsWPGcdC6HKu7Tr1YsvbjHTG900O+Rco8at1EWu7s+b278VcYzRJy5z+HM3N
24+
+0/EDFpu9xsSt3PtRGVk57gtf1nAECSV1OQSBCGy+E/MUpBQEqq3VL4vM9H3ANSz
25+
stX902cJF9ZYpG8Hn5Xt4q8AnFIw
26+
-----END CERTIFICATE-----

src/test/resources/spiffe-cert.pem

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIEajCCAlKgAwIBAgIJAMGzBF0pPMyOMA0GCSqGSIb3DQEBCwUAMFoxCzAJBgNV
3+
BAYTAklMMREwDwYDVQQIDAhUZWwtQXZpdjERMA8GA1UEBwwIVGVsLUF2aXYxEDAO
4+
BgNVBAoMB29rcm8uaW8xEzARBgNVBAMMCm9rcm8uaW8gQ0EwHhcNMTgwNzI5MTMx
5+
OTU4WhcNMjgwNzI2MTMxOTU4WjBpMQswCQYDVQQGEwJJTDERMA8GA1UECAwIVGVs
6+
LUF2aXYxETAPBgNVBAcMCFRlbC1Bdml2MRAwDgYDVQQKDAdva3JvLmlvMRMwEQYD
7+
VQQLDAphcmNoaXRlY3RzMQ0wCwYDVQQDDARzcnYxMIIBIjANBgkqhkiG9w0BAQEF
8+
AAOCAQ8AMIIBCgKCAQEAw0VF+zmGt6q8WGeoIwzyAyWrzRIMGwsUyMK04PkVlHji
9+
Tl6TxW8twISdTiHTOHTuIvwSuVE5PcqtrPLCAvOU5MgGv0BLG92z344peHELAVRI
10+
akgQkBEBIehr9Km2vdQtsYRMRJ8nDQiK8A7jiKVI0YBVpGjFREG9yBGG/DnUu95r
11+
7vJVE3KrWC/zIAS+e83j4DuMHoPXGInDD2dCbw86XSj9EDbGCrPZb9op8TwSP4ym
12+
f61DoeSBca6AzLeY3fsScZKSHd9p4BA69eYKgRVxI+fMYzKAysd3wX+mrqt1eT+w
13+
Lhmyw9/8V+TaMq3Q8eRnVD4zJ65YFushANm37da38wIDAQABoyQwIjAgBgNVHREE
14+
GTAXghVzcGlmZmU6Ly9zcnYxLm9rcm8uaW8wDQYJKoZIhvcNAQELBQADggIBABxQ
15+
vYqXGsCB1gbBpgNFELQ1I6jcPqA/7VCRPgB7l1SvXNw70zBjMgIIgPMdCm+ZnoMK
16+
dd4loizOygU3pWIUJYKo2BobcxdqV4fLvmZkVJQtqlhrxN36DA1sgI7D+TYQJGSR
17+
QFY2aEvlzCZnBatRCQ3c4DPiz7qoB9H1JsRejCipzMvGxXu+aOBjKM7HiFLTznsy
18+
sBh01UHCIxIMdSJTtdGs3PIIgKMlAC5TQR4nqDCiqgzTo0JPCeyPIYmFy8iwigM+
19+
CC280ZHWIkvOYMOBM4VswcSzDF8XRZG/vIf7tV3jp0XtJ1EddL+GM18eLh4vIWt8
20+
b7PZnabtIBvITzjjW5Np05kQu1W3riqBveyQHz2fB1IpwTO2mXPyal1vw1Wrj5WQ
21+
QlFJjUTNxk0J4yVNHQbErrtBnGE0Zl4brcq7HNGsd0d0knOJ8F/edSB+USdeqNMH
22+
qBd1GTI1a/dNQ2oC34zvq+cUSzz4L+pK+2nJSeWpb/WAn0V/EgnH6APluxwpUs6i
23+
QqTqyCu2ecrvR0hhEiRNmBZW4+QaZe4aLrLClCEED+/nuvZf/9qLhIdEsq8bbHtP
24+
SIHhTv8pMfT0uej0JxpLKJDLvsmOUUVKCRMW//kArCZHmHKKOPKKwuNCNCRbIzib
25+
EN4HE4RU0BwVRZscMz7nTuSGqucN/Nrae1yC/k9C
26+
-----END CERTIFICATE-----
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIEPzCCAicCCQDBswRdKTzMkDANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJJ
3+
TDERMA8GA1UECAwIVGVsLUF2aXYxETAPBgNVBAcMCFRlbC1Bdml2MRAwDgYDVQQK
4+
DAdva3JvLmlvMRMwEQYDVQQDDApva3JvLmlvIENBMB4XDTE4MDcyOTEzMzE1MFoX
5+
DTI4MDcyNjEzMzE1MFowaTELMAkGA1UEBhMCSUwxETAPBgNVBAgMCFRlbC1Bdml2
6+
MREwDwYDVQQHDAhUZWwtQXZpdjEQMA4GA1UECgwHb2tyby5pbzETMBEGA1UECwwK
7+
YXJjaGl0ZWN0czENMAsGA1UEAwwEc3J2MjCCASIwDQYJKoZIhvcNAQEBBQADggEP
8+
ADCCAQoCggEBANxdN6zBI4lm86BX9dJ3tsT3OoJ6Ei6AxcfZmVydQJZ/XlOOWIKl
9+
zceANkSeRV4XKfy6qePHHNIMY1jD1I/tLsCWXMy0RuNMmU9ea+ZIY9s3+WkMzXTD
10+
2vdtMM+8nxnwa3aR276r3Bna0ics/KEZG7GrmnQ3OzFMJojGLfowKXIMuZXUkTZE
11+
vGmvpwuaxUVmUsiogMGTIGtCCKCjzSrsrJc6yEK7gygiBInUtZj/2fqHita2hN6M
12+
e1MyCDkIATtO6Q4ahrlWrKwEW1Zn4bgjvXUjyFVlGUfOlm4UeICsp2T1zHPNWGqc
13+
yz+Drp3mWHCWJZBnXLc7xZiXPazw2fRUuMECAwEAATANBgkqhkiG9w0BAQsFAAOC
14+
AgEAD+DOZqMUFF1CboIX9YVokXuWwINLYAYruhQF5kgwU1hLaWDJh+FGOKO4doNO
15+
G4DDGkXFXM+5u6gl7R84sF2thx+mcPABsUheWqOOToEeHpe5+sgjtdMlnOutRHuD
16+
q445OseLARd2XtiGFC7SYFk/mrmeH9N29rA5x/w0uS4eFPQYtVcnhQd8LkUExKjL
17+
2KnYPr0lD5TIHN20mlf0AxRbMa9Mjz2vUTLlqwqG7IQJWYco+VbI76i/walOv0+Z
18+
SQNsoKPF+du10Tg0NGQtcQ9CY/C3lNZLxofKXI0JiAlcjJ5emFYh8MsbUS4ESfjH
19+
dut5RyiTDLa0/LZa5w2CBi0fGSFhGK7+q7xPtvGczD+YIWDePQEkcmsKSoyyRzlu
20+
xl6F8C1nT5xj0qAaxcRpfNuTUhkaqnEvzw0BJRS4Kajd/s5/sTCzqcAdK1Rwy6AI
21+
M+7nqZM4iHE/yx/uxapu7RWiGBi48SBIThOYOizrYktH/eIYYJ0fEGLAqGu3mdV0
22+
bBQxcFXSzMypfzAcwIXUNedpqsHCucbJHcxsMYymm2jAjlgU8VcJRvmW4u2JNizf
23+
yzEttSYG2Zti7hJJtWCf9Ek7kaXusC6Uhii6qnCqEWulE8DrZl4Y6ZYKYpe0UMsm
24+
V7kOqym53MchmkaBrc8yVjDE9R6USb/1OvQrjf6PjyFLIkE=
25+
-----END CERTIFICATE-----

0 commit comments

Comments
 (0)