Skip to content

Commit d461010

Browse files
nkuriharmerlimat
authored andcommitted
Add Athenz authentication plugin (#178)
1 parent d278601 commit d461010

File tree

8 files changed

+403
-0
lines changed

8 files changed

+403
-0
lines changed

conf/broker.conf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ superUserRoles=
122122
brokerClientAuthenticationPlugin=
123123
brokerClientAuthenticationParameters=
124124

125+
# Supported Athenz provider domain names(comma separated) for authentication
126+
athenzDomainNames=
125127

126128
### --- BookKeeper Client --- ###
127129

conf/standalone.conf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ superUserRoles=
9595
brokerClientAuthenticationPlugin=
9696
brokerClientAuthenticationParameters=
9797

98+
# Supported Athenz provider domain names(comma separated) for authentication
99+
athenzDomainNames=
98100

99101
### --- BookKeeper Client --- ###
100102

pom.xml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ flexible messaging model and an intuitive client API.</description>
8181
<module>pulsar-zookeeper-utils</module>
8282
<module>pulsar-checksum</module>
8383
<module>pulsar-testclient</module>
84+
<module>pulsar-broker-auth-athenz</module>
85+
<module>pulsar-client-auth-athenz</module>
8486
<module>all</module>
8587
</modules>
8688

@@ -388,6 +390,19 @@ flexible messaging model and an intuitive client API.</description>
388390
<artifactId>caffeine</artifactId>
389391
<version>2.3.3</version>
390392
</dependency>
393+
394+
<dependency>
395+
<groupId>com.yahoo.athenz</groupId>
396+
<artifactId>zts_java_client</artifactId>
397+
<version>1.1.3</version>
398+
</dependency>
399+
400+
<dependency>
401+
<groupId>com.yahoo.athenz</groupId>
402+
<artifactId>zpe_java_client</artifactId>
403+
<version>1.1.3</version>
404+
</dependency>
405+
391406
</dependencies>
392407
</dependencyManagement>
393408

@@ -710,6 +725,14 @@ flexible messaging model and an intuitive client API.</description>
710725
<id>bookkeeper-yahoo-mvn-repo</id>
711726
<url>https://raw.githubusercontent.com/yahoo/bookkeeper/mvn-repo</url>
712727
</repository>
728+
<repository>
729+
<snapshots>
730+
<enabled>false</enabled>
731+
</snapshots>
732+
<id>bintray-yahoo-maven</id>
733+
<name>bintray</name>
734+
<url>http://yahoo.bintray.com/maven</url>
735+
</repository>
713736
</repositories>
714737

715738
<distributionManagement>

pulsar-broker-auth-athenz/pom.xml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?xml version="1.0"?>
2+
<!--
3+
4+
Copyright 2016 Yahoo Inc.
5+
6+
Licensed under the Apache License, Version 2.0 (the "License");
7+
you may not use this file except in compliance with the License.
8+
You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
18+
-->
19+
<project
20+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
21+
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
22+
<modelVersion>4.0.0</modelVersion>
23+
<parent>
24+
<groupId>com.yahoo.pulsar</groupId>
25+
<artifactId>pulsar</artifactId>
26+
<version>1.17-SNAPSHOT</version>
27+
</parent>
28+
29+
<artifactId>pulsar-broker-auth-athenz</artifactId>
30+
<packaging>jar</packaging>
31+
<description>Athenz authentication plugin for broker</description>
32+
33+
<dependencies>
34+
35+
<dependency>
36+
<groupId>${project.groupId}</groupId>
37+
<artifactId>pulsar-broker</artifactId>
38+
<version>${project.version}</version>
39+
</dependency>
40+
41+
<dependency>
42+
<groupId>com.yahoo.athenz</groupId>
43+
<artifactId>zpe_java_client</artifactId>
44+
</dependency>
45+
46+
</dependencies>
47+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/**
2+
* Copyright 2016 Yahoo Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.yahoo.pulsar.broker.authentication;
17+
18+
import java.io.IOException;
19+
import java.net.SocketAddress;
20+
import java.util.List;
21+
import java.security.PublicKey;
22+
23+
import javax.naming.AuthenticationException;
24+
25+
import org.slf4j.Logger;
26+
import org.slf4j.LoggerFactory;
27+
28+
import com.google.common.collect.Lists;
29+
import com.yahoo.athenz.auth.token.RoleToken;
30+
import com.yahoo.athenz.zpe.AuthZpeClient;
31+
import com.yahoo.pulsar.broker.ServiceConfiguration;
32+
import com.yahoo.pulsar.broker.authentication.AuthenticationDataSource;
33+
import com.yahoo.pulsar.broker.authentication.AuthenticationProvider;
34+
35+
public class AuthenticationProviderAthenz implements AuthenticationProvider {
36+
37+
private static final String DOMAIN_NAME_LIST = "athenzDomainNames";
38+
39+
private List<String> domainNameList = null;
40+
41+
@Override
42+
public void initialize(ServiceConfiguration config) throws IOException {
43+
if (config.getProperty(DOMAIN_NAME_LIST) == null) {
44+
throw new IOException("No athenz domain name specified");
45+
}
46+
String domainNames = (String) config.getProperty(DOMAIN_NAME_LIST);
47+
domainNameList = Lists.newArrayList(domainNames.split(","));
48+
log.info("Supported domain names for athenz: {}", domainNameList);
49+
}
50+
51+
@Override
52+
public String getAuthMethodName() {
53+
return "athenz";
54+
}
55+
56+
@Override
57+
public String authenticate(AuthenticationDataSource authData) throws AuthenticationException {
58+
SocketAddress clientAddress;
59+
String roleToken;
60+
61+
if (authData.hasDataFromPeer()) {
62+
clientAddress = authData.getPeerAddress();
63+
} else {
64+
throw new AuthenticationException("Authentication data source does not have a client address");
65+
}
66+
67+
if (authData.hasDataFromCommand()) {
68+
roleToken = authData.getCommandData();
69+
} else if (authData.hasDataFromHttp()) {
70+
roleToken = authData.getHttpHeader(AuthZpeClient.ZPE_TOKEN_HDR);
71+
} else {
72+
throw new AuthenticationException("Authentication data source does not have a role token");
73+
}
74+
75+
if (roleToken == null) {
76+
throw new AuthenticationException("Athenz token is null, can't authenticate");
77+
}
78+
if (roleToken.isEmpty()) {
79+
throw new AuthenticationException("Athenz RoleToken is empty, Server is Using Athenz Authentication");
80+
}
81+
if (log.isDebugEnabled()) {
82+
log.debug("Athenz RoleToken : [{}] received from Client: {}", roleToken, clientAddress);
83+
}
84+
85+
RoleToken token = new RoleToken(roleToken);
86+
87+
if (!domainNameList.contains(token.getDomain())) {
88+
throw new AuthenticationException(
89+
String.format("Athenz RoleToken Domain mismatch, Expected: %s, Found: %s", domainNameList.toString(), token.getDomain()));
90+
}
91+
92+
// Synchronize for non-thread safe static calls inside athenz library
93+
synchronized (this) {
94+
PublicKey ztsPublicKey = AuthZpeClient.getZtsPublicKey(token.getKeyId());
95+
int allowedOffset = 0;
96+
97+
if (ztsPublicKey == null) {
98+
throw new AuthenticationException("Unable to retrieve ZTS Public Key");
99+
}
100+
101+
if (token.validate(ztsPublicKey, allowedOffset, null)) {
102+
log.info("Athenz Role Token : {}, Authorized for Client: {}", roleToken, clientAddress);
103+
return token.getPrincipal();
104+
} else {
105+
throw new AuthenticationException(
106+
String.format("Athenz Role Token Not Authorized from Client: %s", clientAddress));
107+
}
108+
}
109+
}
110+
111+
@Override
112+
public void close() throws IOException {
113+
}
114+
115+
private static final Logger log = LoggerFactory.getLogger(AuthenticationProviderAthenz.class);
116+
}

pulsar-client-auth-athenz/pom.xml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<!--
2+
3+
Copyright 2016 Yahoo Inc.
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
17+
-->
18+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
19+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
20+
<modelVersion>4.0.0</modelVersion>
21+
22+
<parent>
23+
<groupId>com.yahoo.pulsar</groupId>
24+
<artifactId>pulsar</artifactId>
25+
<version>1.17-SNAPSHOT</version>
26+
<relativePath>..</relativePath>
27+
</parent>
28+
29+
<artifactId>pulsar-client-auth-athenz</artifactId>
30+
<packaging>jar</packaging>
31+
<description>Athenz authentication plugin for java client</description>
32+
33+
<dependencies>
34+
35+
<dependency>
36+
<groupId>${project.groupId}</groupId>
37+
<artifactId>pulsar-client</artifactId>
38+
<version>${project.parent.version}</version>
39+
</dependency>
40+
41+
<dependency>
42+
<groupId>com.yahoo.athenz</groupId>
43+
<artifactId>zts_java_client</artifactId>
44+
</dependency>
45+
46+
</dependencies>
47+
</project>
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/**
2+
* Copyright 2016 Yahoo Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.yahoo.pulsar.client.impl.auth;
17+
18+
import java.io.File;
19+
import java.io.IOException;
20+
import java.util.Map;
21+
import java.util.concurrent.TimeUnit;
22+
import java.security.PrivateKey;
23+
24+
import com.yahoo.athenz.zts.RoleToken;
25+
import com.yahoo.athenz.zts.ZTSClient;
26+
import com.yahoo.athenz.auth.ServiceIdentityProvider;
27+
import com.yahoo.athenz.auth.impl.SimpleServiceIdentityProvider;
28+
import com.yahoo.athenz.auth.util.Crypto;
29+
import com.yahoo.pulsar.client.api.Authentication;
30+
import com.yahoo.pulsar.client.api.AuthenticationDataProvider;
31+
import com.yahoo.pulsar.client.api.PulsarClientException;
32+
import com.yahoo.pulsar.client.api.PulsarClientException.GettingAuthenticationDataException;
33+
34+
public class AuthenticationAthenz implements Authentication {
35+
36+
private transient ZTSClient ztsClient = null;
37+
private String tenantDomain;
38+
private String tenantService;
39+
private String providerDomain;
40+
private String privateKeyPath;
41+
private String keyId = "0";
42+
private long cachedRoleTokenTimestamp;
43+
private String roleToken;
44+
private final int minValidity = 2 * 60 * 60; // athenz will only give this token if it's at least valid for 2hrs
45+
private final int maxValidity = 24 * 60 * 60; // token has upto 24 hours validity
46+
private final int cacheDurationInHour = 1; // we will cache role token for an hour then ask athenz lib again
47+
48+
public AuthenticationAthenz() {
49+
}
50+
51+
@Override
52+
public String getAuthMethodName() {
53+
return "athenz";
54+
}
55+
56+
@Override
57+
synchronized public AuthenticationDataProvider getAuthData() throws PulsarClientException {
58+
if (cachedRoleTokenIsValid()) {
59+
return new AuthenticationDataAthenz(roleToken, getZtsClient().getHeader());
60+
}
61+
try {
62+
// the following would set up the API call that requests tokens from the server
63+
// that can only be used if they are 10 minutes from expiration and last twenty four hours
64+
RoleToken token = getZtsClient().getRoleToken(providerDomain, null, minValidity, maxValidity, false);
65+
roleToken = token.getToken();
66+
cachedRoleTokenTimestamp = System.nanoTime();
67+
return new AuthenticationDataAthenz(roleToken, getZtsClient().getHeader());
68+
} catch (Throwable t) {
69+
throw new GettingAuthenticationDataException(t);
70+
}
71+
}
72+
73+
private boolean cachedRoleTokenIsValid() {
74+
if (roleToken == null) {
75+
return false;
76+
}
77+
// Ensure we refresh the Athenz role token every hour to avoid using an expired role token
78+
return (System.nanoTime() - cachedRoleTokenTimestamp) < TimeUnit.HOURS.toNanos(cacheDurationInHour);
79+
}
80+
81+
@Override
82+
public void configure(Map<String, String> authParams) {
83+
this.tenantDomain = authParams.get("tenant_domain");
84+
this.tenantService = authParams.get("tenant_service");
85+
this.providerDomain = authParams.get("provider_domain");
86+
this.privateKeyPath = authParams.get("private_key_path");
87+
this.keyId = authParams.getOrDefault("key_id", "0");
88+
}
89+
90+
@Override
91+
public void start() throws PulsarClientException {
92+
}
93+
94+
@Override
95+
public void close() throws IOException {
96+
}
97+
98+
ZTSClient getZtsClient() {
99+
if (ztsClient == null) {
100+
PrivateKey privateKey = Crypto.loadPrivateKey(new File(privateKeyPath));
101+
ServiceIdentityProvider siaProvider = new SimpleServiceIdentityProvider(tenantDomain, tenantService,
102+
privateKey, keyId);
103+
ztsClient = new ZTSClient(null, tenantDomain, tenantService, siaProvider);
104+
}
105+
return ztsClient;
106+
}
107+
}

0 commit comments

Comments
 (0)