Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 10 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,9 @@ Frontend utvikles med React og TypeScript og settes opp med Vite. Frontend er an
- brukerinteraksjon
I den nåværende prototypen brukes hovedsakelig mock-data og statiske eksempeldata.
### Backend
Backend skal utvikles med Node.js og TypeScript og tilby et GraphQL API som frontend kan bruke for å hente og endre data.
Backend er foreløpig ikke implementert.
Backend er utviklet med Node.js og TypeScript og tilbyr et GraphQL API. Software-katalogen og detaljsidene henter MITRE ATT&CK-data fra MySQL-databasen.
### Database
Det er planlagt å bruke MySQL som database. MITRE ATT&CK-datasettet skal importeres til databasen ved hjelp av et import-script som transformerer dataene fra det opprinnelige JSON-formatet til databaseskjemaet vårt. Database og backend skal senere kjøres på VMen.
Prosjektet bruker MySQL. MITRE ATT&CK-data importeres fra JSON-datasettet med import-scriptet i `database/import-mitre.ts`. Den lokale databasen må være satt opp og dataene importert før databasefunksjonene kan brukes. Se [lokal databaseoppsett](./docs/local-database-setup.md).
## Nåværende status og prototype
Det som er implementert frem til nå:
- Oppsett av utviklingsmiljø for frontend, backend og database
Expand All @@ -92,41 +91,16 @@ KI har blitt brukt til veiledning i store deler av prosjektet. Ofte har ideer om

## Kjøre prosjektet lokalt

## 1.Dependencies

Installer dependencies, fra Rooten:

```bash
pnpm install
```

## 2.Database

**Følg guiden : `docs/local-database-setup.md`**

`Resultat`: Kjørende CyberDB database lokalt på maskinen

## 3.Backend

Opprett en ny `Terminal 1`:

```bash
cd backend
pnpm run dev
```sh
pnpm --filter backend dev
```
`Resultat`: Kjørende Backend med Apollo GraphQL Server kjører på http://localhost:3001/.

## 4.Frontend

Opprett en ny `Terminal 2`:

```bash
cd frontend
pnpm run dev
```

`Resultat`: Kjørende Frontend på http://localhost:5173/project2 eller noe sånt.



Sørg for at MySQL kjører, og at databaseskjemaet og MITRE-dataene er satt opp
som beskrevet i [lokal databaseoppsett](./docs/local-database-setup.md).

Åpne URL-en Vite skriver ut i frontend-terminalen (vanligvis
`http://localhost:5173`). Frontenden bruker GraphQL API-et på
`http://localhost:3001`. Den lokale frontend-ruten er `/`, mens produksjonsruten
er satt opp for publisering under `/project2`.
3 changes: 2 additions & 1 deletion backend/src/graphql/resolvers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@

import { threatActorResolvers } from "./threatActorResolver.js";
import { techniqueResolvers } from "./techniqueResolver.js";

import { softwareResolvers } from "./softwareResolver.js";


export const resolvers = {
Query: {
...threatActorResolvers.Query,
...techniqueResolvers.Query,
...softwareResolvers.Query,
},
};
24 changes: 12 additions & 12 deletions backend/src/graphql/resolvers/softwareResolver.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/**
* Resolver-laget håndterer GraphQL-spørringer fra klienten.
*
* Resolverne kobler GraphQL-feltene til riktig funksjon i
* repository-laget og sender resultatet tilbake til klienten.
*
* Database-logikk skal ikke ligge direkte i resolverne.
* Den håndteres av repositories.
*
*
* GraphQL -> Resolver -> Repository -> Drizzle -> MySQL
*/
import {
getAllSoftware,
getSoftwareByMitreId,
} from "../../repositories/softwareRepository.js";

export const softwareResolvers = {
Query: {
software: async () => getAllSoftware(),
softwareByMitreId: async (_parent: unknown, args: { mitreId: string }) =>
getSoftwareByMitreId(args.mitreId),
},
};
63 changes: 7 additions & 56 deletions backend/src/graphql/typeDefs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,75 +19,26 @@ export const typeDefs = `#graphql
motivation: String!
}
type ThreatActorGridInfo {
items: [ThreatActorGrid!]!
totalCount: Int!
}
type AssociatedItem {
mitreId: String!
name: String!
}
type AssociatedItemsPage {
items: [AssociatedItem!]!
totalCount: Int!
}
type ThreatActorDetailed {
threatId: String!
type Software {
id: String!
mitreId: String!
name: String!
description: String!
threatEntryCreated: String!
country: String!
motivation: String!
region: String!
firstSeen: String!
lastSeen: String!
status: String!
dangerLevel: String!
xMitreDeprecated: Boolean!
type: String!
aliases: [String!]!
sectors: [String!]!
}
type Technique {
mitreId: String!
name: String!
description: String!
platforms: [String!]!
associatedTechniques: [RelatedSoftwareEntity!]!
usedByThreatActors: [RelatedSoftwareEntity!]!
}
# Operasjoner frontend kan spørre API-et om
type Query {
######################################## ThreatActors vvvv
# Henter alle threat actors som trengs i grid
allThreatActorsGrid(limit: Int!, offset: Int!): ThreatActorGridInfo!
# Henter alt nødvending info for ThreatActorDetailed i frontend
getThreatActorByMitreId(mitreId: String!): ThreatActorDetailed
getThreatActorTechniques(mitreId: String!, limit: Int!, offset: Int!): AssociatedItemsPage!
getThreatActorSoftwares(mitreId: String!, limit: Int!, offset: Int!): AssociatedItemsPage!
######################################## Techinques vvvv
# Henter alle Techniques
allTechniques: [Technique!]!
# Henter én Technique basert på MITRE-ID
techniqueByMitreId(mitreId: String!): Technique
######################################## Software vvvv
######################################## Tactics vvvv
}
`;
150 changes: 134 additions & 16 deletions backend/src/repositories/softwareRepository.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,134 @@
/**
* Repository-laget samler databaseoperasjoner relatert til Software.
*
* Hensikten er å skille database-logikken fra resten av backenden:
* resolvers skal ikke skrive Drizzle-spørringer direkte, men heller
* kalle funksjoner fra dette repositoryet.
*
* Dermed får vi en tydelig ansvarsfordeling:
*
* GraphQL resolver -> Repository -> Drizzle -> MySQL
*
* Dette gjør koden lettere å lese, teste og endre senere.
*
*
* !!!ALTSÅ HER SKRIVER VI KODEN SOM FAKTISK HENTER UT DATA FRA DB VIA DRIZZLE!!!!
*/
import { eq, inArray } from "drizzle-orm";

import { db } from "../db/connection.js";
import {
software,
softwareAlias,
softwarePlatform,
technique,
techniqueSoftware,
threatActor,
threatActorSoftware,
} from "../db/schema.js";

async function getSoftwareMetadata(softwareIds: string[]) {
if (softwareIds.length === 0) {
return {
aliases: [],
platforms: [],
associatedTechniques: [],
usedByThreatActors: [],
};
}

const [aliases, platforms, associatedTechniques, usedByThreatActors] =
await Promise.all([
db
.select({
softwareId: softwareAlias.software_id,
alias: softwareAlias.alias,
})
.from(softwareAlias)
.where(inArray(softwareAlias.software_id, softwareIds)),
db
.select({
softwareId: softwarePlatform.software_id,
platform: softwarePlatform.platform,
})
.from(softwarePlatform)
.where(inArray(softwarePlatform.software_id, softwareIds)),
db
.select({
softwareId: techniqueSoftware.software_id,
id: technique.technique_id,
mitreId: technique.mitre_id,
name: technique.name,
})
.from(techniqueSoftware)
.innerJoin(
technique,
eq(techniqueSoftware.technique_id, technique.technique_id),
)
.where(inArray(techniqueSoftware.software_id, softwareIds)),
db
.select({
softwareId: threatActorSoftware.software_id,
id: threatActor.threat_id,
mitreId: threatActor.mitre_id,
name: threatActor.name,
})
.from(threatActorSoftware)
.innerJoin(
threatActor,
eq(threatActorSoftware.threat_id, threatActor.threat_id),
)
.where(inArray(threatActorSoftware.software_id, softwareIds)),
]);

return { aliases, platforms, associatedTechniques, usedByThreatActors };
}

export async function getAllSoftware() {
const entries = await db
.select({
id: software.software_id,
mitreId: software.mitre_id,
name: software.name,
description: software.description,
type: software.software_type,
})
.from(software)
.orderBy(software.name);

const metadata = await getSoftwareMetadata(entries.map((entry) => entry.id));

return entries.map((entry) => ({
...entry,
aliases: metadata.aliases
.filter((alias) => alias.softwareId === entry.id)
.map((alias) => alias.alias),
platforms: metadata.platforms
.filter((platform) => platform.softwareId === entry.id)
.map((platform) => platform.platform),
}));
}

export async function getSoftwareByMitreId(mitreId: string) {
const [entry] = await db
.select({
id: software.software_id,
mitreId: software.mitre_id,
name: software.name,
description: software.description,
type: software.software_type,
})
.from(software)
.where(eq(software.mitre_id, mitreId))
.limit(1);

if (!entry) {
return null;
}

const metadata = await getSoftwareMetadata([entry.id]);

return {
...entry,
aliases: metadata.aliases.map((alias) => alias.alias),
platforms: metadata.platforms.map((platform) => platform.platform),
associatedTechniques: metadata.associatedTechniques.map(
({ id, mitreId: relatedMitreId, name }) => ({
id,
mitreId: relatedMitreId,
name,
}),
),
usedByThreatActors: metadata.usedByThreatActors.map(
({ id, mitreId: relatedMitreId, name }) => ({
id,
mitreId: relatedMitreId,
name,
}),
),
};
}
35 changes: 23 additions & 12 deletions frontend/src/data/softwareData.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,30 @@
import { graphqlRequest } from "../api/graphql";
import { mockSoftware, type SoftwareEntry } from "./softwareMockData";

export type { SoftwareEntry } from "./softwareMockData";

export const USE_SOFTWARE_MOCK_DATA = false; // ENDRE DENNE FOR Å IKKE BRUKE MOCK!!!

export type SoftwareCatalogEntry = Omit<SoftwareEntry, "associatedTechniques" | "usedByThreatActors">;
export type RelatedSoftwareEntry = {
id: string;
mitreId: string;
name: string;
};

export type SoftwareEntry = {
id: string;
mitreId: string;
name: string;
type: "TOOL" | "MALWARE";
summary?: string;
description: string;
aliases: string[];
platforms: string[];
associatedTechniques: RelatedSoftwareEntry[];
usedByThreatActors: RelatedSoftwareEntry[];
};

export type SoftwareCatalogEntry = Omit<
SoftwareEntry,
"associatedTechniques" | "usedByThreatActors"
>;

export async function getSoftwareCatalog(): Promise<SoftwareCatalogEntry[]> {
if (USE_SOFTWARE_MOCK_DATA) return mockSoftware;

const data = await graphqlRequest<{ software: SoftwareCatalogEntry[] }>(`
query SoftwareCatalog {
software {
Expand All @@ -28,10 +43,6 @@ export async function getSoftwareCatalog(): Promise<SoftwareCatalogEntry[]> {
}

export async function getSoftwareByMitreId(mitreId: string): Promise<SoftwareEntry | null> {
if (USE_SOFTWARE_MOCK_DATA) {
return mockSoftware.find((entry) => entry.mitreId === mitreId) ?? null;
}

const data = await graphqlRequest<{ softwareByMitreId: SoftwareEntry | null }>(`
query SoftwareDetail($mitreId: String!) {
softwareByMitreId(mitreId: $mitreId) {
Expand Down