Skip to content

Commit

Permalink
Added client certificate options to support mutual TLS for OpenID end…
Browse files Browse the repository at this point in the history
…point (#1650)

* Added client certificate options to support mutual TLS

---------

Signed-off-by: Calvin Harrison <[email protected]>
Signed-off-by: Simple-Analysis <[email protected]>
Co-authored-by: Peter Nied <[email protected]>
  • Loading branch information
Simple-Analysis and peternied authored Nov 30, 2023
1 parent d863368 commit ec661f8
Show file tree
Hide file tree
Showing 7 changed files with 243 additions and 7 deletions.
94 changes: 92 additions & 2 deletions server/auth/types/openid/openid_auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,27 @@ import { OpenIdAuthentication } from './openid_auth';
import { SecurityPluginConfigType } from '../../../index';
import { SecuritySessionCookie } from '../../../session/security_cookie';
import { deflateValue } from '../../../utils/compression';
import { getObjectProperties } from '../../../utils/object_properties_defined';
import {
IRouter,
CoreSetup,
ILegacyClusterClient,
Logger,
SessionStorageFactory,
} from '../../../../../../src/core/server';

interface Logger {
debug(message: string): void;
info(message: string): void;
warn(message: string): void;
error(message: string): void;
fatal(message: string): void;
}

describe('test OpenId authHeaderValue', () => {
let router: IRouter;
let core: CoreSetup;
let esClient: ILegacyClusterClient;
let sessionStorageFactory: SessionStorageFactory<SecuritySessionCookie>;
let logger: Logger;

// Consistent with auth_handler_factory.test.ts
beforeEach(() => {});
Expand All @@ -50,6 +57,14 @@ describe('test OpenId authHeaderValue', () => {
},
} as unknown) as SecurityPluginConfigType;

const logger = {
debug: (message: string) => {},
info: (message: string) => {},
warn: (message: string) => {},
error: (message: string) => {},
fatal: (message: string) => {},
};

test('make sure that cookies with authHeaderValue are still valid', async () => {
const openIdAuthentication = new OpenIdAuthentication(
config,
Expand Down Expand Up @@ -117,4 +132,79 @@ describe('test OpenId authHeaderValue', () => {

expect(headers).toEqual(expectedHeaders);
});

test('Make sure that wreckClient can be configured with mTLS', async () => {
const customConfig = {
openid: {
certificate: 'test/certs/cert.pem',
private_key: 'test/certs/private-key.pem',
header: 'authorization',
scope: [],
},
};

const openidConfig = (customConfig as unknown) as SecurityPluginConfigType;

const openIdAuthentication = new OpenIdAuthentication(
openidConfig,
sessionStorageFactory,
router,
esClient,
core,
logger
);

const wreckHttpsOptions = openIdAuthentication.getWreckHttpsOptions();

console.log(
'============= PEM =============',
'\n\n',
getObjectProperties(customConfig.openid, 'OpenID'),
'\n\n',
getObjectProperties(wreckHttpsOptions, 'wreckHttpsOptions')
);

expect(wreckHttpsOptions.key).toBeDefined();
expect(wreckHttpsOptions.cert).toBeDefined();
expect(wreckHttpsOptions.pfx).toBeUndefined();
});

test('Ensure private key and certificate are not exposed when using PFX certificate', async () => {
const customConfig = {
openid: {
pfx: 'test/certs/keyStore.p12',
certificate: 'test/certs/cert.pem',
private_key: 'test/certs/private-key.pem',
passphrase: '',
header: 'authorization',
scope: [],
},
};

const openidConfig = (customConfig as unknown) as SecurityPluginConfigType;

const openIdAuthentication = new OpenIdAuthentication(
openidConfig,
sessionStorageFactory,
router,
esClient,
core,
logger
);

const wreckHttpsOptions = openIdAuthentication.getWreckHttpsOptions();

console.log(
'============= PFX =============',
'\n\n',
getObjectProperties(customConfig.openid, 'OpenID'),
'\n\n',
getObjectProperties(wreckHttpsOptions, 'wreckHttpsOptions')
);

expect(wreckHttpsOptions.pfx).toBeDefined();
expect(wreckHttpsOptions.key).toBeUndefined();
expect(wreckHttpsOptions.cert).toBeUndefined();
expect(wreckHttpsOptions.passphrase).toBeUndefined();
});
});
41 changes: 36 additions & 5 deletions server/auth/types/openid/openid_auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { OpenIdAuthRoutes } from './routes';
import { AuthenticationType } from '../authentication_type';
import { callTokenEndpoint } from './helper';
import { composeNextUrlQueryParam } from '../../../utils/next_url';
import { getObjectProperties } from '../../../utils/object_properties_defined';
import { getExpirationDate } from './helper';
import { AuthType, OPENID_AUTH_LOGIN } from '../../../../common';
import {
Expand All @@ -55,6 +56,10 @@ export interface OpenIdAuthConfig {

export interface WreckHttpsOptions {
ca?: string | Buffer | Array<string | Buffer>;
cert?: string | Buffer | Array<string | Buffer>;
key?: string | Buffer | Array<string | Buffer>;
passphrase?: string;
pfx?: string | Buffer | Array<string | Buffer>;
checkServerIdentity?: (host: string, cert: PeerCertificate) => Error | undefined;
}

Expand All @@ -65,6 +70,7 @@ export class OpenIdAuthentication extends AuthenticationType {
private authHeaderName: string;
private openIdConnectUrl: string;
private wreckClient: typeof wreck;
private wreckHttpsOption: WreckHttpsOptions = {};

constructor(
config: SecurityPluginConfigType,
Expand Down Expand Up @@ -119,21 +125,42 @@ export class OpenIdAuthentication extends AuthenticationType {
}

private createWreckClient(): typeof wreck {
const wreckHttpsOption: WreckHttpsOptions = {};
if (this.config.openid?.root_ca) {
wreckHttpsOption.ca = [fs.readFileSync(this.config.openid.root_ca)];
this.wreckHttpsOption.ca = [fs.readFileSync(this.config.openid.root_ca)];
this.logger.debug(`Using CA Cert: ${this.config.openid.root_ca}`);
}
if (this.config.openid?.pfx) {
// Use PFX or PKCS12 if provided
this.logger.debug(`Using PFX or PKCS12: ${this.config.openid.pfx}`);
this.wreckHttpsOption.pfx = [fs.readFileSync(this.config.openid.pfx)];
} else if (this.config.openid?.certificate && this.config.openid?.private_key) {
// Use 'certificate' and 'private_key' if provided
this.logger.debug(`Using Certificate: ${this.config.openid.certificate}`);
this.logger.debug(`Using Private Key: ${this.config.openid.private_key}`);
this.wreckHttpsOption.cert = [fs.readFileSync(this.config.openid.certificate)];
this.wreckHttpsOption.key = [fs.readFileSync(this.config.openid.private_key)];
} else {
this.logger.debug(
`Client certificates not provided. Mutual TLS will not be used to obtain endpoints.`
);
}
// Check if passphrase is provided, use it for 'pfx' and 'key'
if (this.config.openid?.passphrase !== '') {
this.logger.debug(`Passphrase not provided for private key and/or pfx.`);
this.wreckHttpsOption.passphrase = this.config.openid?.passphrase;
}
if (this.config.openid?.verify_hostnames === false) {
this.logger.debug(`openId auth 'verify_hostnames' option is off.`);
wreckHttpsOption.checkServerIdentity = (host: string, cert: PeerCertificate) => {
this.wreckHttpsOption.checkServerIdentity = (host: string, cert: PeerCertificate) => {
return undefined;
};
}
if (Object.keys(wreckHttpsOption).length > 0) {
this.logger.info(getObjectProperties(this.wreckHttpsOption, 'WreckHttpsOptions'));
if (Object.keys(this.wreckHttpsOption).length > 0) {
return wreck.defaults({
agents: {
http: new HTTP.Agent(),
https: new HTTPS.Agent(wreckHttpsOption),
https: new HTTPS.Agent(this.wreckHttpsOption),
httpsAllowUnauthorized: new HTTPS.Agent({
rejectUnauthorized: false,
}),
Expand All @@ -144,6 +171,10 @@ export class OpenIdAuthentication extends AuthenticationType {
}
}

getWreckHttpsOptions(): WreckHttpsOptions {
return this.wreckHttpsOption;
}

createExtraStorage() {
// @ts-ignore
const hapiServer: Server = this.sessionStorageFactory.asScoped({}).server;
Expand Down
4 changes: 4 additions & 0 deletions server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,10 @@ export const configSchema = schema.object({
base_redirect_url: schema.string({ defaultValue: '' }),
logout_url: schema.string({ defaultValue: '' }),
root_ca: schema.string({ defaultValue: '' }),
certificate: schema.string({ defaultValue: '' }),
private_key: schema.string({ defaultValue: '' }),
passphrase: schema.string({ defaultValue: '' }),
pfx: schema.string({ defaultValue: '' }),
verify_hostnames: schema.boolean({ defaultValue: true }),
refresh_tokens: schema.boolean({ defaultValue: true }),
trust_dynamic_headers: schema.boolean({ defaultValue: false }),
Expand Down
24 changes: 24 additions & 0 deletions server/utils/object_properties_defined.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* 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.
*/

export function getObjectProperties(obj: Record<string, any>, objName: string): string {
const objSummary: string[] = [];

for (const [key, value] of Object.entries(obj)) {
objSummary.push(`${key}: ${value !== undefined ? 'Defined' : 'Not Defined'}`);
}

return `${objName} properties:\n${objSummary.map((option) => ` ${option}`).join('\n')}`;
}
35 changes: 35 additions & 0 deletions test/certs/cert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
-----BEGIN CERTIFICATE-----
MIIGJzCCBA+gAwIBAgIUMlm6Xg1wnOLi9gRLy3v4jF5U2JcwDQYJKoZIhvcNAQEL
BQAwgaIxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQK
DBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxFjAUBgNVBAMMDVRlc3RlciBNY1Vu
aXQxQzBBBgkqhkiG9w0BCQEWNHRlc3Rlci5tY3VuaXRAb3BlbnNlYXJjaGRhc2hi
b2FyZHNzZWN1cml0eXBsdWdpbi5jb20wHhcNMjMxMTIzMDg1NzQwWhcNMjQxMTIy
MDg1NzQwWjCBojELMAkGA1UEBhMCVVMxEzARBgNVBAgMClNvbWUtU3RhdGUxITAf
BgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEWMBQGA1UEAwwNVGVzdGVy
IE1jVW5pdDFDMEEGCSqGSIb3DQEJARY0dGVzdGVyLm1jdW5pdEBvcGVuc2VhcmNo
ZGFzaGJvYXJkc3NlY3VyaXR5cGx1Z2luLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQAD
ggIPADCCAgoCggIBALLtHnXJyc7t50o2AlhzpaoZP81l80BYfEGf8wolNrlMXzJ7
M32X7hG5quSdqlurUSS1L9hkl7Taqbq4fsiGrZX/s+8nkYDRfnaCU6nFH5gvwKcC
bPJyVCYKhuYL0qqKWjek+orknr3P6a7J6Db+eg6HuXlPFLl//JNwYrTBYtaymtaq
ek4mNyxUXlFq/4DXWDCe6DjMhdxdA56vB/3yF4qIdbKjhXuyIsNvMOLEsJe7c7tA
+7E1595vOX+jERSASDn7qA310tc9/NImobHz1fFBD3wL/WWMjfbRPLPC1LSM18EY
o3Cn9mDvHxXy/GHYq9AN5P7esQ0WmIA3AAZR0mUIDOGTG2kFIGlo9sSRBJ+Ygbf7
7cCA9WE8cqAsEgajT9ZQRxYtGFJyK/M8mSldS+k0TMyWWR0wUeEdknGaTnfYx8lX
LcUFjWM6lTdJam4lereX7qkoJlxCadHPDpW+DIE55dOR9PVvVJtAOeMVZtMe7veo
QW4AMZFjV87rQKdzmQYEu1hLWyDOl46QA9U8nZhYc9A5TGGMDUwhvscoNQ7DjUt/
O29IL/n9wpaa3hA/K3adg6hmSL57HBR4OxPRfNLT4c77zEyTTqpK9v0m5UGcKJyF
m7gQpG9MAdCjCA1JfzWXLLkA/Wsz7OVppYmhH7+dawwLKBAHC1bUo2iWx38TAgMB
AAGjUzBRMB0GA1UdDgQWBBQIkBuvFnIXXVsQ4xsWy5/OIoV0uDAfBgNVHSMEGDAW
gBQIkBuvFnIXXVsQ4xsWy5/OIoV0uDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3
DQEBCwUAA4ICAQBRgxLAsPfEiG2rYplRCw/NMfI10EyiiWl0711LiIw3C47eQiC/
sK7YXWsl4UTRtMyuK7kPTJf3g9e8VDHGyUZfqtBK2+ZYZSV49DWjb2ihWyK7TT1p
bvIW64gSWVWi6J5WOjBbHnlqaggjfNxo76VRaWP2e14m0B5QsnNYvG9LqK++27IL
ESZhb+Vd3NmVlcWIae1CXT8rdsAI3MR30aNDLnK2/YStfvDdgPGKC9VDqoBSywos
FPTVL+eqGAie9xDcSPpCpqCqGzPQk/Si25b9rUUWfxNyMomyVYeHTaZbT6XralKH
6ZtZwi8nuKw9/TKnMH2fAoBk9ZEq47ididzrPTZKDk5MDicpwWwX2JJbilcqWOGQ
0qeJsZk4zCNzLRiX8jjd0kLCfV0KcqRp2Skr0bAlO/KU8cvP2+IV92PnsB5ZD+yi
BmurX0tUT8DAusq3JLzJiBrngWpfUR0uQRzzJqHK8vtVOq+4BnxC6YEgnL5MIKCL
FDdQnE9G/t4N+Bot631Zfhm4KDSB9ycodAsdMujBco6GJdWbSueZB8YdGcBx47ig
1u1yDRqETXRyCU2PITxHbtBlgCkeNPRxlulz18WJLTlAPBhiZrS8GTqo7FjlE78N
GhgJ37+mzqK68J/PRVXpF3jLl0jhSEyUtgLTH/+y33WERhF6BG5cnYOlNA==
-----END CERTIFICATE-----
Binary file added test/certs/keyStore.p12
Binary file not shown.
52 changes: 52 additions & 0 deletions test/certs/private-key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
3Q5ijNV6CUa+Ka7Qg7FWESHFemt1gQ==
-----END PRIVATE KEY-----

0 comments on commit ec661f8

Please sign in to comment.