Skip to content

Commit b702bb1

Browse files
authored
Add feature to redirect output to database (#25)
* Update .gitignore and add log.sh Add db_service module to service/mod.rs Add async to main function Update tokio features in Cargo.toml Add postgres and sea-orm dependencies to Cargo.toml Add to_db field to ConfigDto struct in scan.rs Update sensleaks function to aODsync Update detect function to async Add insert_leaks function to db_service.rs Signed-off-by: yaokunzhang <[email protected]> * Refactor insert_leaks function signature and simplify get_db_config function Signed-off-by: yaokunzhang <[email protected]> * Refactor get_db_config function Signed-off-by: yaokunzhang <[email protected]> --------- Signed-off-by: yaokunzhang <[email protected]>
1 parent 65be735 commit b702bb1

File tree

10 files changed

+219
-28
lines changed

10 files changed

+219
-28
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,6 @@ Cargo.lock
1414
*.pdb
1515

1616
# Compiled binary file
17-
.gitignore
17+
.gitignore
18+
19+
log.sh

Cargo.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@ csv = "1.1"
2828
log = "0.4"
2929
env_logger = "0.11.0"
3030
axum = { version = "0.7.4", features = ["macros"] }
31-
tokio = { version = "1.36.0", features = ["macros", "rt-multi-thread"] }
31+
tokio = { version = "1.36.0", features = ["full"] }
3232
tower-http = { version = "0.5.0", features = ["cors"] }
3333
utoipa = { version = "4.2.0", features = ["axum_extras"] }
3434
utoipa-swagger-ui = { version = "6", features = ["axum"] }
35-
hyper = { version = "1.2.0", features = ["full"] }
35+
hyper = { version = "1.2.0", features = ["full"] }
36+
postgres = { version = "0.19.7"}
37+
sea-orm = {version = "0.12", features = ["runtime-tokio-rustls", "sqlx-postgres"]}

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,18 @@ cargo run --bin scan -- -help
3333
```
3434

3535
```shell
36-
Usage: scan.exe [OPTIONS] --repo <REPO>
36+
Usage: scan [OPTIONS] --repo <REPO>
3737

3838
Options:
3939
--repo <REPO> Target repository
4040
--config <CONFIG> Config path [default: gitleaks.toml]
4141
--threads <THREADS> Maximum number of threads sensleak spawns [default: 10]
42-
--chunk <BATCH_SIZE> The number of git files processed in each batch [default: 10]
42+
--chunk <CHUNK> The number of files processed in each batch [default: 10]
4343
--report <REPORT> Path to write json leaks file
4444
--report-format <REPORT_FORMAT> json, csv, sarif [default: json]
4545
-v, --verbose Show verbose output from scan
4646
--pretty Pretty print json if leaks are present
47-
--commit <COMMIT> sha of commit to scan or "latest" to scan the last commit of the repository
47+
--commit <COMMIT> sha of commit to scan
4848
--commits <COMMITS> comma separated list of a commits to scan
4949
--commits-file <COMMITS_FILE> file of new line separated list of a commits to scan
5050
--commit-since <COMMIT_SINCE> Scan commits more recent than a specific date. Ex: '2006-01-02' or '2023-01-02T15:04:05-0700' format
@@ -57,6 +57,7 @@ Options:
5757
--repo-config Load config from target repo. Config file must be ".gitleaks.toml" or "gitleaks.toml"
5858
--debug log debug messages
5959
--disk <DISK> Clones repo(s) to disk
60+
--to-db Output to database
6061
-h, --help Print help (see more with '--help')
6162
-V, --version Print version
6263

src/entity/models.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use chrono::{DateTime, FixedOffset};
22
use clap::Parser;
33
use serde::{Deserialize, Serialize};
44
use utoipa::{ToSchema};
5+
use sea_orm::{entity::prelude::*, ActiveValue};
56
/// Represents the configuration for sensleaks tool.
67
#[derive(Parser, Debug)]
78
#[command(
@@ -98,6 +99,9 @@ pub struct Config {
9899
#[arg(long)]
99100
pub disk: Option<String>,
100101

102+
/// Output to database
103+
#[arg(long)]
104+
pub to_db: bool,
101105
// /// Start API
102106
// #[arg(long, default_value = "false")]
103107
// pub api: bool,
@@ -126,6 +130,7 @@ impl Default for Config {
126130
repo_config: false,
127131
debug: false,
128132
disk: None,
133+
to_db: false,
129134
// api: false,
130135
}
131136
}
@@ -206,6 +211,31 @@ impl Default for Allowlist {
206211
Self::new()
207212
}
208213
}
214+
215+
/// Sea-orm Entity
216+
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
217+
#[sea_orm(table_name = "leaks")]
218+
pub struct Model {
219+
#[sea_orm(primary_key)]
220+
pub id: i32,
221+
pub line: String,
222+
pub line_number: u32,
223+
pub offender: String,
224+
pub commit: String,
225+
pub repo: String,
226+
pub rule: String,
227+
pub commit_message: String,
228+
pub author: String,
229+
pub email: String,
230+
pub file: String,
231+
pub date: String,
232+
}
233+
234+
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
235+
pub enum Relation {}
236+
237+
impl ActiveModelBehavior for ActiveModel {}
238+
209239
/// Represents an item in the scanned output.
210240
#[derive(Debug, Serialize, Deserialize, Clone)]
211241
pub struct Leak {
@@ -243,6 +273,25 @@ pub struct Leak {
243273
pub date: String,
244274
}
245275

276+
impl Leak {
277+
pub fn to_active_model(&self) -> ActiveModel {
278+
ActiveModel {
279+
line: ActiveValue::set(self.line.clone()),
280+
line_number: ActiveValue::set(self.line_number),
281+
offender: ActiveValue::set(self.offender.clone()),
282+
commit: ActiveValue::set(self.commit.clone()),
283+
repo: ActiveValue::set(self.repo.clone()),
284+
rule: ActiveValue::set(self.rule.clone()),
285+
commit_message: ActiveValue::set(self.commit_message.clone()),
286+
author: ActiveValue::set(self.author.clone()),
287+
email: ActiveValue::set(self.email.clone()),
288+
file: ActiveValue::set(self.file.clone()),
289+
date: ActiveValue::set(self.date.clone()),
290+
..Default::default()
291+
}
292+
}
293+
}
294+
246295
/// The scan condition
247296
#[derive(Debug, Clone)]
248297
pub struct Scan {
@@ -356,3 +405,37 @@ pub struct CsvResult {
356405
/// The date of the commit.
357406
pub date: String,
358407
}
408+
409+
/// Config to connect to the database
410+
#[derive(Debug, Default,Serialize, Deserialize)]
411+
pub struct ConnectDbConfig {
412+
/// The host of the database
413+
pub host: String,
414+
/// The user of the database
415+
pub user: String,
416+
/// The password of the database
417+
pub password: String,
418+
/// The name of the database
419+
pub dbname: String,
420+
/// The port of the database
421+
pub port: String,
422+
}
423+
424+
impl ConnectDbConfig {
425+
/// Translate the config to connection url
426+
pub fn to_connection_url(&self) -> String {
427+
format!(
428+
"postgresql://{}:{}@{}:{}/{}",
429+
self.user, self.password, self.host, self.port, self.dbname
430+
)
431+
}
432+
pub fn new() -> Self {
433+
ConnectDbConfig {
434+
host: String::from(""),
435+
user: String::from(""),
436+
password: String::from(""),
437+
dbname: String::from(""),
438+
port: String::from(""),
439+
}
440+
}
441+
}

src/lib.rs

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,43 @@
11
mod errors;
2-
2+
33
mod utils {
44
pub mod detect_utils;
55
pub mod git_util;
6-
76
}
7+
88
pub mod entity{
99
pub mod models;
1010
}
11+
1112
pub mod service{
1213
pub mod detect_service;
1314
pub mod git_service;
15+
pub mod db_service;
1416
}
1517

1618
pub use entity::models;
17-
18-
1919
pub use errors::*;
20-
21-
2220
pub use utils::detect_utils;
2321
pub use utils::git_util;
24-
2522
pub use git_util::*;
2623
pub use models::*;
2724

2825
use axum::{routing, Router};
26+
2927
use utoipa::{
3028
OpenApi,
3129
};
30+
3231
use utoipa_swagger_ui::SwaggerUi;
3332

34-
3533
mod routes{
3634
pub mod scan;
3735
pub mod rules;
3836
}
3937
pub use routes::scan::*;
4038
pub use routes::rules::*;
4139

42-
use crate::routes::*;
43-
44-
45-
40+
use crate::routes::*;
4641

4742
pub async fn start() -> Result<(), Box<dyn std::error::Error>> {
4843
#[derive(OpenApi)]

src/main.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use sensleak::service::detect_service::sensleaks;
22

33
/// The entry of the project
4-
fn main() {
5-
sensleaks();
4+
#[tokio::main]
5+
async fn main() {
6+
sensleaks().await;
67
}
78

89

src/routes/scan.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ pub struct ConfigDto {
4646

4747
/// Clones repo(s) to disk.
4848
pub disk: Option<String>,
49+
50+
/// Output to database
51+
pub to_db: bool,
4952
}
5053

5154
/// The return results of the scan.
@@ -94,8 +97,9 @@ pub async fn scan_repo(Json(json_config): Json<ConfigDto>) -> Json<ScanResponse>
9497
config.user = json_config.user;
9598
config.disk = json_config.disk;
9699
config.repo_config = json_config.repo_config.unwrap_or(false);
100+
config.to_db = json_config.to_db;
97101

98-
match detect(config) {
102+
match detect(config).await {
99103
Ok(results) => Json(ScanResponse {
100104
code: 200,
101105
leaks_number: Some(results.outputs.len()),

src/service/db_service.rs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
use crate::models::{ConnectDbConfig, Entity as Leaks, Leak};
2+
use chrono::Local;
3+
use sea_orm::*;
4+
use std::env;
5+
6+
/// Sets up the database connection using the application's configuration settings.
7+
///
8+
/// This function attempts to establish a connection to the database using environment variables for the database configuration.
9+
/// It reads configuration values such as host, port, user, password, and database name from environment variables
10+
/// and uses them to construct the database URL.
11+
///
12+
/// # Returns
13+
///
14+
/// Returns a `Result<DatabaseConnection, DbErr>`:
15+
/// - `Ok(DatabaseConnection)` if the connection is successfully established.
16+
/// - `Err(DbErr)` if there is an error connecting to the database.
17+
pub async fn set_up_db() -> Result<DatabaseConnection, DbErr> {
18+
let config = get_db_config();
19+
let db_url = config.to_connection_url();
20+
let db = Database::connect(&db_url).await?;
21+
Ok(db)
22+
}
23+
24+
/// Inserts a vector of `Leak` entities into the database and ensures that the `Leaks` table exists.
25+
///
26+
/// This function first checks if the `Leaks` table exists in the database and creates it if not.
27+
/// Then, it proceeds to insert the provided vector of `Leak` entities into the `Leaks` table.
28+
///
29+
/// # Arguments
30+
///
31+
/// * `_leaks` - A reference to a vector of `Leak` entities to be inserted into the database.
32+
///
33+
/// # Returns
34+
///
35+
/// Returns a `Result<(), DbErr>` indicating the outcome of the operation:
36+
/// - `Ok(())` if the insertion is successful and the `Leaks` table is either found or successfully created.
37+
/// - `Err(DbErr)` if there is an error during the table check/creation or insertion process.
38+
pub async fn insert_leaks(_leaks: &[Leak]) -> Result<(), DbErr> {
39+
let db = match set_up_db().await {
40+
Ok(db) => db,
41+
Err(err) => panic!("{}", err),
42+
};
43+
44+
// Check if the table Leaks exists and create it if not
45+
let builder = db.get_database_backend();
46+
let schema = Schema::new(builder);
47+
48+
let stmt = schema
49+
.create_table_from_entity(Leaks)
50+
.if_not_exists()
51+
.to_owned();
52+
53+
let stmt = builder.build(&stmt);
54+
55+
db.execute(stmt).await?;
56+
57+
println!(
58+
"\x1b[34m[INFO]\x1b[0m[{}] Create Success ...",
59+
Local::now().format("%Y-%m-%d %H:%M:%S"),
60+
);
61+
62+
// Insert leaks
63+
for leak in _leaks.iter() {
64+
let active_model = leak.to_active_model();
65+
66+
let insert_result = Leaks::insert(active_model)
67+
.exec(&db)
68+
.await?;
69+
println!("Inserted leak with result: {:?}", insert_result);
70+
}
71+
72+
println!(
73+
"\x1b[34m[INFO]\x1b[0m[{}] Insert Success ...",
74+
Local::now().format("%Y-%m-%d %H:%M:%S"),
75+
);
76+
77+
Ok(())
78+
}
79+
80+
/// Retrieves database connection configuration from environment variables.
81+
///
82+
/// This function constructs a `ConnectDbConfig` struct with database connection details
83+
/// such as host, port, username, password, and database name, reading the values from
84+
/// environment variables. If an environment variable is not set, it defaults to a predefined value.
85+
///
86+
/// # Returns
87+
///
88+
/// Returns a `ConnectDbConfig` struct populated with the database connection details.
89+
fn get_db_config() -> ConnectDbConfig {
90+
ConnectDbConfig {
91+
host: env::var("PG_HOST").unwrap_or("localhost".to_string()),
92+
port: env::var("PG_PORT").unwrap_or("5432".to_string()),
93+
user: env::var("PG_USER").unwrap_or("postgres".to_string()),
94+
password: env::var("PG_PASSWORD").unwrap_or("postgres".to_string()),
95+
dbname: env::var("PG_DBNAME").unwrap_or("postgres".to_string())
96+
}
97+
}

0 commit comments

Comments
 (0)