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

Support RS256 JWTs #99

Closed
wants to merge 23 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
9001fa5
cargo fmt
cole-h Nov 11, 2023
3e0b65a
Migrate to jsonwebtoken
grahamc Nov 6, 2023
9511afd
server: HS256 -> RS256 secrets
grahamc Nov 6, 2023
d49cd33
Make the nix flake check tests pass
grahamc Nov 7, 2023
936e5c4
Cargo.toml: set resolver = 2
cole-h Nov 7, 2023
d0dfdde
server: rename RS256 secret stuff to note that it's base64 encoded
cole-h Nov 8, 2023
7ed3f92
token: `aud` claim is a list-or-string
cole-h Nov 8, 2023
20e0a2b
server: support configuring the `iss`, `aud` claim validation
cole-h Nov 8, 2023
17b2ed7
cargo fmt
cole-h Nov 11, 2023
fcc0494
integration tests: fixup config
cole-h Nov 8, 2023
9e23916
token: fixup JWT creation
cole-h Nov 8, 2023
ba9f3f5
book: fixup docs
cole-h Nov 11, 2023
dcd7d7f
attic: nix_store tests: original_file -> _original_file
cole-h Nov 8, 2023
427ae45
server: support HS256, RS256 JWT secrets
cole-h Nov 9, 2023
bd30211
attic-token: use Not trait instead of is_false function for skipping …
cole-h Nov 11, 2023
d0c726d
book: update nixos config example
cole-h Nov 11, 2023
32e6d85
server: fixup oobe random secret generation
cole-h Nov 12, 2023
0a9d493
Move back to jwt_simple
cole-h Nov 12, 2023
756fef8
Support pubkey-only JWT configuration
cole-h Feb 26, 2024
41b42b6
Merge remote-tracking branch 'upstream/main' into rs256-support
cole-h Feb 26, 2024
c943b52
fixup: missing field
cole-h Feb 26, 2024
858e5be
Merge remote-tracking branch 'upstream/main' into rs256-support
cole-h Jul 26, 2024
119598d
fixup: regenerate test token
cole-h Jul 26, 2024
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
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions attic/src/nix_store/tests/test_nar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use crate::nix_store::StorePath;
/// Expected values for `nm1w9sdm6j6icmhd2q3260hl1w9zj6li-attic-test-no-deps`.
pub const NO_DEPS: TestNar = TestNar {
store_path: "/nix/store/nm1w9sdm6j6icmhd2q3260hl1w9zj6li-attic-test-no-deps",
original_file: include_bytes!("nar/nm1w9sdm6j6icmhd2q3260hl1w9zj6li-attic-test-no-deps"),
_original_file: include_bytes!("nar/nm1w9sdm6j6icmhd2q3260hl1w9zj6li-attic-test-no-deps"),
nar: include_bytes!("nar/nm1w9sdm6j6icmhd2q3260hl1w9zj6li-attic-test-no-deps.nar"),
export: include_bytes!("nar/nm1w9sdm6j6icmhd2q3260hl1w9zj6li-attic-test-no-deps.export"),
closure: &["nm1w9sdm6j6icmhd2q3260hl1w9zj6li-attic-test-no-deps"],
Expand All @@ -31,7 +31,7 @@ pub const NO_DEPS: TestNar = TestNar {
/// as `3k1wymic8p7h5pfcqfhh0jan8ny2a712-attic-test-with-deps-c-final`.
pub const WITH_DEPS_A: TestNar = TestNar {
store_path: "/nix/store/n7q4i7rlmbk4xz8qdsxpm6jbhrnxraq2-attic-test-with-deps-a",
original_file: include_bytes!("nar/n7q4i7rlmbk4xz8qdsxpm6jbhrnxraq2-attic-test-with-deps-a"),
_original_file: include_bytes!("nar/n7q4i7rlmbk4xz8qdsxpm6jbhrnxraq2-attic-test-with-deps-a"),
nar: include_bytes!("nar/n7q4i7rlmbk4xz8qdsxpm6jbhrnxraq2-attic-test-with-deps-a.nar"),
export: include_bytes!("nar/n7q4i7rlmbk4xz8qdsxpm6jbhrnxraq2-attic-test-with-deps-a.export"),
closure: &[
Expand All @@ -46,7 +46,7 @@ pub const WITH_DEPS_A: TestNar = TestNar {
/// This depends on `3k1wymic8p7h5pfcqfhh0jan8ny2a712-attic-test-with-deps-c-final`.
pub const WITH_DEPS_B: TestNar = TestNar {
store_path: "/nix/store/544qcchwgcgpz3xi1bbml28f8jj6009p-attic-test-with-deps-b",
original_file: include_bytes!("nar/544qcchwgcgpz3xi1bbml28f8jj6009p-attic-test-with-deps-b"),
_original_file: include_bytes!("nar/544qcchwgcgpz3xi1bbml28f8jj6009p-attic-test-with-deps-b"),
nar: include_bytes!("nar/544qcchwgcgpz3xi1bbml28f8jj6009p-attic-test-with-deps-b.nar"),
export: include_bytes!("nar/544qcchwgcgpz3xi1bbml28f8jj6009p-attic-test-with-deps-b.export"),
closure: &[
Expand All @@ -58,7 +58,7 @@ pub const WITH_DEPS_B: TestNar = TestNar {
/// Expected values for `3k1wymic8p7h5pfcqfhh0jan8ny2a712-attic-test-with-deps-c-final`.
pub const WITH_DEPS_C: TestNar = TestNar {
store_path: "/nix/store/3k1wymic8p7h5pfcqfhh0jan8ny2a712-attic-test-with-deps-c-final",
original_file: include_bytes!(
_original_file: include_bytes!(
"nar/3k1wymic8p7h5pfcqfhh0jan8ny2a712-attic-test-with-deps-c-final"
),
nar: include_bytes!("nar/3k1wymic8p7h5pfcqfhh0jan8ny2a712-attic-test-with-deps-c-final.nar"),
Expand All @@ -75,7 +75,7 @@ pub struct TestNar {
store_path: &'static str,

/// The original file.
original_file: &'static [u8],
_original_file: &'static [u8],

/// A NAR dump without path metadata.
nar: &'static [u8],
Expand Down
8 changes: 5 additions & 3 deletions book/src/admin-guide/deployment/nixos.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,16 @@ Attic provides [a NixOS module](https://github.com/zhaofengli/attic/blob/main/ni

## Generating the Credentials File

The HS256 JWT secret can be generated with the `openssl` utility:
The RS256 JWT secret can be generated with the `openssl` utility:

```bash
openssl rand 64 | base64 -w0
nix run nixpkgs#openssl -- genrsa -traditional 4096 | base64 -w0
```

Create a file on the server containing the following contents:

```
ATTIC_SERVER_TOKEN_HS256_SECRET_BASE64="output from openssl"
ATTIC_SERVER_TOKEN_RS256_SECRET_BASE64="output from above"
```

Ensure the file is only accessible by root.
Expand All @@ -47,6 +47,8 @@ You can import the module in one of two ways:
settings = {
listen = "[::]:8080";
jwt = { };
# Data chunking
#
# Warning: If you change any of the values here, it will be
Expand Down
10 changes: 6 additions & 4 deletions integration-tests/basic/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ let
serverConfigFile = config.nodes.server.services.atticd.configFile;

cmd = {
atticadm = "atticd-atticadm";
atticd = ". /etc/atticd.env && export ATTIC_SERVER_TOKEN_HS256_SECRET_BASE64 && atticd -f ${serverConfigFile}";
atticadm = ". /etc/atticd.env && export ATTIC_SERVER_TOKEN_RS256_SECRET_BASE64 && atticd-atticadm";
atticd = ". /etc/atticd.env && export ATTIC_SERVER_TOKEN_RS256_SECRET_BASE64 && atticd -f ${serverConfigFile}";
};

makeTestDerivation = pkgs.writeShellScript "make-drv" ''
Expand Down Expand Up @@ -147,7 +147,7 @@ in {

# For testing only - Don't actually do this
environment.etc."atticd.env".text = ''
ATTIC_SERVER_TOKEN_HS256_SECRET_BASE64="dGVzdCBzZWNyZXQ="
ATTIC_SERVER_TOKEN_RS256_SECRET_BASE64='LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBekhqUzFGKzlRaFFUdlJZYjZ0UGhxS09FME5VYkIraTJMOTByWVBNQVVoYVBUMmlKCmVUNk9vWFlmZWszZlZ1dXIrYks1VWFVRjhUbEx2Y1FHa1Arckd0WDRiQUpGTWJBcTF3Y25FQ3R6ZGVERHJnSlIKMGUvNWJhdXQwSS9YS0ticG9oYjNvWVhtUmR5eG9WVGE3akY1bk11ajBsd25kUTcwYTF1ZGkzMGNpYkdTWHZMagpVeGltL3ByYjUrV3ZPdjN4UnhlbDZHYmptUW1RMVBHeHVLcmx3b1ZKRnlWTjl3QmExajBDelJDcURnTFRwQWw0CjhLVWlDY2V1VUZQcmdZaW9vSVhyVExlWmxVbFVVV3FHSDBJbGFKeVUyQ05iNWJtZWM1TnZ4RDlaakFoYytucmgKRS80VzkxajdQMFVyQnp4am9NUTRlKzBPZDhmQnBvSDAwbm4xUXdJREFRQUJBb0lCQUE2RmxEK21Ed3gyM1pJRAoxSGJBbHBuQ0IwaEhvbFJVK0Q5OC96d3k5ZlplaU00VWVCTUcyTjFweE1HTWIweStqeWU4UkVJaXJNSGRsbDRECllvNEF3bmUwODZCRUp3TG81cG4vOVl2RjhqelFla1ZNLzkrZm9nRGlmUVUvZWdIMm5NZzR4bHlQNUhOWXdicmEKQ25SNVNoQlRQQzdRQWJOa0hRTFU3bUwrUHowZUlXaG9KWVRoUUpkU0g3RDB0K1QwZzVVNDdPam5qbXJaTWwxaApHOE1IUHhKMk5WU1l2N0dobnpjblZvcVVxYzlxeldXRDZXZERtV1BPNGJ1K2p0b2E2U2o4cjJtb0RRZ1A5YXNhCm93RUFJbHBmbVkxYUx2dENwWG4rejRTTWJKcHRXMlVvaktGa2dkYm9jZmtXYWdtSGZRa2xmS0dBQ0hibU9ZV24KeDRCbTU3a0NnWUVBN1dXaXJDZnBRR01hR3A2WWxMQlVUc1VJSXJOclF4UmtuRlc3dFVYd0NqWFZ5SDlTR3FqNgphTkNhYzZpaks3QVNBYXlxY1JQRjFPY2gyNmxpVmRKUHNuRGxwUjhEVXB2TzRVOVRzSTJyZ1lZYzNrSWkzVGFKClgzV0Vic1Z6Nk45WXFPSXlnVnZiTEhLS0F4Uyt4b1Z2SjkzQmdWRHN5SkxRdmhrM3VubXk3M2tDZ1lFQTNINnYKeUhOKzllOVAyOS9zMVY1eWZxSjdvdVdKV0lBTHFDYm9zOTRRSVdPSG5HRUtSSGkydWIzR0d6U2tRSzN1eTUrdQo4M0txaFJOejRVMkdOK1pLaFE0NHhNVmV4TUVvZzJVU3lTaVZ0cFdqWXBwT2Q1NnVaMzRWaFU2TWRNZS9zT0JnCnNoei84MUxUSis2cHdFZE9wV2tPVlRaMXJISlZXQmdtVk5qWjc1c0NnWUVBNVd5YjBaU2dyMEVYTVRLa2NzNFcKTENudXV0cDZodEZtaWsrd29IZCtpOStMUThFSU1BdXVOUzJrbHJJYlAxVmhrWXkxQzZMNFJkRTV2M2ZyT05XUApmL3ZyYzdDTkhZREdacWlyVUswWldvdXB5b0pQLzBsOWFXdkJHT3hxSUZ2NDZ2M3ZvV1NNWkdBdFVOenpvaGZDClhOeks3WmF2dndka0JOT0tNQVQ5RU1FQ2dZRUF3NEhaWDRWNUo1d2dWVGVDQ2RjSzhsb2tBbFpBcUNZeEw5SUEKTjZ4STVUSVpSb0dNMXhXcC81dlRrci9rZkMwOU5YUExiclZYbVZPY1JrTzFKTStmZDhjYWN1OEdqck11dHdMaAoyMWVQR0N3cWlQMkZZZTlqZVFTRkZJU0hhZXpMZll3V2NSZmhvdURudGRxYXpaRHNuU0kvd1RMZXVCOVFxU0lRCnF0NzByczBDZ1lCQ2lzV0VKdXpQUUlJNzVTVkU4UnJFZGtUeUdhOEVBOHltcStMdDVLRDhPYk80Q2JHYVFlWXkKWFpjSHVyOFg2cW1lWHZVU3MwMHBMMUdnTlJ3WCtSUjNMVDhXTm9vc0NqVDlEUW9GOFZveEtseDROVTRoUGlrTQpBc0w1RS9wYnVLeXkvSU5LTnQyT3ZPZmJYVitlTXZQdGs5c1dORjNyRTBYcU15TW9maG9NaVE9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo='
'';

services.atticd = {
Expand All @@ -156,6 +156,8 @@ in {
settings = {
listen = "[::]:8080";

jwt = { };

chunking = {
nar-size-threshold = 1;
min-size = 64 * 1024;
Expand All @@ -165,7 +167,7 @@ in {
};
};

environment.systemPackages = [ pkgs.attic-server ];
environment.systemPackages = [ pkgs.openssl pkgs.attic-server ];

networking.firewall.allowedTCPPorts = [ 8080 ];
};
Expand Down
10 changes: 5 additions & 5 deletions nixos/atticd.nix
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ let
} ''
cat $configFile

export ATTIC_SERVER_TOKEN_HS256_SECRET_BASE64="dGVzdCBzZWNyZXQ="
export ATTIC_SERVER_TOKEN_RS256_SECRET_BASE64="$(${pkgs.openssl}/bin/openssl genrsa -traditional 4096 | ${pkgs.coreutils}/bin/base64 -w0)"
export ATTIC_SERVER_DATABASE_URL="sqlite://:memory:"
${cfg.package}/bin/atticd --mode check-config -f $configFile
cat <$configFile >$out
Expand Down Expand Up @@ -78,8 +78,8 @@ in
Path to an EnvironmentFile containing required environment
variables:

- ATTIC_SERVER_TOKEN_HS256_SECRET_BASE64: The Base64-encoded version of the
HS256 JWT secret. Generate it with `openssl rand 64 | base64 -w0`.
- ATTIC_SERVER_TOKEN_RS256_SECRET_BASE64: The base64-encoded RSA PEM PKCS1 of the
RS256 JWT secret. Generate it with `openssl genrsa -traditional 4096 | base64 -w0`.
'';
type = types.nullOr types.path;
default = null;
Expand Down Expand Up @@ -153,9 +153,9 @@ in
message = ''
<option>services.atticd.credentialsFile</option> is not set.

Run `openssl rand 64 | base64 -w0` and create a file with the following contents:
Run `openssl genrsa -traditional -out private_key.pem 4096 | base64 -w0` and create a file with the following contents:

ATTIC_SERVER_TOKEN_HS256_SECRET_BASE64="output from command"
ATTIC_SERVER_TOKEN_RS256_SECRET="output from command"

Then, set `services.atticd.credentialsFile` to the quoted absolute path of the file.
'';
Expand Down
1 change: 1 addition & 0 deletions server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ tracing-subscriber = { version = "0.3.17", features = [ "json" ] }
uuid = { version = "1.3.3", features = ["v4"] }
console-subscriber = "0.2.0"
xdg = "2.5.0"
rsa = "0.9.3"

[dependencies.async-compression]
version = "0.4.0"
Expand Down
15 changes: 12 additions & 3 deletions server/src/access/http.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//! HTTP middlewares for access control.

use attic::cache::CacheName;
use attic_token::util::parse_authorization_header;
use axum::{extract::Request, middleware::Next, response::Response};
use sea_orm::DatabaseConnection;
use tokio::sync::OnceCell;
Expand All @@ -8,8 +10,6 @@ use crate::access::{CachePermission, Token};
use crate::database::{entity::cache::CacheModel, AtticDatabase};
use crate::error::ServerResult;
use crate::{RequestState, State};
use attic::cache::CacheName;
use attic_token::util::parse_authorization_header;

/// Auth state.
#[derive(Debug)]
Expand Down Expand Up @@ -101,10 +101,19 @@ pub async fn apply_auth(req: Request, next: Next) -> Response {
.and_then(parse_authorization_header)
.and_then(|jwt| {
let state = req.extensions().get::<State>().unwrap();
let res_token = Token::from_jwt(&jwt, &state.config.token_hs256_secret);
let signature_type = state.config.jwt.signing_config.clone().into();

let res_token = Token::from_jwt(
&jwt,
&signature_type,
&state.config.jwt.token_bound_issuer,
&state.config.jwt.token_bound_audiences,
);

if let Err(e) = &res_token {
tracing::debug!("Ignoring bad JWT token: {}", e);
}

res_token.ok()
});

Expand Down
8 changes: 7 additions & 1 deletion server/src/adm/command/make_token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,13 @@ pub async fn run(config: Config, opts: Opts) -> Result<()> {
if sub.dump_claims {
println!("{}", serde_json::to_string(token.opaque_claims())?);
} else {
let encoded_token = token.encode(&config.token_hs256_secret)?;
let signature_type = config.jwt.signing_config.into();

let encoded_token = token.encode(
&signature_type,
&config.jwt.token_bound_issuer,
&config.jwt.token_bound_audiences,
)?;
println!("{}", encoded_token);
}

Expand Down
43 changes: 35 additions & 8 deletions server/src/config-template.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,6 @@ allowed-hosts = []
# cache.
#require-proof-of-possession = true

# JWT signing token
#
# Set this to the Base64 encoding of some random data.
# You can also set it via the `ATTIC_SERVER_TOKEN_HS256_SECRET_BASE64` environment
# variable.
token-hs256-secret-base64 = "%token_hs256_secret_base64%"

# Database connection
[database]
# Connection URL
Expand Down Expand Up @@ -85,7 +78,7 @@ path = "%storage_path%"
#[storage.credentials]
# access_key_id = ""
# secret_access_key = ""

# Data chunking
#
# Warning: If you change any of the values here, it will be
Expand Down Expand Up @@ -134,3 +127,37 @@ interval = "12 hours"
# Zero (default) means time-based garbage-collection is
# disabled by default. You can enable it on a per-cache basis.
#default-retention-period = "6 months"

[jwt]
# WARNING: Changing _anything_ in this section will break any existing
# tokens. If you need to regenerate them, ensure that you use the the
# correct secret and include the `iss` and `aud` claims.

# JWT `iss` claim
#
# Set this to the JWT issuer that you want to validate.
# If this is set, all received JWTs will validate that the `iss` claim
# matches this value.
#token-bound-issuer = "some-issuer"

# JWT `aud` claim
#
# Set this to the JWT audience(s) that you want to validate.
# If this is set, all received JWTs will validate that the `aud` claim
# contains at least one of these values.
#token-bound-audiences = ["some-audience1", "some-audience2"]

[jwt.signing]
# JWT RS256 secret key
#
# Set this to the base64-encoded private half of an RSA PEM PKCS1 key.
# You can also set it via the `ATTIC_SERVER_TOKEN_RS256_SECRET_BASE64`
# environment variable.
token-rs256-secret-base64 = "%token_rs256_secret_base64%"

# JWT HS256 secret key
#
# Set this to the base64-encoded HMAC secret key.
# You can also set it via the `ATTIC_SERVER_TOKEN_HS256_SECRET_BASE64`
# environment variable.
#token-hs256-secret-base64 = ""
Loading