#8 Promenjen unos i snimanje kontrola
This commit is contained in:
@@ -36,9 +36,8 @@ class RiskAdmin(admin.ModelAdmin):
|
|||||||
list_display = ['risk_id','risk_name','category']
|
list_display = ['risk_id','risk_name','category']
|
||||||
|
|
||||||
class ControlAdmin(admin.ModelAdmin):
|
class ControlAdmin(admin.ModelAdmin):
|
||||||
list_display = ('risk', 'safeguard', 'weight')
|
list_display = ('id', 'name')
|
||||||
search_fields = ('risk__risk_name', 'safeguard')
|
|
||||||
list_filter = ('risk', 'weight')
|
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(Document, DocumentAdmin)
|
admin.site.register(Document, DocumentAdmin)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from django.core.management.base import BaseCommand
|
|||||||
from backend.core.models import Control
|
from backend.core.models import Control
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = "Export controls to CSV file"
|
help = "Export controls (only safeguard) to CSV file"
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
def add_arguments(self, parser):
|
||||||
parser.add_argument("csv_file", type=str, help="CSV file to export controls to")
|
parser.add_argument("csv_file", type=str, help="CSV file to export controls to")
|
||||||
@@ -12,22 +12,14 @@ class Command(BaseCommand):
|
|||||||
csv_file_path = options["csv_file"]
|
csv_file_path = options["csv_file"]
|
||||||
|
|
||||||
with open(csv_file_path, mode="w", newline="", encoding="utf-8") as csv_file:
|
with open(csv_file_path, mode="w", newline="", encoding="utf-8") as csv_file:
|
||||||
fieldnames = [
|
fieldnames = ["CIS v8.1 Safeguards (Sub-Controls)"]
|
||||||
"Risk #",
|
|
||||||
"Risk Description",
|
|
||||||
"CIS v8.1 Safeguards (Sub-Controls)",
|
|
||||||
"Weight (0-10)",
|
|
||||||
]
|
|
||||||
writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
|
writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
|
||||||
|
|
||||||
writer.writeheader()
|
writer.writeheader()
|
||||||
|
|
||||||
for control in Control.objects.select_related("risk").all():
|
for control in Control.objects.all():
|
||||||
writer.writerow({
|
writer.writerow({
|
||||||
"Risk #": control.risk.risk_id,
|
"CIS v8.1 Safeguards (Sub-Controls)": control.name,
|
||||||
"Risk Description": control.risk.risk_name,
|
|
||||||
"CIS v8.1 Safeguards (Sub-Controls)": control.safeguard,
|
|
||||||
"Weight (0-10)": control.weight,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
self.stdout.write(self.style.SUCCESS(f"Controls exported successfully to {csv_file_path}"))
|
self.stdout.write(self.style.SUCCESS(f"Controls exported successfully to {csv_file_path}"))
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import csv
|
import csv
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from backend.core.models import Risk, Control
|
from backend.core.models import Control
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = "Import controls from CSV file"
|
help = "Import controls from CSV file (only safeguard) and store as name"
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
def add_arguments(self, parser):
|
||||||
parser.add_argument("csv_file", type=str, help="CSV file to import controls from")
|
parser.add_argument("csv_file", type=str, help="CSV file to import controls from")
|
||||||
@@ -15,24 +15,11 @@ class Command(BaseCommand):
|
|||||||
reader = csv.DictReader(csv_file)
|
reader = csv.DictReader(csv_file)
|
||||||
|
|
||||||
for row in reader:
|
for row in reader:
|
||||||
risk_id = int(row["Risk #"].strip())
|
|
||||||
risk_desc = row["Risk Description"].strip()
|
|
||||||
safeguard = row["CIS v8.1 Safeguards (Sub-Controls)"].strip()
|
safeguard = row["CIS v8.1 Safeguards (Sub-Controls)"].strip()
|
||||||
|
|
||||||
weight = int(row["Weight (0-10)"].strip()) if row["Weight (0-10)"].strip().isdigit() else 0
|
|
||||||
|
|
||||||
risk, created = Risk.objects.get_or_create(
|
|
||||||
risk_id=risk_id,
|
|
||||||
defaults={"risk_name": risk_desc},
|
|
||||||
)
|
|
||||||
|
|
||||||
Control.objects.update_or_create(
|
Control.objects.update_or_create(
|
||||||
risk=risk,
|
name=safeguard,
|
||||||
safeguard=safeguard,
|
defaults={"name": safeguard},
|
||||||
defaults={
|
|
||||||
"description": risk_desc,
|
|
||||||
"weight": weight,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.stdout.write(self.style.SUCCESS("Controls imported successfully!"))
|
self.stdout.write(self.style.SUCCESS("Safeguards imported successfully!"))
|
||||||
|
|||||||
@@ -2,25 +2,12 @@ import os
|
|||||||
import csv
|
import csv
|
||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from backend.core.models import Risk, Control
|
from backend.core.models import Control
|
||||||
|
|
||||||
class ExportControlsCommandTest(TestCase):
|
class ExportControlsCommandTest(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.risk1 = Risk.objects.create(risk_id=1, risk_name="Test Risk 1")
|
Control.objects.create(name="Test Safeguard 1")
|
||||||
self.risk2 = Risk.objects.create(risk_id=2, risk_name="Test Risk 2")
|
Control.objects.create(name="Test Safeguard 2")
|
||||||
|
|
||||||
self.control1 = Control.objects.create(
|
|
||||||
risk=self.risk1,
|
|
||||||
safeguard="Test Safeguard 1",
|
|
||||||
description="Description 1",
|
|
||||||
weight=5
|
|
||||||
)
|
|
||||||
self.control2 = Control.objects.create(
|
|
||||||
risk=self.risk2,
|
|
||||||
safeguard="Test Safeguard 2",
|
|
||||||
description="Description 2",
|
|
||||||
weight=10
|
|
||||||
)
|
|
||||||
|
|
||||||
self.csv_file_path = 'test_export_controls.csv'
|
self.csv_file_path = 'test_export_controls.csv'
|
||||||
|
|
||||||
@@ -28,7 +15,7 @@ class ExportControlsCommandTest(TestCase):
|
|||||||
if os.path.exists(self.csv_file_path):
|
if os.path.exists(self.csv_file_path):
|
||||||
os.remove(self.csv_file_path)
|
os.remove(self.csv_file_path)
|
||||||
|
|
||||||
def test_export_controls_success(self):
|
def test_export_controls(self):
|
||||||
call_command('export_controls', self.csv_file_path)
|
call_command('export_controls', self.csv_file_path)
|
||||||
|
|
||||||
self.assertTrue(os.path.exists(self.csv_file_path))
|
self.assertTrue(os.path.exists(self.csv_file_path))
|
||||||
@@ -37,27 +24,7 @@ class ExportControlsCommandTest(TestCase):
|
|||||||
reader = csv.DictReader(csv_file)
|
reader = csv.DictReader(csv_file)
|
||||||
rows = list(reader)
|
rows = list(reader)
|
||||||
|
|
||||||
self.assertEqual(len(rows), 2)
|
self.assertEqual(len(rows), 2)
|
||||||
|
|
||||||
self.assertEqual(rows[0]["Risk #"], str(self.risk1.risk_id))
|
self.assertEqual(rows[0]["CIS v8.1 Safeguards (Sub-Controls)"], "Test Safeguard 1")
|
||||||
self.assertEqual(rows[0]["Risk Description"], self.risk1.risk_name)
|
self.assertEqual(rows[1]["CIS v8.1 Safeguards (Sub-Controls)"], "Test Safeguard 2")
|
||||||
self.assertEqual(rows[0]["CIS v8.1 Safeguards (Sub-Controls)"], self.control1.safeguard)
|
|
||||||
self.assertEqual(rows[0]["Weight (0-10)"], str(self.control1.weight))
|
|
||||||
|
|
||||||
self.assertEqual(rows[1]["Risk #"], str(self.risk2.risk_id))
|
|
||||||
self.assertEqual(rows[1]["Risk Description"], self.risk2.risk_name)
|
|
||||||
self.assertEqual(rows[1]["CIS v8.1 Safeguards (Sub-Controls)"], self.control2.safeguard)
|
|
||||||
self.assertEqual(rows[1]["Weight (0-10)"], str(self.control2.weight))
|
|
||||||
|
|
||||||
def test_export_controls_empty(self):
|
|
||||||
Control.objects.all().delete()
|
|
||||||
|
|
||||||
call_command('export_controls', self.csv_file_path)
|
|
||||||
|
|
||||||
self.assertTrue(os.path.exists(self.csv_file_path))
|
|
||||||
|
|
||||||
with open(self.csv_file_path, mode='r', encoding='utf-8') as csv_file:
|
|
||||||
reader = csv.DictReader(csv_file)
|
|
||||||
rows = list(reader)
|
|
||||||
|
|
||||||
self.assertEqual(len(rows), 0)
|
|
||||||
@@ -1,56 +1,40 @@
|
|||||||
import os
|
import os
|
||||||
import csv
|
import csv
|
||||||
import tempfile
|
|
||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from backend.core.models import Risk, Control
|
from backend.core.models import Control
|
||||||
|
|
||||||
class ImportControlsCommandTest(TestCase):
|
class ImportControlsCommandTest(TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
"""Create a temporary CSV file with test control data"""
|
self.csv_file_path = 'test_import_controls.csv'
|
||||||
self.temp_csv = tempfile.NamedTemporaryFile(mode="w", delete=False, newline='', encoding='utf-8')
|
with open(self.csv_file_path, mode='w', encoding='utf-8') as csv_file:
|
||||||
fieldnames = [
|
writer = csv.DictWriter(csv_file, fieldnames=["CIS v8.1 Safeguards (Sub-Controls)"])
|
||||||
"Risk #", "Risk Description", "CIS v8.1 Safeguards (Sub-Controls)", "Weight (0-10)"
|
writer.writeheader()
|
||||||
]
|
writer.writerow({"CIS v8.1 Safeguards (Sub-Controls)": "Test Safeguard 1"})
|
||||||
|
writer.writerow({"CIS v8.1 Safeguards (Sub-Controls)": "Test Safeguard 2"})
|
||||||
writer = csv.DictWriter(self.temp_csv, fieldnames=fieldnames)
|
|
||||||
writer.writeheader()
|
|
||||||
writer.writerow({
|
|
||||||
"Risk #": "1",
|
|
||||||
"Risk Description": "Test Risk 1",
|
|
||||||
"CIS v8.1 Safeguards (Sub-Controls)": "Test Safeguard 1",
|
|
||||||
"Weight (0-10)": "5"
|
|
||||||
})
|
|
||||||
writer.writerow({
|
|
||||||
"Risk #": "2",
|
|
||||||
"Risk Description": "Test Risk 2",
|
|
||||||
"CIS v8.1 Safeguards (Sub-Controls)": "Test Safeguard 2",
|
|
||||||
"Weight (0-10)": "10"
|
|
||||||
})
|
|
||||||
self.temp_csv.close()
|
|
||||||
|
|
||||||
def test_import_controls_command(self):
|
|
||||||
"""Test importing controls from a CSV file"""
|
|
||||||
self.assertEqual(Control.objects.count(), 0)
|
|
||||||
|
|
||||||
self.assertEqual(Risk.objects.count(), 0)
|
|
||||||
|
|
||||||
Risk.objects.create(risk_id=1, risk_name="Test Risk 1")
|
|
||||||
Risk.objects.create(risk_id=2, risk_name="Test Risk 2")
|
|
||||||
|
|
||||||
call_command('import_controls', self.temp_csv.name)
|
|
||||||
|
|
||||||
self.assertEqual(Control.objects.count(), 2)
|
|
||||||
|
|
||||||
control1 = Control.objects.get(risk__risk_id=1)
|
|
||||||
self.assertEqual(control1.safeguard, "Test Safeguard 1")
|
|
||||||
self.assertEqual(control1.weight, 5)
|
|
||||||
|
|
||||||
control2 = Control.objects.get(risk__risk_id=2)
|
|
||||||
self.assertEqual(control2.safeguard, "Test Safeguard 2")
|
|
||||||
self.assertEqual(control2.weight, 10)
|
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
"""Remove temporary CSV file after test"""
|
if os.path.exists(self.csv_file_path):
|
||||||
os.remove(self.temp_csv.name)
|
os.remove(self.csv_file_path)
|
||||||
|
|
||||||
|
def test_import_controls(self):
|
||||||
|
self.assertEqual(Control.objects.count(), 0)
|
||||||
|
|
||||||
|
call_command('import_controls', self.csv_file_path)
|
||||||
|
|
||||||
|
self.assertEqual(Control.objects.count(), 2)
|
||||||
|
|
||||||
|
safeguards = Control.objects.values_list('name', flat=True)
|
||||||
|
self.assertIn("Test Safeguard 1", safeguards)
|
||||||
|
self.assertIn("Test Safeguard 2", safeguards)
|
||||||
|
|
||||||
|
def test_import_controls_update(self):
|
||||||
|
Control.objects.create(name="Test Safeguard 1")
|
||||||
|
|
||||||
|
call_command('import_controls', self.csv_file_path)
|
||||||
|
|
||||||
|
self.assertEqual(Control.objects.count(), 2)
|
||||||
|
|
||||||
|
safeguards = Control.objects.values_list('name', flat=True)
|
||||||
|
self.assertIn("Test Safeguard 1", safeguards)
|
||||||
|
self.assertIn("Test Safeguard 2", safeguards)
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# Generated by Django 5.1.3 on 2025-02-13 17:09
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0006_documenttemplate'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='control',
|
||||||
|
old_name='safeguard',
|
||||||
|
new_name='name',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='control',
|
||||||
|
name='description',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='control',
|
||||||
|
name='risk',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='control',
|
||||||
|
name='weight',
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='control',
|
||||||
|
name='id',
|
||||||
|
field=models.AutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -153,10 +153,8 @@ class Risk(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
class Control(models.Model):
|
class Control(models.Model):
|
||||||
risk = models.ForeignKey(Risk, on_delete=models.CASCADE, related_name="controls")
|
id = models.AutoField(primary_key=True)
|
||||||
description = models.TextField()
|
name = models.CharField(max_length=255)
|
||||||
safeguard = models.CharField(max_length=255)
|
|
||||||
weight = models.IntegerField()
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.safeguard} ({self.weight})"
|
return f"{self.id} ({self.name})"
|
||||||
24
fix-controls.py
Normal file
24
fix-controls.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import csv
|
||||||
|
|
||||||
|
input_file = 'controls.csv'
|
||||||
|
output_file = 'controls-fix.csv'
|
||||||
|
|
||||||
|
with open(input_file, mode='r', newline='', encoding='utf-8') as infile:
|
||||||
|
reader = csv.DictReader(infile)
|
||||||
|
|
||||||
|
safeguards = set()
|
||||||
|
|
||||||
|
for row in reader:
|
||||||
|
safeguard = row['CIS v8.1 Safeguards (Sub-Controls)']
|
||||||
|
safeguards.add(safeguard)
|
||||||
|
|
||||||
|
with open(output_file, mode='w', newline='', encoding='utf-8') as outfile:
|
||||||
|
fieldnames = ['name']
|
||||||
|
writer = csv.DictWriter(outfile, fieldnames=fieldnames)
|
||||||
|
|
||||||
|
writer.writeheader()
|
||||||
|
|
||||||
|
for safeguard in safeguards:
|
||||||
|
writer.writerow({'name': safeguard})
|
||||||
|
|
||||||
|
print(f"Conversion completed. The new CSV file is saved as '{output_file}'.")
|
||||||
90
test_cs.csv
Normal file
90
test_cs.csv
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
CIS v8.1 Safeguards (Sub-Controls)
|
||||||
|
3.1 - Establish and Maintain Inventory of Enterprise Assets
|
||||||
|
3.3 - Manage Assets
|
||||||
|
5.1 - Establish and Maintain a Secure Configuration Process
|
||||||
|
5.3 - Securely Configure Enterprise Assets and Software
|
||||||
|
8.1 - Establish and Maintain a Vulnerability Management Process
|
||||||
|
9.2 - Deploy and Maintain Anti-Malware Software
|
||||||
|
10.8 - Perform and Test Data Backups
|
||||||
|
15.1 - Develop an Incident Response Plan
|
||||||
|
3.4 - Manage Sensitive Assets
|
||||||
|
4.1 - Establish and Maintain a Secure Access Control Policy and Procedures
|
||||||
|
4.2 - Implement and Manage Multi-Factor Authentication for Enterprise Accounts
|
||||||
|
6.3 - Implement and Manage Network Segmentation
|
||||||
|
7.1 - Establish and Maintain a Data Management Process
|
||||||
|
7.2 - Implement and Enforce Data Retention
|
||||||
|
7.3 - Implement Data Loss Prevention (DLP)
|
||||||
|
12.5 - Enforce Encryption of Data-at-Rest
|
||||||
|
12.6 - Enforce Encryption of Data-in-Transit
|
||||||
|
4.3 - Manage Privileged Access
|
||||||
|
4.4 - Manage Service Accounts
|
||||||
|
4.6 - Manage External Accounts
|
||||||
|
14.5 - Establish and Maintain an Audit Log Review and Analysis Process
|
||||||
|
16.1 - Conduct Security Awareness and Skills Training
|
||||||
|
3.6 - Establish and Maintain an Inventory of Non-Enterprise Assets
|
||||||
|
13.1 - Establish and Maintain a Security Awareness Program
|
||||||
|
18.1 - Establish and Maintain a Penetration Testing Program
|
||||||
|
19.1 - Establish and Maintain an Incident Response Plan
|
||||||
|
20.1 - Establish and Maintain a Business Continuity Plan
|
||||||
|
16.2 - Train Workforce Members on Social Engineering Attacks
|
||||||
|
19.8 - Perform Post-Incident Reviews
|
||||||
|
1.1 - Establish and Maintain Enterprise Governance
|
||||||
|
1.2 - Establish and Maintain Enterprise Security Policies
|
||||||
|
1.3 - Establish and Maintain Enterprise Agreements
|
||||||
|
2.1 - Establish and Maintain an Inventory of Authorized Software
|
||||||
|
10.9 - Perform Off-Site Backups
|
||||||
|
10.10 - Securely Store Backups
|
||||||
|
11.1 - Implement and Manage Email Protections
|
||||||
|
17.1 - Implement Physical Access Controls
|
||||||
|
17.2 - Monitor Physical Environment
|
||||||
|
6.1 - Establish and Maintain a Baseline Configuration of Network Devices
|
||||||
|
6.4 - Implement and Manage Network Infrastructure Device Hardening
|
||||||
|
6.5 - Implement and Manage Distributed Denial of Service (DDoS) Mitigation Techniques
|
||||||
|
14.1 - Establish and Maintain a Security Logging and Monitoring Process
|
||||||
|
8.2 - Remediate Vulnerabilities Based on Risk
|
||||||
|
8.3 - Verify Application of Security Patches
|
||||||
|
3.2 - Utilize an Automated Asset Discovery Tool
|
||||||
|
13.5 - Manage Supplier Access
|
||||||
|
13.6 - Monitor Supplier Security
|
||||||
|
3.5 - Manage Enterprise Assets Connected to the Enterprise Network Remotely
|
||||||
|
4.5 - Manage Mobile Devices
|
||||||
|
5.4 - Securely Configure Cloud Infrastructure
|
||||||
|
5.5 - Securely Configure Cloud Workloads
|
||||||
|
6.2 - Establish and Maintain a Baseline Configuration of Endpoints
|
||||||
|
4.7 - Enforce Account Password Requirements
|
||||||
|
4.8 - Enforce Multi-Factor Authentication for All Users
|
||||||
|
16.4 - Establish and Maintain a Role-Based Security Training Program
|
||||||
|
16.5 - Conduct Skills Gap Assessments
|
||||||
|
17.3 - Plan and Implement Environmental Protections
|
||||||
|
5.6 - Securely Configure Industrial Control Systems (ICS)
|
||||||
|
6.6 - Implement and Manage Network Segmentation for ICS
|
||||||
|
1.5 - Conduct Periodic Security Risk Assessments
|
||||||
|
14.7 - Conduct Security Controls Testing and Validation
|
||||||
|
15.4 - Establish and Maintain a Security Architecture
|
||||||
|
1.4 - Establish and Maintain a Threat Intelligence Program
|
||||||
|
2.2 - Utilize Standard Security Configurations for Enterprise Software and Hardware
|
||||||
|
8.4 - Perform Application Security Testing
|
||||||
|
12.1 - Establish and Maintain a Software Development Life Cycle (SDLC)
|
||||||
|
9.1 - Establish and Maintain a Software Allow List
|
||||||
|
11.2 - Implement and Manage Web Browser Protections
|
||||||
|
6.7 - Implement and Manage Domain Name System (DNS) Security
|
||||||
|
12.7 - Plan and Implement Cryptographic Key Management
|
||||||
|
7.4 - Securely Dispose of Assets
|
||||||
|
12.2 - Secure Software via Secure Coding Practices
|
||||||
|
6.8 - Secure Wireless Access Points
|
||||||
|
4.9 - Manage Access to Enterprise Applications
|
||||||
|
11.3 - Implement and Manage Endpoint Protections
|
||||||
|
"12.6 - Enforce Encryption of Data-in-Transit
|
||||||
|
66,Insufficient Data Encryption"""
|
||||||
|
14.2 - Integrate Threat Intelligence into Security Monitoring
|
||||||
|
14.3 - Establish and Maintain Alerting and Escalation Processes
|
||||||
|
19.2 - Establish and Maintain an Incident Response Team
|
||||||
|
19.3 - Develop and Conduct Incident Response Exercises
|
||||||
|
5.2 - Implement and Manage a Change Management Process
|
||||||
|
5.7 - Securely Configure Containers
|
||||||
|
12.3 - Manage Credentials
|
||||||
|
16.3 - Establish and Maintain a Security Skills Development Program
|
||||||
|
9.3 - Implement and Manage Endpoint Detection and Response (EDR)
|
||||||
|
13.3 - Implement and Manage Secure Software Supply Chain Practices
|
||||||
|
12.4 - Implement and Manage Security for Software Applications
|
||||||
|
13.4 - Implement and Manage Secure Hardware Supply Chain Practices
|
||||||
|
Reference in New Issue
Block a user