|
Reports
@if (data?.device) {
+ (removeDevice)="removeReport(data)">
diff --git a/modules/ui/src/app/pages/reports/reports.component.spec.ts b/modules/ui/src/app/pages/reports/reports.component.spec.ts
index a9f444d21..d95d381fc 100644
--- a/modules/ui/src/app/pages/reports/reports.component.spec.ts
+++ b/modules/ui/src/app/pages/reports/reports.component.spec.ts
@@ -77,7 +77,7 @@ describe('ReportsComponent', () => {
});
};
beforeEach(() => {
- mockService = jasmine.createSpyObj(['getResultClass']);
+ mockService = jasmine.createSpyObj(['getResultClass', 'getReportLink']);
mockReportsStore = jasmine.createSpyObj('ReportsStore', [
'deleteReport',
'setSelectedRow',
@@ -89,8 +89,9 @@ describe('ReportsComponent', () => {
'setActiveFiler',
'setFilterOpened',
'updateSort',
- 'getHistory',
'getReports',
+ 'getReports',
+ 'fetchReports',
]);
mockLiveAnnouncer = jasmine.createSpyObj(['announce']);
@@ -118,7 +119,7 @@ describe('ReportsComponent', () => {
it('should get reports', fakeAsync(() => {
component.ngOnInit();
- expect(mockReportsStore.getReports).toHaveBeenCalled();
+ expect(mockReportsStore.fetchReports).toHaveBeenCalled();
}));
});
@@ -294,12 +295,8 @@ describe('ReportsComponent', () => {
it('#removeDevice should call delete report', () => {
const data = HISTORY[0];
- component.removeDevice(data);
- expect(mockReportsStore.deleteReport).toHaveBeenCalledWith({
- mac_addr: data.mac_addr,
- deviceMacAddr: data.device.mac_addr,
- started: data.started,
- });
+ component.removeReport(data);
+ expect(mockReportsStore.deleteReport).toHaveBeenCalledWith('/report/123');
});
});
diff --git a/modules/ui/src/app/pages/reports/reports.component.ts b/modules/ui/src/app/pages/reports/reports.component.ts
index 356212aaf..a530f97f1 100644
--- a/modules/ui/src/app/pages/reports/reports.component.ts
+++ b/modules/ui/src/app/pages/reports/reports.component.ts
@@ -24,8 +24,9 @@ import {
import { LiveAnnouncer } from '@angular/cdk/a11y';
import { TestRunService } from '../../services/test-run.service';
import {
+ HistoryTestrun,
StatusResultClassName,
- TestrunStatus,
+ TestrunReport,
} from '../../model/testrun-status';
import { CommonModule, DatePipe } from '@angular/common';
import { MatSort, MatSortModule, Sort } from '@angular/material/sort';
@@ -85,7 +86,7 @@ export class ReportsComponent implements OnInit, OnDestroy {
viewModel$ = this.store.viewModel$;
ngOnInit() {
- this.store.getReports();
+ this.store.fetchReports();
const sort = this.sort();
if (sort) {
this.store.updateSort(sort);
@@ -183,7 +184,7 @@ export class ReportsComponent implements OnInit, OnDestroy {
this.store.setSelectedRow(row);
}
- trackByStarted(index: number, item: TestrunStatus) {
+ trackByStarted(index: number, item: HistoryTestrun) {
return item.started;
}
@@ -207,12 +208,8 @@ export class ReportsComponent implements OnInit, OnDestroy {
}
}
- removeDevice(data: TestrunStatus) {
- this.store.deleteReport({
- mac_addr: data.mac_addr,
- deviceMacAddr: data.device.mac_addr,
- started: data.started,
- });
+ removeReport(data: TestrunReport) {
+ this.store.deleteReport(data.delete);
this.focusNextButton();
}
}
diff --git a/modules/ui/src/app/pages/reports/reports.store.spec.ts b/modules/ui/src/app/pages/reports/reports.store.spec.ts
index 9aec5e9ff..fc76a45a7 100644
--- a/modules/ui/src/app/pages/reports/reports.store.spec.ts
+++ b/modules/ui/src/app/pages/reports/reports.store.spec.ts
@@ -161,7 +161,7 @@ describe('ReportsStore', () => {
});
describe('effects', () => {
- describe('getHistory', () => {
+ describe('getReports', () => {
it('should update store', done => {
store.overrideSelector(selectReports, [...HISTORY]);
store.refreshState();
@@ -171,7 +171,7 @@ describe('ReportsStore', () => {
done();
});
- reportsStore.getHistory();
+ reportsStore.getReports();
});
});
@@ -181,11 +181,7 @@ describe('ReportsStore', () => {
store.overrideSelector(selectReports, [...HISTORY]);
store.refreshState();
- reportsStore.deleteReport({
- mac_addr: '01:02:03:04:05:07',
- deviceMacAddr: '01:02:03:04:05:07',
- started: '2023-07-23T10:11:00.123Z',
- });
+ reportsStore.deleteReport('/report/1234');
expect(store.dispatch).toHaveBeenCalledWith(
setReports({ reports: HISTORY_AFTER_REMOVE })
@@ -198,11 +194,7 @@ describe('ReportsStore', () => {
store.overrideSelector(selectReports, [...HISTORY_AFTER_REMOVE]);
store.refreshState();
- reportsStore.deleteReport({
- mac_addr: null,
- deviceMacAddr: '01:02:03:04:05:08',
- started: '2023-06-23T10:11:00.123Z',
- });
+ reportsStore.deleteReport('/report/12345');
expect(store.dispatch).toHaveBeenCalledWith(
setReports({ reports: [HISTORY_AFTER_REMOVE[0]] })
diff --git a/modules/ui/src/app/pages/reports/reports.store.ts b/modules/ui/src/app/pages/reports/reports.store.ts
index 4b53ddaf0..6dce5e217 100644
--- a/modules/ui/src/app/pages/reports/reports.store.ts
+++ b/modules/ui/src/app/pages/reports/reports.store.ts
@@ -4,6 +4,8 @@ import { MatRow, MatTableDataSource } from '@angular/material/table';
import {
HistoryTestrun,
StatusOfTestrun,
+ TestReportsList,
+ TestrunReport,
TestrunStatus,
} from '../../model/testrun-status';
import { DateRange, Filters } from '../../model/filters';
@@ -64,7 +66,7 @@ export class ReportsStore extends ComponentStore {
profiles: this.profiles$,
});
- setDataSource = this.updater((state, reports: TestrunStatus[]) => {
+ setDataSource = this.updater((state, reports: TestReportsList) => {
const data = this.formateData(reports);
const dataSource = new MatTableDataSource(data);
@@ -121,23 +123,17 @@ export class ReportsStore extends ComponentStore {
};
});
- deleteReport = this.effect<{
- mac_addr: string | null;
- deviceMacAddr: string;
- started: string | null;
- }>(trigger$ => {
+ deleteReport = this.effect(trigger$ => {
return trigger$.pipe(
- exhaustMap(({ mac_addr, deviceMacAddr, started }) => {
- return this.testRunService
- .deleteReport(mac_addr || deviceMacAddr, started || '')
- .pipe(
- withLatestFrom(this.history$),
- tap(([remove, current]) => {
- if (remove) {
- this.removeReport(mac_addr, deviceMacAddr, started, current);
- }
- })
- );
+ exhaustMap(url => {
+ return this.testRunService.deleteReport(url).pipe(
+ withLatestFrom(this.history$),
+ tap(([remove, current]) => {
+ if (remove) {
+ this.removeReport(url, current);
+ }
+ })
+ );
})
);
});
@@ -208,7 +204,7 @@ export class ReportsStore extends ComponentStore {
);
});
- getHistory = this.effect(() => {
+ getReports = this.effect(() => {
return this.history$.pipe(
withLatestFrom(this.filteredValues$),
tap(([reports, filteredValues]) => {
@@ -218,7 +214,7 @@ export class ReportsStore extends ComponentStore {
);
});
- getReports = this.effect(trigger$ => {
+ fetchReports = this.effect(trigger$ => {
return trigger$.pipe(
tap(() => {
this.store.dispatch(fetchReports());
@@ -226,20 +222,10 @@ export class ReportsStore extends ComponentStore {
);
});
- private removeReport(
- mac_addr: string | null,
- deviceMacAddr: string,
- started: string | null,
- current: TestrunStatus[]
- ) {
+ private removeReport(url: string, current: TestReportsList) {
const history = [...current];
- const idx = history.findIndex(
- report =>
- report.mac_addr === mac_addr &&
- report.device.mac_addr === deviceMacAddr &&
- report.started === started
- );
- if (typeof idx === 'number') {
+ const idx = history.findIndex(report => report.delete === url);
+ if (idx > -1) {
history.splice(idx, 1);
this.store.dispatch(setReports({ reports: history }));
}
@@ -255,7 +241,7 @@ export class ReportsStore extends ComponentStore {
this.setIsFiltersEmpty(this.isFiltersEmpty(filteredValues));
}
- private formateData(data: TestrunStatus[]): HistoryTestrun[] {
+ private formateData(data: TestReportsList): HistoryTestrun[] {
return data.map(item => {
return {
...item,
@@ -268,7 +254,7 @@ export class ReportsStore extends ComponentStore {
});
}
- private getTestResult(item: TestrunStatus): string {
+ private getTestResult(item: TestrunReport): string {
let result = '';
if (item.device.test_pack === TestingType.Qualification) {
if (
diff --git a/modules/ui/src/app/services/test-run-mqtt.service.spec.ts b/modules/ui/src/app/services/test-run-mqtt.service.spec.ts
index 7ffb12443..3198430da 100644
--- a/modules/ui/src/app/services/test-run-mqtt.service.spec.ts
+++ b/modules/ui/src/app/services/test-run-mqtt.service.spec.ts
@@ -6,7 +6,7 @@ import SpyObj = jasmine.SpyObj;
import { of } from 'rxjs';
import { MOCK_ADAPTERS } from '../mocks/settings.mock';
import { Topic } from '../model/topic';
-import { MOCK_INTERNET } from '../mocks/topic.mock';
+import { MOCK_INFO, MOCK_INTERNET } from '../mocks/topic.mock';
import { MOCK_PROGRESS_DATA_IN_PROGRESS } from '../mocks/testrun.mock';
import { TestRunService } from './test-run.service';
@@ -97,6 +97,26 @@ describe('TestRunMqttService', () => {
});
});
+ describe('getInfo', () => {
+ beforeEach(() => {
+ mockService.observe.and.returnValue(of(getResponse(MOCK_INFO)));
+ });
+
+ it('should subscribe the topic', done => {
+ service.getInfo().subscribe(() => {
+ expect(mockService.observe).toHaveBeenCalledWith(Topic.Info);
+ done();
+ });
+ });
+
+ it('should return object of type', done => {
+ service.getInfo().subscribe(res => {
+ expect(res).toEqual(MOCK_INFO);
+ done();
+ });
+ });
+ });
+
function getResponse(response: Type): IMqttMessage {
const enc = new TextEncoder();
const message = enc.encode(JSON.stringify(response));
diff --git a/modules/ui/src/app/services/test-run-mqtt.service.ts b/modules/ui/src/app/services/test-run-mqtt.service.ts
index c0d78390d..276c1c6b6 100644
--- a/modules/ui/src/app/services/test-run-mqtt.service.ts
+++ b/modules/ui/src/app/services/test-run-mqtt.service.ts
@@ -4,7 +4,7 @@ import { catchError, Observable, of } from 'rxjs';
import { map } from 'rxjs/operators';
import { Adapters } from '../model/setting';
import { TestrunStatus } from '../model/testrun-status';
-import { InternetConnection, Topic } from '../model/topic';
+import { Info, InternetConnection, Topic } from '../model/topic';
import { TestRunService } from './test-run.service';
@Injectable({
@@ -32,6 +32,10 @@ export class TestRunMqttService {
);
}
+ getInfo(): Observable {
+ return this.topic(Topic.Info);
+ }
+
private topic(topicName: string): Observable {
return this.mqttService.observe(topicName).pipe(
map(
diff --git a/modules/ui/src/app/services/test-run.service.spec.ts b/modules/ui/src/app/services/test-run.service.spec.ts
index 5c77a30ee..ba886d9b1 100644
--- a/modules/ui/src/app/services/test-run.service.spec.ts
+++ b/modules/ui/src/app/services/test-run.service.spec.ts
@@ -26,6 +26,7 @@ import { MOCK_PROGRESS_DATA_IN_PROGRESS } from '../mocks/testrun.mock';
import {
StatusOfTestResult,
StatusOfTestrun,
+ TestReportsList,
TestrunStatus,
} from '../model/testrun-status';
import { device, DEVICES_FORM, MOCK_MODULES } from '../mocks/device.mock';
@@ -223,26 +224,26 @@ describe('TestRunService', () => {
});
});
- describe('getHistory', () => {
+ describe('getReports', () => {
it('should return reports', () => {
- let result: TestrunStatus[] | null = null;
+ let result: TestReportsList | null = null;
const reports = [
{
status: 'Complete',
device: device,
- report: 'https://api.testrun.io/report.pdf',
+ report: '/report/123',
started: '2023-06-22T10:11:00.123Z',
finished: '2023-06-22T10:17:00.123Z',
},
- ] as TestrunStatus[];
+ ] as TestReportsList;
- service.getHistory().subscribe(res => {
+ service.getReports().subscribe(res => {
expect(res).toEqual(result);
});
result = reports;
- service.getHistory();
+ service.getReports();
const req = httpTestingController.expectOne(
'http://localhost:8000/reports'
);
@@ -461,36 +462,24 @@ describe('TestRunService', () => {
it('deleteReport should have necessary request data', () => {
const apiUrl = 'http://localhost:8000/report';
- service.deleteReport(device.mac_addr, '').subscribe(res => {
+ service.deleteReport('/report').subscribe(res => {
expect(res).toEqual(true);
});
const req = httpTestingController.expectOne(apiUrl);
expect(req.request.method).toBe('DELETE');
- expect(req.request.body).toEqual(
- JSON.stringify({
- mac_addr: device.mac_addr,
- timestamp: '',
- })
- );
req.flush({});
});
it('deleteReport should return false when error happens', () => {
const apiUrl = 'http://localhost:8000/report';
- service.deleteReport(device.mac_addr, '').subscribe(res => {
+ service.deleteReport('/report').subscribe(res => {
expect(res).toEqual(false);
});
const req = httpTestingController.expectOne(apiUrl);
expect(req.request.method).toBe('DELETE');
- expect(req.request.body).toEqual(
- JSON.stringify({
- mac_addr: device.mac_addr,
- timestamp: '',
- })
- );
req.error(new ErrorEvent(''));
});
@@ -704,4 +693,11 @@ describe('TestRunService', () => {
req.flush(result);
});
});
+
+ describe('getReportLink', () => {
+ it('should return link', () => {
+ const link = service.getReportLink('url');
+ expect(link.includes('url')).toBeTrue();
+ });
+ });
});
diff --git a/modules/ui/src/app/services/test-run.service.ts b/modules/ui/src/app/services/test-run.service.ts
index ba5733cc0..789a36c15 100644
--- a/modules/ui/src/app/services/test-run.service.ts
+++ b/modules/ui/src/app/services/test-run.service.ts
@@ -24,6 +24,7 @@ import {
StatusOfTestResult,
StatusOfTestrun,
StatusResultClassName,
+ TestReportsList,
TestrunStatus,
} from '../model/testrun-status';
import { Version } from '../model/version';
@@ -151,6 +152,7 @@ export class TestRunService {
catchError(() => of(false))
);
}
+
deleteDevice(device: Device): Observable {
return this.http
.delete(`${API_URL}/device`, {
@@ -162,8 +164,8 @@ export class TestRunService {
);
}
- getHistory(): Observable {
- return this.http.get(`${API_URL}/reports`).pipe(
+ getReports(): Observable {
+ return this.http.get(`${API_URL}/reports`).pipe(
map(result => {
result.forEach(item => {
item.report = this.changeReportURL(item.report);
@@ -241,15 +243,15 @@ export class TestRunService {
});
}
- deleteReport(mac_addr: string, started: string): Observable {
- return this.http
- .delete(`${API_URL}/report`, {
- body: JSON.stringify({ mac_addr, timestamp: started }),
- })
- .pipe(
- catchError(() => of(false)),
- map(res => !!res)
- );
+ getReportLink(url: string): string {
+ return `${url}`;
+ }
+
+ deleteReport(url: string): Observable {
+ return this.http.delete(`${API_URL}${url}`).pipe(
+ catchError(() => of(false)),
+ map(res => !!res)
+ );
}
fetchProfiles(): Observable {
diff --git a/modules/ui/src/app/store/actions.ts b/modules/ui/src/app/store/actions.ts
index 65fae5fb8..da981b607 100644
--- a/modules/ui/src/app/store/actions.ts
+++ b/modules/ui/src/app/store/actions.ts
@@ -18,7 +18,7 @@ import { createAction, props } from '@ngrx/store';
import { Adapters, InterfacesValidation, SystemConfig } from '../model/setting';
import { SystemInterfaces } from '../model/setting';
import { Device, TestModule } from '../model/device';
-import { TestrunStatus } from '../model/testrun-status';
+import { TestReportsList, TestrunStatus } from '../model/testrun-status';
import { Profile } from '../model/profile';
export const fetchInterfacesSuccess = createAction(
@@ -125,7 +125,7 @@ export const fetchReports = createAction('[Shared] Fetch reports');
export const setReports = createAction(
'[Shared] Set Reports',
- props<{ reports: TestrunStatus[] }>()
+ props<{ reports: TestReportsList }>()
);
export const setTestModules = createAction(
diff --git a/modules/ui/src/app/store/effects.spec.ts b/modules/ui/src/app/store/effects.spec.ts
index 952b9dbf6..2b23ae542 100644
--- a/modules/ui/src/app/store/effects.spec.ts
+++ b/modules/ui/src/app/store/effects.spec.ts
@@ -76,7 +76,7 @@ describe('Effects', () => {
'testrunInProgress',
'stopTestrun',
'fetchProfiles',
- 'getHistory',
+ 'getReports',
]);
testRunServiceMock.getSystemInterfaces.and.returnValue(of({}));
testRunServiceMock.getSystemConfig.and.returnValue(of({ network: {} }));
@@ -85,7 +85,7 @@ describe('Effects', () => {
of(MOCK_PROGRESS_DATA_IN_PROGRESS)
);
testRunServiceMock.fetchProfiles.and.returnValue(of([]));
- testRunServiceMock.getHistory.and.returnValue(of([]));
+ testRunServiceMock.getReports.and.returnValue(of([]));
mockMqttService.getInternetConnection.and.returnValue(
of({ connection: false })
);
@@ -358,7 +358,7 @@ describe('Effects', () => {
describe('onFetchReports$', () => {
it(' should call setReports on success', done => {
- testRunServiceMock.getHistory.and.returnValue(of([]));
+ testRunServiceMock.getReports.and.returnValue(of([]));
actions$ = of(actions.fetchReports());
effects.onFetchReports$.subscribe(action => {
@@ -372,7 +372,7 @@ describe('Effects', () => {
});
it('should call setReports with empty array if null is returned', done => {
- testRunServiceMock.getHistory.and.returnValue(of(null));
+ testRunServiceMock.getReports.and.returnValue(of(null));
actions$ = of(actions.fetchReports());
effects.onFetchReports$.subscribe(action => {
@@ -386,7 +386,7 @@ describe('Effects', () => {
});
it('should call setReports with empty array if error happens', done => {
- testRunServiceMock.getHistory.and.returnValue(
+ testRunServiceMock.getReports.and.returnValue(
throwError(
new HttpErrorResponse({ error: { error: 'error' }, status: 500 })
)
diff --git a/modules/ui/src/app/store/effects.ts b/modules/ui/src/app/store/effects.ts
index 278cc1bb0..233e01f4a 100644
--- a/modules/ui/src/app/store/effects.ts
+++ b/modules/ui/src/app/store/effects.ts
@@ -35,7 +35,7 @@ import { selectIsOpenWaitSnackBar, selectSystemStatus } from './selectors';
import {
IDLE_STATUS,
StatusOfTestrun,
- TestrunStatus,
+ TestReportsList,
} from '../model/testrun-status';
import {
fetchSystemStatusSuccess,
@@ -253,8 +253,8 @@ export class AppEffects {
return this.actions$.pipe(
ofType(AppActions.fetchReports),
switchMap(() =>
- this.testrunService.getHistory().pipe(
- map((reports: TestrunStatus[] | null) => {
+ this.testrunService.getReports().pipe(
+ map((reports: TestReportsList | null) => {
if (reports !== null) {
return AppActions.setReports({ reports });
}
diff --git a/modules/ui/src/app/store/state.ts b/modules/ui/src/app/store/state.ts
index 9d5613730..62160da3a 100644
--- a/modules/ui/src/app/store/state.ts
+++ b/modules/ui/src/app/store/state.ts
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { TestrunStatus } from '../model/testrun-status';
+import { TestReportsList, TestrunStatus } from '../model/testrun-status';
import { Device, TestModule } from '../model/device';
import { Adapters, SystemConfig, SystemInterfaces } from '../model/setting';
import { Profile } from '../model/profile';
@@ -42,7 +42,7 @@ export interface AppState {
isStopTestrun: boolean;
isOpenWaitSnackBar: boolean;
deviceInProgress: Device | null;
- reports: TestrunStatus[];
+ reports: TestReportsList;
testModules: TestModule[];
adapters: Adapters;
internetConnection: boolean | null;
diff --git a/resources/report/test_report_styles.css b/resources/report/test_report_styles.css
index d6ebf2552..d6f9aeacb 100644
--- a/resources/report/test_report_styles.css
+++ b/resources/report/test_report_styles.css
@@ -276,7 +276,7 @@
.steps-to-resolve-test-name {
display: inline-block;
margin-left: 70px;
- margin-right: 10px;
+ margin-right: -60px;
margin-bottom: 20px;
width: 250px;
vertical-align: top;
@@ -284,6 +284,8 @@
.steps-to-resolve-description {
display: inline-block;
+ margin-left: 70px;
+ margin-bottom: 20px;
}
.steps-to-resolve.subtitle {
diff --git a/test_vm/Vagrantfile b/test_vm/Vagrantfile
index 8d01960f4..08e9ef588 100644
--- a/test_vm/Vagrantfile
+++ b/test_vm/Vagrantfile
@@ -1,6 +1,6 @@
Vagrant.configure("2") do |config|
config.vm.box = "cloud-image/ubuntu-24.04"
- config.vm.box_version = "20251126.0.0"
+ config.vm.box_version = "20260307.0.0"
config.vm.hostname = "ubuntu-vm"
if ENV['DEVICE'] == 'COMPLIANT'
config.vm.network "private_network",
diff --git a/test_vm/provision-compliant.yml b/test_vm/provision-compliant.yml
index 774339cfd..92c6b9a6c 100644
--- a/test_vm/provision-compliant.yml
+++ b/test_vm/provision-compliant.yml
@@ -39,7 +39,7 @@
git:
repo: https://github.com/bacnet-stack/bacnet-stack.git
dest: /opt/bacnet-stack
- version: master
+ version: bacnet-stack-1.4.2
- name: Build BACnet stack
make:
@@ -57,6 +57,7 @@
IP=$(ip -4 addr show $IFACE | awk '/inet / {print $2}' | cut -d/ -f1)
if [ -n "$IP" ]; then
export BACNET_IP=$IP
+ export BACNET_PORT=47808
exec /opt/bacnet-stack/bin/bacserv
fi
sleep 2
@@ -75,7 +76,7 @@
[Service]
Type=simple
ExecStart=/usr/local/bin/bacserv-wrapper.sh
- Restart=always
+ Restart=on-failure
RestartSec=5
[Install]
@@ -269,13 +270,10 @@
fi
sleep 1
done
- # TLS 1.0
- openssl s_client -connect tls-v1-0.badssl.com:1010 -tls1 -bind "$IP"
- # TLS 1.2
- openssl s_client -connect tls-v1-2.badssl.com:1012 -tls1_2 -bind "$IP"
- # TLS 1.3
- openssl s_client -connect tls13.badssl.com:443 -tls1_3 -bind "$IP"
-
+ # TLS 1.2 (using OpenSSL 3.1.4)
+ timeout 3 openssl s_client -connect tls-v1-2.badssl.com:1012 -tls1_2 -bind "$IP" -quiet /dev/null || true
+ # TLS 1.3 (using OpenSSL 3.1.4)
+ timeout 3 openssl s_client -connect google.com:443 -tls1_3 -bind "$IP" -quiet /dev/null || true
- name: Create systemd service for TLS emulation
ansible.builtin.copy:
dest: /etc/systemd/system/tls-emulate.service
diff --git a/test_vm/provision-non-compliant-tls.yml b/test_vm/provision-non-compliant-tls.yml
index dc9388202..4dc419c59 100644
--- a/test_vm/provision-non-compliant-tls.yml
+++ b/test_vm/provision-non-compliant-tls.yml
@@ -12,30 +12,74 @@
ssl_key: /etc/ssl/private/nginx-selfsigned.key
tasks:
- - name: Ensure openssl, dhclient, and nginx are installed
+ - name: Ensure openssl, dhclient, nginx and build dependencies are installed
apt:
name:
- openssl
- isc-dhcp-client
- nginx
+ - perl
+ - pkg-config
+ - zlib1g-dev
+ - wget
+ - gcc
+ - make
state: present
update_cache: yes
+ - name: Download OpenSSL 1.1.1u source
+ get_url:
+ url: https://www.openssl.org/source/openssl-1.1.1u.tar.gz
+ dest: /tmp/openssl-1.1.1u.tar.gz
+ mode: '0644'
+ force: no
+
+ - name: Extract OpenSSL 1.1.1u source
+ unarchive:
+ src: /tmp/openssl-1.1.1u.tar.gz
+ dest: /tmp
+ remote_src: yes
+ creates: /tmp/openssl-1.1.1u
+
+ - name: Build and install OpenSSL 1.1.1
+ shell: |
+ ./config --prefix=/opt/openssl-1.1.1 no-shared
+ make -j"$(nproc)"
+ make install_sw
+ args:
+ chdir: /tmp/openssl-1.1.1u
+ creates: /opt/openssl-1.1.1/bin/openssl
+
- name: Run dhclient on ens5 (in background)
shell: nohup dhclient {{ iface }} &
async: 0
poll: 0
- # NGINX
+ - name: Download OpenSSL 3.1.4 source
+ get_url:
+ url: https://www.openssl.org/source/openssl-3.1.4.tar.gz
+ dest: /tmp/openssl-3.1.4.tar.gz
+ mode: '0644'
+ force: no
+
+ - name: Extract OpenSSL 3.1.4 source
+ unarchive:
+ src: /tmp/openssl-3.1.4.tar.gz
+ dest: /tmp
+ remote_src: yes
+ creates: /tmp/openssl-3.1.4
+
+ - name: Build and install OpenSSL 3.1.4
+ shell: |
+ ./config --prefix=/opt/openssl-3.1.4 no-shared
+ make -j"$(nproc)"
+ make install_sw
+ args:
+ chdir: /tmp/openssl-3.1.4
+ creates: /opt/openssl-3.1.4/bin/openssl
- - name: Ensure openssl, dhclient, and nginx are installed
- apt:
- name:
- - openssl
- - isc-dhcp-client
- - nginx
- state: present
- update_cache: yes
+
+ # NGINX
- name: Generate self-signed SSL certificate for nginx
command: >
@@ -86,6 +130,14 @@
state: restarted
enabled: yes
+ - name: Create custom OpenSSL config for legacy protocol support
+ copy:
+ dest: /etc/ssl/openssl-legacy.cnf
+ content: |
+ .include /etc/ssl/openssl.cnf
+ [system_default_sect]
+ MinProtocol = TLSv1.0
+ MaxProtocol = TLSv1.3
- name: Copy fail_tls_handshake.sh script
copy:
@@ -114,9 +166,18 @@
echo "No IP $ip_addr on $iface" >> $LOG
exit 1
fi
- # TLS 1.2
+ export OPENSSL_CONF=/etc/ssl/openssl-legacy.cnf
+ OLD_OPENSSL=/opt/openssl-1.1.1/bin/openssl
+ NEW_OPENSSL=/opt/openssl-3.1.4/bin/openssl
+ echo "Running TLS 1.0 handshake..." >> $LOG
+ timeout 3 "$OLD_OPENSSL" s_client -connect tls-v1-0.badssl.com:1010 -tls1 -bind $ip_addr -quiet < /dev/null >> $LOG 2>&1 || true
+ echo "Running TLS 1.1 handshake..." >> $LOG
+ timeout 3 "$OLD_OPENSSL" s_client -connect tls-v1-1.badssl.com:1011 -tls1_1 -bind $ip_addr -quiet < /dev/null >> $LOG 2>&1 || true
echo "Running TLS 1.2 handshake..." >> $LOG
- openssl s_client -connect $host:$port -tls1_2 -cipher "CAMELLIA256-SHA256" -bind $ip_addr -ign_eof < /dev/null >> $LOG 2>&1
+ "$NEW_OPENSSL" s_client -connect $host:$port -tls1_2 -cipher "CAMELLIA256-SHA256" -bind $ip_addr -ign_eof < /dev/null >> $LOG 2>&1
+ echo "Running TLS 1.3 handshake..." >> $LOG
+ "$NEW_OPENSSL" s_client -connect tls-v1-2.badssl.com:1012 -tls1_3
+
- name: Create systemd service for failing TLS handshake
copy:
diff --git a/test_vm/provision-non-compliant.yml b/test_vm/provision-non-compliant.yml
index 978f50eab..be26c0ead 100644
--- a/test_vm/provision-non-compliant.yml
+++ b/test_vm/provision-non-compliant.yml
@@ -42,6 +42,14 @@
- net-tools
- netcat-openbsd
- isc-dhcp-client
+ - vsftpd
+ - telnetd
+ - inetutils-inetd
+ - postfix
+ - dovecot-pop3d
+ - dovecot-imapd
+ - x11vnc
+ - xvfb
state: present
# Set up networking on ens5
@@ -51,6 +59,150 @@
async: 0
poll: 0
+ # SNMP (UDP/161)
+
+ - name: Kill only netcat processes listening on UDP/161 (универсально)
+ shell: |
+ for pid in $(lsof -t -iUDP:161 | awk '/nc|netcat/ {print $2}'); do
+ kill $pid
+ done
+ ignore_errors: yes
+
+ - name: Install SNMP daemon
+ apt:
+ name: snmpd
+ state: present
+ update_cache: yes
+
+ - name: Ensure minimal snmpd.conf
+ copy:
+ dest: /etc/snmp/snmpd.conf
+ content: |
+ agentAddress udp:161
+ rocommunity public
+
+ - name: Restart snmpd
+ service:
+ name: snmpd
+ state: restarted
+ enabled: yes
+
+ - name: Wait for SNMP server to listen on UDP/161
+ shell: |
+ for i in {1..20}; do
+ ss -uln | grep -q ':161 ' && exit 0
+ sleep 1
+ done
+ exit 1
+ register: snmpd_udp_check
+ changed_when: false
+ failed_when: snmpd_udp_check.rc != 0
+
+
+
+ # SSH server configuration
+
+ - name: Gather facts
+ setup:
+
+ - name: Set ens4_ip from Ansible facts
+ set_fact:
+ ens4_ip: "{{ ansible_ens4.ipv4.address | default('') }}"
+
+ - name: Fail if ens4_ip is not set
+ fail:
+ msg: "ens4_ip is not set! Please check network configuration."
+ when: ens4_ip == ''
+
+ - name: Debug ens4 IP
+ debug:
+ msg: "ens4 IP is {{ ens4_ip }}"
+
+ - name: Stop and disable ssh.socket (prevents auto-start on all interfaces)
+ service:
+ name: ssh.socket
+ state: stopped
+ enabled: no
+ ignore_errors: yes
+
+ - name: Ensure only ens4 is in ListenAddress for sshd
+ lineinfile:
+ path: /etc/ssh/sshd_config
+ regexp: '^ListenAddress'
+ line: "ListenAddress {{ ens4_ip }}"
+ state: present
+ insertafter: '^#Port'
+
+ - name: Remove all other ListenAddress lines from sshd_config
+ lineinfile:
+ path: /etc/ssh/sshd_config
+ regexp: '^ListenAddress (?!{{ ens4_ip }})'
+ state: absent
+
+ - name: Restart sshd
+ service:
+ name: ssh
+ state: restarted
+
+ - name: Create fake SSH banner systemd service for ens5:22
+ copy:
+ dest: /etc/systemd/system/fake-ssh-banner.service
+ mode: '0644'
+ content: |
+ [Unit]
+ Description=Fake SSH Banner on ens5:22
+ After=network-online.target
+ Wants=network-online.target
+
+ [Service]
+ Type=simple
+ ExecStart=/bin/bash -c '\
+ while true; do \
+ ens5_IP=$(ip -4 addr show ens5 | grep -oP "(?<=inet\s)\d+(\.\d+){3}"); \
+ if [[ -n "$ens5_IP" ]]; then \
+ echo "ens5 IP detected: $ens5_IP"; \
+ break; \
+ fi; \
+ echo "Waiting for ens5 IP..."; \
+ sleep 2; \
+ done; \
+ while true; do echo -e "SSH-1.99-OpenSSH_1.99\r\n" | nc -l -s $ens5_IP -p 22; done'
+ Restart=always
+
+ [Install]
+ WantedBy=multi-user.target
+
+ - name: Reload systemd
+ systemd:
+ daemon_reload: yes
+
+ - name: Restart fake-ssh-banner service
+ systemd:
+ name: fake-ssh-banner.service
+ state: restarted
+ enabled: yes
+
+ # TFTP (UDP/69)
+
+ - name: Copy fake TFTP netcat script
+ copy:
+ dest: /usr/local/bin/fake-tftp-netcat-ens5.sh
+ mode: '0755'
+ content: |
+ #!/bin/bash
+ while true; do
+ ens5_IP=$(ip -4 addr show ens5 | grep -oP '(?<=inet\s)\d+(\.\d+){3}')
+ if [[ -n "$ens5_IP" ]]; then break; fi
+ sleep 2
+ done
+ while true; do
+ echo -ne "\x00\x05\x00\x01File not found\x00" | nc -u -l -s $ens5_IP -p 69
+ done
+ - name: Run fake TFTP netcat script as background process
+ shell: nohup /usr/local/bin/fake-tftp-netcat-ens5.sh > /var/log/fake-tftp-netcat-ens5.log 2>&1 &
+ args:
+ creates: /var/log/fake-tftp-netcat-ens5.log
+
# NTP
- name: Create Python venv for scapy
@@ -173,319 +325,162 @@
# FTP server
- - name: Kill old fake FTP netcat processes
- shell: pkill -f 'nc -l -s .* -p 21' || true
-
- ignore_errors: yes
- - name: Copy fake FTP netcat script
- copy:
- dest: /usr/local/bin/fake-ftp-netcat-ens5.sh
- mode: '0755'
- content: |
- #!/bin/bash
- while true; do
- ens5_IP=$(ip -4 addr show ens5 | grep -oP '(?<=inet\s)\d+(\.\d+){3}')
- if [[ -n "$ens5_IP" ]]; then break; fi
- sleep 2
- done
- while true; do
- echo -ne "220 Fake FTP server ready\r\n" | nc -l -s $ens5_IP -p 21
- done
- - name: Run fake FTP netcat script as background process
- shell: nohup /usr/local/bin/fake-ftp-netcat-ens5.sh > /var/log/fake-ftp-netcat-ens5.log 2>&1 &
- args:
- creates: /var/log/fake-ftp-netcat-ens5.log
-
-
- # SSH server configuration
-
- - name: Gather facts
- setup:
+ - name: Restart vsftpd
+ service:
+ name: vsftpd
+ state: restarted
+ enabled: yes
- - name: Set ens4_ip from Ansible facts
- set_fact:
- ens4_ip: "{{ ansible_ens4.ipv4.address | default('') }}"
+ - name: Check if FTP is listening on port 21
+ wait_for:
+ port: 21
+ state: started
+ timeout: 20
- - name: Fail if ens4_ip is not set
- fail:
- msg: "ens4_ip is not set! Please check network configuration."
- when: ens4_ip == ''
- - name: Debug ens4 IP
- debug:
- msg: "ens4 IP is {{ ens4_ip }}"
- - name: Stop and disable ssh.socket (prevents auto-start on all interfaces)
- service:
- name: ssh.socket
- state: stopped
- enabled: no
- ignore_errors: yes
+ # Telnet server setup
- - name: Ensure only ens4 is in ListenAddress for sshd
+ - name: Ensure telnetd is enabled in /etc/inetd.conf
lineinfile:
- path: /etc/ssh/sshd_config
- regexp: '^ListenAddress'
- line: "ListenAddress {{ ens4_ip }}"
+ path: /etc/inetd.conf
+ regexp: '^telnet'
+ line: 'telnet stream tcp nowait root /usr/sbin/telnetd telnetd'
state: present
- insertafter: '^#Port'
-
- - name: Remove all other ListenAddress lines from sshd_config
- lineinfile:
- path: /etc/ssh/sshd_config
- regexp: '^ListenAddress (?!{{ ens4_ip }})'
- state: absent
- - name: Restart sshd
+ - name: Restart inetutils-inetd
service:
- name: ssh
+ name: inetutils-inetd
state: restarted
+ enabled: yes
- - name: Show what is listening on port 22
- shell: ss -tlnp | grep :22 || netstat -tlnp | grep :22 || true
- register: port22
- changed_when: false
+ - name: Check if Telnet is listening on port 23
+ wait_for:
+ port: 23
+ state: started
+ timeout: 20
- - debug:
- var: port22.stdout_lines
+ # SMTP (TCP/25)
+ - name: Ensure Postfix listens on all interfaces (0.0.0.0:25)
+ lineinfile:
+ path: /etc/postfix/main.cf
+ regexp: '^inet_interfaces ='
+ line: 'inet_interfaces = all'
+ state: present
- - name: Create fake SSH banner systemd service for ens5:22
- copy:
- dest: /etc/systemd/system/fake-ssh-banner.service
- mode: '0644'
- content: |
- [Unit]
- Description=Fake SSH Banner on ens5:22
- After=network-online.target
- Wants=network-online.target
+ - name: Restart and enable Postfix service
+ service:
+ name: postfix
+ state: restarted
+ enabled: yes
- [Service]
- Type=simple
- ExecStart=/bin/bash -c '\
- while true; do \
- ens5_IP=$(ip -4 addr show ens5 | grep -oP "(?<=inet\s)\d+(\.\d+){3}"); \
- if [[ -n "$ens5_IP" ]]; then \
- echo "ens5 IP detected: $ens5_IP"; \
- break; \
- fi; \
- echo "Waiting for ens5 IP..."; \
- sleep 2; \
- done; \
- while true; do echo -e "SSH-1.99-OpenSSH_1.99\r\n" | nc -l -s $ens5_IP -p 22; done'
- Restart=always
+ - name: Wait for SMTP server to listen on port 25
+ wait_for:
+ port: 25
+ state: started
+ timeout: 20
- [Install]
- WantedBy=multi-user.target
+ # POP3 (TCP/110) / IMAP (TCP/143)
- - name: Reload systemd
- systemd:
- daemon_reload: yes
- - name: Restart fake-ssh-banner service
- systemd:
- name: fake-ssh-banner.service
+ - name: Ensure Dovecot service is enabled and restarted
+ service:
+ name: dovecot
state: restarted
enabled: yes
- # Telnet server setup
-
- - name: Kill old fake Telnet netcat processes
- shell: pkill -f 'nc -l -s .* -p 23' || true
- ignore_errors: yes
- - name: Copy fake Telnet netcat script
- copy:
- dest: /usr/local/bin/fake-telnet-netcat-ens5.sh
- mode: '0755'
- content: |
- #!/bin/bash
- while true; do
- ens5_IP=$(ip -4 addr show ens5 | grep -oP '(?<=inet\s)\d+(\.\d+){3}')
- if [[ -n "$ens5_IP" ]]; then break; fi
- sleep 2
- done
- while true; do
- echo -ne "Fake Telnet server\r\n" | nc -l -s $ens5_IP -p 23
- done
- - name: Run fake Telnet netcat script as background process
- shell: nohup /usr/local/bin/fake-telnet-netcat-ens5.sh > /var/log/fake-telnet-netcat-ens5.log 2>&1 &
- args:
- creates: /var/log/fake-telnet-netcat-ens5.log
+ - name: Wait for POP3 server to listen on port 110
+ wait_for:
+ port: 110
+ state: started
+ timeout: 20
- # SMTP (TCP/25)
+ - name: Wait for IMAP server to listen on port 143
+ wait_for:
+ port: 143
+ state: started
+ timeout: 20
- - name: Kill old fake SMTP netcat processes
- shell: pkill -f 'nc -l -s .* -p 25' || true
- ignore_errors: yes
- - name: Copy fake SMTP netcat script
- copy:
- dest: /usr/local/bin/fake-smtp-netcat-ens5.sh
- mode: '0755'
- content: |
- #!/bin/bash
- while true; do
- ens5_IP=$(ip -4 addr show ens5 | grep -oP '(?<=inet\s)\d+(\.\d+){3}')
- if [[ -n "$ens5_IP" ]]; then break; fi
- sleep 2
- done
- while true; do
- echo -ne "220 fake-smtp ESMTP Postfix\r\n" | nc -l -s $ens5_IP -p 25
- done
- - name: Run fake SMTP netcat script as background process
- shell: nohup /usr/local/bin/fake-smtp-netcat-ens5.sh > /var/log/fake-smtp-netcat-ens5.log 2>&1 &
- args:
- creates: /var/log/fake-smtp-netcat-ens5.log
- # POP3 (TCP/110)
+ # HTTP (TCP/80)
- - name: Kill old fake POP3 netcat processes
+ - name: Install nginx
+ apt:
+ name: nginx
+ state: present
+ - name: Ensure nginx is running
+ service:
+ name: nginx
+ state: started
+ enabled: yes
+
+ # VNC (TCP/5901)
- shell: pkill -f 'nc -l -s .* -p 110' || true
- ignore_errors: yes
- - name: Copy fake POP3 netcat script
- copy:
- dest: /usr/local/bin/fake-pop3-netcat-ens5.sh
- mode: '0755'
- content: |
- #!/bin/bash
- while true; do
- ens5_IP=$(ip -4 addr show ens5 | grep -oP '(?<=inet\s)\d+(\.\d+){3}')
- if [[ -n "$ens5_IP" ]]; then break; fi
- sleep 2
- done
- while true; do
- echo -ne "+OK Fake POP3 server ready\r\n" | nc -l -s $ens5_IP -p 110
- done
- - name: Run fake POP3 netcat script as background process
- shell: nohup /usr/local/bin/fake-pop3-netcat-ens5.sh > /var/log/fake-pop3-netcat-ens5.log 2>&1 &
+ - name: Set VNC password (optional, for security)
+ command: x11vnc -storepasswd "vncpassword" /etc/x11vnc.pass
args:
- creates: /var/log/fake-pop3-netcat-ens5.log
+ creates: /etc/x11vnc.pass
- # IMAP (TCP/143)
-
- - name: Kill old fake IMAP netcat processes
- shell: pkill -f 'nc -l -s .* -p 143' || true
- ignore_errors: yes
- - name: Copy fake IMAP netcat script
+ - name: Create systemd unit for Xvfb on display :0
copy:
- dest: /usr/local/bin/fake-imap-netcat-ens5.sh
- mode: '0755'
+ dest: /etc/systemd/system/xvfb.service
+ mode: '0644'
content: |
- #!/bin/bash
- while true; do
- ens5_IP=$(ip -4 addr show ens5 | grep -oP '(?<=inet\s)\d+(\.\d+){3}')
- if [[ -n "$ens5_IP" ]]; then break; fi
- sleep 2
- done
- while true; do
- echo -ne "* OK Fake IMAP4rev1 Service Ready\r\n" | nc -l -s $ens5_IP -p 143
- done
- - name: Run fake IMAP netcat script as background process
- shell: nohup /usr/local/bin/fake-imap-netcat-ens5.sh > /var/log/fake-imap-netcat-ens5.log 2>&1 &
- args:
- creates: /var/log/fake-imap-netcat-ens5.log
+ [Unit]
+ Description=Virtual X server (Xvfb) on :0
+ After=network.target
- # HTTP (TCP/80)
+ [Service]
+ Type=simple
+ ExecStart=/usr/bin/Xvfb :0 -screen 0 1024x768x16
+ Restart=always
- - name: Kill old fake HTTP netcat processes
- shell: pkill -f 'nc -l -s .* -p 80' || true
- ignore_errors: yes
- - name: Copy fake HTTP netcat script
+ [Install]
+ WantedBy=multi-user.target
+
+ - name: Create systemd unit for x11vnc on port 5901
copy:
- dest: /usr/local/bin/fake-http-netcat-ens5.sh
- mode: '0755'
+ dest: /etc/systemd/system/x11vnc.service
+ mode: '0644'
content: |
- #!/bin/bash
- while true; do
- ens5_IP=$(ip -4 addr show ens5 | grep -oP '(?<=inet\s)\d+(\.\d+){3}')
- if [[ -n "$ens5_IP" ]]; then break; fi
- sleep 2
- done
- while true; do
- echo -ne "HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK" | nc -l -s $ens5_IP -p 80
- done
- - name: Run fake HTTP netcat script as background process
- shell: nohup /usr/local/bin/fake-http-netcat-ens5.sh > /var/log/fake-http-netcat-ens5.log 2>&1 &
- args:
- creates: /var/log/fake-http-netcat-ens5.log
+ [Unit]
+ Description=Start x11vnc at startup.
+ After=xvfb.service
- # SNMP (UDP/161)
+ [Service]
+ Type=simple
+ ExecStart=/usr/bin/x11vnc -display :0 -rfbauth /etc/x11vnc.pass -forever -shared -rfbport 5901 -nopw -listen 0.0.0.0
+ Restart=always
- - name: Kill old fake SNMP netcat processes
- shell: pkill -f 'nc -u -l -s .* -p 161' || true
- ignore_errors: yes
- - name: Copy fake SNMP netcat script
- copy:
- dest: /usr/local/bin/fake-snmp-netcat-ens5.sh
- mode: '0755'
- content: |
- #!/bin/bash
- while true; do
- ens5_IP=$(ip -4 addr show ens5 | grep -oP '(?<=inet\s)\d+(\.\d+){3}')
- if [[ -n "$ens5_IP" ]]; then break; fi
- sleep 2
- done
- while true; do
- head -c 48 /dev/zero | nc -u -l -s $ens5_IP -p 161
- done
- - name: Run fake SNMP netcat script as background process
- shell: nohup /usr/local/bin/fake-snmp-netcat-ens5.sh > /var/log/fake-snmp-netcat-ens5.log 2>&1 &
- args:
- creates: /var/log/fake-snmp-netcat-ens5.log
+ [Install]
+ WantedBy=multi-user.target
- # VNC (TCP/5901)
+ - name: Reload systemd daemon
+ systemd:
+ daemon_reload: yes
- - name: Kill old fake VNC netcat processes
- shell: pkill -f 'nc -l -s .* -p 5901' || true
- ignore_errors: yes
- - name: Copy fake VNC netcat script
- copy:
- dest: /usr/local/bin/fake-vnc-netcat-ens5.sh
- mode: '0755'
- content: |
- #!/bin/bash
- while true; do
- ens5_IP=$(ip -4 addr show ens5 | grep -oP '(?<=inet\s)\d+(\.\d+){3}')
- if [[ -n "$ens5_IP" ]]; then break; fi
- sleep 2
- done
- while true; do
- echo -ne "RFB 003.008\n" | nc -l -s $ens5_IP -p 5901
- done
- - name: Run fake VNC netcat script as background process
- shell: nohup /usr/local/bin/fake-vnc-netcat-ens5.sh > /var/log/fake-vnc-netcat-ens5.log 2>&1 &
- args:
- creates: /var/log/fake-vnc-netcat-ens5.log
+ - name: Enable and start Xvfb service
+ systemd:
+ name: xvfb
+ enabled: yes
+ state: started
- # TFTP (UDP/69)
+ - name: Enable and start x11vnc service
+ systemd:
+ name: x11vnc
+ enabled: yes
+ state: started
+
+ - name: Wait for VNC server to listen on port 5901
+ wait_for:
+ port: 5901
+ state: started
+ timeout: 60
- - name: Kill old fake TFTP netcat processes
- shell: pkill -f 'nc -u -l -s .* -p 69' || true
- ignore_errors: yes
- - name: Copy fake TFTP netcat script
- copy:
- dest: /usr/local/bin/fake-tftp-netcat-ens5.sh
- mode: '0755'
- content: |
- #!/bin/bash
- while true; do
- ens5_IP=$(ip -4 addr show ens5 | grep -oP '(?<=inet\s)\d+(\.\d+){3}')
- if [[ -n "$ens5_IP" ]]; then break; fi
- sleep 2
- done
- while true; do
- echo -ne "\x00\x05\x00\x01File not found\x00" | nc -u -l -s $ens5_IP -p 69
- done
- - name: Run fake TFTP netcat script as background process
- shell: nohup /usr/local/bin/fake-tftp-netcat-ens5.sh > /var/log/fake-tftp-netcat-ens5.log 2>&1 &
- args:
- creates: /var/log/fake-tftp-netcat-ens5.log
# NTP (UDP/123)
- - name: Kill old fake NTP netcat processes
- shell: pkill -f 'nc -u -l -s .* -p 123' || true
- ignore_errors: yes
- name: Copy fake NTP netcat script
copy:
dest: /usr/local/bin/fake-ntp-netcat-ens5.sh
@@ -526,9 +521,6 @@
echo -ne "Fake BACnet server\n" | nc -u -l -s $ens5_IP -p 47808
done
-
-
-
# DHCP
- name: Install ISC DHCP server
@@ -575,6 +567,72 @@
msg: "DHCP server status: {{ dhcp_status.stdout }}"
+ - name: Create Python venv for scapy
+ command: python3 -m venv {{ scapy_venv_path }}
+ args:
+ creates: "{{ scapy_venv_path }}/bin/activate"
+ changed_when: false
+
+ - name: Install scapy in venv
+ command: "{{ scapy_venv_path }}/bin/pip install scapy"
+ changed_when: false
+
+ - name: Install netifaces in scapy venv
+ command: "{{ scapy_venv_path }}/bin/pip install netifaces"
+ changed_when: false
+
+ - name: Copy ARP spoof script (continuous, Scapy, wrong IP)
+ copy:
+ dest: /usr/local/bin/send_wrong_arp.py
+ mode: '0755'
+ content: |
+ from scapy.all import ARP, Ether, sendp
+ import time
+
+ iface = "{{ iface }}"
+ src_mac = "{{ device_mac }}"
+ wrong_ip = "{{ wrong_ip }}"
+ target_ip = "{{ target_ip }}"
+
+ while True:
+ arp = Ether(src=src_mac, dst="ff:ff:ff:ff:ff:ff")/ARP(
+ op=2,
+ hwsrc=src_mac,
+ psrc=wrong_ip,
+ hwdst="ff:ff:ff:ff:ff:ff",
+ pdst=target_ip
+ )
+ sendp(arp, iface=iface, count=1, verbose=False)
+ print(f"Sent ARP reply: {wrong_ip} is-at {src_mac} to {target_ip}")
+ time.sleep(1)
+
+ - name: Create systemd unit for continuous ARP spoof
+ copy:
+ dest: /etc/systemd/system/send-wrong-arp.service
+ mode: '0644'
+ content: |
+ [Unit]
+ Description=Continuous ARP Spoof Sender
+ After=network.target
+
+ [Service]
+ Type=simple
+ ExecStart={{ scapy_venv_path }}/bin/python /usr/local/bin/send_wrong_arp.py
+ Restart=always
+ User=root
+
+ [Install]
+ WantedBy=multi-user.target
+
+ - name: Reload systemd daemon
+ systemd:
+ daemon_reload: yes
+
+ - name: Enable and start continuous ARP spoof service
+ systemd:
+ name: send-wrong-arp
+ enabled: yes
+ state: started
# BLOCK ICMP
@@ -624,11 +682,6 @@
sysctl_set: yes
reload: yes
-
- - name: Remove temporary static IP from ens5
- command: ip addr del 10.10.10.14/24 dev ens5
- ignore_errors: yes
-
handlers:
- name: Restart vsftpd
service:
diff --git a/testing/api/devices/device_1/device_config.json b/testing/api/devices/device_1/device_config.json
index 3be69a082..9e3f84328 100644
--- a/testing/api/devices/device_1/device_config.json
+++ b/testing/api/devices/device_1/device_config.json
@@ -50,5 +50,121 @@
"dns": {
"enabled": true
}
- }
+ },
+ "reports": [{
+ "testrun": {
+ "version": "2.1"
+ },
+ "mac_addr": null,
+ "device": {
+ "mac_addr": "00:1e:42:35:73:c4",
+ "manufacturer": "Teltonika",
+ "model": "TRB140",
+ "firmware": "1",
+ "test_modules": {
+ "protocol": {
+ "enabled": false
+ },
+ "services": {
+ "enabled": false
+ },
+ "connection": {
+ "enabled": false
+ },
+ "tls": {
+ "enabled": true
+ },
+ "ntp": {
+ "enabled": false
+ },
+ "dns": {
+ "enabled": false
+ }
+ },
+ "test_pack": "Device Qualification",
+ "device_profile": [
+ {
+ "question": "What type of device is this?",
+ "answer": "Building Automation Gateway"
+ },
+ {
+ "question": "Please select the technology this device falls into",
+ "answer": "Hardware - Access Control"
+ },
+ {
+ "question": "Does your device process any sensitive information? ",
+ "answer": "No"
+ },
+ {
+ "question": "Can all non-essential services be disabled on your device?",
+ "answer": "Yes"
+ },
+ {
+ "question": "Is there a second IP port on the device?",
+ "answer": "Yes"
+ },
+ {
+ "question": "Can the second IP port on your device be disabled?",
+ "answer": "No"
+ }
+ ]
+ },
+ "status": "Non-Compliant",
+ "started": "2024-12-10 16:06:42",
+ "finished": "2024-12-10 16:08:12",
+ "tests": {
+ "total": 5,
+ "results": [
+ {
+ "name": "security.tls.v1_0_client",
+ "description": "No outbound connections were found",
+ "expected_behavior": "The packet indicates a TLS connection with at least TLS 1.0 and support",
+ "required_result": "Informational",
+ "result": "Feature Not Detected"
+ },
+ {
+ "name": "security.tls.v1_2_server",
+ "description": "TLS 1.2 certificate is invalid",
+ "expected_behavior": "TLS 1.2 certificate is issued to the web browser client when accessed",
+ "required_result": "Required if Applicable",
+ "result": "Non-Compliant",
+ "recommendations": [
+ "Enable TLS 1.2 support in the web server configuration",
+ "Disable TLS 1.0 and 1.1",
+ "Sign the certificate used by the web server"
+ ]
+ },
+ {
+ "name": "security.tls.v1_2_client",
+ "description": "An error occurred whilst running this test",
+ "expected_behavior": "The packet indicates a TLS connection with at least TLS 1.2 and support for ECDH and ECDSA ciphers",
+ "required_result": "Required if Applicable",
+ "result": "Error"
+ },
+ {
+ "name": "security.tls.v1_3_server",
+ "description": "TLS 1.3 certificate is invalid",
+ "expected_behavior": "TLS 1.3 certificate is issued to the web browser client when accessed",
+ "required_result": "Informational",
+ "result": "Informational",
+ "optional_recommendations": [
+ "Enable TLS 1.3 support in the web server configuration",
+ "Disable TLS 1.0 and 1.1",
+ "Sign the certificate used by the web server"
+ ]
+ },
+ {
+ "name": "security.tls.v1_3_client",
+ "description": "An error occurred whilst running this test",
+ "expected_behavior": "The packet indicates a TLS connection with at least TLS 1.3",
+ "required_result": "Informational",
+ "result": "Error"
+ }
+ ]
+ },
+ "report": "/report/001e42289e4a_2024-12-10T16:06:42",
+ "export": "/export/001e42289e4a_2024-12-10T16:06:42",
+ "folder_name": "001e42289e4a_2024-12-10T16:06:42"
+}
+]
}
diff --git a/testing/api/local/reports/001e42289e4a_2024-12-10T16:06:42/test/001e42289e4a/report.json b/testing/api/local/reports/001e42289e4a_2024-12-10T16:06:42/test/001e42289e4a/report.json
new file mode 100644
index 000000000..7dc70a7b5
--- /dev/null
+++ b/testing/api/local/reports/001e42289e4a_2024-12-10T16:06:42/test/001e42289e4a/report.json
@@ -0,0 +1,116 @@
+{
+ "testrun": {
+ "version": "2.1"
+ },
+ "mac_addr": null,
+ "device": {
+ "mac_addr": "00:1e:42:35:73:c4",
+ "manufacturer": "Teltonika",
+ "model": "TRB140",
+ "firmware": "1",
+ "test_modules": {
+ "protocol": {
+ "enabled": false
+ },
+ "services": {
+ "enabled": false
+ },
+ "connection": {
+ "enabled": false
+ },
+ "tls": {
+ "enabled": true
+ },
+ "ntp": {
+ "enabled": false
+ },
+ "dns": {
+ "enabled": false
+ }
+ },
+ "test_pack": "Device Qualification",
+ "device_profile": [
+ {
+ "question": "What type of device is this?",
+ "answer": "Building Automation Gateway"
+ },
+ {
+ "question": "Please select the technology this device falls into",
+ "answer": "Hardware - Access Control"
+ },
+ {
+ "question": "Does your device process any sensitive information? ",
+ "answer": "No"
+ },
+ {
+ "question": "Can all non-essential services be disabled on your device?",
+ "answer": "Yes"
+ },
+ {
+ "question": "Is there a second IP port on the device?",
+ "answer": "Yes"
+ },
+ {
+ "question": "Can the second IP port on your device be disabled?",
+ "answer": "No"
+ }
+ ]
+ },
+ "status": "Non-Compliant",
+ "started": "2024-12-10 16:06:42",
+ "finished": "2024-12-10 16:08:12",
+ "tests": {
+ "total": 5,
+ "results": [
+ {
+ "name": "security.tls.v1_0_client",
+ "description": "No outbound connections were found",
+ "expected_behavior": "The packet indicates a TLS connection with at least TLS 1.0 and support",
+ "required_result": "Informational",
+ "result": "Feature Not Detected"
+ },
+ {
+ "name": "security.tls.v1_2_server",
+ "description": "TLS 1.2 certificate is invalid",
+ "expected_behavior": "TLS 1.2 certificate is issued to the web browser client when accessed",
+ "required_result": "Required if Applicable",
+ "result": "Non-Compliant",
+ "recommendations": [
+ "Enable TLS 1.2 support in the web server configuration",
+ "Disable TLS 1.0 and 1.1",
+ "Sign the certificate used by the web server"
+ ]
+ },
+ {
+ "name": "security.tls.v1_2_client",
+ "description": "An error occurred whilst running this test",
+ "expected_behavior": "The packet indicates a TLS connection with at least TLS 1.2 and support for ECDH and ECDSA ciphers",
+ "required_result": "Required if Applicable",
+ "result": "Error"
+ },
+ {
+ "name": "security.tls.v1_3_server",
+ "description": "TLS 1.3 certificate is invalid",
+ "expected_behavior": "TLS 1.3 certificate is issued to the web browser client when accessed",
+ "required_result": "Informational",
+ "result": "Informational",
+ "optional_recommendations": [
+ "Enable TLS 1.3 support in the web server configuration",
+ "Disable TLS 1.0 and 1.1",
+ "Sign the certificate used by the web server"
+ ]
+ },
+ {
+ "name": "security.tls.v1_3_client",
+ "description": "An error occurred whilst running this test",
+ "expected_behavior": "The packet indicates a TLS connection with at least TLS 1.3",
+ "required_result": "Informational",
+ "result": "Error"
+ }
+ ]
+ },
+ "report": "/report/001e42289e4a_2024-12-10T16:06:42",
+ "export": "/export/001e42289e4a_2024-12-10T16:06:42",
+ "folder_name": "001e42289e4a_2024-12-10T16:06:42"
+
+}
\ No newline at end of file
diff --git a/testing/api/local/reports/001e42289e4a_2024-12-10T16:06:42/test/001e42289e4a/report.pdf b/testing/api/local/reports/001e42289e4a_2024-12-10T16:06:42/test/001e42289e4a/report.pdf
new file mode 100644
index 000000000..87fc93be2
Binary files /dev/null and b/testing/api/local/reports/001e42289e4a_2024-12-10T16:06:42/test/001e42289e4a/report.pdf differ
diff --git a/testing/api/reports/report.json b/testing/api/reports/report.json
index e1d9f7003..7dc70a7b5 100644
--- a/testing/api/reports/report.json
+++ b/testing/api/reports/report.json
@@ -109,5 +109,8 @@
}
]
},
- "report": "http://localhost:8000/report/Teltonika TRB140/2024-12-10T16:06:42"
+ "report": "/report/001e42289e4a_2024-12-10T16:06:42",
+ "export": "/export/001e42289e4a_2024-12-10T16:06:42",
+ "folder_name": "001e42289e4a_2024-12-10T16:06:42"
+
}
\ No newline at end of file
diff --git a/testing/api/test_api b/testing/api/test_api
index 095123a3b..649720205 100755
--- a/testing/api/test_api
+++ b/testing/api/test_api
@@ -38,6 +38,7 @@ sudo chown -R $USER local
# Copy configuration to testrun
sudo cp testing/api/sys_config/system.json local/system.json
+sudo cp testing/api/local/reports local/ -r
# Needs to be sudo because this invokes bin/testrun
sudo venv/bin/python3 -m pytest -v testing/api/test_api.py
diff --git a/testing/api/test_api.py b/testing/api/test_api.py
index 9ba6c3f08..431aac56f 100644
--- a/testing/api/test_api.py
+++ b/testing/api/test_api.py
@@ -934,7 +934,6 @@ def test_get_reports(empty_devices_dir, add_devices, # pylint: disable=W0613
"status",
"started",
"finished",
- "tests",
"report",
"export"
]
@@ -948,9 +947,9 @@ def test_get_reports(empty_devices_dir, add_devices, # pylint: disable=W0613
@pytest.mark.parametrize("add_devices", [
["device_1"]
],indirect=True)
-def test_delete_report(empty_devices_dir, add_devices, # pylint: disable=W0613
- create_report_folder, testrun): # pylint: disable=W0613
- """ Test for succesfully delete a report (200) """
+def test_get_report_success(empty_devices_dir, add_devices, # pylint: disable=W0613
+ create_report_folder, testrun): # pylint: disable=W0613
+ """Test for successfully get report when report exists (200)"""
# Load the device using load_json utility method
device = load_json("device_config.json", directory=DEVICE_1_PATH)
@@ -958,86 +957,85 @@ def test_delete_report(empty_devices_dir, add_devices, # pylint: disable=W0613
# Assign the device mac address
mac_addr = device["mac_addr"]
- # Assign the device name
- device_name = f'{device["manufacturer"]} {device["model"]}'
-
- # Payload
- delete_data = {
- "mac_addr": mac_addr,
- "timestamp": get_timestamp()
- }
+ # Construct report_name
+ mac_no_colons = mac_addr.replace(":", "")
+ timestamp = get_timestamp(formatted=True)
+ report_name = f"{mac_no_colons}_{timestamp}"
- # Send a DELETE request to remove the report
- r = requests.delete(f"{API}/report", data=json.dumps(delete_data), timeout=5)
+ # Send the get request
+ r = requests.get(f"{API}/report/{report_name}", timeout=5)
- # Check if status code is 200 (OK)
+ # Check if status code is 200 (ok)
assert r.status_code == 200
- # Parse the json response
- response = r.json()
-
- # Check if "success" in response
- assert "success" in response
+ # Check if the response is a PDF
+ assert r.headers["Content-Type"] == "application/pdf"
- # Construct the 'reports' folder path
- reports_folder = os.path.join(device_name, "reports")
+@pytest.mark.parametrize("add_devices, add_profiles", [
+ (["device_1"], ["valid_profile.json"])
+], indirect=True)
+def test_export_report_with_profile(empty_devices_dir, add_devices, # pylint: disable=W0613
+ empty_profiles_dir, add_profiles, # pylint: disable=W0613
+ create_report_folder, testrun): # pylint: disable=W0613
+ """Test export results with existing profile when report exists (200)"""
- # Check if reports folder has been deleted
- assert not os.path.exists(reports_folder)
+ # Load the profile using load_json utility method
+ profile = load_json("valid_profile.json", directory=PROFILES_PATH)
-@pytest.mark.parametrize("add_devices", [
- ["device_1"]
-],indirect=True)
-def test_delete_report_no_payload(empty_devices_dir, add_devices, # pylint: disable=W0613
- create_report_folder, testrun): # pylint: disable=W0613
- """ Test delete report bad request when the payload is missing (400) """
+ # Load the device using load_json utility method
+ device = load_json("device_config.json", directory=DEVICE_1_PATH)
- # Send a DELETE request to remove the report without the payload
- r = requests.delete(f"{API}/report", timeout=5)
+ # Assign the device mac address
+ mac_addr = device["mac_addr"]
- # Check if status code is 400 (bad request)
- assert r.status_code == 400
+ # Construct report_name
+ mac_no_colons = mac_addr.replace(":", "")
+ timestamp = get_timestamp(formatted=True)
+ report_name = f"{mac_no_colons}_{timestamp}"
- # Parse the json response
- response = r.json()
+ # Send the post request
+ r = requests.post(f"{API}/export/{report_name}",
+ json=profile,
+ timeout=5)
- # Check if "error" in response
- assert "error" in response
+ # Check if status code is 200 (OK)
+ assert r.status_code == 200
- # Check if the correct error message returned
- assert "Invalid request received, missing body" in response["error"]
+ # Check if the response is a zip file
+ assert r.headers["Content-Type"] == "application/zip"
@pytest.mark.parametrize("add_devices", [
["device_1"]
],indirect=True)
-def test_delete_report_invalid_payload(empty_devices_dir, add_devices, # pylint: disable=W0613
- create_report_folder, testrun): # pylint: disable=W0613
- """ Test delete report bad request missing mac addr or timestamp (400) """
-
- # Empty payload
- delete_data = {}
+def test_export_results_with_no_profile(empty_devices_dir, add_devices, # pylint: disable=W0613
+ create_report_folder, testrun): # pylint: disable=W0613
+ """Test export results with no profile when report exists (200)"""
+ # Load the device using load_json utility method
+ device = load_json("device_config.json", directory=DEVICE_1_PATH)
- # Send a DELETE request to remove the report
- r = requests.delete(f"{API}/report", data=json.dumps(delete_data), timeout=5)
+ # Assign the device mac address
+ mac_addr = device["mac_addr"]
- # Check if status code is 400 (bad request)
- assert r.status_code == 400
+ # Construct report_name
+ mac_no_colons = mac_addr.replace(":", "")
+ timestamp = get_timestamp(formatted=True)
+ report_name = f"{mac_no_colons}_{timestamp}"
- # Parse the json response
- response = r.json()
+ # Send the post request
+ r = requests.post(f"{API}/export/{report_name}", timeout=5)
- # Check if "error" in response
- assert "error" in response
+ # Check if status code is 200 (OK)
+ assert r.status_code == 200
- # Check if the correct error message returned
- assert "Missing mac address or timestamp" in response["error"]
+ # Check if the response is a zip file
+ assert r.headers["Content-Type"] == "application/zip"
@pytest.mark.parametrize("add_devices", [
["device_1"]
],indirect=True)
-def test_delete_report_invalid_timestamp(empty_devices_dir, add_devices, # pylint: disable=W0613
- create_report_folder, testrun): # pylint: disable=W0613
- """ Test delete report bad request if timestamp format is not valid (400) """
+def test_delete_report(empty_devices_dir, add_devices, # pylint: disable=W0613
+ create_report_folder, testrun): # pylint: disable=W0613
+ """ Test for succesfully delete a report (200) """
# Load the device using load_json utility method
device = load_json("device_config.json", directory=DEVICE_1_PATH)
@@ -1045,41 +1043,56 @@ def test_delete_report_invalid_timestamp(empty_devices_dir, add_devices, # pylin
# Assign the device mac address
mac_addr = device["mac_addr"]
- # Assign the incorrect timestamp format
- invalid_timestamp = "2024-01-01 invalid"
+ # Assign the device name
+ device_name = f'{device["manufacturer"]} {device["model"]}'
- # Payload
- delete_data = {
- "mac_addr": mac_addr,
- "timestamp": invalid_timestamp
- }
+ # Construct report_name from mac_addr and timestamp
+ mac_no_colons = mac_addr.replace(":", "")
+ timestamp = get_timestamp(formatted=True)
+ report_name = f"{mac_no_colons}_{timestamp}"
# Send a DELETE request to remove the report
- r = requests.delete(f"{API}/report", data=json.dumps(delete_data), timeout=5)
+ r = requests.delete(f"{API}/report/{report_name}", timeout=5)
- # Check if status code is 400 (bad request)
- assert r.status_code == 400
+ # Check if status code is 200 (OK)
+ assert r.status_code == 200
# Parse the json response
response = r.json()
- # Check if "error" in response
- assert "error" in response
+ # Check if "success" in response
+ assert "success" in response
+
+ # Construct the 'reports' folder path
+ reports_folder = os.path.join(device_name, "reports")
+
+ # Check if reports folder has been deleted
+ assert not os.path.exists(reports_folder)
+
+@pytest.mark.parametrize("add_devices", [
+ ["device_1"]
+],indirect=True)
+def test_delete_report_no_payload(empty_devices_dir, add_devices, # pylint: disable=W0613
+ create_report_folder, testrun): # pylint: disable=W0613
+ """ Test delete report with empty report name (404) """
+
+ # Send a DELETE request with empty report name
+ r = requests.delete(f"{API}/report/", timeout=5)
+
+ # Check if status code is 404 (not found)
+ assert r.status_code == 404
- # Check if the correct error message returned
- assert "Incorrect timestamp format" in response["error"]
def test_delete_report_no_device(empty_devices_dir, testrun): # pylint: disable=W0613
""" Test delete report when device does not exist (404) """
- # Payload to be deleted for a non existing device
- delete_data = {
- "mac_addr": "00:1e:42:35:73:c4",
- "timestamp": get_timestamp()
- }
+ # Construct report_name for non-existing device
+ mac_no_colons = "001e423573c4"
+ timestamp = get_timestamp(formatted=True)
+ report_name = f"{mac_no_colons}_{timestamp}"
# Send the delete request to the endpoint
- r = requests.delete(f"{API}/report", data=json.dumps(delete_data), timeout=5)
+ r = requests.delete(f"{API}/report/{report_name}", timeout=5)
# Check if status is 404 (not found)
assert r.status_code == 404
@@ -1091,7 +1104,7 @@ def test_delete_report_no_device(empty_devices_dir, testrun): # pylint: disable=
assert "error" in response
# Check if the correct error message returned
- assert "Could not find device" in response["error"]
+ assert "Report not found" in response["error"]
@pytest.mark.parametrize("add_devices", [
["device_1"]
@@ -1105,16 +1118,13 @@ def test_delete_report_no_report(empty_devices_dir, add_devices, testrun): # pyl
# Assign the device mac address
mac_addr = device["mac_addr"]
- # Prepare the payload for the DELETE request
- delete_data = {
- "mac_addr": mac_addr,
- "timestamp": get_timestamp()
- }
+ # Construct report_name for non-existent report
+ mac_no_colons = mac_addr.replace(":", "")
+ # Use different timestamp than what exists
+ invalid_report_name = f"{mac_no_colons}_2020-01-01T00:00:00"
# Send the delete request to delete the report
- r = requests.delete(f"{API}/report",
- data=json.dumps(delete_data),
- timeout=5)
+ r = requests.delete(f"{API}/report/{invalid_report_name}", timeout=5)
# Check if status code is 404 (not found)
assert r.status_code == 404
@@ -1128,31 +1138,6 @@ def test_delete_report_no_report(empty_devices_dir, add_devices, testrun): # pyl
# Check if the correct error message is returned
assert "Report not found" in response["error"]
-@pytest.mark.parametrize("add_devices", [
- ["device_1"]
-],indirect=True)
-def test_get_report_success(empty_devices_dir, add_devices, # pylint: disable=W0613
- create_report_folder, testrun): # pylint: disable=W0613
- """Test for successfully get report when report exists (200)"""
-
- # Load the device using load_json utility method
- device = load_json("device_config.json", directory=DEVICE_1_PATH)
-
- # Assign the device name
- device_name = f'{device["manufacturer"]} {device["model"]}'
-
- # Assign the timestamp and change the format
- timestamp = get_timestamp(formatted=True)
-
- # Send the get request
- r = requests.get(f"{API}/report/{device_name}/{timestamp}", timeout=5)
-
- # Check if status code is 200 (ok)
- assert r.status_code == 200
-
- # Check if the response is a PDF
- assert r.headers["Content-Type"] == "application/pdf"
-
@pytest.mark.parametrize("add_devices", [
["device_1"]
],indirect=True)
@@ -1162,14 +1147,15 @@ def test_get_report_not_found(empty_devices_dir, add_devices, testrun): # pylint
# Load the device using load_json utility method
device = load_json("device_config.json", directory=DEVICE_1_PATH)
- # Assign the device name
- device_name = f'{device["manufacturer"]} {device["model"]}'
+ # Assign the device mac address
+ mac_addr = device["mac_addr"]
- # Assign the timestamp
- timestamp = get_timestamp()
+ # Construct report_name with non-existent timestamp
+ mac_no_colons = mac_addr.replace(":", "")
+ invalid_report_name = f"{mac_no_colons}_2020-01-01T00:00:00"
# Send the get request
- r = requests.get(f"{API}/report/{device_name}/{timestamp}", timeout=5)
+ r = requests.get(f"{API}/report/{invalid_report_name}", timeout=5)
# Check if status code is 404 (not found)
assert r.status_code == 404
@@ -1180,20 +1166,23 @@ def test_get_report_not_found(empty_devices_dir, add_devices, testrun): # pylint
# Check if "error" in response
assert "error" in response
+ # Check if the correct error message is returned
+ assert "Report not found from list" in response["error"]
+
+ # Check if "error" in response
+ assert "error" in response
+
# Check if the correct error message returned
- assert "Report could not be found" in response["error"]
+ assert "Report not found from list" in response["error"]
def test_get_report_device_not_found(empty_devices_dir, testrun): # pylint: disable=W0613
"""Test getting a report when the device is not found (404)"""
- # Assign device name
- device_name = "nonexistent_device"
-
# Assign the timestamp
timestamp = get_timestamp()
# Send the get request
- r = requests.get(f"{API}/report/{device_name}/{timestamp}", timeout=5)
+ r = requests.get(f"{API}/report/001e423573c4_{timestamp}", timeout=5)
# Check if is 404 (not found)
assert r.status_code == 404
@@ -1205,20 +1194,20 @@ def test_get_report_device_not_found(empty_devices_dir, testrun): # pylint: disa
assert "error" in response
# Check if the correct error message is returned
- assert "Device not found" in response["error"]
+ assert "Report not found from list" in response["error"]
def test_export_report_device_not_found(empty_devices_dir, create_report_folder, # pylint: disable=W0613
testrun): # pylint: disable=W0613
"""Test for export the report result when the device could not be found"""
- # Assign the non-existing device name
- device_name = "non existing device"
-
+ # Assign the non-existing device mac (no colons)
+ mac_no_colons = "001e423573c4"
# Assign the timestamp
- timestamp = get_timestamp()
+ timestamp = get_timestamp(formatted=True)
+ invalid_report_name = f"{mac_no_colons}_{timestamp}"
# Send the post request
- r = requests.post(f"{API}/export/{device_name}/{timestamp}", timeout=5)
+ r = requests.post(f"{API}/export/{invalid_report_name}", timeout=5)
# Check if is 404 (not found)
assert r.status_code == 404
@@ -1229,8 +1218,8 @@ def test_export_report_device_not_found(empty_devices_dir, create_report_folder,
# Check if "error" in response
assert "error" in response
- # Check if the correct error message returned
- assert "A device with that name could not be found" in response["error"]
+ # Check if the correct error message is returned
+ assert "Report not found from list" in response["error"]
@pytest.mark.parametrize("add_devices", [
["device_1"]
@@ -1242,17 +1231,19 @@ def test_export_report_profile_not_found(empty_devices_dir, add_devices, # pylin
# Load the device using load_json utility method
device = load_json("device_config.json", directory=DEVICE_1_PATH)
- # Assign the device name
- device_name = f'{device["manufacturer"]} {device["model"]}'
+ # Assign the device mac address
+ mac_addr = device["mac_addr"]
- # Assign the timestamp
- timestamp = get_timestamp()
+ # Construct report_name
+ mac_no_colons = mac_addr.replace(":", "")
+ timestamp = get_timestamp(formatted=True)
+ report_name = f"{mac_no_colons}_{timestamp}"
# Add a non existing profile into the payload
payload = {"profile": "non_existent_profile"}
# Send the post request
- r = requests.post(f"{API}/export/{device_name}/{timestamp}",
+ r = requests.post(f"{API}/export/{report_name}",
json=payload,
timeout=5)
@@ -1277,14 +1268,15 @@ def test_export_report_not_found(empty_devices_dir, add_devices, testrun): # pyl
# Load the device using load_json utility method
device = load_json("device_config.json", directory=DEVICE_1_PATH)
- # Assign the device name
- device_name = f'{device["manufacturer"]} {device["model"]}'
+ # Assign the device mac address
+ mac_addr = device["mac_addr"]
- # Assign the timestamp
- timestamp = get_timestamp()
+ # Construct report_name
+ mac_no_colons = mac_addr.replace(":", "")
+ invalid_report_name = f"{mac_no_colons}_2020-01-01T00:00:00"
# Send the post request to trigger the zipping process
- r = requests.post(f"{API}/export/{device_name}/{timestamp}", timeout=10)
+ r = requests.post(f"{API}/export/{invalid_report_name}", timeout=10)
# Check if status code is 404 (Not Found)
assert r.status_code == 404
@@ -1296,63 +1288,7 @@ def test_export_report_not_found(empty_devices_dir, add_devices, testrun): # pyl
assert "error" in response
# Check if the correct error message is returned
- assert "Report could not be found" in response["error"]
-
-@pytest.mark.parametrize("add_devices, add_profiles", [
- (["device_1"], ["valid_profile.json"])
-], indirect=True)
-def test_export_report_with_profile(empty_devices_dir, add_devices, # pylint: disable=W0613
- empty_profiles_dir, add_profiles, # pylint: disable=W0613
- create_report_folder, testrun): # pylint: disable=W0613
- """Test export results with existing profile when report exists (200)"""
-
- # Load the profile using load_json utility method
- profile = load_json("valid_profile.json", directory=PROFILES_PATH)
-
- # Load the device using load_json utility method
- device = load_json("device_config.json", directory=DEVICE_1_PATH)
-
- # Assign the device name
- device_name = f'{device["manufacturer"]} {device["model"]}'
-
- # Assign the timestamp and change the format
- timestamp = get_timestamp(formatted=True)
-
- # Send the post request
- r = requests.post(f"{API}/export/{device_name}/{timestamp}",
- json=profile,
- timeout=5)
-
- # Check if status code is 200 (OK)
- assert r.status_code == 200
-
- # Check if the response is a zip file
- assert r.headers["Content-Type"] == "application/zip"
-
-@pytest.mark.parametrize("add_devices", [
- ["device_1"]
-],indirect=True)
-def test_export_results_with_no_profile(empty_devices_dir, add_devices, # pylint: disable=W0613
- create_report_folder, testrun): # pylint: disable=W0613
- """Test export results with no profile when report exists (200)"""
-
- # Load the device using load_json utility method
- device = load_json("device_config.json", directory=DEVICE_1_PATH)
-
- # Assign the device name
- device_name = f'{device["manufacturer"]} {device["model"]}'
-
- # Assign the timestamp and change the format
- timestamp = get_timestamp(formatted=True)
-
- # Send the post request
- r = requests.post(f"{API}/export/{device_name}/{timestamp}", timeout=5)
-
- # Check if status code is 200 (OK)
- assert r.status_code == 200
-
- # Check if the response is a zip file
- assert r.headers["Content-Type"] == "application/zip"
+ assert "Report not found from list" in response["error"]
# Tests for device endpoints
@pytest.fixture()
diff --git a/testing/unit/protocol/protocol_module_test.py b/testing/unit/protocol/protocol_module_test.py
index 9d474ab91..063fd9401 100644
--- a/testing/unit/protocol/protocol_module_test.py
+++ b/testing/unit/protocol/protocol_module_test.py
@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""Module run all the DNS related unit tests"""
-from protocol_bacnet import BACnet
+from protocol_bacnet import BACnet, BACnetDevice
import unittest
import os
import sys
@@ -52,8 +52,10 @@ def setUpClass(cls):
# Test the BACNet traffic for a matching Object ID and HW address
def bacnet_protocol_traffic_test(self):
LOGGER.info(f'Running { inspect.currentframe().f_code.co_name}')
- result = BACNET.validate_bacnet_source(object_id='1761001',
- device_hw_addr=HW_ADDR)
+ result = BACNET.validate_bacnet_source(
+ BACnetDevice(device_id='1761001', ip='10.10.10.14'),
+ device_hw_addr=HW_ADDR
+ )
LOGGER.info(f'Test Result: {result}')
self.assertEqual(result, True)
@@ -61,8 +63,10 @@ def bacnet_protocol_traffic_test(self):
# do not match
def bacnet_protocol_traffic_fail_test(self):
LOGGER.info(f'Running { inspect.currentframe().f_code.co_name}')
- result = BACNET.validate_bacnet_source(object_id='1761001',
- device_hw_addr=HW_ADDR_BAD)
+ result = BACNET.validate_bacnet_source(
+ BACnetDevice(device_id='1761001', ip='10.10.10.14'),
+ device_hw_addr=HW_ADDR_BAD
+ )
LOGGER.info(f'Test Result: {result}')
self.assertEqual(result, False)
@@ -71,7 +75,7 @@ def bacnet_protocol_traffic_fail_test(self):
def bacnet_protocol_validate_device_test(self):
LOGGER.info(f'Running { inspect.currentframe().f_code.co_name}')
# Load bacnet devices to simulate a discovery
- bac_dev = ('TestDevice', 'Testrun', '10.10.10.14', 1761001)
+ bac_dev = BACnetDevice(device_id='1761001', ip='10.10.10.14')
BACNET.devices = [bac_dev]
result = BACNET.validate_device()
LOGGER.info(f'Test Result: {result}')
@@ -82,7 +86,7 @@ def bacnet_protocol_validate_device_test(self):
def bacnet_protocol_validate_device_fail_test(self):
LOGGER.info(f'Running { inspect.currentframe().f_code.co_name}')
# Load bacnet devices to simulate a discovery
- bac_dev = ('TestDevice', 'Testrun', '10.10.10.14', 1761001)
+ bac_dev = BACnetDevice(device_id='1761001', ip='10.10.10.14')
BACNET.devices = [bac_dev]
# Change the MAC address to a different device than expected
BACNET.device_hw_addr = HW_ADDR_BAD
diff --git a/testing/unit/tls/tls_module_test.py b/testing/unit/tls/tls_module_test.py
index f1e47f55c..a0364b4ce 100644
--- a/testing/unit/tls/tls_module_test.py
+++ b/testing/unit/tls/tls_module_test.py
@@ -80,7 +80,7 @@ def security_tls_v1_2_server_no_ip_test(self):
self.assertEqual(result, 'Error')
self.assertEqual(description, 'Could not resolve device IP address')
- self.assertEqual(details, ['Could not resolve device IP address.'])
+ self.assertEqual(details, [])
def security_tls_v1_2_server_no_scan_results_test(self):
"""Tests _security_tls_v1_2_server when scan finds no HTTP/HTTPS ports"""
@@ -607,9 +607,9 @@ def security_tls_client_allowed_protocols_test(self):
# Run the client test
test_results = TLS_UTIL.validate_tls_client(client_mac='e4:5f:01:5f:92:9c',
- tls_version='1.2',
+ tls_version='1.3',
capture_files=[capture_file])
- print(str(test_results))
+ print('results', str(test_results))
self.assertTrue(test_results[0])
def outbound_connections_test(self):
|