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

updateTenableContentSecurityPolicy #6135

Merged
merged 9 commits into from
Aug 29, 2024
4 changes: 4 additions & 0 deletions apps/backend/.env-example
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ DATABASE_SSL_CA=<Full path to SSL certificate authority OR the certificate autho
## Reverse proxy
NGINX_HOST=<Templated out as the 'server_name' for the NGINX configuration (no default, must be set if using the provided example NGINX configuration)>

## External interfaces
SPLUNK_HOST_URL=<The full Uniform Resource Locator (URL) without the port for the Splunk host (no default, must be set if connecting to Splunk)>
TENABLE_HOST_URL=<The full Uniform Resource Locator (URL) without the port for the Tenable.SC host (no default, must be set if connecting to Tenable)>

# Authentication

EXTERNAL_URL=<The external URL for your Heimdall deployment, for example https://heimdall.mitre.org>
Expand Down
18 changes: 18 additions & 0 deletions apps/backend/config/app_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,24 @@ export default class AppConfig {
return process.env[key] || this.envConfig[key];
}

getSplunkHostUrl(): string {
const splunk_host_url = this.get('SPLUNK_HOST_URL');
if (splunk_host_url !== undefined) {
return splunk_host_url;
} else {
return '';
}
}

getTenableHostUrl(): string {
const tenable_host_url = this.get('TENABLE_HOST_URL');
if (tenable_host_url !== undefined) {
return tenable_host_url;
} else {
return '';
}
}

getDatabaseName(): string {
const databaseName = this.get('DATABASE_NAME');
const nodeEnvironment = this.get('NODE_ENV');
Expand Down
12 changes: 11 additions & 1 deletion apps/backend/src/config/config.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,20 @@ export class ConfigService {
oidcName: this.get('OIDC_NAME') || '',
ldap: this.get('LDAP_ENABLED')?.toLocaleLowerCase() === 'true' || false,
registrationEnabled: this.isRegistrationAllowed(),
localLoginEnabled: this.isLocalLoginAllowed()
Amndeep7 marked this conversation as resolved.
Show resolved Hide resolved
localLoginEnabled: this.isLocalLoginAllowed(),
tenableHostUrl: this.getTenableHostUrl(),
splunkHostUrl: this.getSplunkHostUrl()
});
}

getSplunkHostUrl(): string {
return this.appConfig.getSplunkHostUrl();
}

getTenableHostUrl(): string {
return this.appConfig.getTenableHostUrl();
}

getDbConfig(): SequelizeOptions {
return this.appConfig.getDbConfig();
}
Expand Down
4 changes: 4 additions & 0 deletions apps/backend/src/config/dto/startup-settings.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export class StartupSettingsDto implements IStartupSettings {
readonly ldap: boolean;
readonly registrationEnabled: boolean;
readonly localLoginEnabled: boolean;
readonly tenableHostUrl: string;
readonly splunkHostUrl: string;

constructor(settings: IStartupSettings) {
this.apiKeysEnabled = settings.apiKeysEnabled;
Expand All @@ -23,5 +25,7 @@ export class StartupSettingsDto implements IStartupSettings {
this.ldap = settings.ldap;
this.registrationEnabled = settings.registrationEnabled;
this.localLoginEnabled = settings.localLoginEnabled;
this.tenableHostUrl = settings.tenableHostUrl;
this.splunkHostUrl = settings.splunkHostUrl;
}
}
6 changes: 4 additions & 2 deletions apps/backend/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,10 @@ async function bootstrap() {
'connect-src': [
georgedias marked this conversation as resolved.
Show resolved Hide resolved
"'self'",
'https://api.github.com',
'https://sts.amazonaws.com'
]
'https://sts.amazonaws.com',
configService.getTenableHostUrl(),
configService.getSplunkHostUrl()
].filter((source) => source)
}
})
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
<div>
<v-form>
<v-text-field
ref="access_Key"
v-model="accesskey"
label="Access Token (Key)"
for="accesskey_field"
:rules="[reqRule]"
data-cy="tenableaccesskey"
/>
<v-text-field
ref="secret_Key"
v-model="secretkey"
label="Secret Token (Key)"
for="secretkey_field"
Expand All @@ -18,6 +20,7 @@
data-cy="tenablesecretkey"
/>
<v-text-field
ref="hostname_value"
v-model="hostname"
label="Hostname"
for="hostname_field"
Expand Down Expand Up @@ -70,14 +73,40 @@ export default class AuthStep extends Vue {
secretkey = '';
hostname = '';

$refs!: {
access_Key: HTMLInputElement;
secret_Key: HTMLAnchorElement;
hostname_value: HTMLAnchorElement;
};

// Form required field rule
reqRule = requireFieldRule;

async login(): Promise<void> {
if (!this.accesskey) {
SnackbarModule.failure('The Access Token (key) is required');
this.$refs.access_Key.focus();
return;
} else if (!this.secretkey) {
SnackbarModule.failure('The Secret Token (key) is required');
this.$refs.secret_Key.focus();
return;
} else if (!this.hostname) {
SnackbarModule.failure('The Tenable.Sc URL is required');
this.$refs.hostname_value.focus();
return;
}

// If the protocol (https) is missing add it
if (!/^https?:\/\//.test(this.hostname)) {
this.hostname = `https://${this.hostname}`;
}

// If the SSL/TLS port is missing add default 443
if (!this.hostname.split(':')[2]) {
this.hostname = `${this.hostname}:443`;
}

const config: AuthInfo = {
accesskey: this.accesskey,
secretkey: this.secretkey,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
</span>
<br />
<span>
For connection instructions and further information, check here:
For connection instructions and further information, consult:
</span>
<v-btn
target="_blank"
Expand Down
6 changes: 6 additions & 0 deletions apps/frontend/src/store/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ export interface IServerState {
ldap: boolean;
localLoginEnabled: boolean;
userInfo: IUser;
tenableHostUrl: string;
splunkHostUrl: string;
}

interface LoginData {
Expand Down Expand Up @@ -63,6 +65,8 @@ class Server extends VuexModule implements IServerState {
enabledOAuth: string[] = [];
allUsers: ISlimUser[] = [];
oidcName = '';
tenableHostUrl: string = '';
splunkHostUrl: string = '';
/** Our currently granted JWT token */
token = '';
/** Provide a sane default for userInfo in order to avoid having to null check it all the time */
Expand Down Expand Up @@ -106,6 +110,8 @@ class Server extends VuexModule implements IServerState {
this.oidcName = settings.oidcName;
this.ldap = settings.ldap;
this.localLoginEnabled = settings.localLoginEnabled;
this.tenableHostUrl = settings.tenableHostUrl;
this.splunkHostUrl = settings.splunkHostUrl;
}

@Mutation
Expand Down
85 changes: 62 additions & 23 deletions apps/frontend/src/utilities/tenable_util.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Zip from 'adm-zip';
import axios, {AxiosInstance} from 'axios';
import {ServerModule} from '@/store/server';
import {createWinstonLogger} from '../../../../libs/hdf-converters/src/utils/global';

/** represents the information of the current used */
Expand Down Expand Up @@ -49,7 +50,7 @@ export class TenableUtil {
() =>
reject(
new Error(
'Login timed out. Please check your CORS configuration or validate you have inputted the correct domain'
'Login timed out. Please ensure the provided credentials and domain/URL are valid and try again.'
)
),
5000
Expand All @@ -64,28 +65,70 @@ export class TenableUtil {
resolve(response.request.finished);
})
.catch((error) => {
try {
if (error.code == 'ENOTFOUND') {
reject(
`Host: ${this.hostConfig.host_url} not found, check the Host Name (URL) or the network`
);
} else if (error.response.data.error_code == 74) {
reject('Incorrect Access or Secret key');
} else {
reject(error.response.data.error_msg);
}
} catch (e) {
reject(
`Possible network connection blocked by CORS policy. Received error: ${error}`
);
}
reject(this.getRejectConnectionMessage(error));
});
} catch (e) {
reject(`Unknown error: ${e}`);
}
});
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
getRejectConnectionMessage(error: any): string {
georgedias marked this conversation as resolved.
Show resolved Hide resolved
let rejectMsg = '';

if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx

if (error.response.data.error_code == 74) {
rejectMsg = 'Incorrect Access or Secret key';
} else {
rejectMsg = `${error.name} : ${error.response.data.error_msg}`;
}
} else if (error.request) {
// The request was made but no response was received.
// `error.request` is an instance of XMLHttpRequest in the
// browser and an instance of http.ClientRequest in node.js

if (error.code == 'ERR_NETWORK') {
// Check if the tenable url was provided - Content Security Policy (CSP)
const tenableUrl = ServerModule.tenableHostUrl;
if (tenableUrl) {
// If the URL is listed in the allows domains
// (.env variable TENABLE_HOST_URL) check if they match
if (!error.config.baseURL.includes(tenableUrl)) {
rejectMsg = `Hostname: ${error.config.baseURL} violates the Content Security Policy (CSP). The host allowed by the CSP is: ${tenableUrl}`;
} else {
// CSP url didn't match, check for port match - reject appropriately
const portNumber = parseInt(this.hostConfig.host_url.split(':')[2]);
if (portNumber != 443) {
rejectMsg = `Invalid SSL/TSL port number used: ${portNumber} must be 443.`;
} else {
rejectMsg =
'Access blocked by CORS, enable CORS on the browser and try again. See Help for additional instructions.';
}
}
} else if (ServerModule.serverMode) {
// The URL is not listed in the allows domains (CSP) and Heimdall instance is a server
rejectMsg =
'The Content Security Policy directive environment variable "TENABLE_HOST_URL" not configured. See Help for additional instructions.';
} else {
rejectMsg =
'Access blocked by CORS, enable CORS on the browser and try again. See Help for additional instructions.';
}
} else if (error.code == 'ENOTFOUND') {
rejectMsg = `Host: ${error.config.baseURL} not found, check the Hostname (URL) or the network.`;
} else {
rejectMsg = `${error.name} : ${error.message}`;
}
} else {
// Something happened in setting up the request that triggered an Error
rejectMsg = `${error.name} : ${error.message}`;
}
return rejectMsg;
}

/**
* Gets the list of Scan Results.
* Returned values are based on the fields requested:
Expand All @@ -98,7 +141,7 @@ export class TenableUtil {
() =>
reject(
new Error(
'Login timed out. Please check your CORS configuration or validate you have inputted the correct domain'
'Login timed out. Please ensure the provided credentials and domain/URL are valid and try again.'
)
),
5000
Expand All @@ -113,11 +156,7 @@ export class TenableUtil {
resolve(response.data.response.usable);
})
.catch((error) => {
if (error.response.data.error_code == 74) {
reject('Incorrect Access or Secret key');
} else {
reject(error.response.data.error_msg);
}
reject(`${error.name} : ${error.message}`);
});
} catch (e) {
reject(e);
Expand All @@ -140,7 +179,7 @@ export class TenableUtil {
() =>
reject(
new Error(
'Login timed out. Please check your CORS configuration or validate you have inputted the correct domain'
'Login timed out. Please check your CORS configuration and validate that the hostname is correct.'
)
),
5000
Expand Down
2 changes: 2 additions & 0 deletions libs/interfaces/config/startup-settings.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ export interface IStartupSettings {
readonly ldap: boolean;
readonly registrationEnabled: boolean;
readonly localLoginEnabled: boolean;
readonly tenableHostUrl: string;
readonly splunkHostUrl: string;
}
Loading