Skip to content

Commit a4f3ac9

Browse files
committed
🔀Merge branch 'main' into deployment-manager
2 parents 082d695 + ae09b20 commit a4f3ac9

33 files changed

+622
-184
lines changed

controlpanel/api/server.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -572,8 +572,10 @@ app.listen(8050, async () => {
572572

573573
const defaultCustomer = await Customer.findOrCreate({ where: { name: 'default' }});
574574
const defaultApp = await createProtectedApp({ namespace: 'default', application: 'default', cu_id: defaultCustomer[0].id });
575-
createDecoy({ pa_id: defaultApp.data.id, decoy:{decoy:{key:"x-cloud-active-defense",separator:"=",value:"ACTIVE"},inject:{store:{inResponse:".*",as:"header"}}}});
576-
updateConfig({ pa_id:defaultApp.data.id, deployed: true, config:{alert:{session:{in:"cookie",key:"SESSION"}}}});
575+
if (defaultApp.type == 'success') {
576+
createDecoy({ pa_id: defaultApp.data.id, decoy:{decoy:{key:"x-cloud-active-defense",separator:"=",value:"ACTIVE"},inject:{store:{inResponse:".*",as:"header"}}}});
577+
updateConfig({ pa_id:defaultApp.data.id, deployed: true, config:{alert:{session:{in:"cookie",key:"SESSION"}}}});
578+
}
577579

578580
if (process.env.ENVOY_API_KEY && !process.env.DEPLOYMENT_MANAGER_URL) {
579581
ApiKey.findOrCreate({ where: { key: process.env.ENVOY_API_KEY, permissions: ["configmanager"], pa_id: defaultApp.data.id }});

controlpanel/api/services/configmanager.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const Config = require('../models/Config-data')
33
const Decoy = require('../models/Decoy-data')
44
const Blocklist = require('../models/Blocklist')
55
const protectedAppService = require('./protected-app')
6+
const { sortBlocklistDuplicates } = require('../util/blocklist')
67

78
module.exports = {
89
/**
@@ -38,10 +39,12 @@ module.exports = {
3839
const protectedApp = await ProtectedApp.findOne({ where: { namespace, application } });
3940
if (!protectedApp) return { type: 'error', code: 404, message: 'Invalid namespace or application supplied' };
4041
if (!blocklist) return { type: 'error', code: 400, message: 'Invalid blocklist supplied' };
41-
if (blocklist.blocklist && blocklist.blocklist.length)
42-
Blocklist.bulkCreate(blocklist.blocklist.map(item => ({ pa_id: protectedApp.id, content: item, type: 'blocklist' })));
43-
if (blocklist.throttle && blocklist.throttle.length)
44-
Blocklist.bulkCreate(blocklist.throttle.map(item => ({ pa_id: protectedApp.id, content: item, type: 'throttle' })));
42+
const blocklistToUpdate = await sortBlocklistDuplicates(blocklist, protectedApp.id);
43+
44+
if (blocklistToUpdate.blocklist && blocklistToUpdate.blocklist.length)
45+
Blocklist.bulkCreate(blocklistToUpdate.blocklist.map(item => ({ pa_id: protectedApp.id, content: item, type: 'blocklist' })));
46+
if (blocklistToUpdate.throttle && blocklistToUpdate.throttle.length)
47+
Blocklist.bulkCreate(blocklistToUpdate.throttle.map(item => ({ pa_id: protectedApp.id, content: item, type: 'throttle' })));
4548
return { type: 'success', code: 200, message: 'Successful operation' };
4649
} catch(e) {
4750
throw e;

controlpanel/api/util/blocklist.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
const Blocklist = require('../models/Blocklist')
2+
const { Op } = require('sequelize');
3+
4+
async function sortBlocklistDuplicates(newBlocklist, pa_id) {
5+
const finalBlocklist = [];
6+
const finaleThrottlelist = [];
7+
const allSources = Object.fromEntries(['SourceIp', 'Session', 'UserAgent']
8+
.map(key => [key, [...newBlocklist.blocklist, ...newBlocklist.throttle].find(item => item[key])?.[key]])
9+
.filter(([, value]) => value !== undefined));
10+
const existingBlocklist = await Blocklist.findAll({ where: {
11+
pa_id,
12+
type: 'blocklist',
13+
[Op.or]: Object.entries(allSources).map(([key, value]) => ({
14+
[`content.${key}`]: value
15+
}))}
16+
});
17+
const existingThrottlelist = await Blocklist.findAll({ where: {
18+
pa_id,
19+
type: 'throttle',
20+
[Op.or]: Object.entries(allSources).map(([key, value]) => ({
21+
[`content.${key}`]: value
22+
}))}
23+
});
24+
for (const newItem of newBlocklist.blocklist) {
25+
const sourceKeys = ['SourceIp', 'Session', 'UserAgent'].filter(key => newItem[key] !== undefined);
26+
const match = existingBlocklist.find(existing =>
27+
sourceKeys.length > 0 &&
28+
sourceKeys.every(key => existing.content[key] === newItem[key]) &&
29+
sourceKeys.every(key => existing.content[key] !== undefined) &&
30+
sourceKeys.length === Object.keys(existing.content).filter(key => sourceKeys.includes(key) && existing.content[key] !== undefined).length);
31+
if (!match) {
32+
finalBlocklist.push(newItem);
33+
continue;
34+
}
35+
if(isNewBlocklistBehaviorPriority(match, newItem)) {
36+
Blocklist.destroy({ where: { id: match.id } })
37+
finalBlocklist.push(newItem);
38+
}
39+
}
40+
for (const newItem of newBlocklist.throttle) {
41+
const sourceKeys = ['SourceIp', 'Session', 'UserAgent'].filter(key => newItem[key] !== undefined);
42+
const match = existingThrottlelist.find(existing =>
43+
sourceKeys.length > 0 &&
44+
sourceKeys.every(key => existing.content[key] === newItem[key]) &&
45+
sourceKeys.every(key => existing.content[key] !== undefined) &&
46+
sourceKeys.length === Object.keys(existing.content).filter(key => sourceKeys.includes(key) && existing.content[key] !== undefined).length);
47+
if (match && isNewBlocklistBehaviorPriority(match, newItem)) {
48+
Blocklist.destroy({ where: { id: match.id } })
49+
finaleThrottlelist.push(newItem);
50+
}
51+
}
52+
return { blocklist: finalBlocklist, throttle: finaleThrottlelist };
53+
}
54+
55+
function isNewBlocklistBehaviorPriority(existingBlocklist, newBlocklist) {
56+
const priorities = { clone: 1, exhaust: 2, drop: 3, error: 4 };
57+
return priorities[newBlocklist.Behavior] < priorities[existingBlocklist.content.Behavior]
58+
}
59+
60+
module.exports = {
61+
sortBlocklistDuplicates
62+
}

controlpanel/cad/src/app/app.routes.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,7 @@ export const routes: Routes = [{
4949
{
5050
path: ':id',
5151
component: AddDecoyComponent,
52-
children: [{
53-
path: '',
54-
redirectTo: 'injection',
55-
pathMatch: 'full',
56-
},
52+
children: [
5753
{
5854
path: 'injection',
5955
component: InjectionComponent,

controlpanel/cad/src/app/components/alert-action-table/alert-action-table.component.html

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,51 @@
1+
<app-tooltip [showTooltip]="showTooltip"
2+
[title]="tooltipTitle"
3+
[text]="tooltipText"
4+
[link]="tooltipLink"
5+
[topPosition]="topPosition"
6+
[leftPosition]="leftPosition"
7+
(tooltipHover)="onHoverInfo()"
8+
(tooltipLeave)="onLeaveInfo()">
9+
</app-tooltip>
110
<table class="action-table">
211
<thead>
312
<tr>
4-
<th id="source">Source</th>
5-
<th id="behavior">Behavior</th>
6-
<th id="delay">Delay</th>
7-
<th id="duration">Duration</th>
8-
<th id="property">Property</th>
13+
<th id="source">
14+
Source
15+
<img class="info-icon" src="info.svg"
16+
(mouseenter)="onHoverInfo('source', 'Defines what to look for in subsequent request(s)', 'https://github.com/SAP/cloud-active-defense/wiki/Detect#source', $event)"
17+
(mouseout)="onLeaveInfo()"
18+
/>
19+
</th>
20+
21+
<th id="behavior">
22+
Behavior
23+
<img class="info-icon" src="info.svg"
24+
(mouseenter)="onHoverInfo('behavior', 'What will happen to requests matching with what is specified in the source field', 'https://github.com/SAP/cloud-active-defense/wiki/Detect#behavior', $event)"
25+
(mouseout)="onLeaveInfo()"
26+
/>
27+
</th>
28+
<th id="delay">
29+
Delay
30+
<img class="info-icon" src="info.svg"
31+
(mouseenter)="onHoverInfo('delay', 'The number of seconds / minutes / hours to wait before applying the response. \'n\' is for \'now\' (no delay)', 'https://github.com/SAP/cloud-active-defense/wiki/Detect#delay', $event)"
32+
(mouseout)="onLeaveInfo()"
33+
/>
34+
</th>
35+
<th id="duration">
36+
Duration
37+
<img class="info-icon" src="info.svg"
38+
(mouseenter)="onHoverInfo('duration', 'The number of seconds / minutes / hours the response will be applied before expiring. \'d\' is for the default/recommanded maximum value of time and \'f\' is for \'forever\' (infinite duration)', 'https://github.com/SAP/cloud-active-defense/wiki/Detect#duration', $event)"
39+
(mouseout)="onLeaveInfo()"
40+
/>
41+
</th>
42+
<th id="property">
43+
Property
44+
<img class="info-icon" src="info.svg"
45+
(mouseenter)="onHoverInfo('property', 'How many seconds to throttle the request. Can only be in seconds and can be a range of seconds when using \'-\' (e.g. 20-60)', 'https://github.com/SAP/cloud-active-defense/wiki/Detect#property', $event)"
46+
(mouseout)="onLeaveInfo()"
47+
/>
48+
</th>
949
<th class="end-action"></th>
1050
</tr>
1151
<tr class="tr-separator"><td class="row-separator" colspan="6"><hr class="head-separator"/></td></tr>
@@ -34,19 +74,20 @@
3474
</td>
3575
<td>
3676
<div class="table-form-inputception">
37-
<input class="input-action-table inner-input" type="text" name="duration" id="duration" [(ngModel)]="action.duration" (input)="onItemChange()" [disabled]="action.durationExtension == 'forever' || !isEdit" appOnlyNumbers>
77+
<input class="input-action-table inner-input" type="text" name="duration" id="duration" [(ngModel)]="action.formDuration" (input)="onItemChange()" [disabled]="action.durationExtension == 'forever' || !isEdit" appOnlyNumbers>
3878
<select class="inner-select" name="durationExtension" id="durationExtension" [(ngModel)]="action.durationExtension" (change)="onDurationExtensionChange(action.durationExtension, i); onItemChange()" [disabled]="!isEdit">
3979
<option value="s">s</option>
4080
<option value="m">m</option>
4181
<option value="h">h</option>
82+
<option value="default">d</option>
4283
<option value="forever">f</option>
4384
</select>
4485
</div>
4586
</td>
4687
<td><input class="input-action-table" type="text" name="property" id="property" [(ngModel)]="action.property" (input)="onItemChange()" [disabled]="action.behavior != 'throttle' || !isEdit" appOnlyValidRespondProperty></td>
4788
<td class="end-action">
4889
<button (click)="onClickDeleteAction(i)" class="action-delete-btn" [disabled]="!isEdit">
49-
<div class="action-delete-icon" [ngStyle]="{ backgroundColor: !isEdit ? 'grey' : 'black' }"></div>
90+
<div class="action-delete-icon" [ngClass]="{ 'disable-action-delete-icon': !isEdit }"></div>
5091
</button>
5192
</td>
5293
</tr>

controlpanel/cad/src/app/components/alert-action-table/alert-action-table.component.scss

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@
1515
th {
1616
text-align: start;
1717
padding: 0.2rem 0.5rem;
18+
.info-icon {
19+
height: 14px;
20+
align-self: flex-start;
21+
margin-top: -5px;
22+
cursor: default;
23+
}
1824
}
1925
.end-action {
2026
box-sizing: content-box;
@@ -66,26 +72,50 @@
6672
border: none;
6773
border-radius: 5px;
6874
padding: 0.2rem 0;
75+
cursor: pointer;
6976
.add-action-icon {
7077
margin: 0 auto;
7178
mask: url('../../../../public/add.svg') no-repeat center;
7279
width: 24px;
7380
height: 24px;
7481
background-color: #FFF;
7582
}
83+
&:disabled {
84+
cursor: default;
85+
background-color: #C5C1C8;
86+
}
87+
&:disabled .add-action-icon {
88+
background-color: #86838A;
89+
}
90+
91+
&:not(:disabled):hover {
92+
opacity: 0.9;
93+
box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.12),
94+
0px 1px 2px rgba(0, 0, 0, 0.24);
95+
}
7696
}
7797
}
7898
.action-delete-btn {
7999
cursor: pointer;
80100
border: 0;
81101
background-color: transparent;
102+
&:disabled {
103+
cursor: default;
104+
}
105+
&:not(:disabled):hover .action-delete-icon {
106+
background-color: red;
107+
}
82108
}
83109
.action-delete-icon {
84110
margin: 0 auto;
85111
mask: url('../../../../public/delete.svg') no-repeat center;
86112
width: 24px;
87113
height: 24px;
88114
background-color: black;
115+
transition: background-color 0.3s ease;
116+
}
117+
.disable-action-delete-icon {
118+
background-color: #86838A;
89119
}
90120

91121
}

controlpanel/cad/src/app/components/alert-action-table/alert-action-table.component.ts

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,17 @@ import { DelayType, DurationType, RespondType } from '../../models/decoy';
55
import { FormsModule } from '@angular/forms';
66
import { OnlyNumbersDirective } from '../../directives/only-numbers.directive';
77
import { OnlyValidRespondPropertyDirective } from '../../directives/only-valid-respond-property.directive';
8+
import { TooltipComponent } from '../tooltip/tooltip.component';
89

910
export interface FormRespond extends RespondType {
1011
delayExtension: 's' | 'm' | 'h' | 'now',
11-
durationExtension: 's' | 'm' | 'h' | 'forever'
12+
durationExtension: 's' | 'm' | 'h' | 'forever' | 'default',
13+
formDuration?: DurationType | number | undefined
1214
}
1315

1416
@Component({
1517
selector: 'app-alert-action-table',
16-
imports: [CommonModule, SourceSelectComponent, FormsModule, OnlyNumbersDirective, OnlyValidRespondPropertyDirective],
18+
imports: [CommonModule, SourceSelectComponent, FormsModule, OnlyNumbersDirective, OnlyValidRespondPropertyDirective, TooltipComponent],
1719
templateUrl: './alert-action-table.component.html',
1820
styleUrl: './alert-action-table.component.scss'
1921
})
@@ -22,6 +24,37 @@ export class AlertActionTableComponent {
2224
@Output() actionArrayChange = new EventEmitter<FormRespond[]>();
2325
@Input() isEdit = true;
2426

27+
//#region Tooltip
28+
tooltipTitle = '';
29+
showTooltip = false;
30+
tooltipText = '';
31+
tooltipLink = '';
32+
topPosition: any;
33+
leftPosition: any;
34+
tooltipTimeout:any;
35+
36+
onHoverInfo(tooltipTitle?: string, tooltipText?: string, tooltipLink?: string, e?: MouseEvent) {
37+
clearTimeout(this.tooltipTimeout);
38+
this.showTooltip = true;
39+
if (tooltipTitle) this.tooltipTitle = tooltipTitle;
40+
if (tooltipText) this.tooltipText = tooltipText;
41+
if (tooltipLink) this.tooltipLink = tooltipLink;
42+
if (e) {
43+
this.topPosition = e.clientY ?? this.topPosition;
44+
this.leftPosition = e.clientX ?? this.leftPosition;
45+
}
46+
}
47+
onLeaveInfo() {
48+
this.tooltipTimeout = setTimeout(() => {
49+
this.showTooltip = false;
50+
this.tooltipText = '';
51+
this.topPosition = null;
52+
this.leftPosition = null;
53+
}, 100)
54+
}
55+
//#endregion
56+
57+
2558
onClickAddAction() {
2659
this.actionArray.push({ source: '', behavior: 'error', delayExtension: 's', durationExtension: 's' });
2760
this.actionArrayChange.emit(this.actionArray);
@@ -46,9 +79,27 @@ export class AlertActionTableComponent {
4679
if (newExtension == 'now') this.actionArray[index].delay = 'now';
4780
else if (this.actionArray[index].delay == 'now') this.actionArray[index].delay = undefined;
4881
}
49-
onDurationExtensionChange(newExtension: 's' | 'm' | 'h' | 'forever', index: number) {
50-
if (newExtension == 'forever') this.actionArray[index].duration = 'forever';
51-
else if (this.actionArray[index].duration == 'forever') this.actionArray[index].duration = undefined;
82+
onDurationExtensionChange(newExtension: 's' | 'm' | 'h' | 'default' | 'forever', index: number) {
83+
if (newExtension == 'default') {
84+
if (this.actionArray[index].source.includes('userAgent')) {
85+
this.actionArray[index].durationExtension = 'h';
86+
this.actionArray[index].formDuration = 720;
87+
}
88+
else if (this.actionArray[index].source.includes('ip')) {
89+
this.actionArray[index].durationExtension = 'h';
90+
this.actionArray[index].formDuration = 48;
91+
}
92+
else if (this.actionArray[index].source.includes('session')) {
93+
this.actionArray[index].durationExtension = 'h';
94+
this.actionArray[index].formDuration = 24;
95+
}
96+
else {
97+
this.actionArray[index].durationExtension = 'h';
98+
this.actionArray[index].formDuration = 720;
99+
}
100+
}
101+
if (newExtension == 'forever') this.actionArray[index].formDuration = 'forever';
102+
else if (this.actionArray[index].formDuration == 'forever') this.actionArray[index].formDuration = undefined;
52103
}
53104

54105
sourceToArray(source: string) {

controlpanel/cad/src/app/components/injection-when-table/injection-when-table.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
<td class="when-type"><input class="" type="checkbox" name="type" id="type" [(ngModel)]="when.type" (change)="onItemChange()" [disabled]="!isEdit"></td>
2727
<td class="end-when">
2828
<button (click)="onClickDeleteWhen(i)" class="when-delete-btn" [disabled]="!isEdit">
29-
<div class="when-delete-icon" [ngStyle]="{ backgroundColor: !isEdit ? 'grey' : 'black' }"></div>
29+
<div class="when-delete-icon" [ngClass]="{ 'disable-when-delete-icon': !isEdit }"></div>
3030
</button>
3131
</td>
3232
</tr>

0 commit comments

Comments
 (0)