Skip to content

Commit

Permalink
Add get output functionality
Browse files Browse the repository at this point in the history
This adds functionality to get outputs from Skootrs managed
projects. This currently only supports SBOMs.
  • Loading branch information
mlieberman85 committed Feb 23, 2024
1 parent b7f97ba commit 3ce41d9
Show file tree
Hide file tree
Showing 9 changed files with 334 additions and 797 deletions.
920 changes: 162 additions & 758 deletions Cargo.lock

Large diffs are not rendered by default.

7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,17 @@ Commands:
create
daemon
dump
get-facet
get-facet
get
help Print this message or the help of the given subcommand(s)
```

- Create - The main command for creating a Skootrs secure-by-default repo.
- Daemon - The command for running Skootrs as a REST API.
- Dump - The command for dumping the output of Skootrs' state database to stdout. Useful for debugging.
- Get-Facet - The command for dumping the file or API output for a facet for a given project.
- Get - This is a new command that will be the verb for getting items that Skootrs knows abou
- Output - This is a verb for the Get command that will fetch outputs from Skootrs project. Currently this only supports getting SBOMs

To get pretty printing of the logs which are in [bunyan](https://github.com/trentm/node-bunyan) format I recommend piping the skootrs into the bunyan cli. I recommend using [bunyan-rs](https://github.com/LukeMathWalker/bunyan). For example:

Expand All @@ -56,4 +59,4 @@ $ cargo run create | bunyan
The initial talk given on Skootrs appears to not have been recorded but here are the locations of slides that include the reason why Skootrs is being built along with some architecture:

- [OpenSSF Day Japan 2023](https://github.com/mlieberman85/talks/blob/91cf3bef51f7d277a744098863389e362920b4c8/2023-12-04-ossfday/presentation.pdf)
- [NYU Guest Talk](https://github.com/mlieberman85/talks/blob/main/2024-01-30-skootrs/presentation.pdf)
- [NYU Guest Talk](https://github.com/mlieberman85/talks/blob/main/2024-01-30-skootrs/presentation.pdf)
2 changes: 2 additions & 0 deletions shell.nix
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
bunyan-rs
go
maven
pkg-config
openssl
];
RUSTC_VERSION = pkgs.lib.readFile ./rust-toolchain;
# https://github.com/rust-lang/rust-bindgen#environment-variables
Expand Down
3 changes: 3 additions & 0 deletions skootrs-bin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@ tracing-opentelemetry = "0.22.0"
tracing-bunyan-formatter = "0.3.9"
opentelemetry = { version = "0.21.0" }
opentelemetry_sdk = "0.21.2"
serde_yaml = "0.9.32"
reqwest = "0.11.24"
base64 = "0.21.7"
110 changes: 87 additions & 23 deletions skootrs-bin/src/helpers.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
use base64::prelude::*;
use inquire::Text;
use skootrs_model::skootrs::{
EcosystemParams, GithubRepoParams, GithubUser, GoParams, InitializedProject, MavenParams, ProjectParams, RepoParams, SkootError, SourceParams, SUPPORTED_ECOSYSTEMS
};
use octocrab::Page;
use skootrs_lib::service::{
ecosystem::LocalEcosystemService,
Expand All @@ -10,6 +8,13 @@ use skootrs_lib::service::{
repo::LocalRepoService,
source::{LocalSourceService, SourceService},
};
use skootrs_model::{
security_insights::insights10::SecurityInsightsVersion100YamlSchema,
skootrs::{
EcosystemParams, GithubRepoParams, GithubUser, GoParams, InitializedProject, MavenParams,
ProjectParams, RepoParams, SkootError, SourceParams, SUPPORTED_ECOSYSTEMS,
},
};
use std::collections::HashMap;

use skootrs_model::skootrs::facet::InitializedFacet;
Expand Down Expand Up @@ -121,29 +126,16 @@ pub async fn create() -> std::result::Result<(), SkootError> {
}

/// Returns `Ok(())` if the able to print out the content of the facet, otherwise returns an error.
///
///
/// This function prompts the user to select a project and then a facet of that project to fetch from the state store.
/// It then prints out the content of the facet.
///
///
/// # Errors
///
/// Returns an error if the state store is not able to be accessed or if the selected project or facet
/// is not found.
pub async fn get_facet() -> std::result::Result<(), SkootError> {
let projects = get_all().await?;
let repo_to_project: HashMap<String, &InitializedProject> = projects
.iter()
.map(|p| (p.repo.full_url(), p))
.collect::<HashMap<_, _>>();
let selected_project = inquire::Select::new(
"Select a project",
repo_to_project.keys().collect::<Vec<_>>(),
)
.prompt()?;

let project = repo_to_project
.get(selected_project)
.ok_or_else(|| SkootError::from("Failed to get selected project"))?;
let project = prompt_project().await?;

let facet_to_project: HashMap<String, InitializedFacet> = project
.facets
Expand Down Expand Up @@ -174,18 +166,18 @@ pub async fn get_facet() -> std::result::Result<(), SkootError> {
.get(selected_facet)
.ok_or_else(|| SkootError::from("Failed to get selected facet"))?;

let facet_content = get_facet_content(facet, project)?;
let facet_content = get_facet_content(facet, &project)?;

println!("{facet_content}");

Ok(())
}

/// Returns `Ok(())` if the able to print out a dump of the statestore.
///
///
/// This function prints out the content of the state store in a pretty printed JSON format.
/// # Errors
///
///
/// Returns an error if the state store is not able to be accessed.
pub async fn dump() -> std::result::Result<(), SkootError> {
let projects = get_all().await?;
Expand Down Expand Up @@ -222,6 +214,78 @@ fn get_facet_content(
// TODO: This can make it unclear which API was used
let content = f.apis.iter().map(|a| format!("{a:?}")).collect::<Vec<_>>();
Ok(content.join("\n"))
},
}
}
}

/// This function is for prompting the user to get outputs from a Skootrs project
///
/// # Errors
///
/// This return an error if it can't get an intended output for some reason. This
/// includes issues like unable to get or parse SECURITY-INSIGHTS.yml or unable
/// to get the intended output.
pub async fn get_output() -> std::result::Result<(), SkootError> {
let project = prompt_project().await?;

let skootrs_model::skootrs::InitializedRepo::Github(repo) = &project.repo;

let sec_ins_content_items = octocrab::instance()
.repos(repo.organization.get_name(), &repo.name)
.get_content()
.path("SECURITY-INSIGHTS.yml")
.r#ref("main")
.send()
.await?;

let sec_ins = sec_ins_content_items
.items
.first()
.ok_or_else(|| SkootError::from("Failed to get security insights"))?;

let content = sec_ins
.content
.as_ref()
.ok_or_else(|| SkootError::from("Failed to get content of security insights"))?;
let content_decoded =
base64::engine::general_purpose::STANDARD.decode(content.replace('\n', ""))?;
let content_str = std::str::from_utf8(&content_decoded)?;
let insights: SecurityInsightsVersion100YamlSchema =
serde_yaml::from_str::<SecurityInsightsVersion100YamlSchema>(content_str)?;
let sbom_vec = insights
.dependencies
.ok_or_else(|| SkootError::from("Failed to get dependencies value from security insights"))?
.sbom
.ok_or_else(|| SkootError::from("Failed to get sbom value from security insights"))?;

let sbom_url = inquire::Select::new(
"Select an SBOM",
sbom_vec.iter().flat_map(|s| &s.sbom_file).collect(),
)
.prompt()?;

let sbom = reqwest::get(sbom_url).await?.text().await?;

println!("{sbom}");

Ok(())
}

async fn prompt_project() -> Result<InitializedProject, SkootError> {
let projects = get_all().await?;
let repo_to_project: HashMap<String, &InitializedProject> = projects
.iter()
.map(|p| (p.repo.full_url(), p))
.collect::<HashMap<_, _>>();
let selected_project = inquire::Select::new(
"Select a project",
repo_to_project.keys().collect::<Vec<_>>(),
)
.prompt()?;

let project = *repo_to_project
.get(selected_project)
.ok_or_else(|| SkootError::from("Failed to get selected project"))?;

Ok(project.clone())
}
25 changes: 23 additions & 2 deletions skootrs-bin/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@
pub mod helpers;

use clap::Parser;
use clap::{Parser, Subcommand};
use skootrs_model::skootrs::SkootError;

use helpers::{create, dump, get_facet};
use helpers::{create, dump, get_facet, get_output};
use opentelemetry::global;
use opentelemetry_sdk::propagation::TraceContextPropagator;
use tracing::error;
Expand All @@ -48,6 +48,18 @@ enum SkootrsCli {
/// Get the data for a facet of a particular project.
#[command(name = "get-facet")]
GetFacet,

#[command(name = "get")]
Get {
#[clap(subcommand)]
resource: GetCommands,
},
}

/// This is the enum for what nouns the `get` command can take.
#[derive(Subcommand, Debug)]
enum GetCommands {
Output
}

fn init_tracing() {
Expand Down Expand Up @@ -111,6 +123,15 @@ async fn main() -> std::result::Result<(), SkootError> {
error!(error = error.as_ref(), "Failed to get facet");
}
}
SkootrsCli::Get { resource } => {
match resource {
GetCommands::Output => {
if let Err(ref error) = get_output().await {
error!(error = error.as_ref(), "Failed to get output");
}
}
}
}
};
Ok(())
}
2 changes: 1 addition & 1 deletion skootrs-lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ edition = "2021"
[dependencies]
octocrab = "0.33.3"
serde_json = "1.0.112"
serde_yaml = "0.9.30"
serde_yaml = "0.9.32"
serde = { version = "1.0.193", features = ["derive"] }
utoipa = { version = "4.1.0" }
chrono = { version = "0.4.31", features = ["serde"] }
Expand Down
61 changes: 51 additions & 10 deletions skootrs-lib/src/service/facet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
#![allow(clippy::module_name_repetitions)]
#![allow(clippy::unused_self)]

use std::error::Error;
use std::{error::Error, str::FromStr};

use askama::Template;
use chrono::Datelike;
Expand All @@ -30,13 +30,7 @@ use tracing::info;

use skootrs_model::{
security_insights::insights10::{
SecurityInsightsVersion100YamlSchema,
SecurityInsightsVersion100YamlSchemaContributionPolicy,
SecurityInsightsVersion100YamlSchemaHeader,
SecurityInsightsVersion100YamlSchemaHeaderSchemaVersion,
SecurityInsightsVersion100YamlSchemaProjectLifecycle,
SecurityInsightsVersion100YamlSchemaProjectLifecycleStatus,
SecurityInsightsVersion100YamlSchemaVulnerabilityReporting,
SecurityInsightsVersion100YamlSchema, SecurityInsightsVersion100YamlSchemaContributionPolicy, SecurityInsightsVersion100YamlSchemaDependencies, SecurityInsightsVersion100YamlSchemaDependenciesSbomItem, SecurityInsightsVersion100YamlSchemaDependenciesSbomItemSbomCreation, SecurityInsightsVersion100YamlSchemaHeader, SecurityInsightsVersion100YamlSchemaHeaderSchemaVersion, SecurityInsightsVersion100YamlSchemaProjectLifecycle, SecurityInsightsVersion100YamlSchemaProjectLifecycleStatus, SecurityInsightsVersion100YamlSchemaVulnerabilityReporting
},
skootrs::{
facet::{
Expand Down Expand Up @@ -457,6 +451,7 @@ impl DefaultSourceBundleContentHandler {
})
}

#[allow(clippy::too_many_lines)]
fn generate_security_insights_content(
&self,
params: &SourceBundleFacetParams,
Expand All @@ -469,7 +464,51 @@ impl DefaultSourceBundleContentHandler {
code_of_conduct: None,
contributing_policy: None,
},
dependencies: None,
dependencies: Some(SecurityInsightsVersion100YamlSchemaDependencies{
dependencies_lifecycle: None,
dependencies_lists: vec![
format!("{}/blob/main/go.mod", &params.common.repo.full_url())
],
env_dependencies_policy: None,
sbom: Some(vec![
SecurityInsightsVersion100YamlSchemaDependenciesSbomItem {
sbom_creation: Some(
SecurityInsightsVersion100YamlSchemaDependenciesSbomItemSbomCreation::from_str("Created by goreleaser")?),
sbom_file: Some(format!("{}/releases/latest/download/main-linux-amd64.spdx.sbom.json", &params.common.repo.full_url())),
sbom_format: Some("SPDX".to_string()),
sbom_url: Some("https://spdx.github.io/spdx-spec/v2.3/".to_string()),
},
SecurityInsightsVersion100YamlSchemaDependenciesSbomItem {
sbom_creation: Some(
SecurityInsightsVersion100YamlSchemaDependenciesSbomItemSbomCreation::from_str("Created by goreleaser")?),
sbom_file: Some(format!("{}/releases/latest/download/main-linux-arm.spdx.sbom.json", &params.common.repo.full_url())),
sbom_format: Some("SPDX".to_string()),
sbom_url: Some("https://spdx.github.io/spdx-spec/v2.3/".to_string()),
},
SecurityInsightsVersion100YamlSchemaDependenciesSbomItem {
sbom_creation: Some(
SecurityInsightsVersion100YamlSchemaDependenciesSbomItemSbomCreation::from_str("Created by goreleaser")?),
sbom_file: Some(format!("{}/releases/latest/download/main-linux-arm64.spdx.sbom.json", &params.common.repo.full_url())),
sbom_format: Some("SPDX".to_string()),
sbom_url: Some("https://spdx.github.io/spdx-spec/v2.3/".to_string()),
},
SecurityInsightsVersion100YamlSchemaDependenciesSbomItem {
sbom_creation: Some(
SecurityInsightsVersion100YamlSchemaDependenciesSbomItemSbomCreation::from_str("Created by goreleaser")?),
sbom_file: Some(format!("{}/releases/latest/download/main-windows-amd64.exe.spdx.sbom.json", &params.common.repo.full_url())),
sbom_format: Some("SPDX".to_string()),
sbom_url: Some("https://spdx.github.io/spdx-spec/v2.3/".to_string()),
},
SecurityInsightsVersion100YamlSchemaDependenciesSbomItem {
sbom_creation: Some(
SecurityInsightsVersion100YamlSchemaDependenciesSbomItemSbomCreation::from_str("Created by goreleaser")?),
sbom_file: Some(format!("{}/releases/latest/download/main.spdx.sbom.json", &params.common.repo.full_url())),
sbom_format: Some("SPDX".to_string()),
sbom_url: Some("https://spdx.github.io/spdx-spec/v2.3/".to_string()),
},
]),
third_party_packages: Some(true),
}),
distribution_points: Vec::new(),
documentation: None,
header: SecurityInsightsVersion100YamlSchemaHeader {
Expand All @@ -494,6 +533,8 @@ impl DefaultSourceBundleContentHandler {
roadmap: None,
status: SecurityInsightsVersion100YamlSchemaProjectLifecycleStatus::Active,
},
// TODO: Since security insights doesn't support SLSA, scorecard, etc. explicitly we might want to add it
// to security_artifacts.
security_artifacts: None,
security_assessments: None,
security_contacts: Vec::new(),
Expand All @@ -515,7 +556,7 @@ impl DefaultSourceBundleContentHandler {

Ok(SourceBundleContent {
source_files_content: vec![SourceFileContent {
name: "SECURITY_INSIGHTS.yml".to_string(),
name: "SECURITY-INSIGHTS.yml".to_string(),
path: "./".to_string(),
content,
}],
Expand Down
1 change: 0 additions & 1 deletion skootrs-model/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ utoipa = { version = "4.1.0" }
chrono = { version = "0.4.31", features = ["serde"] }
schemars = { version = "0.8.16", features = ["chrono", "url"] }
regress = "0.7.1"
leptos-struct-table = { version = "0.7.0", features = ["chrono", "uuid"] }

[lints]
workspace = true
Expand Down

0 comments on commit 3ce41d9

Please sign in to comment.