diff --git a/.classpath b/.classpath index 0856823..39abf1c 100644 --- a/.classpath +++ b/.classpath @@ -18,7 +18,7 @@ - + diff --git a/.gitignore b/.gitignore index a26e362..fd8e5b7 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,6 @@ buildNumber.properties # VSCode debug stuff .vscode + +# Mac garbage +.DS_Store diff --git a/pom.xml b/pom.xml index 34a861d..96be610 100644 --- a/pom.xml +++ b/pom.xml @@ -5,11 +5,11 @@ com.stack.security.auth.AWS kafka-auth-aws-iam - 1.0-SNAPSHOT + 0.2.0 jar - 12 - 12 + 8 + 8 Kafka AWS IAM LoginModule https://github.com/STACK-Fintech @@ -59,6 +59,24 @@ + + maven-assembly-plugin + 3.1.1 + + + jar-with-dependencies + + + + + make-assembly + package + + single + + + + org.apache.maven.plugins maven-surefire-plugin diff --git a/src/main/java/com/stack/security/auth/aws/AwsIamAuthenticateCallback.java b/src/main/java/com/stack/security/auth/aws/AwsIamAuthenticateCallback.java index e191d18..2e4f274 100644 --- a/src/main/java/com/stack/security/auth/aws/AwsIamAuthenticateCallback.java +++ b/src/main/java/com/stack/security/auth/aws/AwsIamAuthenticateCallback.java @@ -3,7 +3,7 @@ import javax.security.auth.callback.Callback; /* - * Authentication callback for SASL/AWS-IAM authentication. Callback handler must + * Authentication callback for SASL/AWS authentication. Callback handler must * set authenticated flag to true if the client provided password in the callback * matches the expected password. */ @@ -17,11 +17,11 @@ public class AwsIamAuthenticateCallback implements Callback { * Creates a callback with the password provided by the client * * @param accessKeyId The AWS Access Key ID provided by the client during - * SASL/PLAIN authentication + * SASL/AWS authentication * @param secretAccessKey The AWS Secret Access Key provided by the client - * during SASL/PLAIN authentication + * during SASL/AWS authentication * @param sessionToken The AWS Session Token provided by the client during - * SASL/PLAIN authentication + * SASL/AWS authentication * @return */ public AwsIamAuthenticateCallback(char[] accessKeyId, char[] secretAccessKey, char[] sessionToken) { @@ -31,21 +31,21 @@ public AwsIamAuthenticateCallback(char[] accessKeyId, char[] secretAccessKey, ch } /** - * Returns the AWS Access Key ID provided by the client during SASL/AWS-IAM + * Returns the AWS Access Key ID provided by the client during SASL/AWS */ public char[] accessKeyId() { return accessKeyId; } /** - * Returns the AWS Secret Access Key provided by the client during SASL/AWS-IAM + * Returns the AWS Secret Access Key provided by the client during SASL/AWS */ public char[] secretAccessKey() { return secretAccessKey; } /** - * Returns the AWS Session Token provided by the client during SASL/AWS-IAM + * Returns the AWS Session Token provided by the client during SASL/AWS */ public char[] sessionToken() { return sessionToken; diff --git a/src/main/java/com/stack/security/auth/aws/AwsIamLoginModule.java b/src/main/java/com/stack/security/auth/aws/AwsIamLoginModule.java index 0c54dc5..9bf6a3f 100644 --- a/src/main/java/com/stack/security/auth/aws/AwsIamLoginModule.java +++ b/src/main/java/com/stack/security/auth/aws/AwsIamLoginModule.java @@ -1,6 +1,7 @@ package com.stack.security.auth.aws; import java.util.Map; +import java.util.Set; import javax.security.auth.Subject; import javax.security.auth.callback.CallbackHandler; @@ -23,8 +24,8 @@ public class AwsIamLoginModule implements LoginModule { public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) { - var publicCredentials = subject.getPublicCredentials(); - var privateCredentials = subject.getPrivateCredentials(); + Set publicCredentials = subject.getPublicCredentials(); + Set privateCredentials = subject.getPrivateCredentials(); String arn = (String) options.get(ARN); if (arn != null) { publicCredentials.add(arn); diff --git a/src/main/java/com/stack/security/auth/aws/internal/AwsIamCallbackHandler.java b/src/main/java/com/stack/security/auth/aws/internal/AwsIamCallbackHandler.java index 1884723..5a76b69 100644 --- a/src/main/java/com/stack/security/auth/aws/internal/AwsIamCallbackHandler.java +++ b/src/main/java/com/stack/security/auth/aws/internal/AwsIamCallbackHandler.java @@ -34,8 +34,10 @@ import com.amazonaws.auth.AWSStaticCredentialsProvider; import com.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.auth.BasicSessionCredentials; +import com.amazonaws.services.securitytoken.AWSSecurityTokenService; import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceClientBuilder; import com.amazonaws.services.securitytoken.model.GetCallerIdentityRequest; +import com.amazonaws.services.securitytoken.model.GetCallerIdentityResult; import com.stack.security.auth.aws.AwsIamAuthenticateCallback; import com.stack.security.auth.aws.AwsIamLoginModule; @@ -82,7 +84,7 @@ protected boolean authenticate(String arn, char[] accessKeyId, char[] secretAcce } else { awsCreds = new BasicAWSCredentials(accessKeyIdString, secretAccessKeyString); } - var stsService = AWSSecurityTokenServiceClientBuilder.standard() + AWSSecurityTokenService stsService = AWSSecurityTokenServiceClientBuilder.standard() .withCredentials(new AWSStaticCredentialsProvider(awsCreds)).build(); // As an added measure of safety, the server can specify what AWS Account ID it @@ -92,8 +94,8 @@ protected boolean authenticate(String arn, char[] accessKeyId, char[] secretAcce // Check the credentials with AWS STS and GetCallerIdentity. - var request = new GetCallerIdentityRequest(); - var result = stsService.getCallerIdentity(request); + GetCallerIdentityRequest request = new GetCallerIdentityRequest(); + GetCallerIdentityResult result = stsService.getCallerIdentity(request); // Both the ARN returned by the credentials, and the configured account ID need // to match! diff --git a/src/main/java/com/stack/security/auth/aws/internal/AwsIamSaslServer.java b/src/main/java/com/stack/security/auth/aws/internal/AwsIamSaslServer.java index f8f9191..2848198 100644 --- a/src/main/java/com/stack/security/auth/aws/internal/AwsIamSaslServer.java +++ b/src/main/java/com/stack/security/auth/aws/internal/AwsIamSaslServer.java @@ -5,6 +5,7 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Objects; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; @@ -15,24 +16,29 @@ import javax.security.sasl.SaslServerFactory; import org.apache.kafka.common.errors.SaslAuthenticationException; +import org.apache.kafka.common.security.auth.AuthenticateCallbackHandler; + import com.stack.security.auth.aws.AwsIamAuthenticateCallback; /** - * Simple SaslServer implementation for SASL/AWS-IAM. Checks the provided AWS + * Simple SaslServer implementation for SASL/AWS. Checks the provided AWS * credentials against the AWS STS service and compares the returned identity * against the one provided by the user, as well as the allowed AWS Account to * authenticate. */ public class AwsIamSaslServer implements SaslServer { - public static final String AWS_IAM_MECHANISM = "AWS-IAM"; + public static final String AWS_MECHANISM = "AWS"; - private final CallbackHandler callbackHandler; + private final AuthenticateCallbackHandler callbackHandler; private boolean complete; private String authorizationId; public AwsIamSaslServer(CallbackHandler callbackHandler) { - this.callbackHandler = callbackHandler; + if (!(Objects.requireNonNull(callbackHandler) instanceof AuthenticateCallbackHandler)) + throw new IllegalArgumentException(String.format("Callback handler must be castable to %s: %s", + AuthenticateCallbackHandler.class.getName(), callbackHandler.getClass().getName())); + this.callbackHandler = (AuthenticateCallbackHandler) callbackHandler; } /** @@ -96,6 +102,7 @@ public byte[] evaluateResponse(byte[] responseBytes) throws SaslAuthenticationEx try { callbackHandler.handle(new Callback[] { nameCallback, authenticateCallback }); } catch (Throwable e) { + e.printStackTrace(System.out); throw new SaslAuthenticationException("Authentication failed: credentials for user could not be verified", e); } if (!authenticateCallback.authenticated()) @@ -124,8 +131,7 @@ private List extractTokens(String string) { } if (tokens.size() < 4 || tokens.size() > 5) - throw new SaslAuthenticationException( - "Invalid SASL/AWS-IAM response: expected 4 or 5 tokens, got " + tokens.size()); + throw new SaslAuthenticationException("Invalid SASL/AWS response: expected 4 or 5 tokens, got " + tokens.size()); return tokens; } @@ -139,7 +145,7 @@ public String getAuthorizationID() { @Override public String getMechanismName() { - return AWS_IAM_MECHANISM; + return AWS_MECHANISM; } @Override @@ -178,9 +184,8 @@ public static class AwsIamSaslServerFactory implements SaslServerFactory { public SaslServer createSaslServer(String mechanism, String protocol, String serverName, Map props, CallbackHandler cbh) throws SaslException { - if (!AWS_IAM_MECHANISM.equals(mechanism)) - throw new SaslException( - String.format("Mechanism \'%s\' is not supported. Only AWS-IAM is supported.", mechanism)); + if (!AWS_MECHANISM.equals(mechanism)) + throw new SaslException(String.format("Mechanism \'%s\' is not supported. Only AWS is supported.", mechanism)); return new AwsIamSaslServer(cbh); } @@ -188,12 +193,12 @@ public SaslServer createSaslServer(String mechanism, String protocol, String ser @Override public String[] getMechanismNames(Map props) { if (props == null) - return new String[] { AWS_IAM_MECHANISM }; + return new String[] { AWS_MECHANISM }; String noPlainText = (String) props.get(Sasl.POLICY_NOPLAINTEXT); if ("true".equals(noPlainText)) return new String[] {}; else - return new String[] { AWS_IAM_MECHANISM }; + return new String[] { AWS_MECHANISM }; } } } diff --git a/src/main/java/com/stack/security/auth/aws/internal/AwsIamSaslServerProvider.java b/src/main/java/com/stack/security/auth/aws/internal/AwsIamSaslServerProvider.java index f54a356..6fe0a95 100644 --- a/src/main/java/com/stack/security/auth/aws/internal/AwsIamSaslServerProvider.java +++ b/src/main/java/com/stack/security/auth/aws/internal/AwsIamSaslServerProvider.java @@ -11,8 +11,8 @@ public class AwsIamSaslServerProvider extends Provider { @SuppressWarnings("deprecation") protected AwsIamSaslServerProvider() { - super("SASL/AWS-IAM Server Provider", 1.0, "SASL/AWS-IAM Server Provider for Kafka"); - put("SaslServerFactory." + AwsIamSaslServer.AWS_IAM_MECHANISM, AwsIamSaslServerFactory.class.getName()); + super("SASL/AWS Server Provider", 1.0, "SASL/AWS Server Provider for Kafka"); + put("SaslServerFactory." + AwsIamSaslServer.AWS_MECHANISM, AwsIamSaslServerFactory.class.getName()); } public static void initialize() { diff --git a/src/test/java/com/stack/security/auth/aws/AwsIamSaslServerTest.java b/src/test/java/com/stack/security/auth/aws/AwsIamSaslServerTest.java index a9727d5..c26b81f 100644 --- a/src/test/java/com/stack/security/auth/aws/AwsIamSaslServerTest.java +++ b/src/test/java/com/stack/security/auth/aws/AwsIamSaslServerTest.java @@ -24,8 +24,11 @@ import com.amazonaws.auth.AWSStaticCredentialsProvider; import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.securitytoken.AWSSecurityTokenService; import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceClientBuilder; +import com.amazonaws.services.securitytoken.model.Credentials; import com.amazonaws.services.securitytoken.model.GetSessionTokenRequest; +import com.amazonaws.services.securitytoken.model.GetSessionTokenResult; import com.stack.security.auth.aws.internal.AwsIamCallbackHandler; import com.stack.security.auth.aws.internal.AwsIamSaslServer; import com.stack.security.authenticator.FakeJaasConfig; @@ -48,12 +51,12 @@ public class AwsIamSaslServerTest { @BeforeAll public static void setUp() { FakeJaasConfig jaasConfig = new FakeJaasConfig(); - var options = new HashMap(); + HashMap options = new HashMap(); options.put("aws_account_id", AWS_ACCOUNT_ID); jaasConfig.addEntry("jaasContext", AwsIamLoginModule.class.getName(), options); JaasContext jaasContext = new JaasContext("jaasContext", JaasContext.Type.SERVER, jaasConfig, null); AwsIamCallbackHandler callbackHandler = new AwsIamCallbackHandler(); - callbackHandler.configure(null, "AWS-IAM", jaasContext.configurationEntries()); + callbackHandler.configure(null, "AWS", jaasContext.configurationEntries()); saslServer = new AwsIamSaslServer(callbackHandler); } @@ -98,11 +101,11 @@ public void emptyTokens() { e = assertThrows(SaslAuthenticationException.class, () -> saslServer.evaluateResponse(String.format("%s%s%s%s%s%s%s%s%s%s%s", ARN, nul, ARN, nul, AWS_ACCESS_KEY_ID, nul, AWS_SECRET_ACCESS_KEY, nul, "s", nul, "q").getBytes(StandardCharsets.UTF_8))); - assertEquals("Invalid SASL/AWS-IAM response: expected 4 or 5 tokens, got 6", e.getMessage()); + assertEquals("Invalid SASL/AWS response: expected 4 or 5 tokens, got 6", e.getMessage()); e = assertThrows(SaslAuthenticationException.class, () -> saslServer.evaluateResponse( String.format("%s%s%s%s", ARN, nul, ARN, nul, AWS_ACCESS_KEY_ID, nul).getBytes(StandardCharsets.UTF_8))); - assertEquals("Invalid SASL/AWS-IAM response: expected 4 or 5 tokens, got 3", e.getMessage()); + assertEquals("Invalid SASL/AWS response: expected 4 or 5 tokens, got 3", e.getMessage()); } @Test @@ -118,12 +121,12 @@ public void authorizationFailsForWrongAuthorizationId() { @Test public void authorizationSuccessWithValidKeysAndSession() throws Exception { - var awsCreds = new BasicAWSCredentials(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY); - var stsService = AWSSecurityTokenServiceClientBuilder.standard() + BasicAWSCredentials awsCreds = new BasicAWSCredentials(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY); + AWSSecurityTokenService stsService = AWSSecurityTokenServiceClientBuilder.standard() .withCredentials(new AWSStaticCredentialsProvider(awsCreds)).build(); GetSessionTokenRequest request = new GetSessionTokenRequest(); - var sessionTokenResult = stsService.getSessionToken(request); - var creds = sessionTokenResult.getCredentials(); + GetSessionTokenResult sessionTokenResult = stsService.getSessionToken(request); + Credentials creds = sessionTokenResult.getCredentials(); byte[] nextChallenge = saslServer.evaluateResponse( saslMessage(ARN, ARN, creds.getAccessKeyId(), creds.getSecretAccessKey(), creds.getSessionToken())); assertEquals(0, nextChallenge.length); diff --git a/src/test/java/com/stack/security/authenticator/FakeJaasConfig.java b/src/test/java/com/stack/security/authenticator/FakeJaasConfig.java index 268e00f..c26585b 100644 --- a/src/test/java/com/stack/security/authenticator/FakeJaasConfig.java +++ b/src/test/java/com/stack/security/authenticator/FakeJaasConfig.java @@ -98,7 +98,7 @@ public void addEntry(String name, String loginModule, Map option private static String loginModule(String mechanism) { String loginModule; switch (mechanism) { - case "AWS-IAM": + case "AWS": loginModule = AwsIamLoginModule.class.getName(); break; default: @@ -118,7 +118,7 @@ public static Map defaultClientOptions() { public static Map defaultServerOptions(String mechanism) { Map options = new HashMap<>(); switch (mechanism) { - case "AWS-IAM": + case "AWS": options.put(AWS_ACCOUNT_ID, AWS_ACCOUNT_ID_VALUE); break; default: