Skip to content
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

#907 kerberos authentication #1575

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ export const SAML_AUTH_LOGIN = '/auth/saml/login';
export const ANONYMOUS_AUTH_LOGIN = '/auth/anonymous';
export const SAML_AUTH_LOGIN_WITH_FRAGMENT = '/auth/saml/captureUrlFragment';

export const KERBEROS_AUTH_LOGIN = '/auth/kerberos/login';
export const KERBEROS_AUTH_LOGOUT = '/auth/kerberos/logout';

export const OPENID_AUTH_LOGOUT = '/auth/openid/logout';
export const SAML_AUTH_LOGOUT = '/auth/saml/logout';
export const ANONYMOUS_AUTH_LOGOUT = '/auth/anonymous/logout';
Expand Down Expand Up @@ -61,6 +64,7 @@ export enum AuthType {
SAML = 'saml',
PROXY = 'proxy',
ANONYMOUS = 'anonymous',
KERBEROS = 'kerberos',
}

/**
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"start": "node ../../scripts/opensearch-dashboards --dev",
"lint:es": "node ../../scripts/eslint",
"lint:style": "node ../../scripts/stylelint",
"lint": "yarn run lint:es && yarn run lint:style",
"lint": "yarn run lint:es --fix && yarn run lint:style",
"pretest:jest_server": "node ./test/jest_integration/runIdpServer.js &",
"test:jest_server": "node ./test/run_jest_tests.js --config ./test/jest.config.server.js",
"test:jest_ui": "node ./test/run_jest_tests.js --config ./test/jest.config.ui.js"
Expand All @@ -39,4 +39,4 @@
"resolutions": {
"selenium-webdriver": "4.10.0"
}
}
}
2 changes: 2 additions & 0 deletions public/apps/account/log-out-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@
</EuiButtonEmpty>
</div>
);
} else if (props.authType === AuthType.KERBEROS) {
return '';

Check warning on line 58 in public/apps/account/log-out-button.tsx

View check run for this annotation

Codecov / codecov/patch

public/apps/account/log-out-button.tsx#L58

Added line #L58 was not covered by tests
} else if (props.authType === AuthType.PROXY) {
setShouldShowTenantPopup(null);
return <div />;
Expand Down
7 changes: 7 additions & 0 deletions public/apps/account/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
LOGIN_PAGE_URI,
OPENID_AUTH_LOGOUT,
SAML_AUTH_LOGOUT,
KERBEROS_AUTH_LOGOUT,
} from '../../../common';
import { API_ENDPOINT_ACCOUNT_INFO } from './constants';
import { AccountInfo } from './types';
Expand Down Expand Up @@ -51,6 +52,12 @@
window.location.href = `${http.basePath.serverBasePath}${SAML_AUTH_LOGOUT}`;
}

export async function kerberosLogout(http: HttpStart): Promise<void> {

Check warning on line 55 in public/apps/account/utils.tsx

View check run for this annotation

Codecov / codecov/patch

public/apps/account/utils.tsx#L55

Added line #L55 was not covered by tests
// This will ensure tenancy is picked up from local storage in the next login.
setShouldShowTenantPopup(null);
window.location.href = `${http.basePath.serverBasePath}${KERBEROS_AUTH_LOGOUT}`;

Check warning on line 58 in public/apps/account/utils.tsx

View check run for this annotation

Codecov / codecov/patch

public/apps/account/utils.tsx#L57-L58

Added lines #L57 - L58 were not covered by tests
}

export async function openidLogout(http: HttpStart): Promise<void> {
// This will ensure tenancy is picked up from local storage in the next login.
setShouldShowTenantPopup(null);
Expand Down
8 changes: 8 additions & 0 deletions public/apps/login/login-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
AuthType,
OPENID_AUTH_LOGIN,
SAML_AUTH_LOGIN_WITH_FRAGMENT,
KERBEROS_AUTH_LOGIN,
} from '../../../common';

interface LoginPageDeps {
Expand Down Expand Up @@ -238,6 +239,13 @@
formBodyOp.push(renderLoginButton(AuthType.SAML, samlAuthLoginUrl, samlConfig));
break;
}
case AuthType.KERBEROS: {
const kerberosConfig = props.config.ui[AuthType.KERBEROS].login;
formBodyOp.push(

Check warning on line 244 in public/apps/login/login-page.tsx

View check run for this annotation

Codecov / codecov/patch

public/apps/login/login-page.tsx#L243-L244

Added lines #L243 - L244 were not covered by tests
renderLoginButton(AuthType.KERBEROS, KERBEROS_AUTH_LOGIN, kerberosConfig)
);
break;

Check warning on line 247 in public/apps/login/login-page.tsx

View check run for this annotation

Codecov / codecov/patch

public/apps/login/login-page.tsx#L247

Added line #L247 was not covered by tests
}
default: {
setloginFailed(true);
setloginError(
Expand Down
8 changes: 8 additions & 0 deletions public/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,14 @@ export interface ClientConfigType {
buttonstyle: string;
};
};
kerberos: {
login: {
buttonname: string;
showbrandimage: boolean;
brandimage: string;
buttonstyle: string;
};
};
autologout: boolean;
backend_configurable: boolean;
};
Expand Down
4 changes: 4 additions & 0 deletions server/auth/auth_handler_factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
ProxyAuthentication,
SamlAuthentication,
MultipleAuthentication,
KerberosAuthentication,
} from './types';
import { SecuritySessionCookie } from '../session/security_cookie';
import { IAuthenticationType, IAuthHandlerConstructor } from './types/authentication_type';
Expand Down Expand Up @@ -76,6 +77,9 @@ export async function getAuthenticationHandler(
case AuthType.PROXY:
authHandlerType = ProxyAuthentication;
break;
case AuthType.KERBEROS:
authHandlerType = KerberosAuthentication;
break;
default:
throw new Error(`Unsupported authentication type: ${currType}`);
}
Expand Down
1 change: 1 addition & 0 deletions server/auth/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ export { OpenIdAuthentication } from './openid/openid_auth';
export { ProxyAuthentication } from './proxy/proxy_auth';
export { SamlAuthentication } from './saml/saml_auth';
export { MultipleAuthentication } from './multiple/multi_auth';
export { KerberosAuthentication } from './kerberos/kerberos_authentication';
103 changes: 103 additions & 0 deletions server/auth/types/kerberos/kerberos_authentication.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* Copyright OpenSearch Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

import { get } from 'lodash';
import { CoreSetup } from 'opensearch-dashboards/server';
import { AuthenticationType } from '../authentication_type';
import { SecurityPluginConfigType } from '../../../index';
import { SecuritySessionCookie } from '../../../session/security_cookie';
import {
SessionStorageFactory,
IRouter,
ILegacyClusterClient,
OpenSearchDashboardsRequest,
Logger,
IOpenSearchDashboardsResponse,
AuthResult,
LifecycleResponseFactory,
AuthToolkit,
} from '../../../../../../src/core/server';
import { KerberosAuthRoutes, WWW_AUTHENTICATE_HEADER_NAME } from './routes';

export class KerberosAuthentication extends AuthenticationType {
private authHeaderName: string;

requestIncludesAuthInfo(request: OpenSearchDashboardsRequest): boolean {
console.log('HHHHHHH');
console.debug(
get(request.headers, 'authorization') &&
get(request.headers, 'authorization').toString().startsWith('Negotiate')
);
if (
get(request.headers, 'authorization') &&
get(request.headers, 'authorization').toString().startsWith('Negotiate')
) {
return true;
}
return false;
}

public async init() {
const kerberosAuthRoutes = new KerberosAuthRoutes(
this.router,
this.config,
this.sessionStorageFactory,
this.securityClient,
this.coreSetup
);
kerberosAuthRoutes.setupRoutes();
}

constructor(
config: SecurityPluginConfigType,
sessionStorageFactory: SessionStorageFactory<SecuritySessionCookie>,
router: IRouter,
esClient: ILegacyClusterClient,
coreSetup: CoreSetup,
logger: Logger
) {
super(config, sessionStorageFactory, router, esClient, coreSetup, logger);

this.authHeaderName = 'authorization';
}

async getAdditionalAuthHeader(
request: OpenSearchDashboardsRequest<unknown, unknown, unknown, any>
): Promise<any> {
const header: any = {};
return header;
}

getCookie(request: OpenSearchDashboardsRequest, authInfo: any): SecuritySessionCookie {
return {};
}
isValidCookie() {
return Promise.resolve(false);
}

handleUnauthedRequest(
request: OpenSearchDashboardsRequest,
response: LifecycleResponseFactory,
toolkit: AuthToolkit
): IOpenSearchDashboardsResponse | AuthResult {
console.debug('Handling Unauthed Request');

return response.unauthorized({
headers: {
[WWW_AUTHENTICATE_HEADER_NAME]: 'Negotiate',
},
});
}
}
163 changes: 163 additions & 0 deletions server/auth/types/kerberos/routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/*
* Copyright OpenSearch Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
import { schema } from '@osd/config-schema';

import { get } from 'lodash';
import { SecurityPluginConfigType } from '../../../index';
import { SecuritySessionCookie } from '../../../session/security_cookie';
import { SecurityClient } from '../../../backend/opensearch_security_client';
import { SessionStorageFactory, IRouter } from '../../../../../../src/core/server';
import { CoreSetup } from '../../../../../../src/core/server';
import { KERBEROS_AUTH_LOGIN, KERBEROS_AUTH_LOGOUT } from '../../../../common';
import { validateNextUrl } from '../../../utils/next_url';
export const WWW_AUTHENTICATE_HEADER_NAME = 'WWW-Authenticate';

export class KerberosAuthRoutes {
constructor(
private readonly router: IRouter,
// @ts-ignore: unused variable
private readonly config: SecurityPluginConfigType,
private readonly sessionStorageFactory: SessionStorageFactory<SecuritySessionCookie>,
private readonly securityClient: SecurityClient,
private readonly coreSetup: CoreSetup
) {}

public setupRoutes() {
this.router.get(
{
path: KERBEROS_AUTH_LOGIN,
validate: {
query: schema.object({
nextUrl: schema.maybe(
schema.string({
validate: validateNextUrl,
})
),
}),
},
options: {
// TODO: set to false?
authRequired: 'optional',
},
},
async (context, request, response) => {
if (request.auth.isAuthenticated) {
const nextUrl =
request.query.nextUrl ||
`${this.coreSetup.http.basePath.serverBasePath}/app/opensearch-dashboards`;
response.redirected({
headers: {
location: nextUrl,
authorization: request.headers.authorization,
},
});
}

return await this.authenticateWithSPNEGO(request, response, this.securityClient);

// const loginEndpoint = this.config.kerberos.login_endpoint;
// const serverBasePath = this.coreSetup.http.basePath.serverBasePath;
//
// if (loginEndpoint) {
// console.log("redirecting to loginendpoint")
// return response.redirected({
// headers: {
// location: `${serverBasePath}` + KERBEROS_AUTH_LOGIN,
// },
// });
// } else {
// console.log("bad Request")
// return response.badRequest();
// }
}
);

this.router.post(
{
path: KERBEROS_AUTH_LOGOUT,
validate: false,
},
async (context, request, response) => {
this.sessionStorageFactory.asScoped(request).clear();
return response.ok();
}
);
}

// async authenticateWithSPNEGO(request, response, securityClient) {
// let backendError;
// console.log('SP NEGO START' + request.headers.authorization);
// try {
// // const whitelistRoutes = this.config.get('searchguard.auth.unauthenticated_routes');
// // if (whitelistRoutes.includes(request.route.path)) {
// // return this.securityClient.authenticated();
// // }
//
// let headers;
// if (request.headers.authorization) {
// console.log('HHHHHHHHH');
// headers = request.headers;
// }
//
// console.log(
// 'handle Unahuthed Request, this is the request: headers INSIDE',
// '%j',
// request.headers
// );
// console.log('plain hearder %j', headers);
// const authInfo = await securityClient.authenticateWithHeaders(request, headers);
//
// console.log(`Authenticated!: ${JSON.stringify(authInfo, null, 2)}.`);
//
// console.log('GOnna Return The above inside spnego');
// const nextUrl = request.query.nextUrl || `${this.coreSetup.http.basePath.serverBasePath}`;
// console.log('Redirecting TO:::: ' + nextUrl);
// console.log(request.headers.authorization);
// return response.redirected({
// headers: {
// location: nextUrl,
// authorization: request.headers.authorization,
// },
// });
// } catch (error) {
// console.log(
// 'CATCH Error wwwAuthenticateDirective2',
// get(error, `output.headers.${WWW_AUTHENTICATE_HEADER_NAME}`)
// );
// backendError = error.inner || error;
// }
// console.log('Backedn Error: ', backendError.toString());
//
// const negotiationProposal =
// get(backendError, `output.headers[${WWW_AUTHENTICATE_HEADER_NAME}]`, '') ||
// get(backendError, `meta.headers[${WWW_AUTHENTICATE_HEADER_NAME.toLowerCase()}]`, '');
// console.log(`Negotiating: ${negotiationProposal}`);
//
// const isNegotiating: boolean =
// negotiationProposal.startsWith('Negotiate') || // Kerberos negotiation
// negotiationProposal === 'Basic realm="Authorization Required"'; // Basic auth negotiation
//
// // Browser should populate the header and repeat the request after the header is added...
// if (isNegotiating) {
// return response.unauthorized({
// headers: {
// [WWW_AUTHENTICATE_HEADER_NAME]: negotiationProposal,
// },
// });
// }
//
// return response.unauthorized({ body: backendError });
// }
}
Loading
Loading