forked from github-starred/komodo
server onboarding flow using onboarding key
This commit is contained in:
@@ -15,6 +15,7 @@ use komodo_client::entities::{
|
||||
provider::{DockerRegistryAccount, GitProviderAccount},
|
||||
repo::Repo,
|
||||
server::Server,
|
||||
server_onboarding_key::ServerOnboardingKey,
|
||||
stack::Stack,
|
||||
stats::SystemStatsRecord,
|
||||
sync::ResourceSync,
|
||||
@@ -44,6 +45,7 @@ pub struct Client {
|
||||
pub user_groups: Collection<UserGroup>,
|
||||
pub permissions: Collection<Permission>,
|
||||
pub api_keys: Collection<ApiKey>,
|
||||
pub server_onboarding_keys: Collection<ServerOnboardingKey>,
|
||||
pub tags: Collection<Tag>,
|
||||
pub variables: Collection<Variable>,
|
||||
pub git_accounts: Collection<GitProviderAccount>,
|
||||
@@ -80,6 +82,8 @@ impl Client {
|
||||
user_groups: mongo_indexed::collection(&db, true).await?,
|
||||
permissions: mongo_indexed::collection(&db, true).await?,
|
||||
api_keys: mongo_indexed::collection(&db, true).await?,
|
||||
server_onboarding_keys: mongo_indexed::collection(&db, true)
|
||||
.await?,
|
||||
tags: mongo_indexed::collection(&db, true).await?,
|
||||
variables: mongo_indexed::collection(&db, true).await?,
|
||||
git_accounts: mongo_indexed::collection(&db, true).await?,
|
||||
|
||||
@@ -38,6 +38,15 @@ impl EncodedKeyPair {
|
||||
let public = SpkiPublicKey::from_raw_bytes(&keypair.public)?;
|
||||
Ok(EncodedKeyPair { private, public })
|
||||
}
|
||||
|
||||
pub fn from_private_key(
|
||||
maybe_pkcs8_private_key: &str,
|
||||
) -> anyhow::Result<EncodedKeyPair> {
|
||||
let private =
|
||||
Pkcs8PrivateKey::from_maybe_raw_bytes(maybe_pkcs8_private_key)?;
|
||||
let public = private.compute_public_key()?;
|
||||
Ok(Self { private, public })
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_maybe_generate_private_key(
|
||||
|
||||
@@ -24,6 +24,10 @@ impl Pkcs8PrivateKey {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
self.0.as_bytes()
|
||||
}
|
||||
|
||||
pub fn into_inner(self) -> String {
|
||||
self.0
|
||||
}
|
||||
@@ -48,6 +52,33 @@ impl Pkcs8PrivateKey {
|
||||
})
|
||||
}
|
||||
|
||||
/// - For raw bytes: converts to pkcs8 and returns
|
||||
/// - For pkcs8 base64: clones and returns
|
||||
/// - For pkcs8 base64 pem: unwraps and returns
|
||||
pub fn from_maybe_raw_bytes(
|
||||
maybe_pkcs8_private_key: &str,
|
||||
) -> anyhow::Result<Self> {
|
||||
// check pem rfc7468 (openssl)
|
||||
if maybe_pkcs8_private_key.starts_with("-----BEGIN") {
|
||||
let (_label, private_key_der) =
|
||||
pem_rfc7468::decode_vec(maybe_pkcs8_private_key.as_bytes())
|
||||
.map_err(anyhow::Error::msg)
|
||||
.context("Failed to get der from pem")?;
|
||||
return Ok(Self(BASE64_STANDARD.encode(private_key_der)));
|
||||
}
|
||||
let len = maybe_pkcs8_private_key.len();
|
||||
if len == 64 {
|
||||
// already base64 der
|
||||
Ok(Self(maybe_pkcs8_private_key.to_string()))
|
||||
} else if len <= 32 {
|
||||
Self::from_raw_bytes(maybe_pkcs8_private_key.as_bytes())
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"Private key must be less than 32 characters, or pkcs8 encoded."
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_raw_bytes(private_key: &[u8]) -> anyhow::Result<Self> {
|
||||
if private_key.len() > 32 {
|
||||
return Err(anyhow!(
|
||||
|
||||
@@ -25,6 +25,10 @@ impl SpkiPublicKey {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
self.0.as_bytes()
|
||||
}
|
||||
|
||||
pub fn into_inner(self) -> String {
|
||||
self.0
|
||||
}
|
||||
|
||||
@@ -21,7 +21,11 @@ use tracing::warn;
|
||||
use crate::{MessageState, websocket::Websocket};
|
||||
|
||||
pub trait PublicKeyValidator {
|
||||
fn validate(&self, public_key: String) -> anyhow::Result<()>;
|
||||
type ValidationResult;
|
||||
fn validate(
|
||||
&self,
|
||||
public_key: String,
|
||||
) -> impl Future<Output = anyhow::Result<Self::ValidationResult>>;
|
||||
}
|
||||
|
||||
pub struct LoginFlowArgs<'a, 's, V, W> {
|
||||
@@ -34,7 +38,7 @@ pub struct LoginFlowArgs<'a, 's, V, W> {
|
||||
pub trait LoginFlow {
|
||||
fn login<'a, 's, V: PublicKeyValidator, W: Websocket>(
|
||||
args: LoginFlowArgs<'a, 's, V, W>,
|
||||
) -> impl Future<Output = anyhow::Result<()>>;
|
||||
) -> impl Future<Output = anyhow::Result<V::ValidationResult>>;
|
||||
}
|
||||
|
||||
const AUTH_TIMEOUT: Duration = Duration::from_secs(2);
|
||||
@@ -49,7 +53,7 @@ impl LoginFlow for ServerLoginFlow {
|
||||
public_key_validator,
|
||||
socket,
|
||||
}: LoginFlowArgs<'a, 's, V, W>,
|
||||
) -> anyhow::Result<()> {
|
||||
) -> anyhow::Result<V::ValidationResult> {
|
||||
let res = async {
|
||||
// Server generates random nonce and sends to client
|
||||
let nonce = nonce();
|
||||
@@ -118,17 +122,17 @@ impl LoginFlow for ServerLoginFlow {
|
||||
.context("Invalid public key")?
|
||||
.into_inner();
|
||||
|
||||
public_key_validator.validate(public_key)
|
||||
public_key_validator.validate(public_key).await
|
||||
}
|
||||
.await;
|
||||
|
||||
match res {
|
||||
Ok(_) => {
|
||||
Ok(res) => {
|
||||
socket
|
||||
.send(MessageState::Successful.into())
|
||||
.await
|
||||
.context("Failed to send login successful to client")?;
|
||||
Ok(())
|
||||
Ok(res)
|
||||
}
|
||||
Err(e) => {
|
||||
let mut bytes = serialize_error_bytes(&e);
|
||||
@@ -160,7 +164,7 @@ impl LoginFlow for ClientLoginFlow {
|
||||
public_key_validator,
|
||||
socket,
|
||||
}: LoginFlowArgs<'a, 's, V, W>,
|
||||
) -> anyhow::Result<()> {
|
||||
) -> anyhow::Result<V::ValidationResult> {
|
||||
let res = async {
|
||||
// Receive nonce from server
|
||||
let nonce = socket
|
||||
@@ -210,7 +214,8 @@ impl LoginFlow for ClientLoginFlow {
|
||||
SpkiPublicKey::from_raw_bytes(handshake.remote_public_key()?)
|
||||
.context("Invalid public key")?
|
||||
.into_inner();
|
||||
public_key_validator.validate(public_key)?;
|
||||
let validation_result =
|
||||
public_key_validator.validate(public_key).await?;
|
||||
|
||||
// Send handshake_m3
|
||||
let mut handshake_m3 = handshake
|
||||
@@ -231,7 +236,7 @@ impl LoginFlow for ClientLoginFlow {
|
||||
"Authentication state message did not contain state byte",
|
||||
)?;
|
||||
match MessageState::from_byte(*state) {
|
||||
MessageState::Successful => anyhow::Ok(()),
|
||||
MessageState::Successful => anyhow::Ok(validation_result),
|
||||
_ => Err(deserialize_error_bytes(
|
||||
&state_msg[..(state_msg.len() - 1)],
|
||||
)),
|
||||
@@ -239,23 +244,24 @@ impl LoginFlow for ClientLoginFlow {
|
||||
}
|
||||
.await;
|
||||
|
||||
if let Err(e) = res {
|
||||
let mut bytes = serialize_error_bytes(&e);
|
||||
bytes.push(MessageState::Failed.as_byte());
|
||||
if let Err(e) = socket
|
||||
.send(bytes.into())
|
||||
.await
|
||||
.context("Failed to send login failed to client")
|
||||
{
|
||||
// Log additional error
|
||||
warn!("{e:#}");
|
||||
match res {
|
||||
Ok(res) => Ok(res),
|
||||
Err(e) => {
|
||||
let mut bytes = serialize_error_bytes(&e);
|
||||
bytes.push(MessageState::Failed.as_byte());
|
||||
if let Err(e) = socket
|
||||
.send(bytes.into())
|
||||
.await
|
||||
.context("Failed to send login failed to client")
|
||||
{
|
||||
// Log additional error
|
||||
warn!("{e:#}");
|
||||
}
|
||||
// Close socket
|
||||
let _ = socket.close(None).await;
|
||||
// Return the original error
|
||||
Err(e)
|
||||
}
|
||||
// Close socket
|
||||
let _ = socket.close(None).await;
|
||||
// Return the original error
|
||||
Err(e)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user