-
Notifications
You must be signed in to change notification settings - Fork 8.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introduce Kerberos authentication provider. #36112
Changes from 5 commits
b7f3175
f43baad
d87a368
e53c160
29d1efb
0927834
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,25 +26,38 @@ export function authenticateFactory(server) { | |
let authenticationResult; | ||
try { | ||
authenticationResult = await server.plugins.security.authenticate(request); | ||
} catch(err) { | ||
} catch (err) { | ||
server.log(['error', 'authentication'], err); | ||
return wrapError(err); | ||
} | ||
|
||
if (authenticationResult.succeeded()) { | ||
return h.authenticated({ credentials: authenticationResult.user }); | ||
} else if (authenticationResult.redirected()) { | ||
} | ||
|
||
if (authenticationResult.redirected()) { | ||
// Some authentication mechanisms may require user to be redirected to another location to | ||
// initiate or complete authentication flow. It can be Kibana own login page for basic | ||
// authentication (username and password) or arbitrary external page managed by 3rd party | ||
// Identity Provider for SSO authentication mechanisms. Authentication provider is the one who | ||
// decides what location user should be redirected to. | ||
return h.redirect(authenticationResult.redirectURL).takeover(); | ||
} else if (authenticationResult.failed()) { | ||
server.log(['info', 'authentication'], `Authentication attempt failed: ${authenticationResult.error.message}`); | ||
return wrapError(authenticationResult.error); | ||
} else { | ||
return Boom.unauthorized(); | ||
} | ||
|
||
if (authenticationResult.failed()) { | ||
server.log( | ||
['info', 'authentication'], | ||
`Authentication attempt failed: ${authenticationResult.error.message}` | ||
); | ||
|
||
const error = wrapError(authenticationResult.error); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. note: we could potentially move this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like the explicitness of how it's done currently. |
||
if (authenticationResult.challenges) { | ||
error.output.headers['WWW-Authenticate'] = authenticationResult.challenges; | ||
} | ||
|
||
return error; | ||
} | ||
|
||
return Boom.unauthorized(); | ||
}; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ | |
* Represents status that `AuthenticationResult` can be in. | ||
*/ | ||
import { AuthenticatedUser } from '../../../common/model'; | ||
import { getErrorStatusCode } from '../errors'; | ||
|
||
enum AuthenticationResultStatus { | ||
/** | ||
|
@@ -40,6 +41,7 @@ enum AuthenticationResultStatus { | |
*/ | ||
interface AuthenticationOptions { | ||
error?: Error; | ||
challenges?: string[]; | ||
redirectURL?: string; | ||
state?: unknown; | ||
user?: AuthenticatedUser; | ||
|
@@ -73,13 +75,21 @@ export class AuthenticationResult { | |
/** | ||
* Produces `AuthenticationResult` for the case when authentication fails. | ||
* @param error Error that occurred during authentication attempt. | ||
* @param [challenges] Optional list of the challenges that will be returned to the user within | ||
* `WWW-Authenticate` HTTP header. Multiple challenges will result in multiple headers (one per | ||
* challenge) as it's better supported by the browsers than comma separated list within a single | ||
* header. Challenges can only be set for errors with `401` error status. | ||
*/ | ||
public static failed(error: Error) { | ||
public static failed(error: Error, challenges?: string[]) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. note: If I understand that correctly There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I understand the current implementation correctly, when the Kerberos provider returns with The previous WIP approach which I implemented was intending to allow the Other internals might have changed where the previous approach is no longer relevant. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Right, the only way user will be able to login using
Thanks for pointing out! I've played with this approach last week, it looks interesting, but I'd rather tackle this separately if find out that current approach is annoying for our users. Would that work for you? There are three things that make me think about handling this separately:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think what you have is perfectly fine :) I think at some point, we'll likely need to add the "choose your login method" when we have multiple auth providers configured. |
||
if (!error) { | ||
throw new Error('Error should be specified.'); | ||
} | ||
|
||
return new AuthenticationResult(AuthenticationResultStatus.Failed, { error }); | ||
if (challenges != null && getErrorStatusCode(error) !== 401) { | ||
throw new Error('Challenges can only be provided with `401 Unauthorized` errors.'); | ||
} | ||
|
||
return new AuthenticationResult(AuthenticationResultStatus.Failed, { error, challenges }); | ||
} | ||
|
||
/** | ||
|
@@ -117,6 +127,13 @@ export class AuthenticationResult { | |
return this.options.error; | ||
} | ||
|
||
/** | ||
* Challenges that need to be sent to the user within `WWW-Authenticate` HTTP header. | ||
*/ | ||
public get challenges() { | ||
return this.options.challenges; | ||
} | ||
|
||
/** | ||
* URL that should be used to redirect user to complete authentication only available | ||
* for `redirected` result). | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,8 @@ import { | |
AuthenticationProviderOptions, | ||
BaseAuthenticationProvider, | ||
BasicAuthenticationProvider, | ||
KerberosAuthenticationProvider, | ||
RequestWithLoginAttempt, | ||
SAMLAuthenticationProvider, | ||
TokenAuthenticationProvider, | ||
OIDCAuthenticationProvider, | ||
|
@@ -37,6 +39,7 @@ const providerMap = new Map< | |
) => BaseAuthenticationProvider | ||
>([ | ||
['basic', BasicAuthenticationProvider], | ||
['kerberos', KerberosAuthenticationProvider], | ||
['saml', SAMLAuthenticationProvider], | ||
['token', TokenAuthenticationProvider], | ||
['oidc', OIDCAuthenticationProvider], | ||
|
@@ -163,7 +166,7 @@ class Authenticator { | |
* Performs request authentication using configured chain of authentication providers. | ||
* @param request Request instance. | ||
*/ | ||
async authenticate(request: Legacy.Request) { | ||
async authenticate(request: RequestWithLoginAttempt) { | ||
assertRequest(request); | ||
|
||
const isSystemApiRequest = this.server.plugins.kibana.systemApi.isSystemApiRequest(request); | ||
|
@@ -227,7 +230,7 @@ class Authenticator { | |
* Deauthenticates current request. | ||
* @param request Request instance. | ||
*/ | ||
async deauthenticate(request: Legacy.Request) { | ||
async deauthenticate(request: RequestWithLoginAttempt) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would you mind explaining the significance of changing the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I'm on the fence here. There were two reasons to use
But none of these reasons are strong enough and since it confused you it will confuse others too, I'll switch back to |
||
assertRequest(request); | ||
|
||
const sessionValue = await this.getSessionValue(request); | ||
|
@@ -307,8 +310,10 @@ export async function initAuthenticator(server: Legacy.Server) { | |
return loginAttempts.get(request); | ||
}); | ||
|
||
server.expose('authenticate', (request: Legacy.Request) => authenticator.authenticate(request)); | ||
server.expose('deauthenticate', (request: Legacy.Request) => | ||
server.expose('authenticate', (request: RequestWithLoginAttempt) => | ||
authenticator.authenticate(request) | ||
); | ||
server.expose('deauthenticate', (request: RequestWithLoginAttempt) => | ||
authenticator.deauthenticate(request) | ||
); | ||
server.expose('registerAuthScopeGetter', (scopeExtender: ScopesGetter) => | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: @elastic/kibana-operations how do you feel about this change? We need to specify some JVM options for the test ES instance (namely
-Djava.security.krb5.conf
and-Dsun.security.krb5.rcache
) and I'm aware of only two ways of doing that, either viaconfig/jvm.options
orES_JAVA_OPTS
env variable.Dealing with
ES_JAVA_OPTS
seemed easier to me, so I picked that option, but happy to hear about any better ways of doing that. With this change test config would look like this:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Works for me, though I think you might run into problems with paths containing spaces based on your snippet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great!
Thanks for pointing out! We shouldn't have spaces there, but yeah, I pretend this problem doesn't exist yet 🙈