Skip to content

Commit

Permalink
Refactor config and improve example
Browse files Browse the repository at this point in the history
  • Loading branch information
Palladinium committed Dec 26, 2023
1 parent 05446d6 commit b3fc72a
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 53 deletions.
127 changes: 89 additions & 38 deletions examples/config.toml
Original file line number Diff line number Diff line change
@@ -1,58 +1,109 @@
[server]
## The root URL of this registry.

### The externally-reachable root URL of this registry.
## This is required, and it will be used to populate the response for the /index/config.json file.
##
## NOTE: If you get mysterious 404s on publish, try removing trailing slashes from this setting,
## or set RUST_DEBUG=quartermaster=debug and inspect the logs to see what URLs cargo is requesting.
root_url = "https://foo.bar"
## For example, with the above setting, set the registry URL in `.cargo/config.toml` to
##
## For example, with the setting below, you shoould set the registry URL in `.cargo/config.toml` to
## `sparse+https://foo.bar/index/`

## Addresses to bind to. Defaults to 0.0.0.0:80 and [::]:80.
bind = ["10.1.1.1:80"]
root_url = "https://foo.bar"


### Addresses to bind to. Defaults to 0.0.0.0:80 and [::]:80.

#bind = ["10.1.1.1:80"]


[auth]

## Disable auth entirely, and allow all requests. This is generally a bad idea.
# type = "none"
### Disable auth entirely, and allow all requests.
## This is generally a bad idea, even with a reverse proxy or VPN in front of Quartermaster.

type = "none"

## Simple auth based on a single token.

### Simple auth based on a single token that Quartermaster generates.
## This is likely the right auth option if you are using the local storage backend.
##
## Quartermaster will generate a token on startup and store it in `token_file`.
## You can then give that token to `cargo login` to authenticate your requests.
## Make sure you prepend "Bearer " to the token when you pass it to cargo.
type = "auto_token"
## The file to store the token in. Optional, defaults to `/var/lib/quartermaster/token`.
token_file = "/crate-token"
## NOTE: Make sure you prepend "Bearer " to the token when you pass it to cargo.

#type = "auto_token"

## The file to store the token in. Optional, defaults to `/var/lib/quartermaster/token`.

#token_file = "/crate-token"


### Simple auth based on a single token provided by the configuration.
## This is likely the right auth option if you want Quartermaster to run statelessly in a container
## and rely on a remote storage backend like S3.
##
## The token can be an arbitrary string, and should be sufficiently long and (cryptographically secure) random.
## An easy way to generate one on Linux is:
## `openssl rand -base64 64 | tr -d '\n'`

#type = "token"
#token = "a very secure token"


[storage]

## Local filesystem storage
### Local filesystem storage.
## Stores all crates and index files using local files.

type = "local"
path = "/crates"

## S3-backed storage.
### S3 storage.
## Stores all crates and index files using S3. The directory layout is identical to the local storage.
## The bucket and region keys are required.
## If none of the specific auth methods are specified, rust-s3 will attempt to find valid credentials automatically.
# type = "s3"
# bucket = "my-crates"
# region = "ap-southeast-2"

## Explicit access/secret key authentication, and optionally a security and session token.
# aws_access_key_id = "foo"
# aws_secret_access_key = "shoosh"
# aws_security_token = "foo"
# aws_session_token = "foo"

## Fetch access key through an STS request.
## The unprefixed `AWS_ROLE_ARN` `AWS_WEB_IDENTITY_TOKEN_FILE` environment variables are also checked if the corresponding config value isn't set.
# sts_session_name = "quartermaster"
# sts_role_arn = "foo"
# sts_web_identity_token_file = "foo"

## Use profile credentials. This reads from ~/.aws/credentials.
## If profile_section is specified, use that particular section in the credential file.
# use_profile_credentials = true
# profile_section = "quartermaster"

## Use instance credentials.
# use_instance_credentials = true

#type = "s3"
#bucket = "my-crates"
#region = "ap-southeast-2"


### Automatically try to find credentials.
## If none of the specific credential settings are specified and `auto_credentials` is true,
## rust-s3 will attempt to find valid credentials automatically by looking at standard
## used environment variables and config files.
## This generally does the right thing and is fine for testing, but you should turn it off and
## specify the credentials manually in a production environment.

#auto_credentials = true


### Explicit access/secret key credentials, and optionally a security/session token.
## The environment variables `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_SECURITY_TOKEN`
## and `AWS_SESSION_TOKEN` are also checked if the corresponding config value isn't set.

#aws_access_key_id = "foo"
#aws_secret_access_key = "shoosh"
#aws_security_token = "foo"
#aws_session_token = "foo"


### Fetch credentials through an STS request.
## The environment variables `AWS_ROLE_ARN` and `AWS_WEB_IDENTITY_TOKEN_FILE` are also checked if
## the corresponding config value isn't set.

#sts_session_name = "quartermaster"
#sts_role_arn = "foo"
#sts_web_identity_token_file = "foo"


### Use profile credentials. This reads from `~/.aws/credentials`.
## If `profile_section` is specified, use that particular section in the credential file instead of
## the top-level section.

#use_profile_credentials = true
#profile_section = "quartermaster"

### Fetche credentials from an EC2 instance's metadata.

#use_instance_credentials = true
26 changes: 19 additions & 7 deletions src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ use tracing::{info, warn};

use crate::error::ErrorResponse;

pub mod auto_token;
pub mod token;
pub mod token_file;

pub enum Auth {
None,
TokenList(auto_token::AutoToken),
TokenFile(token_file::TokenFile),
Token(token::Token),
}

impl Auth {
Expand All @@ -22,26 +24,36 @@ impl Auth {
Ok(Self::None)
}

crate::config::Auth::AutoToken(token) => {
info!("Using token list authentication");
crate::config::Auth::TokenFile(token_file) => {
info!("Using token file authentication");

Ok(Self::TokenList(auto_token::AutoToken::new(token).await?))
Ok(Self::TokenFile(
token_file::TokenFile::new(token_file).await?,
))
}

crate::config::Auth::Token(token) => {
info!("Using token authentication");

Ok(Self::Token(token::Token::new(token)))
}
}
}

pub fn auth_required(&self) -> bool {
match self {
Self::None => false,
Self::TokenList(_) => true,
Self::TokenFile(_) => true,
Self::Token(_) => true,
}
}

// TODO: Implement more granular authorization
pub fn authorize(&self, token: Option<&str>) -> Result<(), Error> {
match self {
Self::None => Ok(()),
Self::TokenList(tokens) => tokens.authorize(token),
Self::TokenFile(token_file) => token_file.authorize(token),
Self::Token(token_auth) => token_auth.authorize(token),
}
}
}
Expand Down
35 changes: 35 additions & 0 deletions src/auth/token.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use std::{
fmt::{self, Debug, Formatter},
};

use crate::auth::Error;

pub struct Token {
token: String,
}

impl Token {
pub fn new(config: &crate::config::TokenAuth) -> Self {
Self {
token: config.token.clone(),
}
}

pub fn authorize(&self, token: Option<&str>) -> Result<(), Error> {
let token = token.ok_or(Error::Unauthorized)?;

if self.token == token {
Ok(())
} else {
Err(Error::Forbidden)
}
}
}

impl Debug for Token {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_struct("Token")
.field("token", &"<REDACTED>")
.finish()
}
}
14 changes: 8 additions & 6 deletions src/auth/auto_token.rs → src/auth/token_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ use tracing::info;

use crate::auth::Error;

pub struct AutoToken {
pub struct TokenFile {
token: String,
}

impl AutoToken {
pub async fn new(config: &crate::config::AutoTokenAuth) -> Result<Self, Error> {
impl TokenFile {
pub async fn new(config: &crate::config::TokenFileAuth) -> Result<Self, Error> {
let token = match tokio::fs::read_to_string(&config.token_file).await {
Ok(token) => token,
Err(e) => {
Expand All @@ -24,7 +24,9 @@ impl AutoToken {
let new_token = generate_token()?;

if let Some(parent) = config.token_file.parent() {
tokio::fs::create_dir_all(&parent).await.map_err(Error::Io)?;
tokio::fs::create_dir_all(&parent)
.await
.map_err(Error::Io)?;
}

save_token(&config.token_file, &new_token).await?;
Expand All @@ -50,9 +52,9 @@ impl AutoToken {
}
}

impl Debug for AutoToken {
impl Debug for TokenFile {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_struct("AutoToken")
f.debug_struct("TokenFile")
.field("token", &"<REDACTED>")
.finish()
}
Expand Down
21 changes: 19 additions & 2 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::{
env,
fmt::{self, Debug, Formatter},
net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6},
path::PathBuf,
};
Expand Down Expand Up @@ -36,11 +37,12 @@ fn default_bind() -> Vec<SocketAddr> {
#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
pub enum Auth {
None,
AutoToken(AutoTokenAuth),
TokenFile(TokenFileAuth),
Token(TokenAuth),
}

#[derive(Clone, Debug, Deserialize)]
pub struct AutoTokenAuth {
pub struct TokenFileAuth {
#[serde(default = "default_token_path")]
pub token_file: PathBuf,
}
Expand All @@ -49,6 +51,19 @@ fn default_token_path() -> PathBuf {
PathBuf::from("/var/lib/quartermaster/token")
}

#[derive(Clone, Deserialize)]
pub struct TokenAuth {
pub token: String,
}

impl Debug for TokenAuth {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_struct("TokenAuth")
.field("token", &"<REDACTED>")
.finish()
}
}

#[derive(Clone, Debug, Deserialize)]
#[serde(tag = "type", rename_all = "lowercase", deny_unknown_fields)]
pub enum Storage {
Expand All @@ -70,6 +85,8 @@ pub struct S3Storage {
pub bucket: String,
pub region: String,

pub auto_credentials: bool,

pub aws_access_key_id: Option<String>,
pub aws_secret_access_key: Option<String>,
pub aws_security_token: Option<String>,
Expand Down
12 changes: 12 additions & 0 deletions src/storage/s3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::{borrow::Cow, env};

use axum::body::Body;
use relative_path::RelativePathBuf;
use tracing::info;

use crate::{crate_name::CrateName, index::IndexFile, storage::Error};

Expand Down Expand Up @@ -93,6 +94,8 @@ pub fn load_credentials(
config: &crate::config::S3Storage,
) -> Result<s3::creds::Credentials, s3::creds::error::CredentialsError> {
if let Some(session_name) = &config.sts_session_name {
info!("Fetching STS credentials");

let role_arn = if let Some(role_arn) = &config.sts_role_arn {
Cow::Borrowed(role_arn)
} else {
Expand All @@ -112,13 +115,22 @@ pub fn load_credentials(
}

if config.use_profile_credentials {
info!("Using profile credentials");
return s3::creds::Credentials::from_profile(config.profile_section.as_deref());
}

if config.use_instance_credentials {
info!("Using instance metadata credentials");
return s3::creds::Credentials::from_instance_metadata();
}

// Credentials::new will try automatically detecting credentials if the access_key is None
match (&config.aws_access_key_id, config.auto_credentials) {
(Some(_), _) => info!("Using explicit credentials"),
(None, true) => info!("Automatically detecting credentials"),
(None, false) => return Err(s3::creds::error::CredentialsError::ConfigNotFound),
}

s3::creds::Credentials::new(
config.aws_access_key_id.as_deref(),
config.aws_secret_access_key.as_deref(),
Expand Down

0 comments on commit b3fc72a

Please sign in to comment.