Include server fingerprint in auth responses and rename project
This commit is contained in:
parent
979cc61acd
commit
6c654072a5
4 changed files with 52 additions and 32 deletions
38
Cargo.lock
generated
38
Cargo.lock
generated
|
|
@ -1,6 +1,6 @@
|
||||||
# This file is automatically @generated by Cargo.
|
# This file is automatically @generated by Cargo.
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 4
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "addr2line"
|
name = "addr2line"
|
||||||
|
|
@ -83,6 +83,24 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "auth_ssh_games"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"axum",
|
||||||
|
"base64 0.22.1",
|
||||||
|
"clap",
|
||||||
|
"hmac",
|
||||||
|
"jsonwebtoken",
|
||||||
|
"ring",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"sha2",
|
||||||
|
"tokio",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
|
|
@ -847,24 +865,6 @@ dependencies = [
|
||||||
"time",
|
"time",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sish_games"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"axum",
|
|
||||||
"base64 0.22.1",
|
|
||||||
"clap",
|
|
||||||
"hmac",
|
|
||||||
"jsonwebtoken",
|
|
||||||
"ring",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"sha2",
|
|
||||||
"tokio",
|
|
||||||
"uuid",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "1.13.2"
|
version = "1.13.2"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "sish_games"
|
name = "auth_ssh_games"
|
||||||
description = "Server for sish games"
|
description = "Authentication server for SSH-based games"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,5 +5,5 @@ COPY . .
|
||||||
RUN cargo build --release
|
RUN cargo build --release
|
||||||
|
|
||||||
FROM alpine:3.20
|
FROM alpine:3.20
|
||||||
COPY --from=builder /usr/src/app/target/release/sish_games /usr/local/bin/sish_games
|
COPY --from=builder /usr/src/app/target/release/auth_ssh_games /usr/local/bin/auth_ssh_games
|
||||||
ENTRYPOINT [ "sish_games" ]
|
ENTRYPOINT [ "auth_ssh_games" ]
|
||||||
38
src/main.rs
38
src/main.rs
|
|
@ -27,8 +27,15 @@ struct GenerationRequest {
|
||||||
password: String,
|
password: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct GenerationResponse {
|
||||||
|
jwt: String,
|
||||||
|
server_fingerprint: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct GenerationState {
|
struct GenerationState {
|
||||||
|
server_fingerprint: Arc<String>,
|
||||||
encoding_key: Arc<EncodingKey>,
|
encoding_key: Arc<EncodingKey>,
|
||||||
audiences: Arc<HashSet<String>>,
|
audiences: Arc<HashSet<String>>,
|
||||||
}
|
}
|
||||||
|
|
@ -49,16 +56,24 @@ struct ValidationState {
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(version, about, long_about = None)]
|
#[command(version, about, long_about = None)]
|
||||||
struct Args {
|
struct Args {
|
||||||
|
/// Fingerprint of the server's SSH key, to avoid MitM attacks.
|
||||||
|
#[arg(short = 'f', long, value_name = "FINGERPRINT")]
|
||||||
|
server_fingerprint: String,
|
||||||
|
|
||||||
|
/// Private key for signing JWTs.
|
||||||
#[arg(short = 'i', long, value_name = "FILE")]
|
#[arg(short = 'i', long, value_name = "FILE")]
|
||||||
private_key: PathBuf,
|
private_key: PathBuf,
|
||||||
|
|
||||||
|
/// Public key for validating JWTs.
|
||||||
#[arg(short, long, value_name = "FILE")]
|
#[arg(short, long, value_name = "FILE")]
|
||||||
public_key: PathBuf,
|
public_key: PathBuf,
|
||||||
|
|
||||||
#[arg(short, long, num_args(1..))]
|
/// Allowed audiences for JWTs.
|
||||||
|
#[arg(short, long, num_args(1..), value_name = "AUDIENCE")]
|
||||||
audiences: Vec<String>,
|
audiences: Vec<String>,
|
||||||
|
|
||||||
#[arg(short, long)]
|
/// Secret token that's part of the URL for authentication.
|
||||||
|
#[arg(short, long, value_name = "TOKEN")]
|
||||||
validation_token: String,
|
validation_token: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,6 +97,7 @@ async fn main() -> Result<()> {
|
||||||
let router = Router::new()
|
let router = Router::new()
|
||||||
.route("/", post(generation_handler))
|
.route("/", post(generation_handler))
|
||||||
.with_state(GenerationState {
|
.with_state(GenerationState {
|
||||||
|
server_fingerprint: Arc::new(args.server_fingerprint),
|
||||||
encoding_key,
|
encoding_key,
|
||||||
audiences: Arc::new(audiences.iter().cloned().collect()),
|
audiences: Arc::new(audiences.iter().cloned().collect()),
|
||||||
})
|
})
|
||||||
|
|
@ -103,16 +119,16 @@ async fn generation_handler(
|
||||||
Json(payload): Json<GenerationRequest>,
|
Json(payload): Json<GenerationRequest>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
if !state.audiences.contains(&payload.audience) {
|
if !state.audiences.contains(&payload.audience) {
|
||||||
return StatusCode::FORBIDDEN.into_response();
|
return (StatusCode::FORBIDDEN, "invalid payload").into_response();
|
||||||
}
|
}
|
||||||
let password = match BASE64_STANDARD.decode(payload.password) {
|
let password = match BASE64_STANDARD.decode(payload.password) {
|
||||||
Ok(password) => password,
|
Ok(password) => password,
|
||||||
Err(_) => return StatusCode::FORBIDDEN.into_response(),
|
Err(_) => return (StatusCode::FORBIDDEN, "invalid payload").into_response(),
|
||||||
};
|
};
|
||||||
let mut hmac = Hmac::<Sha256>::new_from_slice(payload.audience.as_bytes()).unwrap();
|
let mut hmac = Hmac::<Sha256>::new_from_slice(payload.audience.as_bytes()).unwrap();
|
||||||
hmac.update(payload.user.as_bytes());
|
hmac.update(payload.user.as_bytes());
|
||||||
if hmac.verify_slice(&password).is_err() {
|
if hmac.verify_slice(&password).is_err() {
|
||||||
return StatusCode::FORBIDDEN.into_response();
|
return (StatusCode::FORBIDDEN, "invalid payload").into_response();
|
||||||
}
|
}
|
||||||
let claims = AuthenticationClaims {
|
let claims = AuthenticationClaims {
|
||||||
aud: payload.audience,
|
aud: payload.audience,
|
||||||
|
|
@ -125,7 +141,11 @@ async fn generation_handler(
|
||||||
&state.encoding_key,
|
&state.encoding_key,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
jwt.into_response()
|
Json(GenerationResponse {
|
||||||
|
jwt,
|
||||||
|
server_fingerprint: state.server_fingerprint.as_ref().clone(),
|
||||||
|
})
|
||||||
|
.into_response()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn validation_handler(
|
async fn validation_handler(
|
||||||
|
|
@ -138,11 +158,11 @@ async fn validation_handler(
|
||||||
&state.validation,
|
&state.validation,
|
||||||
) {
|
) {
|
||||||
Ok(token) => token.claims,
|
Ok(token) => token.claims,
|
||||||
Err(_) => return StatusCode::FORBIDDEN,
|
Err(_) => return (StatusCode::FORBIDDEN, "invalid auth").into_response(),
|
||||||
};
|
};
|
||||||
if claims.sub == payload.user && Uuid::try_parse(&payload.user).is_ok() {
|
if claims.sub == payload.user && Uuid::try_parse(&payload.user).is_ok() {
|
||||||
StatusCode::OK
|
StatusCode::OK.into_response()
|
||||||
} else {
|
} else {
|
||||||
StatusCode::FORBIDDEN
|
(StatusCode::FORBIDDEN, "invalid auth").into_response()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue