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

Suggested recommendations for test, dev env, supply chain and error handling #62

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
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
4 changes: 2 additions & 2 deletions src/en/01_introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ Rust ecosystem for secure development.
A following chapter focuses on precautions to take when choosing and using
external libraries.
Then, recommendations about the Rust language constructs are exposed.
<!-- TODO: Finally, we introduce advices for writing
tests for a project in Rust, and for using Rust fuzzing tools.-->
TODO: Finally, we introduce advices for writing
tests for a project in Rust<!-- , and for using Rust fuzzing tools-->.
A summary of recommendations presented throughout the document is listed at the
end of this guide.
74 changes: 74 additions & 0 deletions src/en/02_devenv.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,3 +250,77 @@ There exist other useful tools or `cargo` subcommands for enforcing program
security whether by searching for specific code patterns or by providing
convenient commands for testing or fuzzing. They are discussed in the following
chapters, according to their goals.

## Using an internal registry

The default Rust registry is [crates.io](https://crates.io). However, it is now possible to set up an internal registry for your organisation.

This allows you to limit the libraries and versions available within an organisation.

> ### Rule {{#check DENV-REGISTRY | Use of internal registry}}
>
> It is recommended that an internal registry is created if the organisation wishes or needs to have full control over the technical stack that may be used for projects.

### Deploying a server

There are two ways of deploying an internal registry.
One way is to develop your own solution. This must meet the API requirements [described here](https://doc.rust-lang.org/cargo/reference/registry-web-api.html).

Second, it is possible to use a solution that has already been built. A relatively exhaustive list, validated by the rust project, can be found on the [cargo project wiki pages](https://github.com/rust-lang/cargo/wiki/Third-party-registries
)

> ### Rule {{#check DENV-REGISTRY-LC | Internal registry management processes}}
>
> If an internal register is deployed, a lifecycle management process needs to be put in place.
>
> This should contain procedures for :
> - requesting the addition of a crate
> - validating of a crate on
> - a security point of view
> - ensuring that the tool/lib is consistent with the company's requirements
> - updating the various crates
> - removing/deleting crates

### Using multiple registers

It is possible to configure `cargo` to retrieve data from multiple registries. This is done by adding the registers to the `.cargo/config.toml` file.

```toml
[registries]
<registry-name> = { index = "https://<fqdn>:<port>/git/index" }
```

Each new registry must be added by a new line in the configuration file. The name of the registry is defined by the value of \<registry-name\>.

> ### Rule {{#check DENV-REGISTRY-CONF | Authorised registry configuration}}
>
> All registries authorised within an organisation must be approved and reviewed on a regular basis by the various competent bodies.
>
>The naming of registries must be consistent throughout the organisation in order to guarantee the operation of projects, regardless of the developer.

### Setting up a default registry

To globally configure the use of a default registry, you need to modify the `.cargo/config.toml` file to add the lines below.

```toml
[registry]
default = "<registry-name>"
```

> ### Rule {{#check DENV-REGISTRY-DEFAULT | Setting up a default registry}}
>
> In the event that an internal registry is deployed, it is advisable to set the organisation's internal registry as the default Rust registry.

### Publishing a project

If a user wishes to publish their project internally only or on several private registries, then they need to add the following lines to their `Cargo.toml` file.


```toml
[package]
publish = ["<registry-name>"]
```

> ### Rule {{#check DENV-REGISTRY-PUBLISH | Publication in internal registry}}
>
> If a project or library is developed in an internal context and is not intended to be made public, then it is necessary to ensure that the package is published in the internal registry only.
63 changes: 50 additions & 13 deletions src/en/03_libraries.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,53 @@ reported to the RustSec Advisory Database.

[cargo-audit]: https://github.com/RustSec/cargo-audit

<!-- ## Unsafe code in libraries -->

<!--
<mark>TODO</mark>: `unsafe` blocks are discussed in the following chapter.
One needs to ensure that this kind of block is not misused in project
dependencies.
-->

<!--
> ### Recommendation {{#check LIBS-UNSAFE | Check for unsafe code in dependencies}}
> <mark>TODO</mark>: check that no `unsafe` blocks appear in the imported
> dependencies (with a tool?).
-->
## Checking the supply chain

Through its security working group, Rust offers a number of tools for checking the security of a program's supply-chain at library level.

### Cargo supply-chain

[Cargo-supply-chain] is the tool developed by the rust foundation's official working group, which collects all the people who can work on the libraries used by the project.

> ### Rule {{#check LIBS-SUPPLY-CHAIN | Check developers implicitly trusted}}
>
> The `cargo-supply-chain` tool must be used to find out who your organisation implicitly trust to run your project.

[cargo-supply-chain]: https://github.com/rust-secure-code/cargo-supply-chain
### Cargo vet / crev

[Cargo-vet] is a tool developed by the Mozilla Foundation that allows you to check whether the libraries you can use have been audited by trusted third parties.

> Rule {{#check LIBS-VET | Priority use of libraries that have been audited}}
>
> It is advisable to use the `cargo-vet` tool to prioritise the use of libraries which have been audited by third parties.

Security audits can be created using a tool called [Cargo-crev]. The use of this tool will not be detailed in this guide.

For more information, please consult the tool's [official documentation].

> ### Advises
>
> We recommend that you carry out security audits using the `cargo-crev` tool in order to check the security of the
> of the libraries used in your project and to share them with the community.

[cargo-vet]: https://github.com/mozilla/cargo-vet
[cargo-crev]: https://github.com/crev-dev/cargo-crev
[official documentation]: https://github.com/crev-dev/cargo-crev/blob/main/cargo-crev/src/doc/getting_started.md

## Unsafe code in libraries

[Cargo-geiger] is a tool maintained by the Rust security working group.
Its aim is to detect the use of the `unsafe` block in a project's supply chain.

The results have three levels:
1) 🔒 = No `unsafe` usage found, declares #![forbid(unsafe_code)]
2) ❓ = No `unsafe` usage found, missing #![forbid(unsafe_code)]
3) ☢️ = `unsafe` usage found

> ### Rule {{#check LIBS-UNSAFE | Check *unsafe* code in dependencies}}
>
> Use the `cargo-geiger` tool to check that uses of the `unsafe` block comply with the recommendations described in the following section of this guide.


[cargo-geiger]: https://github.com/geiger-rs/cargo-geiger
59 changes: 52 additions & 7 deletions src/en/04_language.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,19 @@ manipulations of memory pointers, the language provides the `unsafe` keyword.
> the crate root (typically `main.rs` or `lib.rs`) to generate compilation
> errors if `unsafe` is used in the code base.

> ### Information
>
>You can also obtain the same result by adding one of the two blocks below to the `Cargo.toml` file.

```toml
[lints.rust]
unsafe_code="forbid"
```

```toml
[lints.clippy]
unsafe_code = "forbid"
```
## Integer overflows

Although some verification is performed by Rust regarding potential integer
Expand Down Expand Up @@ -112,25 +125,57 @@ else { println!("{}", res); }
> specialized functions `overflowing_<op>`, `wrapping_<op>`, or the
> `Wrapping` type must be used.



## Error handling

<!-- <mark>TODO</mark>: explicit good practices in error handling. -->

The `Result` type is the preferred way of handling functions that can fail.
A `Result` object must be tested, and never ignored.

> ### Recommendation {{#check LANG-ERRDO | Use the `?` operator and do not use the `try!` macro}}
>
> The `?` operator should be used to improve readability of code.
> The `try!` macro should not be used.

### Custom Error type implementation

> ### Recommendation {{#check LANG-ERRWRAP | Implement custom `Error` type, wrapping all possible errors}}
>
> A crate can implement its own `Error` type, wrapping all possible errors.
> It must be careful to make this type exception-safe (RFC 1236), and implement
> `Error + Send + Sync + 'static` as well as `Display`.

To ensure that the above recommendation is implemented correctly, you can use the following code:
```rust
pub enum Error {
... // Implement Error enum here
}

> ### Recommendation {{#check LANG-ERRDO | Use the `?` operator and do not use the `try!` macro}}
#[cfg(test)]
mod test {
fn rfc1236<T: std::error::Error + Send + Sync + 'static >(){}

#[test]
fn test_rfc1236(){
rfc1236::<super::Error>();
}

}
```
> ### Recommendation {{#check LANG-ERR-FLAT | root positioning of type `Error` }}
>
> The `?` operator should be used to improve readability of code.
> The `try!` macro should not be used.
> It is advisable to publicly position this type at the root of your API. For example: `crate::Error`.

To do this, you can flatten the `Result` and `Error` types using the following piece of code placed at the root of the `src/lib.rs` or `src/main.rs` file:

```rust
pub use error::Error;
```

The advantage of this technique is that, in your code, you can make the position of the type in your project agnostic for the user or for yourself.

When using external libraries, you can either wrap the type or use a library such as [derive_more].

[derive_more]: https://crates.io/crates/derive_more
### Third-party library use

Third-party crates may be used to facilitate error handling. Most of them
(notably [failure], [snafu], [thiserror]) address the creation of new custom
Expand Down
140 changes: 138 additions & 2 deletions src/en/08_testfuzz.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,146 @@

## Writing tests

<mark>TODO</mark>: good practices in writing tests.
Rust offers two types of test built in by default: internal tests and integration tests.
In this section, we will discuss these two types of test as well as a rather special type of test, which is the trait implementation test.

## Fuzzing
> Recommendation {{#check TEST-DRIVEN-DEV | Adopt a test-driven development's method}}
>
> One of the best development habits is to start development by writing the set of tests to which the functionality must respond.

### Internal

Internal tests define all the tests present in the `src/` folder of a Rust project. They have the great advantage of being able to test all the functions (even private ones) if they are placed in the same file as the project.


> ### Recommendation {{#check TEST-INTERNE | Test all functions}}
>
> It is advisable to test all the functions of your programme, even those that may seem the most trivial.
>
> This way, if a future modification causes a side-effect that modifies the behaviour of another function, you will notice it much more quickly.
> This also helps to limit the number of bugs as early as possible.

```rust
// private function
fn my_function(){
... // Your code here
}

#[cfg(test)]
mod tests{
#[test]
fn test_my_function(){
... // Your tests here
}
}
```

It is also possible to ignore certain tests if they take too long to run. This can be done by adding `#[ignore]` above the test function.

```rust
#[cfg(test)]
mod tests{
#[test]
fn test_non_ignore(){ ... }

#[ignore]
#[test]
fn test_ignore() { ... }
}
```

> Important
>
> If a test is marked as ‘ignore’, it will no longer be possible to run it even if you specify its name using the `cargo test <test_name>` command.
>
> The only way it can be run is to remove the `#[ignore]` above it.

> ### Recommendation {{#check TEST-IGNORE | Limit the number of ignored tests}}
>
> It is recommended to limit the number of tests that will be ignored as much as possible.

Rust has an attribute system that allows part of the code to be compiled only when necessary.
This makes it possible to define code that will only be compiled when a particular feature is requested.

One of the basic features of any project is `test`. This allows you to describe code which will only be present when the code is compiled for testing (via the `cargo test` command).

To do this, add the `#[cfg(test)]` attribute to the line above the function or module concerned:
```rust
#[cfg(test)]
mod test{

#[test]
fn test_1(){}
}
```

> ### Rules {{#check TEST-CFG | Wrap tests in a sub-module with the attribute `#[cfg(test)]`}}
>
> All internal tests must be wrapped in a sub-module with the `#[cfg(test)]` attribute.
>
> Similarly, any potential functions you may develop to help these tests must also have the `#[cfg(test)]` attribute.
### Integration

> Attention
>
> This type of test is only available for crates which are libraries.

The integration tests are the set of tests in the `tests/` folder at the root of the crate.
In this folder, each `*.rs` file will be compiled as a different crate and the library tested will be used as if an external project were using it.

For example, if we were developing a library called `example`, we could run the following integration test:
```rust
use example::method_name;

#[test]
fn test_api(){
method_name();
}
```

These tests are run at the same time as all the other tests using the following command:
```bash
cargo test
```

> ### Rule {{#check TEST-IMPL | Check that the public behaviour of the API is as expected}}
>
> Integration tests must ensure that the library's behaviour is as expected. These tests must cover all the solution's public functions (including the import of types, functions, enums, etc.).
>
> This also ensures that the API is user-friendly.

### Implementing a trait

The example below is used to create a test to ensure that a struct or enum implements a trait.

These tests are a little unusual. If positioned in a project, they can prevent the project from compiling if they are not valid.

Here is an example of code used to ensure that an enum has the Send and Sync traits:

```rust
enum Example {}

#[cfg(test)]
mod test{
use super::*;

fn send_sync_trait<T : Sendc + Sync>(){}

#[test]
fn test_traits_impl(){
send_sync_trait::<Exemple>();
}
}
```

> ### Recommendation {{#check TEST-TRAIT | Create tests to ensure that certain traits are implemented for structures/enums}}
>
> In certain contexts, it is necessary for certain struct or enum to implement particular traits.
> In such cases, it is strongly recommended that you implement this type of test.

<!-- ## Fuzzing

### cargo-fuzz

<mark>TODO</mark>: good practices in fuzzing programs or part of programs.
-->
Loading