scopeInfo, Resource resource, String operation) {
95 | for (int i = 0; i < scopeInfo.size(); i++) {
96 | OAuthScope scope = scopeInfo.get(i);
97 | String lowerCaseOperation = operation.toLowerCase();
98 | String lowerCaseResourceName = resource.name().toLowerCase();
99 | String lowerCaseCaseResourceType = resource.resourceType().toString().toLowerCase();
100 |
101 | boolean operationVal = scope.getOperation().toLowerCase().equals(lowerCaseOperation);
102 | boolean nameVal = scope.getResourceName().toLowerCase().equals(lowerCaseResourceName);
103 | boolean typeVal = scope.getResourceType().toLowerCase().equals(lowerCaseCaseResourceType);
104 |
105 | if (operationVal && nameVal && typeVal) {
106 | log.info("Successfully Authorized.");
107 | return true;
108 | }
109 | }
110 | log.info("Not Authorized to operate on the given resource.");
111 | return false;
112 | }
113 |
114 | /**
115 | * Parse topic and Operation out of scope.
116 | *
117 | * @param scopes set of scopes
118 | * @return return list of pairs, each pair is a topic/operation Scope format urn:kafka:::
119 | */
120 | protected List parseScopes(java.util.Set scopes) {
121 | List result = new ArrayList<>();
122 | for (String scope : scopes) {
123 | String[] scopeArray = scope.split("\\s+");
124 | for (String str : scopeArray){
125 | convertScope(result, str);
126 | }
127 | }
128 | return result;
129 | }
130 |
131 | /**
132 | * convertScope.
133 | * @param result list of scopesInfo
134 | * @param scope string of scope
135 | */
136 | private void convertScope(List result, String scope) {
137 | String[] str = scope.split(":");
138 | if (str.length == 5) {
139 | String type = str[2];
140 | String name = str[3];
141 | String operation = str[4];
142 | OAuthScope oAuthScope = new OAuthScope();
143 | oAuthScope.setOperation(operation);
144 | oAuthScope.setResourceName(name);
145 | oAuthScope.setResourceType(type);
146 | result.add(oAuthScope);
147 | } else {
148 | log.error("Unable to parse scope. Incorrect format: {}.", scope);
149 | }
150 | }
151 |
152 | @Override
153 | public void addAcls(Set acls, Resource resource) {
154 |
155 | }
156 |
157 | @Override
158 | public boolean removeAcls(Set acls, Resource resource) {
159 | return false;
160 | }
161 |
162 | @Override
163 | public boolean removeAcls(Resource resource) {
164 | return false;
165 | }
166 |
167 | @Override
168 | public Set getAcls(Resource resource) {
169 | return null;
170 | }
171 |
172 | @Override
173 | public Map> getAcls(KafkaPrincipal principal) {
174 | return null;
175 | }
176 |
177 | @Override
178 | public Map> getAcls() {
179 | return null;
180 | }
181 |
182 | @Override
183 | public void close() {
184 |
185 | }
186 |
187 |
188 | @Override
189 | public void configure(java.util.Map map) {
190 |
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/kafka-oauth/src/main/java/com/bfm/kafka/security/oauthbearer/CustomPrincipal.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright © 2020 BlackRock 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.bfm.kafka.security.oauthbearer;
17 |
18 | import org.apache.kafka.common.security.auth.KafkaPrincipal;
19 |
20 | /**
21 | * The type Custom principal.
22 | */
23 | public class CustomPrincipal extends KafkaPrincipal {
24 |
25 | private OAuthBearerTokenJwt oauthBearerTokenJwt;
26 |
27 | /**
28 | * Instantiates a new Custom principal.
29 | *
30 | * @param principalType the principal type
31 | * @param name the name
32 | */
33 | public CustomPrincipal(String principalType, String name) {
34 | super(principalType, name);
35 | }
36 |
37 | /**
38 | * Gets oauth bearer token jwt.
39 | *
40 | * @return the oauth bearer token jwt
41 | */
42 | public OAuthBearerTokenJwt getOauthBearerTokenJwt() {
43 | return oauthBearerTokenJwt;
44 | }
45 |
46 | /**
47 | * Sets oauth bearer token jwt.
48 | *
49 | * @param oauthBearerTokenJwt the oauth bearer token jwt
50 | */
51 | public void setOauthBearerTokenJwt(OAuthBearerTokenJwt oauthBearerTokenJwt) {
52 | this.oauthBearerTokenJwt = oauthBearerTokenJwt;
53 | }
54 |
55 | @Override
56 | public String toString() {
57 | return "CustomPrincipal{" +
58 | "oauthBearerTokenJwt=" + oauthBearerTokenJwt +
59 | "} " + super.toString();
60 | }
61 | }
--------------------------------------------------------------------------------
/kafka-oauth/src/main/java/com/bfm/kafka/security/oauthbearer/CustomPrincipalBuilder.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright © 2020 BlackRock 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.bfm.kafka.security.oauthbearer;
17 |
18 | import org.apache.kafka.common.KafkaException;
19 | import org.apache.kafka.common.security.auth.AuthenticationContext;
20 | import org.apache.kafka.common.security.auth.KafkaPrincipalBuilder;
21 | import org.apache.kafka.common.security.auth.SaslAuthenticationContext;
22 |
23 | /**
24 | * The type Custom principal builder.
25 | */
26 | public class CustomPrincipalBuilder implements KafkaPrincipalBuilder {
27 | @Override
28 | public CustomPrincipal build(AuthenticationContext authenticationContext) throws KafkaException{
29 | try {
30 | CustomPrincipal customPrincipal;
31 |
32 | if (authenticationContext instanceof SaslAuthenticationContext) {
33 | SaslAuthenticationContext context = (SaslAuthenticationContext) authenticationContext;
34 | OAuthBearerTokenJwt token = (OAuthBearerTokenJwt) context.server()
35 | .getNegotiatedProperty("OAUTHBEARER.token");
36 |
37 | customPrincipal = new CustomPrincipal("User", token.principalName());
38 | customPrincipal.setOauthBearerTokenJwt(token);
39 |
40 | return customPrincipal;
41 | } else {
42 | throw new KafkaException("Failed to build CustomPrincipal. SaslAuthenticationContext is required.");
43 | }
44 | } catch (Exception ex) {
45 | throw new KafkaException("Failed to build CustomPrincipal due to: ", ex);
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/kafka-oauth/src/main/java/com/bfm/kafka/security/oauthbearer/EnvironmentVariablesUtil.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright © 2020 BlackRock 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.bfm.kafka.security.oauthbearer;
17 |
18 | /**
19 | * The type Environment variables util.
20 | */
21 | public class EnvironmentVariablesUtil {
22 |
23 | /**
24 | * Gets boolean environment variable.
25 | *
26 | * @param envName the env name
27 | * @param defaultValue the default value
28 | * @return the boolean environment variable
29 | */
30 | public static Boolean getBooleanEnvironmentVariable(String envName, Boolean defaultValue) {
31 | Boolean result;
32 | String env = System.getenv(envName);
33 | if (env == null) {
34 | result = defaultValue;
35 | } else {
36 | result = Boolean.valueOf(env);
37 | }
38 | return result;
39 | }
40 |
41 | /**
42 | * Gets string environment variable.
43 | *
44 | * @param envName the env name
45 | * @param defaultValue the default value
46 | * @return the string environment variable
47 | */
48 | public static String getStringEnvironmentVariable(String envName, String defaultValue) {
49 | String result;
50 | String env = System.getenv(envName);
51 | if (env == null) {
52 | result = defaultValue;
53 | } else {
54 | result = env;
55 | }
56 | return result;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/kafka-oauth/src/main/java/com/bfm/kafka/security/oauthbearer/OAuthAuthenticateCallbackHandler.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright © 2020 BlackRock 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.bfm.kafka.security.oauthbearer;
17 |
18 | import org.apache.kafka.common.security.auth.AuthenticateCallbackHandler;
19 | import org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginModule;
20 | import org.slf4j.Logger;
21 | import org.slf4j.LoggerFactory;
22 |
23 | import javax.security.auth.callback.Callback;
24 | import javax.security.auth.callback.UnsupportedCallbackException;
25 | import javax.security.auth.login.AppConfigurationEntry;
26 | import java.io.IOException;
27 | import java.util.Collections;
28 | import java.util.List;
29 | import java.util.Map;
30 | import java.util.Objects;
31 |
32 | /**
33 | * As the implementation of the Kafka AuthenticateCallbackHandler interface, this class is an abstract callback handler
34 | * that allows subclasses to reuse common logic and to inject custom functions
35 | *
36 | * @param the generic type for callback functions
37 | */
38 | public abstract class OAuthAuthenticateCallbackHandler implements AuthenticateCallbackHandler {
39 | //region Member Variables
40 |
41 | private final Logger log = LoggerFactory.getLogger(OAuthAuthenticateCallbackHandler.class);
42 | private OAuthService oauthService;
43 | private boolean configured = false;
44 | private Map moduleOptions = null;
45 | private List jaasConfigEntries;
46 | private Class callBackHandlerClass;
47 |
48 | //endregion
49 |
50 | //region Constructors
51 |
52 | /**
53 | * Instantiates a new O auth authenticate callback handler.
54 | *
55 | * @param oauthService the oauth service
56 | * @param callBackHandlerClass the call back handler class
57 | */
58 | public OAuthAuthenticateCallbackHandler(OAuthService oauthService, Class callBackHandlerClass) {
59 | // validate the parameters
60 | Objects.requireNonNull(oauthService);
61 | Objects.requireNonNull(callBackHandlerClass);
62 |
63 | // initialize member variables
64 | this.oauthService = oauthService;
65 | this.callBackHandlerClass = callBackHandlerClass;
66 | }
67 |
68 | //endregion
69 |
70 | //region Protected Properties
71 |
72 | /**
73 | * Gets oauth service.
74 | *
75 | * @return the oauth service
76 | */
77 | protected OAuthService getOauthService() {
78 | return this.oauthService;
79 | }
80 |
81 | /**
82 | * Is configured boolean.
83 | *
84 | * @return the boolean
85 | */
86 | protected boolean isConfigured() {
87 | return this.configured;
88 | }
89 |
90 | //endregion
91 |
92 | //region Public Methods
93 |
94 | /**
95 | * Configures this callback handler for the specified SASL mechanism.
96 | *
97 | * @param configs Key-value pairs containing the parsed configuration options of the client or broker.
98 | * Note that these are the Kafka configuration options and not the JAAS configuration
99 | * options. JAAS config options may be obtained from `jaasConfigEntries` for callbacks
100 | * which obtain some configs from the JAAS configuration. For configs that may be specified
101 | * as both Kafka config as well as JAAS config (e.g. sasl.kerberos.service.name), the
102 | * configuration is treated as invalid if conflicting values are provided.
103 | * @param saslMechanism Negotiated SASL mechanism. For clients, this is the SASL mechanism configured for the
104 | * client. For brokers, this is the mechanism negotiated with the client and is one of the
105 | * mechanisms enabled on the broker.
106 | * @param jaasConfigEntries JAAS configuration entries from the JAAS login context. This list contains a single
107 | * entry for clients and may contain more than one entry for brokers if multiple mechanisms
108 | * are enabled on a listener using static JAAS configuration where there is no mapping
109 | * between mechanisms and login module entries. In this case, callback handlers can use
110 | * the login module in `jaasConfigEntries` to identify the entry corresponding to
111 | * `saslMechanism`. Alternatively, dynamic JAAS configuration option
112 | * SaslConfigs.SASL_JAAS_CONFIG may be configured on brokers with listener and mechanism
113 | * prefix, in which case only the configuration entry corresponding to `saslMechanism` will
114 | * be provided in `jaasConfigEntries`.
115 | */
116 | @Override
117 | public void configure(Map configs, String saslMechanism, List jaasConfigEntries) {
118 | log.debug("Starting to configure OAuth authentication callback handler.");
119 |
120 | log.debug("Validate method parameters.");
121 | Objects.requireNonNull(configs);
122 | Objects.requireNonNull(saslMechanism);
123 | Objects.requireNonNull(jaasConfigEntries);
124 | String errMsg;
125 |
126 | // validate the SASL Mechanism is set correctly
127 | if (OAuthBearerLoginModule.OAUTHBEARER_MECHANISM.equals(saslMechanism)) {
128 | if (jaasConfigEntries.size() == 1 && jaasConfigEntries.get(0) != null) {
129 | this.moduleOptions = Collections.unmodifiableMap(
130 | (Map) jaasConfigEntries.get(0).getOptions());
131 | //Configure OAuthService using the JAAS file properties
132 | this.oauthService.setOAuthConfiguration(moduleOptions);
133 | // the handle has been configured
134 | configured = true;
135 | } else {
136 | errMsg = String.format(
137 | "Must supply exactly 1 non-null JAAS mechanism configuration (size was %d)",
138 | jaasConfigEntries.size());
139 |
140 | log.error(errMsg);
141 | }
142 | } else {
143 | errMsg = String.format("Unexpected SASL mechanism: %s", saslMechanism);
144 |
145 | log.error(errMsg);
146 | }
147 | }
148 |
149 | /**
150 | * Handle OAuth bearer token callbacks which invokes the generic function provided by the sub classs
151 | *
152 | * The handle method implementation checks the instance(s) of the Callback object(s) passed in to retrieve or
153 | * display the requested information.
154 | *
155 | * @param callbacks An array of Callback objects provided by an underlying security service which contains the
156 | * information requested to be retrieved or displayed.
157 | * @throws IOException If the generic callback function throws an Exception
158 | * @throws UnsupportedCallbackException If the input callbackType does not match the subclass
159 | */
160 | @Override
161 | public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
162 | log.debug("Starting to handle OAuth bearer token login callbacks.");
163 |
164 | // check to see if the handler has been configured
165 | log.debug("Check to see of the handler has been configured.");
166 | if (!isConfigured()) {
167 | String errMsg = "Callback handler not configured";
168 |
169 | log.error(errMsg);
170 |
171 | throw new IllegalStateException(errMsg);
172 | }
173 |
174 | // loop through all of the callbacks looking for the OAuth bearer token callbacks
175 | log.debug("Loop through all of the callbacks looking for the OAuth bearer token callbacks.");
176 | for (Callback callback : callbacks) {
177 | // check the type of the callback
178 | log.debug("Check the type of the callback.");
179 | Class callBackType = callback.getClass();
180 | if (callBackType.equals(this.callBackHandlerClass)) {
181 | // the callback is the correct type for this handler, process the callback
182 | log.debug("The callback is the correct type of this handler, process the callback.");
183 | try {
184 | T oauthBearerTokenCallback = (T) callback;
185 | handleCallback(oauthBearerTokenCallback);
186 | } catch (Exception e) { // handle exception here so that we can log exception where it happens
187 | String errMsg = String.format(
188 | "Error handing OAuth bearer token login callback, Message: %s",
189 | e.getMessage());
190 |
191 | log.error(errMsg);
192 |
193 | throw new IOException(errMsg, e);
194 | }
195 | } else {
196 | // the callback is not the correct type for this handler, throw a exception
197 | String errMsg = String.format(
198 | "Callback is not a OAuthBearerTokenCallback, Type: %s",
199 | callback);
200 | log.debug(errMsg);
201 |
202 | throw new UnsupportedCallbackException(callback);
203 | }
204 | }
205 | log.debug("Finished handling OAuth bearer token login callbacks.");
206 | }
207 |
208 | /**
209 | * Implementation of the interface.
210 | * This class does not have resource dependent attributes, therefore this method is empty.
211 | */
212 | @Override
213 | public void close() {
214 | }
215 |
216 | //endregion
217 |
218 | //region Protected Methods
219 |
220 | /**
221 | * Handle callback and should be implemented by sub classes
222 | *
223 | * @param oauthBearerTokenCallback the oauth bearer token callback
224 | */
225 | protected abstract void handleCallback(T oauthBearerTokenCallback) throws IOException;
226 |
227 | //endregion
228 | }
229 |
--------------------------------------------------------------------------------
/kafka-oauth/src/main/java/com/bfm/kafka/security/oauthbearer/OAuthAuthenticateLoginCallbackHandler.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright © 2020 BlackRock 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.bfm.kafka.security.oauthbearer;
17 |
18 | import org.apache.kafka.common.security.oauthbearer.OAuthBearerTokenCallback;
19 | import org.slf4j.Logger;
20 | import org.slf4j.LoggerFactory;
21 |
22 | import java.io.IOException;
23 | import java.util.Objects;
24 |
25 | /**
26 | * A Callback for use by the SaslClient and Login implementations when they require an OAuth 2 bearer token.
27 | */
28 | public class OAuthAuthenticateLoginCallbackHandler extends OAuthAuthenticateCallbackHandler {
29 | //region Member Variables
30 |
31 | private final Logger log = LoggerFactory.getLogger(OAuthAuthenticateLoginCallbackHandler.class);
32 |
33 | //endregion
34 |
35 | //region Constructors
36 |
37 | /**
38 | * Instantiates a new O auth authenticate login callback handler.
39 | */
40 | public OAuthAuthenticateLoginCallbackHandler() {
41 | super(new OAuthServiceImpl(), OAuthBearerTokenCallback.class);
42 | }
43 |
44 | //endregion
45 |
46 | //region Protected Methods
47 |
48 | /**
49 | * This method handles attempt to login by requesting an access token from the OAuth Server
50 | * @param oauthBearerTokenCallback the oauth bearer token callback
51 | * @throws IOException - if OAuth Server did not grant an access token
52 | */
53 | protected void handleCallback(OAuthBearerTokenCallback oauthBearerTokenCallback) throws IOException {
54 | log.debug("Starting to handle OAuth bearer token login callback.");
55 |
56 | // make sure that the parameters are valid
57 | log.debug("Validate method parameters.");
58 | Objects.requireNonNull(oauthBearerTokenCallback);
59 |
60 | // check to see if oauthBearerTokenCallback already had a token
61 | if (oauthBearerTokenCallback.token() != null) {
62 |
63 | String errMsg = "OAuth bearer token callback already had a token.";
64 |
65 | log.error(errMsg);
66 |
67 | throw new IllegalArgumentException(errMsg);
68 | }
69 |
70 | // acquire access token
71 | log.debug("Acquire access token for OAuth bearer token callback.");
72 | OAuthBearerTokenJwt token = this.getOauthService().requestAccessToken();
73 |
74 | // check to see an access token was returned
75 | log.debug("Check to see if an access token as returned.");
76 | if (token == null) {
77 | String errMsg = "Access token was not returned.";
78 |
79 | log.error(errMsg);
80 |
81 | throw new IllegalArgumentException(errMsg);
82 | }
83 |
84 | // access token was returned, set the access token on the OAuth bearer token callback
85 | log.info("Access token was returned, set the access token on the OAuth bearer token callback.");
86 | oauthBearerTokenCallback.token(token);
87 | log.debug("Finished handling OAuth bearer token login callback.");
88 | }
89 |
90 | //endregion
91 | }
92 |
--------------------------------------------------------------------------------
/kafka-oauth/src/main/java/com/bfm/kafka/security/oauthbearer/OAuthAuthenticateValidatorCallbackHandler.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright © 2020 BlackRock 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.bfm.kafka.security.oauthbearer;
17 |
18 | import org.apache.kafka.common.security.oauthbearer.OAuthBearerValidatorCallback;
19 | import org.apache.kafka.common.utils.Time;
20 | import org.slf4j.Logger;
21 | import org.slf4j.LoggerFactory;
22 |
23 | import java.io.IOException;
24 | import java.util.Objects;
25 |
26 | /**
27 | * A Callback for use by the SaslClient and Validator implementations when they require an OAuth 2 bearer token.
28 | */
29 | public class OAuthAuthenticateValidatorCallbackHandler extends OAuthAuthenticateCallbackHandler {
30 | //region Member Variables
31 |
32 | private final Logger log = LoggerFactory.getLogger(OAuthAuthenticateValidatorCallbackHandler.class);
33 | private Time time = Time.SYSTEM;
34 |
35 |
36 | //endregion
37 |
38 | //region Constructors
39 |
40 | /**
41 | * Instantiates a new O auth authenticate validator callback handler.
42 | */
43 | public OAuthAuthenticateValidatorCallbackHandler() {
44 | super(new OAuthServiceImpl(), OAuthBearerValidatorCallback.class);
45 | }
46 |
47 | //endregion
48 |
49 | //region Protected Methods
50 |
51 | /**
52 | * This callback validates an access token in the OAuth Server
53 | * @param callback
54 | * @throws IOException - if the access token cannot be validated in the OAuth Server
55 | */
56 | protected void handleCallback(OAuthBearerValidatorCallback callback) throws IOException {
57 | log.debug("Starting to handle OAuth bearer token validation callback.");
58 |
59 | // make sure that the parameters are valid
60 | log.debug("Validate method parameters.");
61 | Objects.requireNonNull(callback);
62 |
63 | String accessToken = callback.tokenValue();
64 | if (accessToken == null) {
65 | String errMsg = "Callback missing required token value.";
66 | log.error(errMsg);
67 | throw new IllegalArgumentException(errMsg);
68 | }
69 |
70 | log.debug("Validate access token.");
71 | OAuthBearerTokenJwt token = this.getOauthService().validateAccessToken(accessToken);
72 |
73 | // the access token is valid, set token on the callback
74 | if (token == null) {
75 | log.info("The access token is not valid or has expired.");
76 | } else {
77 | log.info("The access token is valid, set token on the callback.");
78 | }
79 |
80 | callback.token(token);
81 |
82 | log.debug("Finished handling OAuth bearer token validation callback.");
83 | }
84 |
85 | //endregion
86 | }
87 |
88 |
--------------------------------------------------------------------------------
/kafka-oauth/src/main/java/com/bfm/kafka/security/oauthbearer/OAuthBearerTokenJwt.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright © 2020 BlackRock 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.bfm.kafka.security.oauthbearer;
17 |
18 | import org.apache.kafka.common.security.oauthbearer.OAuthBearerToken;
19 |
20 | import java.util.List;
21 | import java.util.Map;
22 | import java.util.Set;
23 | import java.util.TreeSet;
24 |
25 | /**
26 | * The b64token value as defined in RFC 6750 Section 2.1 along with the token's specific scope and lifetime and
27 | * principal name.
28 | */
29 | public class OAuthBearerTokenJwt implements OAuthBearerToken {
30 |
31 | //region Constants
32 |
33 | private static final String SUB = "sub";
34 | private static final String SCOPE = "scope";
35 | private static final String EXP = "exp";
36 | private static final String IAT = "iat";
37 | private static final String JTI = "jti";
38 |
39 | //endregion
40 |
41 | //region Member Variables
42 |
43 | private String accessToken;
44 | private long lifetimeMs;
45 | private String principalName;
46 | private Long startTimeMs;
47 | private Set scope;
48 | private long expirationTime;
49 | private String jti;
50 |
51 | //endregion
52 |
53 | //region Constructors
54 |
55 | /**
56 | * Initializes a new instance of the OAuthBearerTokenJwt class.
57 | *
58 | * @param accessToken The b64token value as defined in RFC 6750 Section 2.1
59 | * @param lifeTimeMs The token's lifetime, expressed as the number of milliseconds since the epoch, as per RFC 6749 Section 1.4
60 | * @param startTimeMs When the credential became valid, in terms of the number of milliseconds since the epoch, if known, otherwise null.
61 | * @param principalName The name of the principal to which this credential applies
62 | */
63 | public OAuthBearerTokenJwt(String accessToken, long lifeTimeMs, long startTimeMs, String principalName) {
64 | super();
65 |
66 | this.accessToken = accessToken;
67 | this.principalName = principalName;
68 | this.lifetimeMs = startTimeMs + (lifeTimeMs * 1000);
69 | this.startTimeMs = startTimeMs;
70 | this.expirationTime = startTimeMs + (lifeTimeMs * 1000);
71 | }
72 |
73 | /**
74 | * Initializes a new instance of the OAuthBearerTokenJwt class based on a collection of key value pairs
75 | *
76 | * @param jwtToken - A JWT Token expressed as a key value pair
77 | * @param accessToken The b64token value as defined in RFC 6750 Section 2.1
78 | */
79 | public OAuthBearerTokenJwt(Map jwtToken, String accessToken) {
80 | super();
81 | this.accessToken = accessToken;
82 | this.principalName = (String) jwtToken.get(SUB);
83 |
84 | if (this.scope == null) {
85 | this.scope = new TreeSet<>();
86 | }
87 |
88 | if (jwtToken.get(SCOPE) instanceof String) {
89 | this.scope.add((String) jwtToken.get(SCOPE));
90 | } else if (jwtToken.get(SCOPE) instanceof List) {
91 | for (String s : (List) jwtToken.get(SCOPE)) {
92 | this.scope.add(s);
93 | }
94 | }
95 |
96 | Object exp = jwtToken.get(EXP);
97 | if (exp instanceof Integer) {
98 | this.expirationTime = Integer.toUnsignedLong((Integer) jwtToken.get(EXP));
99 | } else {
100 | this.expirationTime = (Long) jwtToken.get(EXP);
101 | }
102 |
103 | Object iat = jwtToken.get(IAT);
104 | if (iat instanceof Integer) {
105 | this.startTimeMs = Integer.toUnsignedLong((Integer) jwtToken.get(IAT));
106 | } else {
107 | this.startTimeMs = (Long) jwtToken.get(IAT);
108 | }
109 |
110 | this.lifetimeMs = expirationTime * 1000;
111 | this.jti = (String) jwtToken.get(JTI);
112 | }
113 |
114 | //endregion
115 |
116 | //region Public Properties
117 |
118 | @Override
119 | public String value() {
120 | return this.accessToken;
121 | }
122 |
123 | @Override
124 | public Set scope() {
125 | return this.scope;
126 | }
127 |
128 | @Override
129 | public long lifetimeMs() {
130 | return this.lifetimeMs;
131 | }
132 |
133 | @Override
134 | public String principalName() {
135 | return this.principalName;
136 | }
137 |
138 | @Override
139 | public Long startTimeMs() {
140 | return this.startTimeMs != null ? this.startTimeMs : 0;
141 | }
142 |
143 | public long expirationTime() {
144 | return this.expirationTime;
145 | }
146 |
147 | //endregion
148 |
149 | //region Public Methods
150 |
151 | @Override
152 | public String toString() {
153 | return "OauthBearerTokenJwt {" +
154 | "value='" + accessToken + '\'' +
155 | ", lifetimeMs=" + lifetimeMs +
156 | ", principalName='" + principalName + '\'' +
157 | ", startTimeMs=" + startTimeMs +
158 | ", scope=" + scope +
159 | ", expirationTime=" + expirationTime +
160 | ", jti='" + jti + '\'' +
161 | '}';
162 | }
163 |
164 | //endregion
165 | }
--------------------------------------------------------------------------------
/kafka-oauth/src/main/java/com/bfm/kafka/security/oauthbearer/OAuthConfiguration.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright © 2020 BlackRock 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.bfm.kafka.security.oauthbearer;
17 |
18 | import org.slf4j.Logger;
19 | import org.slf4j.LoggerFactory;
20 |
21 | import java.io.FileInputStream;
22 | import java.io.FileNotFoundException;
23 | import java.io.IOException;
24 | import java.io.InputStream;
25 | import java.util.Map;
26 | import java.util.Objects;
27 | import java.util.Properties;
28 |
29 | /**
30 | * The type O auth configuration.
31 | */
32 | public class OAuthConfiguration {
33 |
34 | //region Constants
35 |
36 | private static final String KAFKA_OAUTH_SERVER_PROP_FILE_ENV_VAR = "KAFKA_OAUTH_SERVER_PROP_FILE";
37 | private static final String KAFKA_OAUTH_SERVER_BASE_URI_ENV_VAR = "KAFKA_OAUTH_SERVER_BASE_URI";
38 | private static final String KAFKA_OAUTH_SERVER_TOKEN_ENDPOINT_PATH_ENV_VAR = "KAFKA_OAUTH_SERVER_TOKEN_ENDPOINT_PATH";
39 | private static final String KAFKA_OAUTH_SERVER_INTROSPECTION_ENDPOINT_PATH_ENV_VAR = "KAFKA_OAUTH_SERVER_INTROSPECTION_ENDPOINT_PATH";
40 | private static final String KAFKA_OAUTH_SERVER_CLIENT_ID_ENV_VAR = "KAFKA_OAUTH_SERVER_CLIENT_ID";
41 | private static final String KAFKA_OAUTH_SERVER_CLIENT_SECRET_ENV_VAR = "KAFKA_OAUTH_SERVER_CLIENT_SECRET";
42 | private static final String KAFKA_OAUTH_SERVER_GRANT_TYPE_ENV_VAR = "KAFKA_OAUTH_SERVER_GRANT_TYPE";
43 | private static final String KAFKA_OAUTH_SERVER_SCOPES_ENV_VAR = "KAFKA_OAUTH_SERVER_SCOPES";
44 | private static final String KAFKA_OAUTH_SERVER_ACCEPT_UNSECURE_SERVER_ENV_VAR = "KAFKA_OAUTH_SERVER_ACCEPT_UNSECURE_SERVER";
45 |
46 | private static final String KAFKA_OAUTH_SERVER_BASE_URI = "oauth.server.base.uri";
47 | private static final String KAFKA_OAUTH_SERVER_TOKEN_ENDPOINT_PATH = "oauth.server.token.endpoint.path";
48 | private static final String KAFKA_OAUTH_SERVER_INTROSPECTION_ENDPOINT_PATH = "oauth.server.introspection.endpoint.path";
49 | private static final String KAFKA_OAUTH_SERVER_CLIENT_ID = "oauth.server.client.id";
50 | private static final String KAFKA_OAUTH_SERVER_CLIENT_SECRET = "oauth.server.client.secret";
51 | private static final String KAFKA_OAUTH_SERVER_GRANT_TYPE = "oauth.server.grant.type";
52 | private static final String KAFKA_OAUTH_SERVER_SCOPES = "oauth.server.scopes";
53 | private static final String KAFKA_OAUTH_SERVER_ACCEPT_UNSECURE_SERVER = "oauth.server.accept.unsecure.server";
54 |
55 | //endregion
56 |
57 | //region Member Variables
58 | private final Logger log = LoggerFactory.getLogger(OAuthConfiguration.class);
59 | private String baseServerUri;
60 | private String tokenEndpointPath;
61 | private String introspectionEndpointPath;
62 | private String clientId;
63 | private String clientSecret;
64 | private String grantType;
65 | private String scopes;
66 | private Boolean unsecureServer;
67 |
68 | //endregion
69 |
70 | //region Constructors
71 |
72 | /**
73 | * Instantiates a new O auth configuration.
74 | */
75 | public OAuthConfiguration() {
76 |
77 | try {
78 | log.debug("Starting to initialize OAuth configuration");
79 |
80 | log.debug("Look for OAuthConfiguration property file.");
81 | Properties prop = this.getConfigurationFileProperties();
82 |
83 |
84 | // get the OAuth server base Uri
85 | log.debug("Configure the OAuth server base Uri");
86 | String defaultBaseServerUri = null;
87 | if (prop.containsKey(KAFKA_OAUTH_SERVER_BASE_URI)) {
88 | defaultBaseServerUri = prop.getProperty(KAFKA_OAUTH_SERVER_BASE_URI);
89 | }
90 |
91 | this.baseServerUri = EnvironmentVariablesUtil.getStringEnvironmentVariable(
92 | KAFKA_OAUTH_SERVER_BASE_URI_ENV_VAR,
93 | defaultBaseServerUri);
94 |
95 | // get the OAuth server token path
96 | log.debug("Configure the OAuth server token endpoint path.");
97 | String defaultTokenEndpointPath = null;
98 | if (prop.containsKey(KAFKA_OAUTH_SERVER_TOKEN_ENDPOINT_PATH)) {
99 | defaultTokenEndpointPath = prop.getProperty(KAFKA_OAUTH_SERVER_TOKEN_ENDPOINT_PATH);
100 | }
101 |
102 | this.tokenEndpointPath = EnvironmentVariablesUtil.getStringEnvironmentVariable(
103 | KAFKA_OAUTH_SERVER_TOKEN_ENDPOINT_PATH_ENV_VAR,
104 | defaultTokenEndpointPath);
105 |
106 | // get the OAuth server introspection endpoint path
107 | log.debug("Configure the OAuth server introspection endpoint path.");
108 | String defaultIntrospectionEndpointPath = null;
109 | if (prop.containsKey(KAFKA_OAUTH_SERVER_INTROSPECTION_ENDPOINT_PATH)) {
110 | defaultIntrospectionEndpointPath = prop.getProperty(KAFKA_OAUTH_SERVER_INTROSPECTION_ENDPOINT_PATH);
111 | }
112 |
113 | this.introspectionEndpointPath = EnvironmentVariablesUtil.getStringEnvironmentVariable(
114 | KAFKA_OAUTH_SERVER_INTROSPECTION_ENDPOINT_PATH_ENV_VAR,
115 | defaultIntrospectionEndpointPath);
116 |
117 | // get the OAuth server client id
118 | log.debug("Configure the OAuth server client id.");
119 | String defaultClientId = null;
120 | if (prop.containsKey(KAFKA_OAUTH_SERVER_CLIENT_ID)) {
121 | defaultClientId = prop.getProperty(KAFKA_OAUTH_SERVER_CLIENT_ID);
122 | }
123 |
124 | this.clientId = EnvironmentVariablesUtil.getStringEnvironmentVariable(
125 | KAFKA_OAUTH_SERVER_CLIENT_ID_ENV_VAR,
126 | defaultClientId);
127 |
128 | // get the OAuth server client secret
129 | log.debug("Configure the OAuth server client secret.");
130 | String defaultClientSecret = null;
131 | if (prop.containsKey(KAFKA_OAUTH_SERVER_CLIENT_SECRET)) {
132 | defaultClientSecret = prop.getProperty(KAFKA_OAUTH_SERVER_CLIENT_SECRET);
133 | }
134 |
135 | this.clientSecret = EnvironmentVariablesUtil.getStringEnvironmentVariable(
136 | KAFKA_OAUTH_SERVER_CLIENT_SECRET_ENV_VAR,
137 | defaultClientSecret);
138 |
139 | // get the OAuth server grant type
140 | log.debug("Configure the OAuth server grant type");
141 | String defaultGrantType = "client_credentials";
142 | if (prop.containsKey(KAFKA_OAUTH_SERVER_GRANT_TYPE)) {
143 | defaultGrantType = prop.getProperty(KAFKA_OAUTH_SERVER_GRANT_TYPE);
144 | }
145 |
146 | this.grantType = EnvironmentVariablesUtil.getStringEnvironmentVariable(
147 | KAFKA_OAUTH_SERVER_GRANT_TYPE_ENV_VAR,
148 | defaultGrantType);
149 |
150 | // get the OAuth server scopes
151 | log.debug("Configure the OAuth server scopes");
152 | String defaultScopes = null;
153 | if (prop.containsKey(KAFKA_OAUTH_SERVER_SCOPES)) {
154 | defaultScopes = prop.getProperty(KAFKA_OAUTH_SERVER_SCOPES);
155 | }
156 |
157 | this.scopes = EnvironmentVariablesUtil.getStringEnvironmentVariable(
158 | KAFKA_OAUTH_SERVER_SCOPES_ENV_VAR,
159 | defaultScopes);
160 |
161 | // get the OAuth server unsecure server
162 | log.debug("Configure the OAuth server unsecure server");
163 | Boolean defaultUnsecureServer = false;
164 | if (prop.containsKey(KAFKA_OAUTH_SERVER_ACCEPT_UNSECURE_SERVER)) {
165 | String propValue = prop.getProperty(KAFKA_OAUTH_SERVER_ACCEPT_UNSECURE_SERVER);
166 |
167 | defaultUnsecureServer = Boolean.valueOf(propValue);
168 | }
169 |
170 | this.unsecureServer = EnvironmentVariablesUtil.getBooleanEnvironmentVariable(
171 | KAFKA_OAUTH_SERVER_ACCEPT_UNSECURE_SERVER_ENV_VAR,
172 | defaultUnsecureServer);
173 |
174 | if (!this.isValid()) {
175 | throw new IllegalStateException("Configuration entries are invalid.");
176 | }
177 |
178 | } catch (Exception ex) {
179 | log.error("Error getting OAuth configuration, Message: %s", ex);
180 | throw ex;
181 | } finally {
182 | log.debug("Finished initializing OAuth configuration");
183 | }
184 | }
185 |
186 | //endregion
187 |
188 | //region Public Properties
189 |
190 | /**
191 | * Gets base server uri.
192 | *
193 | * @return the base server uri
194 | */
195 | public String getBaseServerUri() {
196 | return this.baseServerUri;
197 | }
198 |
199 | /**
200 | * Gets token endpoint path.
201 | *
202 | * @return the token endpoint path
203 | */
204 | public String getTokenEndpointPath() {
205 | return tokenEndpointPath;
206 | }
207 |
208 |
209 | /**
210 | * Gets token endpoint.
211 | *
212 | * @return the token endpoint
213 | */
214 | public String getTokenEndpoint() {
215 | return this.baseServerUri + this.tokenEndpointPath;
216 | }
217 |
218 | /**
219 | * Gets introspection endpoint.
220 | *
221 | * @return the introspection endpoint
222 | */
223 | public String getIntrospectionEndpoint() {
224 | return this.baseServerUri + this.introspectionEndpointPath;
225 | }
226 |
227 | /**
228 | * Gets introspection endpoint path.
229 | *
230 | * @return the introspection endpoint path
231 | */
232 | public String getIntrospectionEndpointPath() {
233 | return this.introspectionEndpointPath;
234 | }
235 |
236 | /**
237 | * Gets client id.
238 | *
239 | * @return the client id
240 | */
241 | public String getClientId() {
242 | return this.clientId;
243 | }
244 |
245 |
246 | /**
247 | * Gets client secret.
248 | *
249 | * @return the client secret
250 | */
251 | public String getClientSecret() {
252 | return this.clientSecret;
253 | }
254 |
255 |
256 | /**
257 | * Gets grant type.
258 | *
259 | * @return the grant type
260 | */
261 | public String getGrantType() {
262 | return this.grantType;
263 | }
264 |
265 |
266 | /**
267 | * Gets scopes.
268 | *
269 | * @return the scopes
270 | */
271 | public String getScopes() {
272 | return this.scopes;
273 | }
274 |
275 | /**
276 | * Gets unsecure server.
277 | *
278 | * @return the unsecure server
279 | */
280 | public Boolean getUnsecureServer() {
281 | return this.unsecureServer;
282 | }
283 |
284 |
285 | //endregion
286 |
287 | //region Public Methods
288 |
289 | /**
290 | * Is valid boolean.
291 | *
292 | * @return the boolean
293 | */
294 | private Boolean isValid() {
295 |
296 | if (!Utils.isURIValid(this.baseServerUri)) {
297 | // the token endpoint is not valid
298 | return false;
299 | }
300 |
301 | if (!Utils.isURIValid(this.baseServerUri + this.tokenEndpointPath)) {
302 | // the token endpoint is not valid
303 | return false;
304 | }
305 |
306 | if (!Utils.isURIValid(this.baseServerUri + this.introspectionEndpointPath)) {
307 | // the introspection endpoint is not valid
308 | return false;
309 | }
310 |
311 | if (Utils.isNullOrEmpty(this.clientId)) {
312 | return false;
313 | }
314 |
315 | if (Utils.isNullOrEmpty(this.clientSecret)) {
316 | return false;
317 | }
318 |
319 | if (Utils.isNullOrEmpty(this.grantType)) {
320 | return false;
321 | }
322 |
323 | return true;
324 | }
325 |
326 | public void setConfigurationFromJaasConfigEntries(Map jaasConfigEntries) {
327 | //validate the parameters
328 | Objects.requireNonNull(jaasConfigEntries);
329 |
330 | // get the OAuth server base Uri
331 | String defaultBaseServerUri = jaasConfigEntries.getOrDefault(KAFKA_OAUTH_SERVER_BASE_URI, "");
332 | if (!Utils.isNullOrEmpty(defaultBaseServerUri)) {
333 | this.baseServerUri = defaultBaseServerUri;
334 | }
335 |
336 | // get the OAuth server token path
337 | String defaultTokenEndpointPath = jaasConfigEntries.getOrDefault(KAFKA_OAUTH_SERVER_TOKEN_ENDPOINT_PATH, "");
338 | if (!Utils.isNullOrEmpty(defaultTokenEndpointPath)) {
339 | this.tokenEndpointPath = defaultTokenEndpointPath;
340 | }
341 |
342 | // get the OAuth server introspection endpoint path
343 | String defaultIntrospectionEndpointPath = jaasConfigEntries.getOrDefault(KAFKA_OAUTH_SERVER_INTROSPECTION_ENDPOINT_PATH, "");
344 | if (!Utils.isNullOrEmpty(defaultIntrospectionEndpointPath)) {
345 | this.introspectionEndpointPath = defaultIntrospectionEndpointPath;
346 | }
347 |
348 | // get the OAuth server client id
349 | String defaultClientId = jaasConfigEntries.getOrDefault(KAFKA_OAUTH_SERVER_CLIENT_ID, "");
350 | if (!Utils.isNullOrEmpty(defaultClientId)) {
351 | this.clientId = defaultClientId;
352 | }
353 |
354 | // get the OAuth server client secret
355 | String defaultClientSecret = jaasConfigEntries.getOrDefault(KAFKA_OAUTH_SERVER_CLIENT_SECRET, "");
356 | if (!Utils.isNullOrEmpty(defaultClientSecret)) {
357 | this.clientSecret = defaultClientSecret;
358 | }
359 |
360 | // get the OAuth server grant type
361 | String defaultGrantType = jaasConfigEntries.getOrDefault(KAFKA_OAUTH_SERVER_GRANT_TYPE, "");
362 | if (!Utils.isNullOrEmpty(defaultGrantType)) {
363 | this.grantType = defaultGrantType;
364 | }
365 |
366 | // get the OAuth server scopes
367 | String defaultScopes = jaasConfigEntries.getOrDefault(KAFKA_OAUTH_SERVER_SCOPES, "");
368 | if (!Utils.isNullOrEmpty(defaultScopes)) {
369 | this.scopes = defaultScopes;
370 | }
371 |
372 | // get the OAuth server unsecure server
373 | String defaultUnsecureServer = jaasConfigEntries.getOrDefault(KAFKA_OAUTH_SERVER_ACCEPT_UNSECURE_SERVER, "");
374 | if (!Utils.isNullOrEmpty(defaultUnsecureServer)) {
375 | this.unsecureServer = Boolean.valueOf(defaultUnsecureServer);
376 | }
377 |
378 | //check if the configuration remains valid
379 | if (!this.isValid()) {
380 | throw new IllegalStateException("Configuration entries at jaas configuration file are invalid.");
381 | }
382 | }
383 |
384 | //endregion
385 |
386 | //region Protected Methods
387 |
388 |
389 | /**
390 | * Gets configuration file properties.
391 | *
392 | * @return the configuration file properties
393 | */
394 | protected Properties getConfigurationFileProperties() {
395 |
396 | Properties properties = new Properties();
397 | try {
398 | log.debug("Starting to load configuration properties from property file.");
399 |
400 | String configurationFilePath = EnvironmentVariablesUtil.getStringEnvironmentVariable(
401 | KAFKA_OAUTH_SERVER_PROP_FILE_ENV_VAR,
402 | null);
403 |
404 | if (!Utils.isNullOrEmpty(configurationFilePath)) {
405 | log.debug(
406 | "Load properties from {}",
407 | KAFKA_OAUTH_SERVER_PROP_FILE_ENV_VAR);
408 |
409 | InputStream input = new FileInputStream(configurationFilePath);
410 |
411 | // load a properties file
412 | properties.load(input);
413 | } else {
414 | properties.setProperty(KAFKA_OAUTH_SERVER_BASE_URI, "http://localhost:8080/auth/realms/master/protocol/openid-connect");
415 | properties.setProperty(KAFKA_OAUTH_SERVER_TOKEN_ENDPOINT_PATH, "/token");
416 | properties.setProperty(KAFKA_OAUTH_SERVER_INTROSPECTION_ENDPOINT_PATH, "/token/introspect");
417 | properties.setProperty(KAFKA_OAUTH_SERVER_CLIENT_ID, "test-consumer");
418 | properties.setProperty(KAFKA_OAUTH_SERVER_CLIENT_SECRET, "7b3c23ef-8909-489e-bf4a-64a84abb197e");
419 | properties.setProperty(KAFKA_OAUTH_SERVER_GRANT_TYPE, "client_credentials");
420 | properties.setProperty(KAFKA_OAUTH_SERVER_SCOPES, "test");
421 | properties.setProperty(KAFKA_OAUTH_SERVER_ACCEPT_UNSECURE_SERVER, "true");
422 |
423 | log.debug(
424 | "{} enviroment variable is not set, cannot load properties.",
425 | KAFKA_OAUTH_SERVER_PROP_FILE_ENV_VAR);
426 | }
427 | }catch (FileNotFoundException ex) {
428 | String errMsg = String.format("OAuth property file not found, Message: %s", ex.getMessage());
429 | log.warn(errMsg);
430 | } catch (IOException ex) {
431 | String errMsg = String.format("Error reading OAuth property file not found, Message: %s", ex.getMessage());
432 | log.warn(errMsg);
433 | } finally {
434 | log.debug("Finished loading configuration properties from property file.");
435 | }
436 |
437 | return properties;
438 | }
439 |
440 | //endregion
441 | }
442 |
443 |
444 |
445 |
--------------------------------------------------------------------------------
/kafka-oauth/src/main/java/com/bfm/kafka/security/oauthbearer/OAuthScope.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright © 2020 BlackRock 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.bfm.kafka.security.oauthbearer;
17 |
18 | /**
19 | * The type O auth scope.
20 | */
21 | public class OAuthScope {
22 | private String resourceType;
23 | private String resourceName;
24 | private String operation;
25 |
26 | /**
27 | * Instantiates a new O auth scope.
28 | */
29 | public OAuthScope() {
30 |
31 | }
32 |
33 | /**
34 | * Gets resource type.
35 | *
36 | * @return the resource type
37 | */
38 | public String getResourceType() {
39 | return resourceType;
40 | }
41 |
42 | /**
43 | * Sets resource type.
44 | *
45 | * @param resourceType the resource type
46 | */
47 | public void setResourceType(String resourceType) {
48 | this.resourceType = resourceType;
49 | }
50 |
51 | /**
52 | * Gets resource name.
53 | *
54 | * @return the resource name
55 | */
56 | public String getResourceName() {
57 | return resourceName;
58 | }
59 |
60 | /**
61 | * Sets resource name.
62 | *
63 | * @param resourceName the resource name
64 | */
65 | public void setResourceName(String resourceName) {
66 | this.resourceName = resourceName;
67 | }
68 |
69 | /**
70 | * Gets operation.
71 | *
72 | * @return the operation
73 | */
74 | public String getOperation() {
75 | return operation;
76 | }
77 |
78 | /**
79 | * Sets operation.
80 | *
81 | * @param operation the operation
82 | */
83 | public void setOperation(String operation) {
84 | this.operation = operation;
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/kafka-oauth/src/main/java/com/bfm/kafka/security/oauthbearer/OAuthService.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright © 2020 BlackRock 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.bfm.kafka.security.oauthbearer;
17 |
18 | import java.io.IOException;
19 | import java.util.Map;
20 |
21 | /**
22 | * This interface defines services from an OAuth Server that are needed for lib-kafka-oauth
23 | */
24 | public interface OAuthService {
25 |
26 | /**
27 | * Request an access token for an OAuth client from an OAuth Server
28 | *
29 | * @return the o auth bearer token jwt
30 | */
31 | OAuthBearerTokenJwt requestAccessToken() throws IOException;
32 |
33 | /**
34 | * Validate an access token in an OAuth Server
35 | *
36 | * @param accessToken the access token
37 | * @return the o auth bearer token jwt
38 | */
39 | OAuthBearerTokenJwt validateAccessToken(String accessToken) throws IOException;
40 |
41 | /**
42 | * Gets configurations that are needed to connect to an OAuth server
43 | *
44 | * @return the o auth configuration
45 | */
46 | OAuthConfiguration getOAuthConfiguration();
47 |
48 | void setOAuthConfiguration(Map jaasConfigEntries);
49 | }
--------------------------------------------------------------------------------
/kafka-oauth/src/main/java/com/bfm/kafka/security/oauthbearer/OAuthServiceImpl.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright © 2020 BlackRock 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.bfm.kafka.security.oauthbearer;
17 |
18 | import org.apache.kafka.common.utils.Time;
19 | import org.slf4j.Logger;
20 | import org.slf4j.LoggerFactory;
21 |
22 | import java.io.DataOutputStream;
23 | import java.io.IOException;
24 | import java.net.HttpURLConnection;
25 | import java.net.URL;
26 | import java.nio.charset.StandardCharsets;
27 | import java.util.Map;
28 | import java.util.Objects;
29 |
30 | /**
31 | * The class that handles the logic to interact with the OAuth server.
32 | */
33 | public class OAuthServiceImpl implements OAuthService {
34 |
35 | //region Constants
36 |
37 | public static final String OAUTH_GRANT_TYPE = "grant_type";
38 | public static final String OAUTH_SCOPE = "scope";
39 | public static final String OAUTH_ACCESS_TOKEN = "access_token";
40 | public static final String OAUTH_ACCESS_TOKEN_EXPIRES_IN = "expires_in";
41 |
42 | //endregion
43 |
44 | //region Member Variables
45 |
46 | private static final Logger log = LoggerFactory.getLogger(OAuthServiceImpl.class);
47 | private OAuthConfiguration oauthConfiguration;
48 | private static Time time = Time.SYSTEM;
49 |
50 | //endregion
51 |
52 | //region Constructors
53 |
54 | /**
55 | * Instantiates a new O auth service.
56 | */
57 | public OAuthServiceImpl() {
58 | this.oauthConfiguration = new OAuthConfiguration();
59 | }
60 |
61 | //endregion
62 |
63 | //region Public Properties
64 |
65 | public OAuthConfiguration getOAuthConfiguration() {
66 | return this.oauthConfiguration;
67 | }
68 |
69 | @Override
70 | public void setOAuthConfiguration(Map jaasConfigEntries) {
71 | try {
72 | //validate parameters
73 | Objects.requireNonNull(jaasConfigEntries);
74 | this.oauthConfiguration.setConfigurationFromJaasConfigEntries(jaasConfigEntries);
75 | } catch (RuntimeException e) {
76 | log.warn("Error on trying to configure oauth using jaas configuration entries. Using environment or properties file configuration");
77 | }
78 | }
79 |
80 | //endregion
81 |
82 | //region Public Methods
83 |
84 | /**
85 | * @return OAuthBearerTokenJwt which has accessToken string as an attribute of the object
86 | */
87 | /**
88 | * This method gets a JWT token from an OAuth Server.
89 | * The JWT token contains the access token string.
90 | * @return a JWT token if client ID exist in the OAuth Server or null if not exist in the OAuth Server
91 | * @throws IOException - if Call to OAuth Server fails
92 | */
93 | public OAuthBearerTokenJwt requestAccessToken() throws IOException {
94 | OAuthBearerTokenJwt result = null;
95 | log.debug("Starting to request access token from OAuth server.");
96 |
97 | String clientId = this.oauthConfiguration.getClientId();
98 |
99 | // initialize the time of the request
100 | long callTime = time.milliseconds();
101 |
102 | // setup post parameters
103 | String grantType = String.format("%s=%s", OAUTH_GRANT_TYPE, this.oauthConfiguration.getGrantType());
104 | String scope = String.format("%s=%s", OAUTH_SCOPE, this.oauthConfiguration.getScopes());
105 | String postParameters = String.format("%s&%s", grantType, scope);
106 |
107 | log.info("Send access token request to the OAuth server.");
108 | Map resp = doHttpCall(
109 | this.oauthConfiguration.getTokenEndpoint(),
110 | postParameters,
111 | Utils.createBasicAuthorizationHeader(clientId, this.oauthConfiguration.getClientSecret()));
112 |
113 | // check to see if the response is not null
114 | if (resp != null) {
115 | // create a new token from the response
116 | log.debug("Access token response is not null, create an token.");
117 | String accessToken = (String) resp.get(OAUTH_ACCESS_TOKEN);
118 | long expiresIn = ((Integer) resp.get(OAUTH_ACCESS_TOKEN_EXPIRES_IN)).longValue();
119 | result = new OAuthBearerTokenJwt(accessToken, expiresIn, callTime, clientId);
120 | } else {
121 | log.error("Error requesting access token from OAuth server, the HTTP response was null.");
122 | }
123 | log.debug("Finished requesting access token from OAuth server.");
124 | return result;
125 | }
126 |
127 | /**
128 | * This method vaidates an access token string in the OAuth Server
129 | * @param accessToken the access token string
130 | * @return a JWT token if accessToken exists in the OAuth Server, or null if not exist in the OAuth Server
131 | * @throws IOException - if Call to OAuth Server fails
132 | */
133 | public OAuthBearerTokenJwt validateAccessToken(String accessToken) throws IOException {
134 | OAuthBearerTokenJwt result = null;
135 | log.debug("Starting to validate access token against OAuth server.");
136 |
137 | // check parameters
138 | log.debug("Validate method parameters.");
139 | Objects.requireNonNull(accessToken);
140 |
141 | // create post parameters
142 | String token = "token=" + accessToken;
143 |
144 | // validate the access token by calling the oauth introspection endpoint
145 | log.debug("Validate the access token by calling the OAuth introspection endpoint.");
146 | Map resp = doHttpCall(
147 | this.oauthConfiguration.getIntrospectionEndpoint(),
148 | token,
149 | Utils.createBasicAuthorizationHeader(this.oauthConfiguration.getClientId(), this.oauthConfiguration.getClientSecret()));
150 |
151 | // check to see if the response is not null - accessToken exists in the OAuth Server
152 | if (resp != null) {
153 | // check to see if the access token is still active
154 | log.debug("Validation response was not null check to see if the access token is active.");
155 | boolean active = (boolean) resp.get("active");
156 |
157 | if (active) {
158 | // the access token is still active create a new token with the response
159 | log.debug("Access token is still active create a new token with the response.");
160 | result = new OAuthBearerTokenJwt(resp, accessToken);
161 | } else {
162 | // the access token is no longer active
163 | String errMsg = String.format("Access token has expired.");
164 | log.error(errMsg);
165 | }
166 | } else { // accessToken does not exist in the OAuth Server
167 | // the http response was null, cannot validate access token
168 | String errMsg = "Error validating access token against OAuth server, the HTTP response was null.";
169 | log.error(errMsg);
170 | }
171 | log.debug("Finished validating access token against OAuth server.");
172 | return result;
173 | }
174 |
175 | //endregion
176 |
177 | //region Protected Methods
178 |
179 | /**
180 | * Do http call to the OAuth Server.
181 | * @param urlStr OAuth Server URL
182 | * @param postParameters
183 | * @param authorizationHeaderValue
184 | * @return null if HTTP response code is not 200
185 | * @throws IOException
186 | */
187 | protected Map doHttpCall(String urlStr, String postParameters, String authorizationHeaderValue) throws IOException {
188 | log.debug(String.format("Starting to make HTTP call, Url: %s.", urlStr));
189 |
190 | // check parameters
191 | log.debug("Validate method parameters.");
192 | Objects.requireNonNull(urlStr);
193 | Objects.requireNonNull(postParameters);
194 | //Objects.requireNonNull(authorizationHeaderValue);
195 |
196 | // configure SSL context to allow unsecured connections if configured
197 | log.debug("Configure SSL context to allow unsecured connections if configured.");
198 | Utils.acceptUnsecureServer(this.oauthConfiguration.getUnsecureServer());
199 |
200 | log.debug(String.format("Send POST request, Url: %s.", urlStr));
201 | byte[] postData = postParameters.getBytes(StandardCharsets.UTF_8);
202 | int postDataLength = postData.length;
203 |
204 | URL url = new URL(urlStr);
205 | HttpURLConnection con = (HttpURLConnection) url.openConnection();
206 | con.setInstanceFollowRedirects(true);
207 | con.setRequestMethod("POST");
208 | con.setRequestProperty("Authorization", authorizationHeaderValue);
209 | con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
210 | con.setRequestProperty("charset", "utf-8");
211 | con.setRequestProperty("Content-Length", Integer.toString(postDataLength));
212 | con.setUseCaches(false);
213 | con.setDoOutput(true);
214 |
215 | try (DataOutputStream wr = new DataOutputStream(con.getOutputStream())) {
216 | wr.write(postData);
217 | }
218 |
219 | log.debug(String.format("Get HTTP response code, Url: %s.", urlStr));
220 | int responseCode = con.getResponseCode();
221 |
222 | // check to see if the response was successful
223 | log.debug(String.format("Check to see if the response was successful, Url: %s.", urlStr));
224 | if (responseCode == 200) {
225 | // the response was successful, parse to json into a key value pairs
226 | log.debug("The response was successful, parse to json into a key value pairs, Url: {}.", urlStr);
227 | return Utils.handleJsonResponse(con.getInputStream());
228 | } else {
229 | // the response was not successful
230 | String errMsg = String.format(
231 | "The response was not successful, Url: %s, Response Code: %s",
232 | urlStr,
233 | responseCode);
234 |
235 | log.error(errMsg);
236 | return null;
237 |
238 | }
239 | }
240 |
241 | //endregion
242 | }
243 |
--------------------------------------------------------------------------------
/kafka-oauth/src/main/java/com/bfm/kafka/security/oauthbearer/Utils.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright © 2020 BlackRock 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.bfm.kafka.security.oauthbearer;
17 |
18 | import com.fasterxml.jackson.core.type.TypeReference;
19 | import com.fasterxml.jackson.databind.ObjectMapper;
20 | import org.slf4j.Logger;
21 | import org.slf4j.LoggerFactory;
22 |
23 | import javax.net.ssl.HttpsURLConnection;
24 | import javax.net.ssl.SSLContext;
25 | import javax.net.ssl.TrustManager;
26 | import javax.net.ssl.X509TrustManager;
27 | import java.io.BufferedReader;
28 | import java.io.InputStream;
29 | import java.io.InputStreamReader;
30 | import java.net.URI;
31 | import java.net.URISyntaxException;
32 | import java.security.KeyManagementException;
33 | import java.security.NoSuchAlgorithmException;
34 | import java.util.Base64;
35 | import java.util.List;
36 | import java.util.Map;
37 | import java.util.Objects;
38 |
39 | /**
40 | * The type Utils.
41 | */
42 | public class Utils {
43 | private static final Logger log = LoggerFactory.getLogger(Utils.class);
44 |
45 | /**
46 | * Is uri valid boolean.
47 | *
48 | * @param str the str
49 | * @return the boolean
50 | */
51 | public static Boolean isURIValid(String str) {
52 | try {
53 | URI uri = new URI(str);
54 | } catch (NullPointerException e) {
55 | // If str is null
56 | return false;
57 |
58 | } catch (URISyntaxException e) {
59 | // str is not valid
60 | return false;
61 | }
62 |
63 | // the str is valid uri
64 | return true;
65 | }
66 |
67 | /**
68 | * Is null or empty boolean.
69 | *
70 | * @param str the str
71 | * @return the boolean
72 | */
73 | public static Boolean isNullOrEmpty(String str) {
74 | if ((str == null) || (str.isEmpty())) {
75 | return true;
76 | }
77 | return false;
78 | }
79 |
80 | /**
81 | * Create basic authorization header string.
82 | *
83 | * @param clientId the client id
84 | * @param clientSecret the client secret
85 | * @return the string
86 | */
87 | protected static String createBasicAuthorizationHeader(String clientId, String clientSecret) {
88 | try {
89 | log.debug("Starting to create basic authorization header value.");
90 |
91 | String usernameAndPassword = String.format(
92 | "%s:%s",
93 | clientId,
94 | clientSecret);
95 |
96 | String base64UsernameAndPassword = Base64.getEncoder().encodeToString(usernameAndPassword.getBytes());
97 |
98 | String basicAuthHeader = String.format("Basic %s", base64UsernameAndPassword);
99 |
100 | return basicAuthHeader;
101 | } catch (Exception ex) {
102 | String errMsg = String.format(
103 | "Error creating basic authorization header value., Message: %s",
104 | ex.getMessage());
105 |
106 | log.error(errMsg, ex);
107 |
108 | throw ex;
109 | } finally {
110 | log.debug("Finished creating basic authorization header value.");
111 | }
112 | }
113 |
114 | /**
115 | * Handle json response map.
116 | *
117 | * @param inputStream the input stream
118 | * @return the map
119 | */
120 | protected static Map handleJsonResponse(InputStream inputStream) {
121 | // initialize the result
122 | Map result = null;
123 | try {
124 | log.debug("Starting to convert HTTP JSON response into a key value pairs.");
125 |
126 | // check parameters
127 | log.debug("Validate method parameters.");
128 | Objects.requireNonNull(inputStream);
129 |
130 | // read the response into a string
131 | log.debug("Read the HTTP response into a string.");
132 | BufferedReader in = new BufferedReader(new InputStreamReader(inputStream));
133 | String inputLine;
134 | StringBuilder response = new StringBuilder();
135 |
136 | while ((inputLine = in.readLine()) != null) {
137 | response.append(inputLine);
138 | }
139 | in.close();
140 |
141 | String jsonResponse = response.toString();
142 |
143 | // parse the response into a key value pairs
144 | log.debug("Parse JSON string into a key value pairs.");
145 | ObjectMapper objectMapper = new ObjectMapper();
146 | result = objectMapper.readValue(jsonResponse, new TypeReference