diff --git a/Cargo.lock b/Cargo.lock
index 165b9a9..b4aa5a1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -317,6 +317,7 @@ dependencies = [
"maybe-owned",
"rand",
"regex",
+ "rsa 0.9.6",
"ryu",
"sea-orm",
"sea-orm-migration",
@@ -348,6 +349,7 @@ dependencies = [
"jwt-simple",
"lazy_static",
"regex",
+ "rsa 0.9.6",
"serde",
"serde_with",
"tracing",
diff --git a/attic/src/nix_store/tests/test_nar.rs b/attic/src/nix_store/tests/test_nar.rs
index ae0759a..36d8900 100644
--- a/attic/src/nix_store/tests/test_nar.rs
+++ b/attic/src/nix_store/tests/test_nar.rs
@@ -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"],
@@ -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: &[
@@ -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: &[
@@ -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"),
@@ -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],
diff --git a/book/src/admin-guide/deployment/nixos.md b/book/src/admin-guide/deployment/nixos.md
index 27615d2..b30f7de 100644
--- a/book/src/admin-guide/deployment/nixos.md
+++ b/book/src/admin-guide/deployment/nixos.md
@@ -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.
@@ -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
diff --git a/integration-tests/basic/default.nix b/integration-tests/basic/default.nix
index d6b17bc..ba18071 100644
--- a/integration-tests/basic/default.nix
+++ b/integration-tests/basic/default.nix
@@ -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" ''
@@ -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 = {
@@ -156,6 +156,8 @@ in {
settings = {
listen = "[::]:8080";
+ jwt = { };
+
chunking = {
nar-size-threshold = 1;
min-size = 64 * 1024;
@@ -165,7 +167,7 @@ in {
};
};
- environment.systemPackages = [ pkgs.attic-server ];
+ environment.systemPackages = [ pkgs.openssl pkgs.attic-server ];
networking.firewall.allowedTCPPorts = [ 8080 ];
};
diff --git a/nixos/atticd.nix b/nixos/atticd.nix
index f14141a..223af05 100644
--- a/nixos/atticd.nix
+++ b/nixos/atticd.nix
@@ -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
@@ -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;
@@ -153,9 +153,9 @@ in
message = ''
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.
'';
diff --git a/server/Cargo.toml b/server/Cargo.toml
index a4794d3..766eaef 100644
--- a/server/Cargo.toml
+++ b/server/Cargo.toml
@@ -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"
diff --git a/server/src/access/http.rs b/server/src/access/http.rs
index 1d108ff..5ba064a 100644
--- a/server/src/access/http.rs
+++ b/server/src/access/http.rs
@@ -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;
@@ -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)]
@@ -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::().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()
});
diff --git a/server/src/adm/command/make_token.rs b/server/src/adm/command/make_token.rs
index 1cfef1c..c027d41 100644
--- a/server/src/adm/command/make_token.rs
+++ b/server/src/adm/command/make_token.rs
@@ -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);
}
diff --git a/server/src/config-template.toml b/server/src/config-template.toml
index 84127d2..f81f61d 100644
--- a/server/src/config-template.toml
+++ b/server/src/config-template.toml
@@ -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
@@ -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
@@ -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 = ""
diff --git a/server/src/config.rs b/server/src/config.rs
index 7f24ba8..321034e 100644
--- a/server/src/config.rs
+++ b/server/src/config.rs
@@ -1,5 +1,6 @@
//! Server configuration.
+use std::collections::HashSet;
use std::env;
use std::net::SocketAddr;
use std::path::{Path, PathBuf};
@@ -7,12 +8,16 @@ use std::time::Duration;
use anyhow::Result;
use async_compression::Level as CompressionLevel;
+use attic_token::SignatureType;
use base64::{engine::general_purpose::STANDARD as BASE64_STANDARD, Engine};
use derivative::Derivative;
use serde::{de, Deserialize};
use xdg::BaseDirectories;
-use crate::access::{decode_token_hs256_secret_base64, HS256Key};
+use crate::access::{
+ decode_token_hs256_secret_base64, decode_token_rs256_pubkey_base64,
+ decode_token_rs256_secret_base64, HS256Key, RS256KeyPair, RS256PublicKey,
+};
use crate::narinfo::Compression as NixCompression;
use crate::storage::{LocalStorageConfig, S3StorageConfig};
@@ -26,9 +31,18 @@ const XDG_PREFIX: &str = "attic";
/// This is useful for deploying to certain application platforms like Fly.io
const ENV_CONFIG_BASE64: &str = "ATTIC_SERVER_CONFIG_BASE64";
-/// Environment variable storing the Base64-encoded HS256 JWT secret.
+/// Environment variable storing the base64-encoded HMAC secret (used for signing and verifying
+/// received JWTs).
const ENV_TOKEN_HS256_SECRET_BASE64: &str = "ATTIC_SERVER_TOKEN_HS256_SECRET_BASE64";
+/// Environment variable storing the base64-encoded RSA PEM PKCS1 private key (used for signing and
+/// verifying received JWTs).
+const ENV_TOKEN_RS256_SECRET_BASE64: &str = "ATTIC_SERVER_TOKEN_RS256_SECRET_BASE64";
+
+/// Environment variable storing the base64-encoded RSA PEM PKCS1 public key (used for verifying
+/// received JWTs only).
+const ENV_TOKEN_RS256_PUBKEY_BASE64: &str = "ATTIC_SERVER_TOKEN_RS256_PUBKEY_BASE64";
+
/// Environment variable storing the database connection string.
const ENV_DATABASE_URL: &str = "ATTIC_SERVER_DATABASE_URL";
@@ -108,14 +122,71 @@ pub struct Config {
#[serde(default = "Default::default")]
pub garbage_collection: GarbageCollectionConfig,
+ /// JSON Web Token.
+ pub jwt: JWTConfig,
+}
+
+/// JSON Web Token configuration.
+#[derive(Clone, Derivative, Deserialize)]
+#[derivative(Debug)]
+pub struct JWTConfig {
+ /// The `iss` claim of the JWT.
+ ///
+ /// If specified, received JWTs must have this claim, and its value must match this
+ /// configuration.
+ #[serde(rename = "token-bound-issuer")]
+ #[serde(default = "Default::default")]
+ pub token_bound_issuer: Option,
+
+ /// The `aud` claim of the JWT.
+ ///
+ /// If specified, received JWTs must have this claim, and must contain one of the configured
+ /// values.
+ #[serde(rename = "token-bound-audiences")]
+ #[serde(default = "Default::default")]
+ pub token_bound_audiences: Option>,
+
+ /// JSON Web Token signing.
+ #[serde(rename = "signing")]
+ #[serde(default = "load_jwt_signing_config_from_env")]
+ #[derivative(Debug = "ignore")]
+ pub signing_config: JWTSigningConfig,
+}
+
+/// JSON Web Token signing configuration.
+#[derive(Clone, Deserialize)]
+pub enum JWTSigningConfig {
+ /// JSON Web Token RSA pubkey.
+ ///
+ /// Set this to the base64-encoded RSA PEM PKCS1 public key to use for verifying JWTs only.
+ #[serde(rename = "token-rs256-pubkey-base64")]
+ #[serde(deserialize_with = "deserialize_token_rs256_pubkey_base64")]
+ RS256VerifyOnly(RS256PublicKey),
+
+ /// JSON Web Token RSA secret.
+ ///
+ /// Set this to the base64-encoded RSA PEM PKCS1 private key to use for signing and verifying
+ /// JWTs.
+ #[serde(rename = "token-rs256-secret-base64")]
+ #[serde(deserialize_with = "deserialize_token_rs256_secret_base64")]
+ RS256SignAndVerify(RS256KeyPair),
+
/// JSON Web Token HMAC secret.
///
- /// Set this to the base64 encoding of a randomly generated secret.
+ /// Set this to the base64-encoded HMAC secret to use for signing and verifying JWTs.
#[serde(rename = "token-hs256-secret-base64")]
#[serde(deserialize_with = "deserialize_token_hs256_secret_base64")]
- #[serde(default = "load_token_hs256_secret_from_env")]
- #[derivative(Debug = "ignore")]
- pub token_hs256_secret: HS256Key,
+ HS256SignAndVerify(HS256Key),
+}
+
+impl From for SignatureType {
+ fn from(value: JWTSigningConfig) -> Self {
+ match value {
+ JWTSigningConfig::RS256VerifyOnly(key) => Self::RS256PubkeyOnly(key),
+ JWTSigningConfig::RS256SignAndVerify(key) => Self::RS256(key),
+ JWTSigningConfig::HS256SignAndVerify(key) => Self::HS256(key),
+ }
+ }
}
/// Database connection configuration.
@@ -240,16 +311,82 @@ pub struct GarbageCollectionConfig {
pub default_retention_period: Duration,
}
-fn load_token_hs256_secret_from_env() -> HS256Key {
- let s = env::var(ENV_TOKEN_HS256_SECRET_BASE64)
- .expect("The HS256 secret must be specified in either token_hs256_secret or the ATTIC_SERVER_TOKEN_HS256_SECRET_BASE64 environment.");
+fn load_jwt_signing_config_from_env() -> JWTSigningConfig {
+ let config = if let Some(config) = load_token_rs256_pubkey_from_env() {
+ config
+ } else if let Some(config) = load_token_rs256_secret_from_env() {
+ config
+ } else if let Some(config) = load_token_hs256_secret_from_env() {
+ config
+ } else {
+ panic!(
+ "\n\
+ You must configure JWT signing and verification inside your TOML \
+ configuration by setting one of the following options in the \
+ [jwt.signing] block:\n\
+ \n\
+ * token-rs256-pubkey-base64\n\
+ * token-rs256-secret-base64\n\
+ * token-hs256-secret-base64\n\
+ \n\
+ or by setting one of the following environment variables:\n\
+ \n\
+ * {ENV_TOKEN_RS256_PUBKEY_BASE64}\n\
+ * {ENV_TOKEN_RS256_SECRET_BASE64}\n\
+ * {ENV_TOKEN_HS256_SECRET_BASE64}\n\
+ \n\
+ Options will be tried in that same order (configuration options \
+ first, then environment options if none of the configuration options \
+ were set, starting with the respective RSA pubkey option, the RSA \
+ secret option, and finally the HMAC secret option). \
+ The first option that is found will be used.\n\
+ \n\
+ If an RS256 pubkey (asymmetric RSA PEM PKCS1 public key) is \
+ provided, it will only be possible to verify received JWTs, and not \
+ sign new JWTs.\n\
+ \n\
+ If an RS256 secret (asymmetric RSA PEM PKCS1 private key) is \
+ provided, it will be used for both signing new JWTs and verifying \
+ received JWTs.\n\
+ \n\
+ If an HS256 secret (symmetric HMAC secret) is provided, it will be \
+ used for both signing new JWTs and verifying received JWTs.\n\
+ "
+ )
+ };
+
+ config
+}
+
+fn load_token_hs256_secret_from_env() -> Option {
+ let s = env::var(ENV_TOKEN_HS256_SECRET_BASE64).ok()?;
- decode_token_hs256_secret_base64(&s).expect("Failed to load as decoding key")
+ decode_token_hs256_secret_base64(&s)
+ .ok()
+ .map(JWTSigningConfig::HS256SignAndVerify)
+}
+
+fn load_token_rs256_secret_from_env() -> Option {
+ let s = env::var(ENV_TOKEN_RS256_SECRET_BASE64).ok()?;
+
+ decode_token_rs256_secret_base64(&s)
+ .ok()
+ .map(JWTSigningConfig::RS256SignAndVerify)
+}
+
+fn load_token_rs256_pubkey_from_env() -> Option {
+ let s = env::var(ENV_TOKEN_RS256_PUBKEY_BASE64).ok()?;
+
+ decode_token_rs256_pubkey_base64(&s)
+ .ok()
+ .map(JWTSigningConfig::RS256VerifyOnly)
}
fn load_database_url_from_env() -> String {
- env::var(ENV_DATABASE_URL)
- .expect("Database URL must be specified in either database.url or the ATTIC_SERVER_DATABASE_URL environment.")
+ env::var(ENV_DATABASE_URL).expect(&format!(
+ "Database URL must be specified in either database.url \
+ or the {ENV_DATABASE_URL} environment."
+ ))
}
impl CompressionConfig {
@@ -308,6 +445,32 @@ where
Ok(key)
}
+fn deserialize_token_rs256_secret_base64<'de, D>(deserializer: D) -> Result
+where
+ D: de::Deserializer<'de>,
+{
+ use de::Error;
+
+ let s = String::deserialize(deserializer)?;
+ let key = decode_token_rs256_secret_base64(&s).map_err(Error::custom)?;
+
+ Ok(key)
+}
+
+fn deserialize_token_rs256_pubkey_base64<'de, D>(
+ deserializer: D,
+) -> Result
+where
+ D: de::Deserializer<'de>,
+{
+ use de::Error;
+
+ let s = String::deserialize(deserializer)?;
+ let key = decode_token_rs256_pubkey_base64(&s).map_err(Error::custom)?;
+
+ Ok(key)
+}
+
fn default_listen_address() -> SocketAddr {
"[::]:8080".parse().unwrap()
}
diff --git a/server/src/oobe.rs b/server/src/oobe.rs
index 987edde..d3d912d 100644
--- a/server/src/oobe.rs
+++ b/server/src/oobe.rs
@@ -14,11 +14,10 @@
use anyhow::Result;
use base64::{engine::general_purpose::STANDARD as BASE64_STANDARD, Engine};
use chrono::{Months, Utc};
-use rand::distributions::Alphanumeric;
-use rand::Rng;
+use rsa::pkcs1::EncodeRsaPrivateKey;
use tokio::fs::{self, OpenOptions};
-use crate::access::{decode_token_hs256_secret_base64, Token};
+use crate::access::{decode_token_rs256_secret_base64, SignatureType, Token};
use crate::config;
use attic::cache::CacheNamePattern;
@@ -45,20 +44,18 @@ pub async fn run_oobe() -> Result<()> {
let storage_path = data_path.join("storage");
fs::create_dir_all(&storage_path).await?;
- let hs256_secret_base64 = {
- let random: String = rand::thread_rng()
- .sample_iter(&Alphanumeric)
- .take(128)
- .map(char::from)
- .collect();
+ let rs256_secret_base64 = {
+ let mut rng = rand::thread_rng();
+ let private_key = rsa::RsaPrivateKey::new(&mut rng, 4096)?;
+ let pkcs1_pem = private_key.to_pkcs1_pem(rsa::pkcs1::LineEnding::LF)?;
- BASE64_STANDARD.encode(random)
+ BASE64_STANDARD.encode(pkcs1_pem.as_bytes())
};
let config_content = CONFIG_TEMPLATE
.replace("%database_url%", &database_url)
.replace("%storage_path%", storage_path.to_str().unwrap())
- .replace("%token_hs256_secret_base64%", &hs256_secret_base64);
+ .replace("%token_rs256_secret_base64%", &rs256_secret_base64);
fs::write(&config_path, config_content.as_bytes()).await?;
@@ -76,8 +73,8 @@ pub async fn run_oobe() -> Result<()> {
perm.configure_cache_retention = true;
perm.destroy_cache = true;
- let key = decode_token_hs256_secret_base64(&hs256_secret_base64).unwrap();
- token.encode(&key)?
+ let key = decode_token_rs256_secret_base64(&rs256_secret_base64).unwrap();
+ token.encode(&SignatureType::RS256(key), &None, &None)?
};
eprintln!();
diff --git a/token/Cargo.toml b/token/Cargo.toml
index a6880d3..37f75f5 100644
--- a/token/Cargo.toml
+++ b/token/Cargo.toml
@@ -9,7 +9,7 @@ edition = "2021"
attic = { path = "../attic", default-features = false }
base64 = "0.22.1"
-chrono = "0.4.24"
+chrono = "0.4.31"
displaydoc = "0.2.4"
indexmap = { version = "2.2.6", features = ["serde"] }
jwt-simple = "0.11.5"
@@ -18,3 +18,4 @@ regex = "1.8.3"
serde = "1.0.163"
serde_with = "3.0.0"
tracing = "0.1.37"
+rsa = "0.9.3"
diff --git a/token/src/lib.rs b/token/src/lib.rs
index 3b1bf46..6f1487e 100644
--- a/token/src/lib.rs
+++ b/token/src/lib.rs
@@ -1,7 +1,7 @@
//! Access control.
//!
//! Access control in Attic is simple and stateless [0] - The server validates
-//! the JWT against a HS256 key and allows access based on the `https://jwt.attic.rs/v1`
+//! the JWT against the configured key and allows access based on the `https://jwt.attic.rs/v1`
//! claim.
//!
//! One primary goal of the Attic Server is easy scalability. It's designed
@@ -83,14 +83,16 @@ pub mod util;
#[cfg(test)]
mod tests;
+use std::collections::HashSet;
use std::error::Error as StdError;
use base64::{engine::general_purpose::STANDARD as BASE64_STANDARD, Engine};
use chrono::{DateTime, Utc};
use displaydoc::Display;
use indexmap::IndexMap;
+use jwt_simple::prelude::{Duration, RSAKeyPairLike, RSAPublicKeyLike, VerificationOptions};
pub use jwt_simple::{
- algorithms::{HS256Key, MACLike},
+ algorithms::{HS256Key, MACLike, RS256KeyPair, RS256PublicKey},
claims::{Claims, JWTClaims},
prelude::UnixTimeStamp,
};
@@ -155,49 +157,49 @@ pub struct AtticAccess {
pub struct CachePermission {
/// Can pull objects from the cache.
#[serde(default = "CachePermission::permission_default")]
- #[serde(skip_serializing_if = "is_false")]
+ #[serde(skip_serializing_if = "std::ops::Not::not")]
#[serde(rename = "r")]
#[serde_as(as = "BoolFromInt")]
pub pull: bool,
/// Can push objects to the cache.
#[serde(default = "CachePermission::permission_default")]
- #[serde(skip_serializing_if = "is_false")]
+ #[serde(skip_serializing_if = "std::ops::Not::not")]
#[serde(rename = "w")]
#[serde_as(as = "BoolFromInt")]
pub push: bool,
/// Can delete objects from the cache.
#[serde(default = "CachePermission::permission_default")]
- #[serde(skip_serializing_if = "is_false")]
+ #[serde(skip_serializing_if = "std::ops::Not::not")]
#[serde(rename = "d")]
#[serde_as(as = "BoolFromInt")]
pub delete: bool,
/// Can create the cache itself.
#[serde(default = "CachePermission::permission_default")]
- #[serde(skip_serializing_if = "is_false")]
+ #[serde(skip_serializing_if = "std::ops::Not::not")]
#[serde(rename = "cc")]
#[serde_as(as = "BoolFromInt")]
pub create_cache: bool,
/// Can reconfigure the cache.
#[serde(default = "CachePermission::permission_default")]
- #[serde(skip_serializing_if = "is_false")]
+ #[serde(skip_serializing_if = "std::ops::Not::not")]
#[serde(rename = "cr")]
#[serde_as(as = "BoolFromInt")]
pub configure_cache: bool,
/// Can configure retention/quota settings.
#[serde(default = "CachePermission::permission_default")]
- #[serde(skip_serializing_if = "is_false")]
+ #[serde(skip_serializing_if = "std::ops::Not::not")]
#[serde(rename = "cq")]
#[serde_as(as = "BoolFromInt")]
pub configure_cache_retention: bool,
/// Can destroy the cache itself.
#[serde(default = "CachePermission::permission_default")]
- #[serde(skip_serializing_if = "is_false")]
+ #[serde(skip_serializing_if = "std::ops::Not::not")]
#[serde(rename = "cd")]
#[serde_as(as = "BoolFromInt")]
pub destroy_cache: bool,
@@ -223,14 +225,68 @@ pub enum Error {
/// Base64 decode error: {0}
Base64Error(base64::DecodeError),
+
+ /// RSA Key error: {0}
+ RsaKeyError(rsa::pkcs1::Error),
+
+ /// Failure decoding the base64 layer of the base64 encoded PEM
+ Utf8Error(std::str::Utf8Error),
+
+ /// Pubkey-only JWT authentication cannot create signed JWTs
+ PubkeyOnlyCannotCreateToken,
+}
+
+/// The supported JWT signature types.
+pub enum SignatureType {
+ HS256(HS256Key),
+ RS256(RS256KeyPair),
+ RS256PubkeyOnly(RS256PublicKey),
}
impl Token {
/// Verifies and decodes a token.
- pub fn from_jwt(token: &str, key: &HS256Key) -> Result {
- key.verify_token(token, None)
- .map_err(Error::TokenError)
- .map(Token)
+ pub fn from_jwt(
+ token: &str,
+ signature_type: &SignatureType,
+ maybe_bound_issuer: &Option,
+ maybe_bound_audiences: &Option>,
+ ) -> Result {
+ let opts = VerificationOptions {
+ reject_before: None,
+ accept_future: false,
+ required_subject: None,
+ required_key_id: None,
+ required_public_key: None,
+ required_nonce: None,
+ allowed_issuers: maybe_bound_issuer
+ .as_ref()
+ .map(|s| [s.to_owned()].into())
+ .to_owned(),
+ allowed_audiences: maybe_bound_audiences.to_owned(),
+ time_tolerance: None,
+ max_validity: None,
+ max_token_length: None,
+ max_header_length: None,
+ artificial_time: None,
+ };
+
+ match signature_type {
+ SignatureType::HS256(key) => key
+ .verify_token(token, Some(opts))
+ .map_err(Error::TokenError)
+ .map(Token),
+ SignatureType::RS256(key) => {
+ let public_key = key.public_key();
+ public_key
+ .verify_token(token, Some(opts))
+ .map_err(Error::TokenError)
+ .map(Token)
+ }
+ SignatureType::RS256PubkeyOnly(key) => key
+ .verify_token(token, Some(opts))
+ .map_err(Error::TokenError)
+ .map(Token),
+ }
}
/// Creates a new token with an expiration timestamp.
@@ -239,12 +295,17 @@ impl Token {
attic_ns: Default::default(),
};
+ let now_epoch = Utc::now().signed_duration_since(DateTime::UNIX_EPOCH);
+
Self(JWTClaims {
issued_at: None,
expires_at: Some(UnixTimeStamp::from_secs(
exp.timestamp().try_into().unwrap(),
)),
- invalid_before: None,
+ invalid_before: Some(Duration::new(
+ now_epoch.num_seconds().try_into().unwrap(),
+ 0,
+ )),
issuer: None,
subject: Some(sub),
audiences: None,
@@ -255,8 +316,28 @@ impl Token {
}
/// Encodes the token.
- pub fn encode(&self, key: &HS256Key) -> Result {
- key.authenticate(self.0.clone()).map_err(Error::TokenError)
+ pub fn encode(
+ &self,
+ signature_type: &SignatureType,
+ maybe_bound_issuer: &Option,
+ maybe_bound_audiences: &Option>,
+ ) -> Result {
+ let mut token = self.0.clone();
+
+ if let Some(issuer) = maybe_bound_issuer {
+ token = token.with_issuer(issuer);
+ }
+ if let Some(audiences) = maybe_bound_audiences {
+ token = token.with_audiences(audiences.to_owned());
+ }
+
+ match signature_type {
+ SignatureType::HS256(key) => key.authenticate(token).map_err(Error::TokenError),
+ SignatureType::RS256(key) => key.sign(token).map_err(Error::TokenError),
+ SignatureType::RS256PubkeyOnly(_) => {
+ return Err(Error::PubkeyOnlyCannotCreateToken);
+ }
+ }
}
/// Returns the subject of the token.
@@ -362,11 +443,23 @@ impl CachePermission {
impl StdError for Error {}
pub fn decode_token_hs256_secret_base64(s: &str) -> Result {
- let secret = BASE64_STANDARD.decode(s).map_err(Error::Base64Error)?;
- Ok(HS256Key::from_bytes(&secret))
+ let decoded = BASE64_STANDARD.decode(s).map_err(Error::Base64Error)?;
+ let secret = std::str::from_utf8(&decoded).map_err(Error::Utf8Error)?;
+ Ok(HS256Key::from_bytes(&secret.as_bytes()))
}
-// bruh
-fn is_false(b: &bool) -> bool {
- !b
+pub fn decode_token_rs256_secret_base64(s: &str) -> Result {
+ let decoded = BASE64_STANDARD.decode(s).map_err(Error::Base64Error)?;
+ let secret = std::str::from_utf8(&decoded).map_err(Error::Utf8Error)?;
+ let keypair = RS256KeyPair::from_pem(secret).map_err(Error::TokenError)?;
+
+ Ok(keypair)
+}
+
+pub fn decode_token_rs256_pubkey_base64(s: &str) -> Result {
+ let decoded = BASE64_STANDARD.decode(s).map_err(Error::Base64Error)?;
+ let pubkey = std::str::from_utf8(&decoded).map_err(Error::Utf8Error)?;
+ let pubkey = RS256PublicKey::from_pem(pubkey).map_err(Error::TokenError)?;
+
+ Ok(pubkey)
}
diff --git a/token/src/tests.rs b/token/src/tests.rs
index 948ffc9..5ea2156 100644
--- a/token/src/tests.rs
+++ b/token/src/tests.rs
@@ -10,15 +10,12 @@ macro_rules! cache {
#[test]
fn test_basic() {
- // "very secure secret"
- let base64_secret = "dmVyeSBzZWN1cmUgc2VjcmV0";
-
- let dec_key = decode_token_hs256_secret_base64(base64_secret).unwrap();
-
/*
+ $ cat json
{
"sub": "meow",
"exp": 4102324986,
+ "nbf": 0,
"https://jwt.attic.rs/v1": {
"caches": {
"all-*": {"r":1},
@@ -30,8 +27,15 @@ fn test_basic() {
}
}
*/
+ // nix shell nixpkgs#jwt-cli
+ // openssl genpkey -out rs256 -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -outform der
+ // BASE64_SECRET=$(openssl rsa -in rs256 -outform PEM -traditional | base64 -w0)
+ let base64_secret = "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBNUZranRMRzV5eS9pMFlnYkQxeUJBK21GckNmLzZiQ2F0TDFFQ3ppNG1tZWhSZTcwCkFEL0dSSHhTVUErc0pZeCtZNjlyL0RqQWs2OFJlQ1c4b2FQWXhtc21RNG5VM2ZwZ2E3WWFqZ3ZoWmVsa3JtaC8KZ1ZURWtFTG1IZlJtQkwvOWlsT20yRHNtYTVhUFo0SFl6ellpdjJvcFF5UGRndXcyWXFtbzE3Nk5MdllCMmpJTwovR3FkdE55K3NPV296NktVSVlJa0hWWU5HMENVcFNzdXBqUTJ6VTVZMFc2UXlNQWFWd1BONElJT3lXWUNwZXRECjFJbWxYekhROXM4NXFSWnlLa21iZFhtTVBVWmUvekRxc2FFd3lscFlpT0RjbDdRYU5QTzEzZnk3UGtQMmVwdUkKTk5tZ1E0WEF0MkF4ZXNKck5ibUs4aG1iM3doRXZkNjRFMGdEV1FJREFRQUJBb0lCQUJEemNRd2IyVi8wK1JCMgoyeE5qMll2eHpPTi93S2FYWHBTbUxDUHRIUDhSVEU2RnM0VkZOckdrelBOMmhsL3ZNdjZ4YWdHNk1NbUZ5SFV6CnovSHIyTTY1NjRnOTloaFlXc29FSmFwL3hVYXNjYlhrdWZwZTBZeW4rcThra21JdDRtTmZYRlpXNWI0ODJmNWsKRERVdG5weTVBOEVoSzNOcGw0dnhia0E5dS90TlVlT1NHTkhPYVZjcHdERVhDNXJ4bmFxTm5wMkMwa1A4ODRINgpSb2lZVkF4bytHaVpNVzhIOFRmSXVsenh3c04yQnVNcUNmOGVhNG1EM0pRVHZ2REhhUHM4eVJTUlB3UmlHYUkzCnVybFRmdjg4U20va09oL0N2SkpoRnhCVkVNVjIydWRNUmU3L3NpTWtlbVlvUnhaTWJjRGVQK2h1RktJWTRSMEoKNnRJUHQ3VUNnWUVBOTlhL2IzeFBsQWh0ck02dUlUUXNQd0FYQUg3Q1NXL1FSdVJUTWVhYXVIMk9sRitjZmpMNApJS1Nsdy9QaUtaUEk1TFRWM2ZVZk5WNTVsOFZHTytsT2ViTFhnaXBYM3BqSDBma3AyY3Q2Smk3aGw0aUlXK0h0ClpJNE9KYkYwTTBETHdySkd3T25QL2trRHNxSW9IbC9MdTBRM2FxSm1RVCsvcG54R083R21kbDhDZ1lFQTY5NFcKZHF2NnF4VjF5V0Z4QWZOOE1hZStpTC9xY1VhTm85ZzMva2YvOXZ3VXdtcERvR0xnaVVLMWZKb3BUYlBjcWgwRwptbUZEQ3V2M1Q0OS9yU2k5dU4zYm82cmlXRUl4VFg1YUtFSjlpSEFMWDJGWDdGSDJRdUZGWEwzQ2c0ckdvL1pDCmdjUkxuS3dma3JUVnRxeEdaNjN4YmsvcFpHWjZtTW01VkNDck1VY0NnWUVBc3JUT1pQMG1CSC92VldQU2UyNjcKV05JZncrT2pCSUR6bGFxZHNxV3Rlc3BPUFA2VVFRdFBqM29wYlJvMlFmU21Md09XRXUzbEN2Nk1mcnRvNFZwaAprNjg1WmtwU0FkZjRmWmRFYmg4aWZOWGhKUHIyR0FyWXVtRVVJbW5LZUFxSTRtTGFVZEJHZ2Z6MEJhS1hldzlvClFDZjRMWlBjVjhBMzJUeFRDRWdZMTlFQ2dZQU04U2F5WkVWZzFkQ2N1Q2dIUDJEMUtJc2YzY2Z6WnplbVlkclEKclFxeWRxcDg4Rys5Z1M5bzJLdzBwaERXSHFSaEFTNjNrZGFuNXNLdkx1U0dqOUc1THhNNks4bzNwWW9uQW1QWQpDYTN4cXBRMUs1WXpkVnZaMTVxQ3VEYlFHUEZGVmVIWVZQa0JJOENud0J4cDVaSUhabGYxQVpXQTJNNnBTNGhMCndXOGpTUUtCZ1FDQmNJbjU4Y0lmZkhmMjM4SUJvZnR1UVVzREZGcnkzaUVpaWpTYmJ1WnB1Vm8zL2pWbUsyaEYKS2xUL2xoRDdWdGJ1V3phMG9WQmZDaWZqMnZ2S2pmZ0l6NnF3Um1UbC9DSjlWdUNHTUI1VG55cGl3OEtodXorSAo0L2twdDdNcW9WQ0dRSjd1WVQyQzY1K0JqNklnUnBQT09za3VKNW1RZ0FlbTQ3eDBrVnRSemc9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=";
+
+ let dec_key = decode_token_rs256_secret_base64(base64_secret).unwrap();
- let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjQxMDIzMjQ5ODYsImh0dHBzOi8vand0LmF0dGljLnJzL3YxIjp7ImNhY2hlcyI6eyJhbGwtKiI6eyJyIjoxfSwiYWxsLWNpLSoiOnsidyI6MX0sImNhY2hlLXJvIjp7InIiOjF9LCJjYWNoZS1ydyI6eyJyIjoxLCJ3IjoxfSwidGVhbS0qIjp7ImNjIjoxLCJyIjoxLCJ3IjoxfX19LCJpYXQiOjE3MTY2NjA1ODksInN1YiI6Im1lb3cifQ.8vtxp_1OEYdcnkGPM4c9ORXooJZV7DOTS4NRkMKN8mw";
+ // TOKEN=$(jq -c < json | jwt encode --alg RS256 --secret @./rs256 -)
+ let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJleHAiOjQxMDIzMjQ5ODYsImh0dHBzOi8vand0LmF0dGljLnJzL3YxIjp7ImNhY2hlcyI6eyJhbGwtKiI6eyJyIjoxfSwiYWxsLWNpLSoiOnsidyI6MX0sImNhY2hlLXJvIjp7InIiOjF9LCJjYWNoZS1ydyI6eyJyIjoxLCJ3IjoxfSwidGVhbS0qIjp7ImNjIjoxLCJyIjoxLCJ3IjoxfX19LCJpYXQiOjE3MjIwMDUwNzksIm5iZiI6MCwic3ViIjoibWVvdyJ9.Zs24IUbQOpOjhEe0sfsoSSJhDrzf4v-_wX_ceKqHeb2MERY8XSIQ1RPTNVeOW4LfJHumJj_rxh8Wv2BRGZSMldrTt0Ab_N7FnkhA37_jnRvgvEjSG3V4fC8aA4KoOa-43NRpg4HmPxiXte5-6LneBOR94Wss868wC1b_2yX2zCc1wQoZA3LNo-CRLnL4Yp5wY4Bbgyguv_9mfqXVYZykZnxumyGwVFD-Rub3KQ9d53Rf9tKcvRk9qxO2q8F2PKjeaUBG2xZtGwkWTMvSmwR1dKtkPUyPggOzbLoUG-6fxfo7D3NyL5qWCSN_7CkI-xlsRSLY1gTq-FqXvcpHeZbc8w";
// NOTE(cole-h): check that we get a consistent iteration order when getting permissions for
// caches -- this depends on the order of the fields in the token, but should otherwise be
@@ -40,7 +44,8 @@ fn test_basic() {
for _ in 0..=1_000 {
// NOTE(cole-h): we construct a new Token every iteration in order to get different "random
// state"
- let decoded = Token::from_jwt(token, &dec_key).unwrap();
+ let decoded =
+ Token::from_jwt(token, &SignatureType::RS256(dec_key.clone()), &None, &None).unwrap();
let perm_all_ci = decoded.get_permission_for_cache(&cache! { "all-ci-abc" });
// NOTE(cole-h): if the iteration order of the token is inconsistent, the permissions may be
@@ -55,7 +60,7 @@ fn test_basic() {
"Iteration order should be consistent to prevent random auth failures (and successes)"
);
- let decoded = Token::from_jwt(token, &dec_key).unwrap();
+ let decoded = Token::from_jwt(token, &SignatureType::RS256(dec_key), &None, &None).unwrap();
let perm_rw = decoded.get_permission_for_cache(&cache! { "cache-rw" });