refactor: Update MonitorSecrets page to display monitors with access to secrets

This commit is contained in:
Simon Larsen 2024-05-29 12:47:21 +01:00
parent c9874df43f
commit ab0b7bb6ec
No known key found for this signature in database
GPG Key ID: 96C5DCA24769DBCA
6 changed files with 305 additions and 97 deletions

View File

@ -1,10 +1,10 @@
import Typeof from './Typeof';
import BaseModel from '../Models/BaseModel';
import DatabaseProperty from './Database/DatabaseProperty';
import OneUptimeDate from './Date';
import BaseModel from '../Models/BaseModel';
import { JSONArray, JSONObject, JSONValue, ObjectType } from './JSON';
import SerializableObject from './SerializableObject';
import SerializableObjectDictionary from './SerializableObjectDictionary';
import Typeof from './Typeof';
import JSON5 from 'json5';
export default class JSONFunctions {
@ -139,6 +139,10 @@ export default class JSONFunctions {
}
public static toString(val: JSONValue): string {
if (typeof val === Typeof.String) {
return val as string;
}
return JSON.stringify(val);
}

View File

@ -1,58 +1,15 @@
import Slug from 'Common/Utils/Slug';
import FindOneBy from '../Types/Database/FindOneBy';
import UpdateOneBy from '../Types/Database/UpdateOneBy';
import CountBy from '../Types/Database/CountBy';
import DeleteOneBy from '../Types/Database/DeleteOneBy';
import SearchBy from '../Types/Database/SearchBy';
import DeleteBy from '../Types/Database/DeleteBy';
import PositiveNumber from 'Common/Types/PositiveNumber';
import FindBy from '../Types/Database/FindBy';
import UpdateBy from '../Types/Database/UpdateBy';
import Query, { FindWhere } from '../Types/Database/Query';
import CreateBy from '../Types/Database/CreateBy';
import BadDataException from 'Common/Types/Exception/BadDataException';
import DatabaseNotConnectedException from 'Common/Types/Exception/DatabaseNotConnectedException';
import Exception from 'Common/Types/Exception/Exception';
import SearchResult from '../Types/Database/SearchResult';
import Encryption from '../Utils/Encryption';
import { JSONObject, JSONValue } from 'Common/Types/JSON';
import BaseModel from 'Common/Models/BaseModel';
import { AppApiHostname, EncryptionSecret } from '../EnvironmentConfig';
import PostgresDatabase, {
PostgresAppInstance,
} from '../Infrastructure/PostgresDatabase';
import { DataSource, Repository, SelectQueryBuilder } from 'typeorm';
import ObjectID from 'Common/Types/ObjectID';
import SortOrder from 'Common/Types/BaseDatabase/SortOrder';
import { EncryptionSecret, AppApiHostname } from '../EnvironmentConfig';
import { WorkflowRoute } from 'Common/ServiceRoute';
import HashedString from 'Common/Types/HashedString';
import UpdateByID from '../Types/Database/UpdateByID';
import Columns from 'Common/Types/Database/Columns';
import FindOneByID from '../Types/Database/FindOneByID';
import Dictionary from 'Common/Types/Dictionary';
import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps';
import QueryHelper from '../Types/Database/QueryHelper';
import { getUniqueColumnsBy } from 'Common/Types/Database/UniqueColumnBy';
import Typeof from 'Common/Types/Typeof';
import TableColumnType from 'Common/Types/Database/TableColumnType';
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
import LIMIT_MAX from 'Common/Types/Database/LimitMax';
import { TableColumnMetadata } from 'Common/Types/Database/TableColumn';
import ModelPermission, {
CheckReadPermissionType,
} from '../Types/Database/ModelPermission';
import Select from '../Types/Database/Select';
import RelationSelect from '../Types/Database/RelationSelect';
import UpdateByIDAndFetch from '../Types/Database/UpdateByIDAndFetch';
import API from 'Common/Utils/API';
import Protocol from 'Common/Types/API/Protocol';
import Route from 'Common/Types/API/Route';
import URL from 'Common/Types/API/URL';
import ClusterKeyAuthorization from '../Middleware/ClusterKeyAuthorization';
import Text from 'Common/Types/Text';
import logger from '../Utils/Logger';
import BaseService from './BaseService';
import { getMaxLengthFromTableColumnType } from 'Common/Types/Database/ColumnLength';
import CountBy from '../Types/Database/CountBy';
import CreateBy from '../Types/Database/CreateBy';
import DeleteBy from '../Types/Database/DeleteBy';
import DeleteOneBy from '../Types/Database/DeleteOneBy';
import FindBy from '../Types/Database/FindBy';
import FindOneBy from '../Types/Database/FindOneBy';
import FindOneByID from '../Types/Database/FindOneByID';
import {
DatabaseTriggerType,
OnCreate,
@ -60,7 +17,50 @@ import {
OnFind,
OnUpdate,
} from '../Types/Database/Hooks';
import ModelPermission, {
CheckReadPermissionType,
} from '../Types/Database/ModelPermission';
import Query, { FindWhere } from '../Types/Database/Query';
import QueryHelper from '../Types/Database/QueryHelper';
import RelationSelect from '../Types/Database/RelationSelect';
import SearchBy from '../Types/Database/SearchBy';
import SearchResult from '../Types/Database/SearchResult';
import Select from '../Types/Database/Select';
import UpdateBy from '../Types/Database/UpdateBy';
import UpdateByID from '../Types/Database/UpdateByID';
import UpdateByIDAndFetch from '../Types/Database/UpdateByIDAndFetch';
import UpdateOneBy from '../Types/Database/UpdateOneBy';
import Encryption from '../Utils/Encryption';
import logger from '../Utils/Logger';
import BaseService from './BaseService';
import BaseModel from 'Common/Models/BaseModel';
import { WorkflowRoute } from 'Common/ServiceRoute';
import Protocol from 'Common/Types/API/Protocol';
import Route from 'Common/Types/API/Route';
import URL from 'Common/Types/API/URL';
import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps';
import SortOrder from 'Common/Types/BaseDatabase/SortOrder';
import { getMaxLengthFromTableColumnType } from 'Common/Types/Database/ColumnLength';
import Columns from 'Common/Types/Database/Columns';
import LIMIT_MAX from 'Common/Types/Database/LimitMax';
import { TableColumnMetadata } from 'Common/Types/Database/TableColumn';
import TableColumnType from 'Common/Types/Database/TableColumnType';
import { getUniqueColumnsBy } from 'Common/Types/Database/UniqueColumnBy';
import Dictionary from 'Common/Types/Dictionary';
import BadDataException from 'Common/Types/Exception/BadDataException';
import DatabaseNotConnectedException from 'Common/Types/Exception/DatabaseNotConnectedException';
import Exception from 'Common/Types/Exception/Exception';
import HashedString from 'Common/Types/HashedString';
import { JSONObject, JSONValue } from 'Common/Types/JSON';
import JSONFunctions from 'Common/Types/JSONFunctions';
import ObjectID from 'Common/Types/ObjectID';
import PositiveNumber from 'Common/Types/PositiveNumber';
import Text from 'Common/Types/Text';
import Typeof from 'Common/Types/Typeof';
import API from 'Common/Utils/API';
import Slug from 'Common/Utils/Slug';
import { DataSource, Repository, SelectQueryBuilder } from 'typeorm';
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
class DatabaseService<TBaseModel extends BaseModel> extends BaseService {
private postgresDatabase!: PostgresDatabase;
@ -276,11 +276,8 @@ class DatabaseService<TBaseModel extends BaseModel> extends BaseService {
}
protected async encrypt(data: TBaseModel): Promise<TBaseModel> {
for (const key of data.getEncryptedColumns().columns) {
if(!data.hasValue(key)){
if (!data.hasValue(key)) {
continue;
}
@ -290,7 +287,7 @@ class DatabaseService<TBaseModel extends BaseModel> extends BaseService {
for (const key in dataObj) {
dataObj[key] = await Encryption.encrypt(
dataObj[key] as string,
dataObj[key] as string
);
}
@ -298,7 +295,7 @@ class DatabaseService<TBaseModel extends BaseModel> extends BaseService {
} else {
//If its string or other type.
(data as any)[key] = await Encryption.encrypt(
(data as any)[key] as string,
(data as any)[key] as string
);
}
}
@ -324,10 +321,8 @@ class DatabaseService<TBaseModel extends BaseModel> extends BaseService {
}
protected async decrypt(data: TBaseModel): Promise<TBaseModel> {
for (const key of data.getEncryptedColumns().columns) {
if(!data.hasValue(key)){
if (!data.hasValue(key)) {
continue;
}
@ -337,15 +332,17 @@ class DatabaseService<TBaseModel extends BaseModel> extends BaseService {
for (const key in dataObj) {
dataObj[key] = await Encryption.decrypt(
dataObj[key] as string,
dataObj[key] as string
);
}
data.setValue(key, dataObj);
} else {
//If its string or other type.
data.setValue(key, await Encryption.decrypt((data as any)[key]));
data.setValue(
key,
await Encryption.decrypt((data as any)[key])
);
}
}

View File

@ -1,31 +1,35 @@
import CryptoJS from 'crypto-js';
import { EncryptionSecret } from '../EnvironmentConfig';
import CryptoJS from 'crypto-js';
export default class Encryption {
public static async encrypt(text: string): Promise<string> {
if(!text) {
if (!text) {
return '';
}
const secret = await this.getEncryptionSecret();
const encryptedText = CryptoJS.AES.encrypt(text, secret).toString();
const secret: string = await this.getEncryptionSecret();
const encryptedText: string = CryptoJS.AES.encrypt(
text,
secret
).toString();
return encryptedText;
}
public static async decrypt(encryptedText: string): Promise<string> {
if(!encryptedText) {
if (!encryptedText) {
return '';
}
const secret = await this.getEncryptionSecret();
const decryptedText = CryptoJS.AES.decrypt(encryptedText, secret).toString();
const secret: string = await this.getEncryptionSecret();
const decryptedText: string = CryptoJS.AES.decrypt(
encryptedText,
secret
).toString();
return decryptedText;
}
private static async getEncryptionSecret(): Promise<string> {
const encryptionKey = EncryptionSecret.toString();
const encryptionKey: string = EncryptionSecret.toString();
return CryptoJS.SHA256(encryptionKey).toString();
}
}

View File

@ -43,6 +43,9 @@ const MonitorSecrets: FunctionComponent<PageComponentProps> = (
placeholder: 'Secret Name',
validation: {
minLength: 2,
noSpaces: true,
noNumbers: true,
noSpecialCharacters: true,
},
},
{

View File

@ -1,3 +1,21 @@
import ProbeAuthorization from '../Middleware/ProbeAuthorization';
import { ProbeExpressRequest } from '../Types/Request';
import MonitorUtil from '../Utils/Monitor';
import BaseModel from 'Common/Models/BaseModel';
import SortOrder from 'Common/Types/BaseDatabase/SortOrder';
import SubscriptionStatus from 'Common/Types/Billing/SubscriptionStatus';
import LIMIT_MAX from 'Common/Types/Database/LimitMax';
import OneUptimeDate from 'Common/Types/Date';
import BadDataException from 'Common/Types/Exception/BadDataException';
import { JSONObject } from 'Common/Types/JSON';
import ObjectID from 'Common/Types/ObjectID';
import PositiveNumber from 'Common/Types/PositiveNumber';
import Semaphore from 'CommonServer/Infrastructure/Semaphore';
import ClusterKeyAuthorization from 'CommonServer/Middleware/ClusterKeyAuthorization';
import MonitorProbeService from 'CommonServer/Services/MonitorProbeService';
import Query from 'CommonServer/Types/Database/Query';
import QueryHelper from 'CommonServer/Types/Database/QueryHelper';
import CronTab from 'CommonServer/Utils/CronTab';
import Express, {
ExpressRequest,
ExpressResponse,
@ -5,27 +23,10 @@ import Express, {
NextFunction,
OneUptimeRequest,
} from 'CommonServer/Utils/Express';
import Response from 'CommonServer/Utils/Response';
import ProbeAuthorization from '../Middleware/ProbeAuthorization';
import MonitorProbe from 'Model/Models/MonitorProbe';
import MonitorProbeService from 'CommonServer/Services/MonitorProbeService';
import QueryHelper from 'CommonServer/Types/Database/QueryHelper';
import OneUptimeDate from 'Common/Types/Date';
import { ProbeExpressRequest } from '../Types/Request';
import BadDataException from 'Common/Types/Exception/BadDataException';
import CronTab from 'CommonServer/Utils/CronTab';
import Monitor from 'Model/Models/Monitor';
import PositiveNumber from 'Common/Types/PositiveNumber';
import { JSONObject } from 'Common/Types/JSON';
import SubscriptionStatus from 'Common/Types/Billing/SubscriptionStatus';
import ObjectID from 'Common/Types/ObjectID';
import ClusterKeyAuthorization from 'CommonServer/Middleware/ClusterKeyAuthorization';
import LIMIT_MAX from 'Common/Types/Database/LimitMax';
import SortOrder from 'Common/Types/BaseDatabase/SortOrder';
import Query from 'CommonServer/Types/Database/Query';
import logger from 'CommonServer/Utils/Logger';
import BaseModel from 'Common/Models/BaseModel';
import Semaphore from 'CommonServer/Infrastructure/Semaphore';
import Response from 'CommonServer/Utils/Response';
import Monitor from 'Model/Models/Monitor';
import MonitorProbe from 'Model/Models/MonitorProbe';
const router: ExpressRouter = Express.getRouter();
@ -302,13 +303,24 @@ router.post(
return Boolean(monitor._id);
});
// check if the monitor needs secrets to be filled.
const monitorsWithSecretPopulated: Array<Monitor> = [];
for (const monitor of monitors) {
const monitorWithSecrets: Monitor =
await MonitorUtil.populateSecrets(monitor);
monitorsWithSecretPopulated.push(monitorWithSecrets);
}
// return the list of monitors to be monitored
return Response.sendEntityArrayResponse(
req,
res,
monitors,
new PositiveNumber(monitors.length),
monitorsWithSecretPopulated,
new PositiveNumber(monitorsWithSecretPopulated.length),
Monitor
);
} catch (err) {

188
Ingestor/Utils/Monitor.ts Normal file
View File

@ -0,0 +1,188 @@
import Hostname from 'Common/Types/API/Hostname';
import URL from 'Common/Types/API/URL';
import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax';
import Dictionary from 'Common/Types/Dictionary';
import { PromiseVoidFunction } from 'Common/Types/FunctionTypes';
import IP from 'Common/Types/IP/IP';
import { JSONObject } from 'Common/Types/JSON';
import JSONFunctions from 'Common/Types/JSONFunctions';
import MonitorType from 'Common/Types/Monitor/MonitorType';
import MonitorSecretService from 'CommonServer/Services/MonitorSecretService';
import QueryHelper from 'CommonServer/Types/Database/QueryHelper';
import VMUtil from 'CommonServer/Utils/VM/VMAPI';
import Monitor from 'Model/Models/Monitor';
import MonitorSecret from 'Model/Models/MonitorSecret';
export default class MonitorUtil {
public static async populateSecrets(monitor: Monitor): Promise<Monitor> {
const isSecretsLoaded: boolean = false;
let monitorSecrets: MonitorSecret[] = [];
const loadSecrets: PromiseVoidFunction = async (): Promise<void> => {
if (isSecretsLoaded) {
return;
}
if (!monitor.id) {
return;
}
const secrets: Array<MonitorSecret> =
await MonitorSecretService.findBy({
query: {
monitors: QueryHelper.in([monitor.id]),
},
select: {
secretValue: true,
name: true,
},
limit: LIMIT_PER_PROJECT,
skip: 0,
props: {
isRoot: true,
},
});
monitorSecrets = secrets;
};
if (!monitor.monitorSteps) {
return monitor;
}
if (monitor.monitorType === MonitorType.API) {
for (const monitorStep of monitor.monitorSteps?.data
?.monitorStepsInstanceArray || []) {
if (
monitorStep.data?.requestHeaders &&
this.hasSecrets(
JSONFunctions.toString(monitorStep.data.requestHeaders)
)
) {
await loadSecrets();
monitorStep.data.requestHeaders =
(await MonitorUtil.fillSecretsInStringOrJSON({
secrets: monitorSecrets,
populateSecretsIn: monitorStep.data.requestHeaders,
})) as Dictionary<string>;
} else if (
monitorStep.data?.requestBody &&
this.hasSecrets(
JSONFunctions.toString(monitorStep.data.requestBody)
)
) {
await loadSecrets();
monitorStep.data.requestBody =
(await MonitorUtil.fillSecretsInStringOrJSON({
secrets: monitorSecrets,
populateSecretsIn: monitorStep.data.requestBody,
})) as string;
}
}
}
if (
monitor.monitorType === MonitorType.API ||
monitor.monitorType === MonitorType.IP ||
monitor.monitorType === MonitorType.Ping ||
monitor.monitorType === MonitorType.Port ||
monitor.monitorType === MonitorType.Website ||
monitor.monitorType === MonitorType.SSLCertificate
) {
for (const monitorStep of monitor.monitorSteps?.data
?.monitorStepsInstanceArray || []) {
if (
monitorStep.data?.monitorDestination &&
this.hasSecrets(
JSONFunctions.toString(
monitorStep.data.monitorDestination
)
)
) {
// replace secret in monitorDestination.
await loadSecrets();
monitorStep.data.monitorDestination =
(await MonitorUtil.fillSecretsInStringOrJSON({
secrets: monitorSecrets,
populateSecretsIn:
monitorStep.data.monitorDestination,
})) as URL | Hostname | IP;
}
}
}
if (
monitor.monitorType === MonitorType.SyntheticMonitor ||
monitor.monitorType === MonitorType.CustomJavaScriptCode
) {
for (const monitorStep of monitor.monitorSteps?.data
?.monitorStepsInstanceArray || []) {
if (
monitorStep.data?.customCode &&
this.hasSecrets(
JSONFunctions.toString(monitorStep.data.customCode)
)
) {
// replace secret in script
await loadSecrets();
monitorStep.data.customCode =
(await MonitorUtil.fillSecretsInStringOrJSON({
secrets: monitorSecrets,
populateSecretsIn: monitorStep.data.customCode,
})) as string;
}
}
}
return monitor;
}
private static hasSecrets(prepopulatedString: string): boolean {
return prepopulatedString.includes('monitorSecrets.');
}
private static async fillSecretsInStringOrJSON(data: {
secrets: MonitorSecret[];
populateSecretsIn: string | JSONObject | URL | Hostname | IP;
}): Promise<string | JSONObject | URL | Hostname | IP> {
// get all secrets for this monitor.
const secrets: MonitorSecret[] = data.secrets;
if (secrets.length === 0) {
return data.populateSecretsIn;
}
// replace all secrets in the populateSecretsIn
const storageMap: JSONObject = {
monitorSecrets: {},
};
for (const monitorSecret of secrets) {
if (!monitorSecret.name) {
continue;
}
if (!monitorSecret.secretValue) {
continue;
}
(storageMap['monitorSecrets'] as JSONObject)[
monitorSecret.name as string
] = monitorSecret.secretValue;
}
const isValueJSON: boolean = typeof data.populateSecretsIn === 'object';
return VMUtil.replaceValueInPlace(
storageMap,
data.populateSecretsIn as string,
isValueJSON
);
}
}