Compare commits

...

14 Commits

Author SHA1 Message Date
054f523347 Merge branch 'write-auth-key' 2024-12-21 12:10:00 -05:00
2d2bff0948 Write auth key to file
This commit allows us to now write the auth key to the proper file.
After writing and restarting tsdproxy everything seems to be getting
picked up and is working successfully.
2024-12-21 12:08:06 -05:00
7aa22c88ef Merge branch 'get-auth-key'
Merging get-auth-key into main as it does solve issue #7.
2024-12-19 20:32:36 -05:00
bf64901f50 Generate Auth Keys
Added the ability to generate new auth keys. Can see in the tailscale
logs that new keys are being generated with a 90 day expiration time.
This should be enough to close out issue #7.
2024-12-19 20:30:34 -05:00
f81ef75e00 Merge branch 'access-token'
Merges in the access token fixes into main.
2024-12-16 20:00:40 -05:00
70a88ada6a Generate API Token
Wrote function that generates a tailscale API token from our client id
and client secret values that we have. Have checked with the logs on
tailscale and can see that it is indeed generating the keys. This can be
considered a solution to issue #6.
2024-12-16 19:57:45 -05:00
d82f2fa04c Merge pull request 'Get client secret and key' (#5) from get-client into main
Reviewed-on: #5
2024-12-16 00:42:14 +00:00
dee3723e47 Get client secret and key
This is to solve issue #4. This pulls in the client secret and key and
is able to print them out on the screen successfully from our own
created data type.
2024-12-15 19:37:57 -05:00
46cb8ad2bb Merge pull request 'Testing Key Signing' (#3) from signing-check into main
Reviewed-on: #3
2024-12-15 20:58:59 +00:00
ca3be780da Testing Key Signing
Testing key signing to see if it works.
2024-12-15 15:57:13 -05:00
14251e4ebf Merge pull request 'Allow Cargo Lock' (#2) from allow-cargo-lock into main
Reviewed-on: #2
2024-12-15 20:41:30 +00:00
b8d3300cec Allow Cargo Lock
Modified .gitignore to allow Cargo.lock as per the recommendations
in the documentation located at:
https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
2024-12-15 15:38:54 -05:00
af08be3a3e Merge pull request 'Updated Dependencies' (#1) from dependencies into main
Reviewed-on: #1
2024-12-15 20:33:16 +00:00
908094d91d Updated Dependencies
Added dependencies into the Cargo.toml file and updated .gitignore to
ignore files associated with this project that are secret.
2024-12-15 15:29:55 -05:00
7 changed files with 2256 additions and 2 deletions

12
.gitignore vendored
View File

@@ -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
@@ -449,3 +449,13 @@ FodyWeavers.xsd
# Built Visual Studio Code Extensions
*.vsix
# Added by cargo
/target
# Ignore the .client-auth file
.client-auth
# Ignore the config directory as this is used for testing purposes before the executable is moved
config/

2028
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

10
Cargo.toml Normal file
View File

@@ -0,0 +1,10 @@
[package]
name = "tsdproxy-keygen"
version = "0.1.0"
edition = "2021"
[dependencies]
oauth2 = "4.4.2"
reqwest = { version = "0.12.9", features = ["blocking"] }
serde = "1.0.216"
serde_json = "1.0.133"

View File

@@ -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
View 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
View 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
View 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
}
}