mod journal;
mod schema;
mod sqlite;
pub use journal::{CommitHook, JournalOp, JournalRecord};
pub use sqlite::{SqliteStore, StoreConfig};
use crate::proto::api::{Limits, RotationRequest, Scope, SignedBundle};
use crate::proto::audit::{Actor, AuditEvent, AuditRow, ChainHead};
use crate::proto::children::ChildrenBlob;
use crate::proto::descriptor::SignedDescriptor;
use crate::proto::ids::{Hash32, Key32, NameHmac, Sig64, TokenId, VaultId};
pub type VaultPk = i64;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Ctx {
pub actor: Actor,
pub now: i64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VaultRow {
pub pk: VaultPk,
pub vault_id: VaultId,
pub owner_sign_pub: Key32,
pub owner_box_pub: Key32,
pub owner_bundle: SignedBundle,
pub generation: u32,
pub revision: u64,
pub created_at: i64,
pub last_active_at: i64,
pub bytes_used: u64,
pub descriptor: SignedDescriptor,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NewVault {
pub vault_id: VaultId,
pub owner_sign_pub: Key32,
pub owner_box_pub: Key32,
pub descriptor: SignedDescriptor,
pub owner_bundle: SignedBundle,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TokenRow {
pub token_id: TokenId,
pub vault_pk: VaultPk,
pub auth_pub: Key32,
pub box_pub: Key32,
pub scope: Scope,
pub created_at: i64,
pub expires_at: i64,
pub bundle: SignedBundle,
pub generation: u32,
pub allow_list: Option<Vec<NameHmac>>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NewToken {
pub token_id: TokenId,
pub auth_pub: Key32,
pub box_pub: Key32,
pub scope: Scope,
pub expires_at: i64,
pub bundle: SignedBundle,
pub generation: u32,
pub allow_list: Option<Vec<NameHmac>>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VersionRow {
pub name_hmac: NameHmac,
pub version: u64,
pub name_ct: Vec<u8>,
pub value_ct: Option<Vec<u8>>,
pub generation: u32,
pub written_at: i64,
pub written_by: Actor,
pub tombstone: bool,
pub sig: Sig64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HeadRow {
pub name_hmac: NameHmac,
pub name_ct: Vec<u8>,
pub version: u64,
pub written_at: i64,
pub size: u32,
pub tombstone: bool,
pub generation: u32,
pub value_ct_hash: Hash32,
pub sig: Sig64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Precondition {
IfNoneMatch,
IfMatch(u64),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RecordWrite {
pub name_ct: Vec<u8>,
pub value_ct: Option<Vec<u8>>,
pub generation: u32,
pub version: u64,
pub written_at: i64,
pub sig: Sig64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RecordKind {
Secret,
Config,
}
impl RecordKind {
pub fn wire(self) -> crate::proto::record::RecordKind {
match self {
RecordKind::Secret => crate::proto::record::RecordKind::Secret,
RecordKind::Config => crate::proto::record::RecordKind::Config,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Quota {
Names,
ValueSize,
VaultBytes,
Tokens,
Configs,
ConfigSize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SpentKind {
Challenge = 1,
Nonce = 2,
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum StoreError {
#[error("not found")]
NotFound,
#[error("already exists")]
Exists,
#[error("precondition failed; the current version is {current:?}")]
PreconditionFailed { current: Option<u64> },
#[error("the write signs version {signed}, but this write would create version {assigned}")]
VersionMismatch { signed: u64, assigned: u64 },
#[error("the write is for a stale key generation")]
StaleGeneration,
#[error("the vault changed since this batch was built")]
Conflict,
#[error("the rotation batch is incomplete: {0}")]
IncompleteRotation(&'static str),
#[error("quota exceeded: {0:?}")]
Quota(Quota),
#[error("already used")]
Spent,
#[error("the journal write failed, so nothing was applied: {0}")]
Journal(String),
#[error("the database is not in WAL mode (it is in {0} mode)")]
NotWal(String),
#[error("database error: {0}")]
Database(String),
#[error(
"the database has schema version {found}, newer than this gv-server knows ({known}); run the newer binary"
)]
NewerSchema { found: i64, known: i64 },
}
impl From<rusqlite::Error> for StoreError {
fn from(e: rusqlite::Error) -> Self {
match e {
rusqlite::Error::SqliteFailure(f, _)
if f.code == rusqlite::ErrorCode::ConstraintViolation =>
{
StoreError::Exists
}
other => StoreError::Database(other.to_string()),
}
}
}
pub trait Store: Send + Sync {
fn create_vault(&self, vault: &NewVault, now: i64) -> Result<VaultRow, StoreError>;
fn vault_by_id(&self, id: &VaultId) -> Result<Option<VaultRow>, StoreError>;
fn vault_by_pk(&self, pk: VaultPk) -> Result<Option<VaultRow>, StoreError>;
fn touch(&self, pk: VaultPk, now: i64) -> Result<(), StoreError>;
fn descriptors(&self, pk: VaultPk, after: u32) -> Result<Vec<SignedDescriptor>, StoreError>;
fn register_token(
&self,
pk: VaultPk,
token: &NewToken,
ctx: Ctx,
limits: &Limits,
) -> Result<TokenRow, StoreError>;
fn token_by_id(&self, id: &TokenId) -> Result<Option<TokenRow>, StoreError>;
fn tokens(&self, pk: VaultPk) -> Result<Vec<TokenRow>, StoreError>;
fn revoke_tokens(
&self,
pk: VaultPk,
ids: &[TokenId],
reported: bool,
ctx: Ctx,
hook: CommitHook<'_>,
) -> Result<Vec<TokenId>, StoreError>;
fn list_records(
&self,
pk: VaultPk,
kind: RecordKind,
after: Option<&NameHmac>,
limit: usize,
) -> Result<Vec<HeadRow>, StoreError>;
fn latest_record(
&self,
pk: VaultPk,
kind: RecordKind,
name: &NameHmac,
) -> Result<Option<VersionRow>, StoreError>;
fn record_version(
&self,
pk: VaultPk,
kind: RecordKind,
name: &NameHmac,
version: u64,
) -> Result<Option<VersionRow>, StoreError>;
fn record_versions(
&self,
pk: VaultPk,
kind: RecordKind,
name: &NameHmac,
) -> Result<Vec<VersionRow>, StoreError>;
fn all_record_versions(
&self,
pk: VaultPk,
kind: RecordKind,
) -> Result<Vec<VersionRow>, StoreError>;
#[allow(clippy::too_many_arguments)]
fn put_record(
&self,
pk: VaultPk,
kind: RecordKind,
name: &NameHmac,
pre: Precondition,
write: &RecordWrite,
ctx: Ctx,
limits: &Limits,
) -> Result<u64, StoreError>;
#[allow(clippy::too_many_arguments)]
fn delete_record(
&self,
pk: VaultPk,
kind: RecordKind,
name: &NameHmac,
if_match: u64,
write: &RecordWrite,
ctx: Ctx,
limits: &Limits,
) -> Result<u64, StoreError>;
fn live_records(&self, pk: VaultPk, kind: RecordKind) -> Result<u32, StoreError>;
fn children(&self, pk: VaultPk) -> Result<Option<ChildrenBlob>, StoreError>;
fn put_children(
&self,
pk: VaultPk,
pre: Precondition,
blob: &ChildrenBlob,
ctx: Ctx,
limits: &Limits,
) -> Result<u64, StoreError>;
fn record(&self, pk: VaultPk, event: AuditEvent, now: i64) -> Result<(), StoreError>;
fn audit_after(
&self,
pk: VaultPk,
after: u64,
limit: usize,
) -> Result<(Vec<AuditRow>, Option<ChainHead>), StoreError>;
fn apply_rotation(
&self,
pk: VaultPk,
request: &RotationRequest,
ctx: Ctx,
hook: CommitHook<'_>,
) -> Result<u32, StoreError>;
fn delete_vault(&self, pk: VaultPk, now: i64, hook: CommitHook<'_>) -> Result<(), StoreError>;
fn spend(
&self,
kind: SpentKind,
id: &[u8],
expires_at: i64,
now: i64,
) -> Result<(), StoreError>;
fn journal_applied(&self) -> Result<u64, StoreError>;
fn replay(&self, record: &JournalRecord) -> Result<bool, StoreError>;
}