Implement `AuthProvider`

next
Sayan Nandan 3 years ago
parent 3a6083b3f1
commit c8ccfca09b
No known key found for this signature in database
GPG Key ID: 8BC07A0A4D41DD52

80
Cargo.lock generated

@ -59,6 +59,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "base64"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]]
name = "bb8"
version = "0.7.1"
@ -87,6 +93,22 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "blowfish"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7"
dependencies = [
"byteorder",
"cipher",
]
[[package]]
name = "byteorder"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "bytes"
version = "1.1.0"
@ -118,6 +140,16 @@ dependencies = [
"winapi",
]
[[package]]
name = "cipher"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e"
dependencies = [
"crypto-common",
"inout",
]
[[package]]
name = "clap"
version = "2.34.0"
@ -220,6 +252,16 @@ dependencies = [
"winapi",
]
[[package]]
name = "crypto-common"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "devtimer"
version = "4.0.1"
@ -370,6 +412,16 @@ dependencies = [
"slab",
]
[[package]]
name = "generic-array"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "getrandom"
version = "0.2.4"
@ -405,6 +457,15 @@ version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "inout"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1f03d4ab4d5dc9ec2d219f86c15d2a15fc08239d1cd3b2d6a19717c0a2f443"
dependencies = [
"generic-array",
]
[[package]]
name = "instant"
version = "0.1.12"
@ -810,6 +871,17 @@ dependencies = [
"num_cpus",
]
[[package]]
name = "rcrypt"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c70c3bf2f0b523cc6f2420446e6fca7e14db888cbec907991df043a4d6d2e1e"
dependencies = [
"base64",
"blowfish",
"getrandom",
]
[[package]]
name = "redox_syscall"
version = "0.2.10"
@ -1005,6 +1077,7 @@ name = "skyd"
version = "0.7.3"
dependencies = [
"ahash",
"base64",
"bincode",
"bytes",
"cc",
@ -1021,6 +1094,7 @@ dependencies = [
"openssl",
"parking_lot 0.12.0",
"rand",
"rcrypt",
"regex",
"serde",
"sky_macros",
@ -1210,6 +1284,12 @@ dependencies = [
"serde",
]
[[package]]
name = "typenum"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
[[package]]
name = "unicode-segmentation"
version = "1.9.0"

@ -25,6 +25,8 @@ serde = { version = "1.0.136", features = ["derive"] }
tokio = { version = "1.16.1", features = ["full"] }
tokio-openssl = "0.6.3"
toml = "0.5.8"
rcrypt = "0.4.0"
base64 = "0.13.0"
[target.'cfg(all(not(target_env = "msvc"), not(miri)))'.dependencies]
# external deps

@ -0,0 +1,54 @@
/*
* Created on Mon Feb 21 2022
*
* This file is a part of Skytable
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
* NoSQL database written by Sayan Nandan ("the Author") with the
* vision to provide flexibility in data modelling without compromising
* on performance, queryability or scalability.
*
* Copyright (c) 2022, Sayan Nandan <ohsayan@outlook.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
use crate::corestore::array::Array;
type AuthkeyArray = Array<u8, { super::AUTHKEY_SIZE }>;
/// Return a "human readable key" and the "authbytes" that can be stored
/// safely. To do this:
/// - Generate 64 random bytes
/// - Encode that into base64. This is the client key
/// - Hash the key using rcrypt. This is the server key that
/// will be stored
pub fn generate_full() -> (String, super::Authkey) {
let mut bytes: [u8; 64] = [0u8; 64];
openssl::rand::rand_bytes(&mut bytes).unwrap();
let ret = base64::encode(&bytes);
let hash = rcrypt::hash(&ret, rcrypt::DEFAULT_COST).unwrap();
let store_in_db = unsafe {
let mut array = AuthkeyArray::new();
// we guarantee that the size is equal to 40
array.extend_from_slice_unchecked(&hash);
array.into_array_unchecked()
};
(ret, store_in_db)
}
/// Verify a "human readable key" against the provided "authbytes"
pub fn verify_key(input: &[u8], hash: &[u8]) -> bool {
rcrypt::verify(input, hash).unwrap()
}

@ -26,34 +26,57 @@
#![allow(dead_code)] // TODO(@ohsayan): Remove this once we're done
use crate::corestore::heap_array::HeapArray;
use crate::corestore::lazy::Once;
use crate::corestore::map::Skymap;
use openssl::rand::rand_bytes;
/*
* For our authn/authz, we have two important keys:
* - The origin key: This is the key saved in the configuration file that can also be
* used as the "recovery key" in the event the "root key" is lost. To claim the root
* account, one needs this key. This is a variable width key with a maximum size of
* 64
* - The root key: This is the superuser key that can be used to create/deny other
* accounts. On claiming the root account, this key is issued
*
* When the root account is claimed, it can be used to create "standard users". Standard
* users have access to everything but the ability to create/revoke other users
*/
use crate::corestore::array::Array;
use crate::corestore::htable::Coremap;
use core::mem::MaybeUninit;
use std::sync::Arc;
mod keys;
#[cfg(test)]
mod tests;
// constants
/// Size of an authn key in bytes
const AUTHKEY_SIZE: usize = 64;
const AUTHKEY_SIZE: usize = 40;
/// Size of an authn ID in bytes
const AUTHID_SIZE: usize = 40;
#[sky_macros::array]
const USER_ROOT_ARRAY: [MaybeUninit<u8>; 40] = [b'r', b'o', b'o', b't'];
/// The root user
const USER_ROOT: AuthID = unsafe { AuthID::from_const(USER_ROOT_ARRAY, 4) };
/// An authn ID
type AuthID = HeapArray;
type AuthID = Array<u8, AUTHID_SIZE>;
/// An authn key
type Authkey = [u8; AUTHKEY_SIZE];
/// An authn key that can only be assigned to once
type OnceAuthkey = Once<Authkey>;
pub type Authkey = [u8; AUTHKEY_SIZE];
/// Result of an auth operation
type AuthResult<T> = Result<T, AuthError>;
/// The authn/authz provider
///
pub struct AuthProvider {
/// the origin key
origin: OnceAuthkey,
/// the root key
root: OnceAuthkey,
origin: Option<Authkey>,
/// the current user
whoami: Option<AuthID>,
/// a map of standard users
standard: Skymap<AuthID, Authkey>,
standard: Arc<Coremap<AuthID, Authkey>>,
}
/// Auth erros
#[derive(PartialEq, Debug)]
pub enum AuthError {
/// The auth slot was already claimed
AlreadyClaimed,
@ -61,40 +84,72 @@ pub enum AuthError {
BadCredentials,
/// Auth is disabled
Disabled,
/// The action is not available to the current account
PermissionDenied,
/// The user is anonymous and doesn't have the right to execute this
Anonymous,
}
impl AuthProvider {
pub fn new(
origin: Option<Authkey>,
root: Option<Authkey>,
standard: Skymap<AuthID, Authkey>,
) -> Self {
pub fn new(standard: Arc<Coremap<AuthID, Authkey>>, origin: Option<Authkey>) -> Self {
Self {
origin: OnceAuthkey::from(origin),
root: OnceAuthkey::from(root),
standard,
whoami: None,
origin,
}
}
/// Claim the root account
pub fn claim_root(&self, origin: &[u8]) -> AuthResult<Authkey> {
match self.origin.get() {
Some(orig) if orig.eq(origin) => {
let id = Self::generate_full();
let idc = id.clone();
if self.root.set(idc) {
Ok(id)
} else {
Err(AuthError::AlreadyClaimed)
}
pub fn claim_root(&self, origin_key: &[u8]) -> AuthResult<String> {
let origin = self.get_origin()?;
if origin == origin_key {
// the origin key was good, let's try claiming root
let (key, store) = keys::generate_full();
if self.standard.true_if_insert(USER_ROOT, store) {
Ok(key)
} else {
Err(AuthError::AlreadyClaimed)
}
Some(_) => Err(AuthError::BadCredentials),
} else {
Err(AuthError::BadCredentials)
}
}
fn are_you_root(&self) -> AuthResult<bool> {
match self.whoami.as_ref().map(|v| v.eq(&USER_ROOT)) {
Some(v) => Ok(v),
None => Err(AuthError::Anonymous),
}
}
pub fn claim_user(&self, claimant: &[u8]) -> AuthResult<String> {
if self.are_you_root()? {
self._claim_user(claimant)
} else {
Err(AuthError::PermissionDenied)
}
}
fn _claim_user(&self, claimant: &[u8]) -> AuthResult<String> {
let (key, store) = keys::generate_full();
if self
.standard
.true_if_insert(Array::try_from_slice(claimant).unwrap(), store)
{
Ok(key)
} else {
Err(AuthError::AlreadyClaimed)
}
}
fn get_origin(&self) -> AuthResult<&Authkey> {
match self.origin.as_ref() {
Some(key) => Ok(key),
None => Err(AuthError::Disabled),
}
}
/// Generate an authentication key
fn generate_full() -> Authkey {
let mut bytes: Authkey = [0u8; AUTHKEY_SIZE];
rand_bytes(&mut bytes).unwrap();
bytes
}
impl Clone for AuthProvider {
fn clone(&self) -> Self {
Self {
standard: self.standard.clone(),
whoami: None,
origin: self.origin,
}
}
}

@ -0,0 +1,75 @@
/*
* Created on Tue Feb 22 2022
*
* This file is a part of Skytable
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
* NoSQL database written by Sayan Nandan ("the Author") with the
* vision to provide flexibility in data modelling without compromising
* on performance, queryability or scalability.
*
* Copyright (c) 2022, Sayan Nandan <ohsayan@outlook.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
mod keys {
use super::super::keys::{generate_full, verify_key};
#[test]
fn test_verify_key() {
let (key, store) = generate_full();
assert!(verify_key(key.as_bytes(), &store));
}
}
mod authn {
use super::super::{AuthError, AuthProvider};
use crate::corestore::htable::Coremap;
use std::sync::Arc;
#[test]
fn claim_root_okay() {
let orig = b"c4299d190fb9a00626797fcc138c56eae9971664";
let authmap = Arc::new(Coremap::new());
let provider = AuthProvider::new(authmap, Some(*orig));
let _ = provider.claim_root(orig).unwrap();
}
#[test]
fn claim_root_wrongkey() {
let orig = b"c4299d190fb9a00626797fcc138c56eae9971664";
let authmap = Arc::new(Coremap::new());
let provider = AuthProvider::new(authmap, Some(*orig));
let claim_err = provider.claim_root(&orig[1..]).unwrap_err();
assert_eq!(claim_err, AuthError::BadCredentials);
}
#[test]
fn claim_root_disabled() {
let provider = AuthProvider::new(Arc::new(Coremap::new()), None);
assert_eq!(
provider.claim_root(b"abcd").unwrap_err(),
AuthError::Disabled
);
}
#[test]
fn claim_root_already_claimed() {
let orig = b"c4299d190fb9a00626797fcc138c56eae9971664";
let authmap = Arc::new(Coremap::new());
let provider = AuthProvider::new(authmap, Some(*orig));
let _ = provider.claim_root(orig).unwrap();
assert_eq!(
provider.claim_root(orig).unwrap_err(),
AuthError::AlreadyClaimed
);
}
}

@ -33,7 +33,6 @@ use core::fmt;
use core::hash::Hash;
use core::hash::Hasher;
use core::iter::FromIterator;
use core::marker::PhantomData;
use core::mem::ManuallyDrop;
use core::mem::MaybeUninit;
use core::ops;
@ -90,14 +89,6 @@ impl<'a, T: Copy> Drop for LenScopeGuard<'a, T> {
}
}
// defy the compiler; just some silly hackery here -- move on
struct UninitArray<T, const N: usize>(PhantomData<T>);
impl<T, const N: usize> UninitArray<T, N> {
const VALUE: MaybeUninit<T> = MaybeUninit::uninit();
const ARRAY: [MaybeUninit<T>; N] = [Self::VALUE; N];
}
macro_rules! impl_zeroed_nm {
($($ty:ty),* $(,)?) => {
$(
@ -118,10 +109,13 @@ impl_zeroed_nm! {
}
impl<T, const N: usize> Array<T, N> {
// just some silly hackery here because uninit_array isn't stabilized -- move on
const VALUE: MaybeUninit<T> = MaybeUninit::uninit();
const ARRAY: [MaybeUninit<T>; N] = [Self::VALUE; N];
/// Create a new array
pub const fn new() -> Self {
Array {
stack: UninitArray::ARRAY,
stack: Self::ARRAY,
init_len: 0,
}
}
@ -140,7 +134,7 @@ impl<T, const N: usize> Array<T, N> {
/// This function is extremely unsafe. I mean, I don't even know how to call it safe.
/// There's one way though: make M == N. This will panic in debug mode if M > N. In
/// release mode, good luck
pub unsafe fn from_const_array<const M: usize>(arr: [T; M]) -> Self {
unsafe fn from_const_array<const M: usize>(arr: [T; M]) -> Self {
debug_assert!(
N >= M,
"Provided const array exceeds size limit of initialized array"
@ -278,13 +272,20 @@ impl<T, const N: usize> Array<T, N> {
// not fully initialized
Err(self)
} else {
unsafe {
Ok({
// make sure we don't do a double free or end up deleting the elements
let _self = ManuallyDrop::new(self);
ptr::read(_self.as_ptr() as *const [T; N])
})
}
unsafe { Ok(self.into_array_unchecked()) }
}
}
pub unsafe fn into_array_unchecked(self) -> [T; N] {
// make sure we don't do a double free or end up deleting the elements
let _self = ManuallyDrop::new(self);
ptr::read(_self.as_ptr() as *const [T; N])
}
pub fn try_from_slice(slice: impl AsRef<[T]>) -> Option<Self> {
let slice = slice.as_ref();
if slice.len() != N {
None
} else {
Some(unsafe { Self::from_slice(slice) })
}
}
/// Extend self from a slice
@ -408,7 +409,7 @@ impl<T, const N: usize> Array<T, N> {
unsafe {
// the ptr to start writing from
let mut ptr = Self::as_mut_ptr(self).add(self.len());
let end_ptr = Self::as_ptr(self).add(self.remaining_cap());
let end_ptr = Self::as_ptr(self).add(self.capacity());
let mut guard = LenScopeGuard::new(&mut self.init_len);
let mut iter = iterable.into_iter();
loop {
@ -623,6 +624,16 @@ fn test_array_clone() {
assert_eq!(arr, myclone);
}
#[test]
fn test_array_extend_okay() {
let mut arr: Array<u8, 64> = Array::new();
arr.extend(
"qHwRsmyBYHbqyHfdShOfVSayVUmeKlEagvJoGuTyvaCqpsfFkZabeuqmVeiKbJxV"
.as_bytes()
.to_owned(),
);
}
#[test]
#[should_panic]
fn test_array_extend_fail() {

@ -212,6 +212,7 @@ pub struct Once<T> {
value: AtomicPtr<T>,
}
#[allow(dead_code)] // TODO: Remove this
impl<T> Once<T> {
pub const fn new() -> Self {
Self {

@ -199,6 +199,10 @@ impl Memstore {
},
}
}
/// Get a reference to the system keyspace
pub fn get_system_keyspace(&self) -> Arc<Keyspace> {
self.get_keyspace_atomic_ref(&SYSTEM).unwrap()
}
/// Get an atomic reference to a keyspace
pub fn get_keyspace_atomic_ref<Q>(&self, keyspace_identifier: &Q) -> Option<Arc<Keyspace>>
where

Loading…
Cancel
Save