Compare commits
12 Commits
af08be3a3e
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
054f523347
|
|||
|
2d2bff0948
|
|||
|
7aa22c88ef
|
|||
|
bf64901f50
|
|||
|
f81ef75e00
|
|||
|
70a88ada6a
|
|||
| d82f2fa04c | |||
|
dee3723e47
|
|||
| 46cb8ad2bb | |||
|
ca3be780da
|
|||
| 14251e4ebf | |||
| b8d3300cec |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -6,7 +6,7 @@ target/
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||
Cargo.lock
|
||||
#Cargo.lock
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
2028
Cargo.lock
generated
Normal file
2028
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,7 @@ version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
http = "1.2.0"
|
||||
oauth2 = "4.4.2"
|
||||
reqwest = { version = "0.12.9", features = ["blocking"] }
|
||||
serde = "1.0.216"
|
||||
serde_json = "1.0.133"
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# tsdproxy-keygen
|
||||
|
||||
Automatically generates new auth keys for TSDProxy.
|
||||
Wesley Irvin - 2024
|
||||
|
||||
Automatically generates new auth keys for TSDProxy.
|
||||
|
||||
99
src/lib.rs
Normal file
99
src/lib.rs
Normal file
@@ -0,0 +1,99 @@
|
||||
use std::error::Error;
|
||||
use std::fs::{read_to_string, File};
|
||||
use std::io::Write;
|
||||
|
||||
use oauth2::basic::BasicClient;
|
||||
use oauth2::reqwest::http_client;
|
||||
use oauth2::{AccessToken, AuthUrl, ClientId, ClientSecret, TokenResponse, TokenUrl};
|
||||
|
||||
mod types;
|
||||
use types::{AuthKeyRequest, AuthKeyResponse, ClientAuth};
|
||||
|
||||
pub fn run() -> Result<(), Box<dyn Error>> {
|
||||
println!("Reading Client ID And Secret From File");
|
||||
let client_auth = read_client_auth()?;
|
||||
|
||||
println!("Getting API Token With Client ID: {}", client_auth.id);
|
||||
let access_token = get_api_token(client_auth.id, client_auth.secret)?;
|
||||
|
||||
println!("Generating New Auth Key");
|
||||
let auth_key = get_auth_key(access_token)?;
|
||||
|
||||
println!("Writing Auth Key to File");
|
||||
write_auth_key(auth_key)?;
|
||||
|
||||
println!(
|
||||
"Auth key has been successfully updated.\n
|
||||
*** Make sure in your tsdproxy config you are pointing to the file '/config/.auth-key' to pick up the key. ***
|
||||
*** Make sure you restart tsdproxy for the new keys to be used ***"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_client_auth() -> Result<ClientAuth, Box<dyn Error>> {
|
||||
let mut id = String::new();
|
||||
let mut secret = String::new();
|
||||
|
||||
for line in read_to_string(".client-auth")?.lines() {
|
||||
let line_str = line.to_string();
|
||||
let cur_line: Vec<&str> = line_str.split('=').collect();
|
||||
if cur_line[0] == "client_id" {
|
||||
id = cur_line[1].to_string();
|
||||
} else if cur_line[0] == "client_secret" {
|
||||
secret = cur_line[1].to_string();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ClientAuth { id, secret })
|
||||
}
|
||||
|
||||
fn get_api_token(client_id: String, client_secret: String) -> Result<AccessToken, Box<dyn Error>> {
|
||||
let client = BasicClient::new(
|
||||
ClientId::new(client_id),
|
||||
Some(ClientSecret::new(client_secret)),
|
||||
AuthUrl::new("https://api.tailscale.com/api/v2/oauth/token".to_string())?,
|
||||
Some(TokenUrl::new(
|
||||
"https://api.tailscale.com/api/v2/oauth/token".to_string(),
|
||||
)?),
|
||||
);
|
||||
|
||||
let token_result = client.exchange_client_credentials().request(http_client)?;
|
||||
|
||||
let access_token = token_result.access_token().to_owned();
|
||||
|
||||
Ok(access_token)
|
||||
}
|
||||
|
||||
fn get_auth_key(api_token: AccessToken) -> Result<String, Box<dyn Error>> {
|
||||
let mut auth_request = AuthKeyRequest::new();
|
||||
let client = reqwest::blocking::Client::new();
|
||||
|
||||
auth_request.is_reusable(true);
|
||||
auth_request.is_ephemeral(true);
|
||||
auth_request.is_preauthorized(true);
|
||||
auth_request.expiry_seconds(1800);
|
||||
auth_request.set_tags(vec!["tag:tsdproxy-testing".to_string()]);
|
||||
auth_request.description("Test of JSON creation".to_string());
|
||||
|
||||
let json_request = serde_json::to_string(&auth_request)?;
|
||||
|
||||
let res = client
|
||||
.post("https://api.tailscale.com/api/v2/tailnet/-/keys?all=true")
|
||||
.body(json_request)
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Authorization", "Bearer ".to_owned() + api_token.secret())
|
||||
.send()?;
|
||||
|
||||
let auth_response: AuthKeyResponse = serde_json::from_str(res.text()?.as_str())?;
|
||||
|
||||
Ok(auth_response.get_auth_key())
|
||||
}
|
||||
|
||||
fn write_auth_key(auth_key: String) -> Result<(), Box<dyn Error>> {
|
||||
let mut keyfile = File::create("config/.auth-key")?;
|
||||
|
||||
keyfile.write_all(auth_key.as_bytes())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
10
src/main.rs
Normal file
10
src/main.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
use std::process;
|
||||
|
||||
use tsdproxy_keygen::run;
|
||||
|
||||
fn main() {
|
||||
if let Err(e) = run() {
|
||||
println!("An error occured: {e}");
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
95
src/types.rs
Normal file
95
src/types.rs
Normal file
@@ -0,0 +1,95 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub struct ClientAuth {
|
||||
pub id: String,
|
||||
pub secret: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct AuthKeyRequest {
|
||||
capabilities: CapabilitiesCreate,
|
||||
expiry_seconds: i32,
|
||||
description: String,
|
||||
}
|
||||
|
||||
impl AuthKeyRequest {
|
||||
pub fn new() -> Self {
|
||||
let create_options = CreateOptions {
|
||||
reusable: false,
|
||||
ephemeral: false,
|
||||
preauthorized: false,
|
||||
tags: Vec::new(),
|
||||
};
|
||||
|
||||
let devices_create = DevicesCreate {
|
||||
create: create_options,
|
||||
};
|
||||
|
||||
let capabilities_create = CapabilitiesCreate {
|
||||
devices: devices_create,
|
||||
};
|
||||
|
||||
Self {
|
||||
capabilities: capabilities_create,
|
||||
expiry_seconds: 3600,
|
||||
description: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_reusable(&mut self, reusable: bool) {
|
||||
self.capabilities.devices.create.reusable = reusable;
|
||||
}
|
||||
|
||||
pub fn is_ephemeral(&mut self, ephemeral: bool) {
|
||||
self.capabilities.devices.create.ephemeral = ephemeral;
|
||||
}
|
||||
|
||||
pub fn is_preauthorized(&mut self, preauthorized: bool) {
|
||||
self.capabilities.devices.create.preauthorized = preauthorized;
|
||||
}
|
||||
|
||||
pub fn expiry_seconds(&mut self, expiry_seconds: i32) {
|
||||
self.expiry_seconds = expiry_seconds;
|
||||
}
|
||||
|
||||
pub fn description(&mut self, description: String) {
|
||||
self.description = description;
|
||||
}
|
||||
|
||||
pub fn set_tags(&mut self, tags: Vec<String>) {
|
||||
self.capabilities.devices.create.tags = tags;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct CapabilitiesCreate {
|
||||
devices: DevicesCreate,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct DevicesCreate {
|
||||
create: CreateOptions,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct CreateOptions {
|
||||
reusable: bool,
|
||||
ephemeral: bool,
|
||||
preauthorized: bool,
|
||||
tags: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct AuthKeyResponse {
|
||||
id: String,
|
||||
key: String,
|
||||
created: String,
|
||||
expires: String,
|
||||
capabilities: CapabilitiesCreate,
|
||||
}
|
||||
|
||||
impl AuthKeyResponse {
|
||||
pub fn get_auth_key(self) -> String {
|
||||
self.key
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user