Skip to content

Commit

Permalink
Merge pull request #177 from zhaofengli/rs256-support
Browse files Browse the repository at this point in the history
Support RS256 JWTs
  • Loading branch information
zhaofengli authored Oct 5, 2024
2 parents 61ebdef + d2363b5 commit 858120c
Show file tree
Hide file tree
Showing 14 changed files with 501 additions and 134 deletions.
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 @@ -79,8 +79,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 @@ -154,9 +154,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 @@ -60,6 +60,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

0 comments on commit 858120c

Please sign in to comment.