Skip to content

Commit

Permalink
🎉 initial release
Browse files Browse the repository at this point in the history
  • Loading branch information
liamdawson committed Apr 14, 2019
0 parents commit 601368a
Show file tree
Hide file tree
Showing 18 changed files with 888 additions and 0 deletions.
12 changes: 12 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
root = true

[*]
insert_final_newline = true
charset = utf-8
indent_style = space
end_of_line = lf
tab_width = 2

[*.rs]
tab_width = 4
trim_trailing_whitespace = true
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/target
**/*.rs.bk
Cargo.lock
22 changes: 22 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[package]
name = "authorized_keys"
version = "0.9.0"
authors = ["Liam Dawson <[email protected]>"]
edition = "2018"
description = "Parse and manipulate OpenSSH `authorized_keys` files"
homepage = "https://github.com/hubauth/authorized_keys"
repository = "https://github.com/hubauth/authorized_keys.git"
readme = "README.md"
keywords = ["openssh", "authorized_keys"]
categories = ["config", "parser-implementations"]
license = "MIT OR Apache-2.0"

[dependencies]
pest = { version = "2.0", optional = true }
pest_derive = { version = "2.0", optional = true }
data-encoding = { version = "2.1", optional = true }

[features]
default = ['parse']
parse = ['pest', 'pest_derive']
key_encoding = ['data-encoding']
19 changes: 19 additions & 0 deletions Justfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
check: check-fmt check-tests check-clippy check-docs

check-fmt:
cargo fmt -- --check

check-tests:
cargo test
cargo test --features "key_encoding"

check-clippy:
cargo clippy --all-targets --all-features -- -D warnings

check-docs:
cargo doc

fix: fix-fmt

fix-fmt:
cargo fmt
52 changes: 52 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# authorized_keys-rs

Parse and manipulate OpenSSH `authorized_keys` files.

## Installation

`Cargo.toml`:

```toml
[features]
authorized_keys = "0.9"
```

## Features

* Parse `authorized_keys` files
* Parse individual lines from `authorized_keys` files
* Change the parts of a line (options, key type, encoded key, comments)
with convenience methods
* Write `authorized_keys` files in the correct format
* Depends on `pest`, `pest_derive` by default:
* `data-encoding` if you want to edit keys as bytes
* No dependencies if you disable the default `parsing` feature

## Contributing

### Requirements

* `rustc`/`cargo` 1.34+ ([`rustup`] recommended)
* `cargo clippy --version` 0.0.212+
* `cargo fmt --version` 1.0.3-stable+

### Process

1. Open an issue for the contribution you'd like to make
* Check for duplicates first :slightly_smiling_face:
1. Fork the repository, make your changes on a branch
1. Add tests for new features
1. Ensure `just check` passes locally
1. Open a PR for the changes
* Add yourself as an author to `Cargo.toml` and `README.md`, if you'd like!

## Authors

* [Liam Dawson](https://github.com/liamdawson)

## License

Rustfmt is distributed under the terms of both the MIT license and the
Apache License (Version 2.0).

See [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT) for details.
32 changes: 32 additions & 0 deletions examples/harden_keys.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Take a list of GitHub user keys, and restrict them so they can only run the command uptime (and cannot port-forward/etc)

extern crate authorized_keys;

use authorized_keys::openssh::v2::*;
use std::iter::FromIterator;
use std::str::FromStr;

const SAMPLE_FILE: &str = include_str!("./harden_keys_data.txt");

fn main() {
let key_file =
AuthorizedKeysFile::from_str(SAMPLE_FILE).expect("that was a valid authorized_keys file!");

println!("Before:\n{}", SAMPLE_FILE);

println!(
"After:\n{}",
AuthorizedKeysFile::from_iter(key_file.into_iter().flat_map(|line| {
match line {
AuthorizedKeysFileLine::Comment(_) => None,
AuthorizedKeysFileLine::AuthorizedKey(key) => {
Some(AuthorizedKeysFileLine::AuthorizedKey(
key.clear_options()
.option_name("restrict".to_owned())
.option(("command".to_owned(), Some("uptime".to_owned()))),
))
}
}
}))
);
}
6 changes: 6 additions & 0 deletions examples/harden_keys_data.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
agent-forwarding ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCve8gcxEQKaue8XynCD8rG4KowiW3RGFJzOWhAvCi+WTRlujUE8zRLBBtsXcHCC85+N6g+ZjMLJql1JIhCncov7Lp5jBAiBi847RegvbALeux774PHGpc6A+xPMpEDbfMWjVXwbPG/B/A+hA1G/wMEYmfzkRi4DUyCS7wPBNWM9510WxNYZzjk0zA3o+/ezCuaeX4xzBFXkX84Z3J8bI79yuRBhqSm0MVGyh2R+w75YZbaSGhqgeWXhKtV0ZtVKOP6/nDaJ4kx2f2RguqF/E2yp/liyiggDGz53kGfJ5nizsr6SwB1qIh85m/rEiYmWib+ZrFBc1KyefV9Tpztc1dr0RcVRuXAfb+nxAVuZbDTL1A2nY9+g0byEVX6jm6uEaJ2yascaqyw0NXjhTsXR9v4H50z4wMp0l1vtCXHF3dawKdMIHjia+feFmopT8QZJm5omK3xemTqwRrdiWp6IdADwN3q1nqg1wKm5D702hzAchRJ6BOMINRBtkMn+2mQLjbmKiXoEm6Yxq1RKCG7w89wrm9tGdxrikJ/dxcdWQt2gY9YubrjsW3BpLqA+Y73KX3dv3STwKgr2kEscZO1OxE2kqHYYCGBWWR6BmXLtQ2FqRtehTRQD2QR0aIF1l/7S06HqfwC4KExV1bGyIiq9JCfAzp3KfS/H4VeRVln7WhduQ==
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDMztcDi7sGTxckdd6GrxI7LVVwf98rnYMvnjLWL/jxiS5Iz0F4dWu3nrms3iVVD6hY4eLaNNuAocfa0CbcAQWNddomjLn+6OnQGDpm5q5I2KaOm+xgGmpj69kyrBx4QtMq0e2OcPMTY/qBTBYbILg2pW8TEx9CjuF5RJSpKbeWE1KPJGJI7pd2D/1G5ZTvbPiq6lNk3O0VhXPRw1Jlkb2ND470vif3ymC0FeA6iLyguTZY+OYIqismu1gD1UFSWNfakIgWmvIPsoOs/Z46mj7GEk82CQF6qe5fOioPle4B9zNnXWAyxiJwEnF7TAh/vul1aeGVOT58AA61wjIYp14R
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCvadoe8lkYt2hhTVrMrLUM7ZWmE3a3GMOUstixaWaZ+tSvk6DPIcv9AoD69YNJ+eix0BTAHovhI0WHNyWRvJCJnzJMv7bXTpwN5vNe5PjSgVdQLaZKZSLBJVOr0lMzb3SlOZD4a2TfhbAhk98ItcdlOBiDSYHB2u57kwfqAZleyW4okWBZ1zRTZjaCA6yvAVYrFFvQ+jRLSkvAvuhjH+EpvF+kJvJ7OtIZm7g05fx7HBWIL/lVJnUOYm4SFjh4vOEYsF2dE+8FlwAwt3YJiykU1kSGtOcwXWdTYqEEnhqfsi6P945UagqOvf7q2PWnQrMvoICdTLPbzU3ciInVziqt
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDNqTam+/remPBe8o9g1eaRhT21Hp8FOPQ+FrYD5/9/E/pHm+0mrhx50ACCqslSvWKz2sk4urWk6HMj75icjyWXQhTMa8wiWRClX/Ni3HELEkM1o4lGaEMaY8TVhd8Vdd0RlckGpdt6e8oiYNplbnQwe/vqsz5Wgu2vhh8k02DtohGEPKtmsCB2s4FX1bpmRG6J0cImeO1umFubnkCF5tFPhnuuSW2xuKG94bYL6TYz7C2bHJXCk4/s/Bas9n4Pp+NYy/vYYk1KJWbyOmsMkfVxd0QJxvroLKAt1bwOWIwLN0+GZnoOgG3xX7/bZJirBaDnylt3dwQr0Mps11EI4QOp
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDhdFJxWKJHGaTmihR6kpgseQQ8fc1NFTHUlpUW9M5QJErZ9CbPVHKXAc4gFFB85PiY2JFJyORhmZiUMPqlGRZ/CwdYQ7AoCSjaGqxi06jRynywYPkCyw/YzE+R0ZgSCrQsGrdenPz9NmWEXfmY1BDol4gO5zecg5X+EMbG35Kwhm6FqcX5j8aF+d09cQtAflFGUTAX/QUFLxZXRdtxCj7SdczFczCdsEY1SCG8LUALXdBSLJNsqwJaEUvvqkjkZuD6cAPU6pDCgifQeegzpAqVTwRE+mDG4J6p7mEqMxAOrqd9X+PZCAmENS2fo9C4BGqESe7F5vf5P/RKkiZE2pSz
agent-forwarding ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC7hGnhOradeDBJ6ibZYXBtCti7UGmr9B0R9QjlQlAg35gW4bpBkT+Vij01jMP+dZMDZcLxfEOmmF9QFy2KMnxa42XXb8EeXovYiNvWeqAWfzQYKo7r2pgtwMLlN+ITRktE5tnEu7F5vgkSuDTyk0s2fvrKU5u88IID0k4aqBLp14oOAIur4Z+Zm4a545XOhJ7bEvM+nn/GGlzXATq7+Vd5DTcR/hn+Hi+YOuVT7BAmRsRTrCHT0xF9NiLZTw8AevUuUiCkoGQeyxU6p0D65emqWE9Wgu7xPR1B0DVV1t4zDwWULAvmFyLgwISL2WW7RwJfckTj3VCnSD6/4OEVFTqbISUM0FPNl2s9mme9yq3e5JR8ZpcxnLRybE4Gt+8ykiUgNcBxHsM2iJB5Ine512Vip5SiVZcRBTTY7bdy0wouvMvaL8UeNWql9q/9J+37T3+AYYHesr4zsdvD0NbVVtDKcgG3YhFIs6+B5rE99vYe8QnPzg2RiSxQz1yPaZFRbfMkAGS9G9mzbouxZSYfNOONlp7Xa0xnVqq9pAYDUmZf5JzpOvwmSuhTeQ+xJTxfZ7WtKpWnSdw5khx9N0i9ex4hNo8jcLLPBqwGztAkZbFHiFqGbA9qmkCMcuTpJkPYmviWFrdNoH+JiTJMFKVojGwGYtScEG5QwgdYBpxeeUhSjQ==
26 changes: 26 additions & 0 deletions examples/sanitize_keys.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Remove all comments and other human-readable data from a list of keys

extern crate authorized_keys;

use authorized_keys::openssh::v2::*;
use std::iter::FromIterator;
use std::str::FromStr;

const SAMPLE_FILE: &str = include_str!("./sanitize_keys_data.txt");

fn main() {
let key_file =
AuthorizedKeysFile::from_str(SAMPLE_FILE).expect("that was a valid authorized_keys file!");

println!("Before:\n{}", SAMPLE_FILE);

println!(
"After:\n{}",
AuthorizedKeysFile::from_iter(key_file.into_iter().flat_map(|line| match line {
AuthorizedKeysFileLine::Comment(_) => None,
AuthorizedKeysFileLine::AuthorizedKey(key) => {
Some(AuthorizedKeysFileLine::AuthorizedKey(key.remove_comments()))
}
}))
);
}
12 changes: 12 additions & 0 deletions examples/sanitize_keys_data.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# this is a sample file showing the process of removing all "human
# readable information", for whatever purposes.

ssh-rsa AAAAuQ== here are
ssh-rsa AAAAp14R some details
# here's a comment about the following keys
ssh-rsa AAAAziqt for humans to read

ssh-rsa AAAA4QOp or non-humans, I guess
ssh-rsa AAAA2pSz just probably not raw

ssh-rsa AAAAjQ== CPUs
16 changes: 16 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//! Parse and manipulate OpenSSH `authorized_keys` files.
//!
//! This [crate] provides parsers and manipulations for OpenSSH
//! `authorized_key` files. [Example usages] include validating,
//! sanitizing, and hardening `authorized_keys` files.
//!
//! [crate]: https://crates.io/crates/authorized_keys
//! [Example usages]: https://github.com/hubauth/authorized_keys/blob/master/examples/
#![deny(missing_docs)]
#![warn(clippy::all, clippy::pedantic)]

#[macro_use]
extern crate pest_derive;

pub mod openssh;
3 changes: 3 additions & 0 deletions src/openssh/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
//! Formats and functions for OpenSSH `authorized_keys` files
pub mod v2;
98 changes: 98 additions & 0 deletions src/openssh/v2/display.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
use super::models::{AuthorizedKey, AuthorizedKeysFile, AuthorizedKeysFileLine};
use std::fmt::{Display, Error, Formatter};

impl Display for AuthorizedKey {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
let options = self.options_string();

if !options.is_empty() {
write!(f, "{} ", options)?;
}
write!(f, "{}", self.key_def())?;
if !self.comments.trim().is_empty() {
write!(f, " {}", self.comments.trim())?;
}

Ok(())
}
}

impl Display for AuthorizedKeysFile {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
for line in &self.lines {
match line {
AuthorizedKeysFileLine::Comment(val) => writeln!(f, "{}", val)?,
AuthorizedKeysFileLine::AuthorizedKey(val) => writeln!(f, "{}", val)?,
}
}

Ok(())
}
}

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

#[test]
fn it_writes_a_key() {
let mut subject = AuthorizedKey::default();
subject.key_type = "ssh-ed25519".to_owned();
subject.encoded_key =
"AAAAC3NzaC1lZDI1NTE5AAAAIGgqo1o+dOHqeIc7A5MG53s5iYwpMQm7f3hnn+uxtHUM".to_owned();

assert_eq!(
&subject.to_string(),
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGgqo1o+dOHqeIc7A5MG53s5iYwpMQm7f3hnn+uxtHUM"
);
}

#[test]
fn it_writes_a_key_with_comments() {
let mut subject = AuthorizedKey::default();
subject.key_type = "ssh-ed25519".to_owned();
subject.encoded_key =
"AAAAC3NzaC1lZDI1NTE5AAAAIGgqo1o+dOHqeIc7A5MG53s5iYwpMQm7f3hnn+uxtHUM".to_owned();
subject.comments = " the quick brown fox jumped over the lazy dog ".to_owned();

assert_eq!(&subject.to_string(), "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGgqo1o+dOHqeIc7A5MG53s5iYwpMQm7f3hnn+uxtHUM the quick brown fox jumped over the lazy dog");
}

#[test]
fn it_writes_a_key_with_an_option() {
let mut subject = AuthorizedKey::default();
subject
.options
.push(("no-agent-forwarding".to_owned(), None));
subject.key_type = "ssh-ed25519".to_owned();
subject.encoded_key =
"AAAAC3NzaC1lZDI1NTE5AAAAIGgqo1o+dOHqeIc7A5MG53s5iYwpMQm7f3hnn+uxtHUM".to_owned();

assert_eq!(&subject.to_string(), "no-agent-forwarding ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGgqo1o+dOHqeIc7A5MG53s5iYwpMQm7f3hnn+uxtHUM");
}

#[test]
fn it_writes_a_complex_key() {
let mut subject = AuthorizedKey::default();
subject
.options
.push(("no-agent-forwarding".to_owned(), None));
subject.options.push((
"command".to_owned(),
Some("echo \\\"Hello, world!\\\"".to_owned()),
));
subject
.options
.push(("environment".to_owned(), Some("PATH=/bin:/sbin".to_owned())));
subject.options.push((
"environment".to_owned(),
Some("LOGNAME=ssh-user".to_owned()),
));
subject.key_type = "ssh-ed25519".to_owned();
subject.encoded_key =
"AAAAC3NzaC1lZDI1NTE5AAAAIGgqo1o+dOHqeIc7A5MG53s5iYwpMQm7f3hnn+uxtHUM".to_owned();
subject.comments = "this is a more complex example".to_owned();

assert_eq!(&subject.to_string(), "no-agent-forwarding,command=\"echo \\\"Hello, world!\\\"\",environment=\"PATH=/bin:/sbin\",environment=\"LOGNAME=ssh-user\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGgqo1o+dOHqeIc7A5MG53s5iYwpMQm7f3hnn+uxtHUM this is a more complex example");
}
}
Loading

0 comments on commit 601368a

Please sign in to comment.