Skip to content

Commit

Permalink
Implement configurable token auth claim (apache#3826)
Browse files Browse the repository at this point in the history
  • Loading branch information
klevy-toast authored and merlimat committed Mar 14, 2019
1 parent c83631b commit 85e577d
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 1 deletion.
3 changes: 3 additions & 0 deletions conf/broker.conf
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,9 @@ tokenSecretKey=
# tokenPublicKey=file:///my/public.key
tokenPublicKey=

# The token "claim" that will be interpreted as the authentication "role" or "principal" by AuthenticationProviderToken (defaults to "sub" if blank)
tokenAuthClaim=

### --- BookKeeper Client --- ###

# Authentication plugin to use when connecting to bookies
Expand Down
2 changes: 2 additions & 0 deletions conf/proxy.conf
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,8 @@ tokenSecretKey=
# tokenPublicKey=file:///my/public.key
tokenPublicKey=

# The token "claim" that will be interpreted as the authentication "role" or "principal" by AuthenticationProviderToken (defaults to "sub" if blank)
tokenAuthClaim=

### --- Deprecated config variables --- ###

Expand Down
3 changes: 3 additions & 0 deletions conf/standalone.conf
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,9 @@ athenzDomainNames=
# When this parameter is not empty, unauthenticated users perform as anonymousUserRole
anonymousUserRole=

# The token "claim" that will be interpreted as the authentication "role" or "principal" by AuthenticationProviderToken (defaults to "sub" if blank)
tokenAuthClaim=

### --- BookKeeper Client --- ###

# Authentication plugin to use when connecting to bookies
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,13 @@ public class AuthenticationProviderToken implements AuthenticationProvider {
// When public/private key pair is configured
final static String CONF_TOKEN_PUBLIC_KEY = "tokenPublicKey";

// The token's claim that corresponds to the "role" string
final static String CONF_TOKEN_AUTH_CLAIM = "tokenAuthClaim";

final static String TOKEN = "token";

private Key validationKey;
private String roleClaim;

@Override
public void close() throws IOException {
Expand All @@ -55,6 +59,7 @@ public void close() throws IOException {
@Override
public void initialize(ServiceConfiguration config) throws IOException {
this.validationKey = getValidationKey(config);
this.roleClaim = getTokenRoleClaim(config);
}

@Override
Expand Down Expand Up @@ -106,7 +111,7 @@ private String parseToken(final String token) throws AuthenticationException {
.setSigningKey(validationKey)
.parse(token);

return jwt.getBody().getSubject();
return jwt.getBody().get(roleClaim, String.class);
} catch (JwtException e) {
throw new AuthenticationException("Failed to authentication token: " + e.getMessage());
}
Expand All @@ -130,4 +135,13 @@ private Key getValidationKey(ServiceConfiguration conf) throws IOException {
throw new IOException("No secret key was provided for token authentication");
}
}

private String getTokenRoleClaim(ServiceConfiguration conf) throws IOException {
if (conf.getProperty(CONF_TOKEN_AUTH_CLAIM) != null
&& StringUtils.isNotBlank((String) conf.getProperty(CONF_TOKEN_AUTH_CLAIM))) {
return (String) conf.getProperty(CONF_TOKEN_AUTH_CLAIM);
} else {
return Claims.SUBJECT;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import java.security.KeyPair;
import java.security.PrivateKey;
import java.sql.Date;
import java.util.HashMap;
import java.util.Optional;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -293,6 +294,56 @@ public String getCommandData() {
provider.close();
}

@Test
public void testAuthSecretKeyPairWithCustomClaim() throws Exception {
String authRoleClaim = "customClaim";
String authRole = "my-test-role";

KeyPair keyPair = Keys.keyPairFor(SignatureAlgorithm.RS256);

String privateKeyStr = AuthTokenUtils.encodeKeyBase64(keyPair.getPrivate());
String publicKeyStr = AuthTokenUtils.encodeKeyBase64(keyPair.getPublic());

AuthenticationProviderToken provider = new AuthenticationProviderToken();

Properties properties = new Properties();
// Use public key for validation
properties.setProperty(AuthenticationProviderToken.CONF_TOKEN_PUBLIC_KEY, publicKeyStr);
// Set custom claim field
properties.setProperty(AuthenticationProviderToken.CONF_TOKEN_AUTH_CLAIM, authRoleClaim);

ServiceConfiguration conf = new ServiceConfiguration();
conf.setProperties(properties);
provider.initialize(conf);


// Use private key to generate token
PrivateKey privateKey = AuthTokenUtils.decodePrivateKey(Decoders.BASE64.decode(privateKeyStr));
String token = Jwts.builder()
.setClaims(new HashMap<String, Object>() {{
put(authRoleClaim, authRole);
}})
.signWith(privateKey)
.compact();


// Pulsar protocol auth
String role = provider.authenticate(new AuthenticationDataSource() {
@Override
public boolean hasDataFromCommand() {
return true;
}

@Override
public String getCommandData() {
return token;
}
});
assertEquals(role, authRole);

provider.close();
}

@Test(expectedExceptions = AuthenticationException.class)
public void testAuthenticateWhenNoJwtPassed() throws AuthenticationException {
AuthenticationProviderToken provider = new AuthenticationProviderToken();
Expand Down
2 changes: 2 additions & 0 deletions site2/docs/reference-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ Pulsar brokers are responsible for handling incoming messages from producers, di
|tlsCiphers|Specify the tls cipher the broker will use to negotiate during TLS Handshake. Multiple values can be specified, separated by commas. Example:- ```TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256```||
|tokenSecretKey| Configure the secret key to be used to validate auth tokens. The key can be specified like: `tokenSecretKey=data:base64,xxxxxxxxx` or `tokenSecretKey=file:///my/secret.key`||
|tokenPublicKey| Configure the public key to be used to validate auth tokens. The key can be specified like: `tokenPublicKey=data:base64,xxxxxxxxx` or `tokenPublicKey=file:///my/secret.key`||
|tokenAuthClaim| Specify which of the token's claims will be used as the authentication "principal" or "role". The default "sub" claim will be used if this is left blank ||
|maxUnackedMessagesPerConsumer| Max number of unacknowledged messages allowed to receive messages by a consumer on a shared subscription. Broker will stop sending messages to consumer once, this limit reaches until consumer starts acknowledging messages back. Using a value of 0, is disabling unackeMessage limit check and consumer can receive messages without any restriction |50000|
|maxUnackedMessagesPerSubscription| Max number of unacknowledged messages allowed per shared subscription. Broker will stop dispatching messages to all consumers of the subscription once this limit reaches until consumer starts acknowledging messages back and unack count reaches to limit/2. Using a value of 0, is disabling unackedMessage-limit check and dispatcher can dispatch messages without any restriction |200000|
|subscriptionRedeliveryTrackerEnabled| Enable subscription message redelivery tracker |true|
Expand Down Expand Up @@ -448,6 +449,7 @@ The [Pulsar proxy](concepts-architecture-overview.md#pulsar-proxy) can be config
|tlsCiphers|Specify the tls cipher the broker will use to negotiate during TLS Handshake. Multiple values can be specified, separated by commas. Example:- ```TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256```||
|tokenSecretKey| Configure the secret key to be used to validate auth tokens. The key can be specified like: `tokenSecretKey=data:base64,xxxxxxxxx` or `tokenSecretKey=file:///my/secret.key`||
|tokenPublicKey| Configure the public key to be used to validate auth tokens. The key can be specified like: `tokenPublicKey=data:base64,xxxxxxxxx` or `tokenPublicKey=file:///my/secret.key`||
|tokenAuthClaim| Specify the token claim that will be used as the authentication "principal" or "role". The "subject" field will be used if this is left blank ||

## ZooKeeper

Expand Down

0 comments on commit 85e577d

Please sign in to comment.