|
17 | 17 | import org.elasticsearch.common.util.concurrent.ThreadContext; |
18 | 18 | import org.elasticsearch.core.Tuple; |
19 | 19 | import org.elasticsearch.rest.RestStatus; |
| 20 | +import org.elasticsearch.telemetry.metric.MeterRegistry; |
20 | 21 | import org.elasticsearch.xpack.core.common.IteratingActionListener; |
21 | 22 | import org.elasticsearch.xpack.core.security.authc.Authentication; |
22 | 23 | import org.elasticsearch.xpack.core.security.authc.AuthenticationResult; |
|
25 | 26 | import org.elasticsearch.xpack.core.security.authc.Realm; |
26 | 27 | import org.elasticsearch.xpack.core.security.user.User; |
27 | 28 | import org.elasticsearch.xpack.security.authc.support.RealmUserLookup; |
| 29 | +import org.elasticsearch.xpack.security.metric.InstrumentedSecurityActionListener; |
| 30 | +import org.elasticsearch.xpack.security.metric.SecurityMetricType; |
| 31 | +import org.elasticsearch.xpack.security.metric.SecurityMetrics; |
28 | 32 |
|
29 | 33 | import java.util.ArrayList; |
30 | 34 | import java.util.Collections; |
| 35 | +import java.util.HashMap; |
31 | 36 | import java.util.LinkedHashMap; |
32 | 37 | import java.util.List; |
33 | 38 | import java.util.Map; |
34 | 39 | import java.util.Objects; |
35 | 40 | import java.util.concurrent.atomic.AtomicLong; |
36 | 41 | import java.util.concurrent.atomic.AtomicReference; |
37 | 42 | import java.util.function.BiConsumer; |
| 43 | +import java.util.function.LongSupplier; |
38 | 44 |
|
39 | 45 | import static org.elasticsearch.core.Strings.format; |
40 | 46 |
|
41 | 47 | public class RealmsAuthenticator implements Authenticator { |
42 | 48 |
|
| 49 | + public static final String ATTRIBUTE_REALM_NAME = "es.security.realm_name"; |
| 50 | + public static final String ATTRIBUTE_REALM_TYPE = "es.security.realm_type"; |
| 51 | + public static final String ATTRIBUTE_REALM_AUTHC_FAILURE_REASON = "es.security.realm_authc_failure_reason"; |
| 52 | + |
43 | 53 | private static final Logger logger = LogManager.getLogger(RealmsAuthenticator.class); |
44 | 54 |
|
45 | 55 | private final AtomicLong numInvalidation; |
46 | 56 | private final Cache<String, Realm> lastSuccessfulAuthCache; |
| 57 | + private final SecurityMetrics<Realm> authenticationMetrics; |
| 58 | + |
| 59 | + public RealmsAuthenticator(AtomicLong numInvalidation, Cache<String, Realm> lastSuccessfulAuthCache, MeterRegistry meterRegistry) { |
| 60 | + this(numInvalidation, lastSuccessfulAuthCache, meterRegistry, System::nanoTime); |
| 61 | + } |
47 | 62 |
|
48 | | - public RealmsAuthenticator(AtomicLong numInvalidation, Cache<String, Realm> lastSuccessfulAuthCache) { |
| 63 | + RealmsAuthenticator( |
| 64 | + AtomicLong numInvalidation, |
| 65 | + Cache<String, Realm> lastSuccessfulAuthCache, |
| 66 | + MeterRegistry meterRegistry, |
| 67 | + LongSupplier nanoTimeSupplier |
| 68 | + ) { |
49 | 69 | this.numInvalidation = numInvalidation; |
50 | 70 | this.lastSuccessfulAuthCache = lastSuccessfulAuthCache; |
| 71 | + this.authenticationMetrics = new SecurityMetrics<>( |
| 72 | + SecurityMetricType.AUTHC_REALMS, |
| 73 | + meterRegistry, |
| 74 | + this::buildMetricAttributes, |
| 75 | + nanoTimeSupplier |
| 76 | + ); |
51 | 77 | } |
52 | 78 |
|
53 | 79 | @Override |
@@ -141,66 +167,69 @@ private void consumeToken(Context context, ActionListener<AuthenticationResult<A |
141 | 167 | realm, |
142 | 168 | authenticationToken.getClass().getName() |
143 | 169 | ); |
144 | | - realm.authenticate(authenticationToken, ActionListener.wrap(result -> { |
145 | | - assert result != null : "Realm " + realm + " produced a null authentication result"; |
146 | | - logger.debug( |
147 | | - "Authentication of [{}] using realm [{}] with token [{}] was [{}]", |
148 | | - authenticationToken.principal(), |
149 | | - realm, |
150 | | - authenticationToken.getClass().getSimpleName(), |
151 | | - result |
152 | | - ); |
153 | | - if (result.getStatus() == AuthenticationResult.Status.SUCCESS) { |
154 | | - // user was authenticated, populate the authenticated by information |
155 | | - authenticatedByRef.set(realm); |
156 | | - authenticationResultRef.set(result); |
157 | | - if (lastSuccessfulAuthCache != null && startInvalidation == numInvalidation.get()) { |
158 | | - lastSuccessfulAuthCache.put(authenticationToken.principal(), realm); |
159 | | - } |
160 | | - userListener.onResponse(result.getValue()); |
161 | | - } else { |
162 | | - // the user was not authenticated, call this so we can audit the correct event |
163 | | - context.getRequest().realmAuthenticationFailed(authenticationToken, realm.name()); |
164 | | - if (result.getStatus() == AuthenticationResult.Status.TERMINATE) { |
165 | | - final var resultException = result.getException(); |
166 | | - if (resultException != null) { |
167 | | - logger.info( |
168 | | - () -> format( |
169 | | - "Authentication of [%s] was terminated by realm [%s] - %s", |
| 170 | + realm.authenticate( |
| 171 | + authenticationToken, |
| 172 | + InstrumentedSecurityActionListener.wrapForAuthc(authenticationMetrics, realm, ActionListener.wrap(result -> { |
| 173 | + assert result != null : "Realm " + realm + " produced a null authentication result"; |
| 174 | + logger.debug( |
| 175 | + "Authentication of [{}] using realm [{}] with token [{}] was [{}]", |
| 176 | + authenticationToken.principal(), |
| 177 | + realm, |
| 178 | + authenticationToken.getClass().getSimpleName(), |
| 179 | + result |
| 180 | + ); |
| 181 | + if (result.getStatus() == AuthenticationResult.Status.SUCCESS) { |
| 182 | + // user was authenticated, populate the authenticated by information |
| 183 | + authenticatedByRef.set(realm); |
| 184 | + authenticationResultRef.set(result); |
| 185 | + if (lastSuccessfulAuthCache != null && startInvalidation == numInvalidation.get()) { |
| 186 | + lastSuccessfulAuthCache.put(authenticationToken.principal(), realm); |
| 187 | + } |
| 188 | + userListener.onResponse(result.getValue()); |
| 189 | + } else { |
| 190 | + // the user was not authenticated, call this so we can audit the correct event |
| 191 | + context.getRequest().realmAuthenticationFailed(authenticationToken, realm.name()); |
| 192 | + if (result.getStatus() == AuthenticationResult.Status.TERMINATE) { |
| 193 | + final var resultException = result.getException(); |
| 194 | + if (resultException != null) { |
| 195 | + logger.info( |
| 196 | + () -> format( |
| 197 | + "Authentication of [%s] was terminated by realm [%s] - %s", |
| 198 | + authenticationToken.principal(), |
| 199 | + realm.name(), |
| 200 | + result.getMessage() |
| 201 | + ), |
| 202 | + resultException |
| 203 | + ); |
| 204 | + userListener.onFailure(resultException); |
| 205 | + } else { |
| 206 | + logger.info( |
| 207 | + "Authentication of [{}] was terminated by realm [{}] - {}", |
170 | 208 | authenticationToken.principal(), |
171 | 209 | realm.name(), |
172 | 210 | result.getMessage() |
173 | | - ), |
174 | | - resultException |
175 | | - ); |
176 | | - userListener.onFailure(resultException); |
| 211 | + ); |
| 212 | + userListener.onFailure(AuthenticationTerminatedSuccessfullyException.INSTANCE); |
| 213 | + } |
177 | 214 | } else { |
178 | | - logger.info( |
179 | | - "Authentication of [{}] was terminated by realm [{}] - {}", |
180 | | - authenticationToken.principal(), |
181 | | - realm.name(), |
182 | | - result.getMessage() |
183 | | - ); |
184 | | - userListener.onFailure(AuthenticationTerminatedSuccessfullyException.INSTANCE); |
185 | | - } |
186 | | - } else { |
187 | | - if (result.getMessage() != null) { |
188 | | - messages.put(realm, new Tuple<>(result.getMessage(), result.getException())); |
| 215 | + if (result.getMessage() != null) { |
| 216 | + messages.put(realm, new Tuple<>(result.getMessage(), result.getException())); |
| 217 | + } |
| 218 | + userListener.onResponse(null); |
189 | 219 | } |
190 | | - userListener.onResponse(null); |
191 | 220 | } |
192 | | - } |
193 | | - }, (ex) -> { |
194 | | - logger.warn( |
195 | | - () -> format( |
196 | | - "An error occurred while attempting to authenticate [%s] against realm [%s]", |
197 | | - authenticationToken.principal(), |
198 | | - realm.name() |
199 | | - ), |
200 | | - ex |
201 | | - ); |
202 | | - userListener.onFailure(ex); |
203 | | - })); |
| 221 | + }, (ex) -> { |
| 222 | + logger.warn( |
| 223 | + () -> format( |
| 224 | + "An error occurred while attempting to authenticate [%s] against realm [%s]", |
| 225 | + authenticationToken.principal(), |
| 226 | + realm.name() |
| 227 | + ), |
| 228 | + ex |
| 229 | + ); |
| 230 | + userListener.onFailure(ex); |
| 231 | + })) |
| 232 | + ); |
204 | 233 | } else { |
205 | 234 | userListener.onResponse(null); |
206 | 235 | } |
@@ -362,4 +391,14 @@ public synchronized Throwable fillInStackTrace() { |
362 | 391 | return this; |
363 | 392 | } |
364 | 393 | } |
| 394 | + |
| 395 | + private Map<String, Object> buildMetricAttributes(Realm realm, String failureReason) { |
| 396 | + final Map<String, Object> attributes = new HashMap<>(failureReason != null ? 3 : 2); |
| 397 | + attributes.put(ATTRIBUTE_REALM_NAME, realm.name()); |
| 398 | + attributes.put(ATTRIBUTE_REALM_TYPE, realm.type()); |
| 399 | + if (failureReason != null) { |
| 400 | + attributes.put(ATTRIBUTE_REALM_AUTHC_FAILURE_REASON, failureReason); |
| 401 | + } |
| 402 | + return attributes; |
| 403 | + } |
365 | 404 | } |
0 commit comments