Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow scanning private repos, update error handling in frontend #73

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
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[![Current Release](https://img.shields.io/github/release/IBM/cbomkit.svg?logo=IBM)](https://github.com/IBM/cbomkit/releases)

CBOMkit is a toolset for dealing with Cryptography Bill of Materials (CBOM). CBOMkit includes a
- **CBOM Generation** ([CBOMkit-hyperion](https://github.com/IBM/sonar-cryptography), [CBOMkit-theia](https://github.com/IBM/cbomkit-theia)): Generate CBOMs from source code by scanning git repositories to find the used cryptography.
- **CBOM Generation** ([CBOMkit-hyperion](https://github.com/IBM/sonar-cryptography), [CBOMkit-theia](https://github.com/IBM/cbomkit-theia)): Generate CBOMs from source code by scanning private and public git repositories to find the used cryptography.
- **CBOM Viewer ([CBOMkit-coeus](https://github.com/IBM/cbomkit?tab=readme-ov-file#cbomkit-coeus))**: Visualize a generated or uploaded CBOM and access comprehensive statistics.
- **CBOM Compliance Check**: Evaluate CBOMs created or uploaded against specified compliance policies and receive detailed compliance status reports.
- **CBOM Database**: Collect and store CBOMs into the database and expose this data through a RESTful API.
Expand Down Expand Up @@ -80,15 +80,14 @@ The API server functions as the central component of the CBOMkit, offering a com

#### Features
- Retrieve the most recent generated CBOMs
- Access stored CBOMs from the database using Package URLs (PURL)
- Execute targeted searches for scanned Git repositories utilizing specific algorithms (by name and Oid)
- Access stored CBOMs from the database
- Perform compliance checks for user-provided CBOMs against specified policies
- Conduct compliance assessments for stored or generated CBOMs against defined policies

*Sample Query to Retrieve CBOM by Package URL*
*Sample Query to Retrieve CBOM project identifier*
```shell
curl --request GET \
--url 'http://localhost:8081/api/v1/cbom?purls=pkg:github/OddSource/java-license-manager'
--url 'http://localhost:8081/api/v1/cbom/github.com%2Fkeycloak%2Fkeycloak'
```

In addition to the RESTful API, the server incorporates WebSocket integration, enabling:
Expand Down
27 changes: 11 additions & 16 deletions frontend/src/components/global/NotificationsView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,47 +31,42 @@ export default {
var kind = "error";
var title = "Unknown error";
var description = "An unknown error has occured.";
if (error == ErrorStatus.NoConnection) {
if (error.status === ErrorStatus.NoConnection) {
kind = "error";
title = "No connection";
description =
"Connection to the server has failed. Please try again later.";
} else if (error == ErrorStatus.InvalidRepo) {
} else if (error.status === ErrorStatus.InvalidRepo) {
kind = "error";
title = "Invalid repository";
description =
"The provided address does not lead to a readable repository.";
} else if (error == ErrorStatus.JsonParsing) {
} else if (error.status === ErrorStatus.JsonParsing) {
kind = "error";
title = "Parsing error";
description = "An incorrect JSON file cannot be parsed.";
} else if (error == ErrorStatus.B64Decoding) {
} else if (error.status === ErrorStatus.ScanError) {
kind = "error";
title = "Decoding error";
description = "An incorrect base 64 file cannot be decoded.";
} else if (error == ErrorStatus.BranchNotSpecified) {
kind = "error";
title = "Clone fail";
description =
"Could not clone git repo branch 'main' or 'master'. Try to specify the branch.";
} else if (error == ErrorStatus.InvalidCbom) {
title = "Error while scanning";
description = error.message;
} else if (error.status === ErrorStatus.InvalidCbom) {
kind = "error";
title = "Invalid CBOM";
description = "The provided CBOM does not respect the expected format.";
} else if (error == ErrorStatus.IgnoredComponent) {
} else if (error.status === ErrorStatus.IgnoredComponent) {
kind = "info";
title = "Some components are not shown";
description = "The provided CBOM contains one or several components that are not cryptographic assets. They are not displayed here.";
} else if (error == ErrorStatus.MultiUpload) {
} else if (error.status === ErrorStatus.MultiUpload) {
kind = "error";
title = "Multiple upload";
description = "Please only upload a single CBOM file.";
} else if (error == ErrorStatus.EmptyDatabase) {
} else if (error.status === ErrorStatus.EmptyDatabase) {
kind = "warning";
title = "Empty database";
description =
"Connection to the server was successful, but the CBOM database is empty.";
} else if (error == ErrorStatus.FallBackLocalComplianceReport) {
} else if (error.status === ErrorStatus.FallBackLocalComplianceReport) {
kind = "warning";
title = "Limited compliance results";
description =
Expand Down
59 changes: 40 additions & 19 deletions frontend/src/components/home/SearchBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
class="search-bar"
placeholder="Enter the Git URL to scan"
v-model="model.codeOrigin.gitLink"
@keyup.enter="connectAndScan(gitInfo()[0], gitInfo()[1])"
@keyup.enter="connectAndScan(advancedOptions()[0], advancedOptions()[1], advancedOptions()[2])"
/>
<cv-button
class="search-button"
:icon="ArrowRight24"
@click="connectAndScan(gitInfo()[0], gitInfo()[1])"
@click="connectAndScan(advancedOptions()[0], advancedOptions()[1], advancedOptions()[2])"
:disabled="!model.codeOrigin.gitLink"
>Scan</cv-button
>
Expand All @@ -26,18 +26,37 @@
</div>
<Transition name="filters">
<div v-show="filterOpen">
<cv-text-input
class="filter-input"
label="Branch"
placeholder="Specify a specific branch"
v-model="gitBranch"
/>
<cv-text-input
class="filter-input"
label="Subfolder"
placeholder="Specify a specific subfolder to scan"
v-model="gitSubfolder"
/>
<cv-tabs style="padding-top: 15px; padding-bottom: 10px">
<cv-tab label="Scan">
<cv-text-input
class="filter-input"
label="Branch"
placeholder="Specify a specific branch"
v-model="gitBranch"
/>
<cv-text-input
class="filter-input"
label="Subfolder"
placeholder="Specify a specific subfolder to scan"
v-model="gitSubfolder"
/>
</cv-tab>
<cv-tab label="Authentication">
<cv-text-input
class="filter-input"
label="Username"
placeholder="If using an access Token (PAT), leave blank"
v-model="username"
/>
<cv-text-input
type="password"
class="filter-input"
label="Password / Access Token (PAT)"
placeholder="The password for the user or anccess token (PAT) for authentication"
v-model="passwordOrPAT"
/>
</cv-tab>
</cv-tabs>
</div>
</Transition>
</div>
Expand All @@ -58,14 +77,16 @@ export default {
filterOpen: false,
gitBranch: null,
gitSubfolder: null,
username: null,
passwordOrPAT: null,
};
},
methods: {
gitInfo: function () {
advancedOptions: function () {
if (this.filterOpen) {
return [this.gitBranch, this.gitSubfolder];
return [this.gitBranch, this.gitSubfolder, { username: this.username, passwordOrPAT: this.passwordOrPAT }];
} else {
return [null, null];
return [null, null, null];
}
},
},
Expand All @@ -92,11 +113,11 @@ export default {
.filters-leave-active {
transition: all 0.4s;
/* max-height should be larger than the tallest element: https://stackoverflow.com/questions/42591331/animate-height-on-v-if-in-vuejs-using-transition */
max-height: 150px;
max-height: 250px;
}
.filters-enter,
.filters-leave-to {
opacity: 0;
max-height: 0px;
max-height: 0;
}
</style>
51 changes: 39 additions & 12 deletions frontend/src/helpers/scan.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ function startWebSocket(socketURL) {
// In safari, manually closing the connection creates an error:
// Do not display an error when the connection is manually closed by the user
console.warn(
"The connection was closed by the client. An connection error occured, but has NOT been notified in the UI."
"The connection was closed by the client. An connection error occurred, but has NOT been notified in the UI."
);
} else {
console.error("WebSocket error:", error);
Expand All @@ -68,9 +68,10 @@ export function stopWebSocket() {
// }
}

export function connectAndScan(gitBranch, gitSubfolder) {
export function connectAndScan(gitBranch, gitSubfolder, credentials) {
model.resetScanningInfo();
setAndCleanCodeOrigin(gitBranch, gitSubfolder);
setCodeOrigin(gitBranch, gitSubfolder);
setCredentials(credentials)
let clientId = uuid4();
let socketURL = `${API_SCAN_URL}/${clientId}`;
startWebSocket(socketURL);
Expand All @@ -81,19 +82,30 @@ function scan() {
model.addError(ErrorStatus.NoConnection);
console.log("No socket in model");
} else if (!model.codeOrigin.gitLink) {
// TODO: Should I validate the look of the Git link in the frontend?
model.addError(ErrorStatus.InvalidRepo);
console.log("Git URL not valid");
} else {
var request = {};
request["gitUrl"] = model.codeOrigin.gitLink;
// build scan request
const scanRequest = {};
// set scan options
scanRequest["gitUrl"] = model.codeOrigin.gitLink;
if (model.codeOrigin.gitBranch) {
request["branch"] = model.codeOrigin.gitBranch;
scanRequest["branch"] = model.codeOrigin.gitBranch;
}
if (model.codeOrigin.gitSubfolder) {
request["subfolder"] = model.codeOrigin.gitSubfolder;
scanRequest["subfolder"] = model.codeOrigin.gitSubfolder;
}
model.scanning.socket.send(JSON.stringify(request));
// set credentials
if (model.credentials.pat) {
scanRequest["credentials"] = {}
scanRequest["credentials"]["pat"] = model.credentials.pat;
} else if (model.credentials.username && model.credentials.password) {
scanRequest["credentials"] = {}
scanRequest["credentials"]["username"] = model.credentials.username;
scanRequest["credentials"]["password"] = model.credentials.password;
}

model.scanning.socket.send(JSON.stringify(scanRequest));
// this.filterOpen = false
model.scanning.isScanning = true;
model.scanning.scanningStatus = STATES.LOADING;
Expand All @@ -118,11 +130,13 @@ function handleMessage(messageJson) {
); // Time in seconds
}
} else if (obj["type"] === "ERROR") {
model.addError(ErrorStatus.BranchNotSpecified); // TODO: When several different error messages will exist, this will have to be changed
model.addError(ErrorStatus.ScanError, model.scanning.scanningStatusMessage = obj["message"]); //
// update state
model.scanning.scanningStatusMessage = obj["message"];
console.error("Error from backend:", model.scanning.scanningStatusMessage);
model.scanning.scanningStatus = STATES.ERROR;
model.scanning.isScanning = false;
// log
console.error("Error from backend:", model.scanning.scanningStatusMessage);
} else if (obj["type"] === "PURL") {
model.codeOrigin.gitPurls = obj["purls"];
// This is not strictly necessary anymore now that I read PURLs from the CBOM, but it arrives before the CBOM so I leave it
Expand Down Expand Up @@ -150,7 +164,7 @@ function handleMessage(messageJson) {
}
}

function setAndCleanCodeOrigin(gitBranch, gitSubfolder) {
function setCodeOrigin(gitBranch, gitSubfolder) {
if (model.codeOrigin.gitLink) {
model.codeOrigin.gitLink = model.codeOrigin.gitLink.trim();
}
Expand All @@ -161,3 +175,16 @@ function setAndCleanCodeOrigin(gitBranch, gitSubfolder) {
model.codeOrigin.gitSubfolder = gitSubfolder.trim();
}
}

function setCredentials(credentials) {
if (credentials === null) {
return
}

if (credentials.username && credentials.passwordOrPAT) {
model.credentials.username = credentials.username;
model.credentials.password = credentials.passwordOrPAT;
} else if (credentials.passwordOrPAT) {
model.credentials.pat = credentials.passwordOrPAT;
}
}
16 changes: 13 additions & 3 deletions frontend/src/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ export const model = reactive({
gitPurls: [],
uploadedFileName: null,
},
credentials: {
username: null,
password: null,
pat: null,
},
policyCheckResult: null,
errors: [],
lastCboms: [],
Expand Down Expand Up @@ -62,8 +67,13 @@ export const model = reactive({
model.codeOrigin.gitPurls = [];
model.codeOrigin.uploadedFileName = null;
},
addError(error) {
this.errors.push(error);
resetCredentials() {
model.credentials.username = null;
model.credentials.password = null;
model.credentials.pat = null;
},
addError(errorStatus, message) {
this.errors.push({status: errorStatus, message: message});
},
closeError(index) {
this.errors.splice(index, 1);
Expand All @@ -73,7 +83,7 @@ export const model = reactive({
export const ErrorStatus = {
NoConnection: "NoConnection",
InvalidRepo: "InvalidRepo",
BranchNotSpecified: "BranchNotSpecified",
ScanError: "ScanError",
JsonParsing: "JsonParsing",
InvalidCbom: "InvalidCbom",
IgnoredComponent: "IgnoredComponent",
Expand Down
Loading
Loading