Compare commits

..
Author SHA1 Message Date
igor04091968 acf767360f refactor(portal): move role model into module 2026-06-14 21:25:30 +03:00
IgorRachkovandGitHub 9ad5b2fc34 Merge pull request #27 from igor04091968/release/github-release-assets
rust-binary-build / build-linux-x86_64 (push) Canceled after 0s
ci: publish Rust binary package to GitHub Releases on tags
2026-06-14 21:00:59 +03:00
igor04091968 8596cd057b ci: publish Rust binary package to GitHub Releases on tags 2026-06-14 18:32:04 +03:00
IgorRachkovandGitHub 42b0fdb718 Merge pull request #26 from igor04091968/codex/rust-professionalization
docs(rust): professionalize production guardrails
2026-06-14 18:14:27 +03:00
3 changed files with 99 additions and 71 deletions
+20 -1
View File
@@ -13,6 +13,9 @@ on:
tags: tags:
- 'v*' - 'v*'
permissions:
contents: write
jobs: jobs:
build-linux-x86_64: build-linux-x86_64:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -39,7 +42,10 @@ jobs:
--release-dir adk-rust/target/release \ --release-dir adk-rust/target/release \
--out-dir dist/awatch-rus-linux-x86_64 \ --out-dir dist/awatch-rus-linux-x86_64 \
--archive dist/awatch-rus-linux-x86_64-release-binaries.tar.gz \ --archive dist/awatch-rus-linux-x86_64-release-binaries.tar.gz \
--target linux-x86_64 --target linux-x86_64 \
--commit "${GITHUB_SHA}" \
--ref "${GITHUB_REF}" \
--run-id "${GITHUB_RUN_ID}"
- name: Upload release binaries artifact - name: Upload release binaries artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
@@ -53,3 +59,16 @@ jobs:
dist/awatch-rus-linux_x86_64/BUILD_MANIFEST.json dist/awatch-rus-linux_x86_64/BUILD_MANIFEST.json
if-no-files-found: error if-no-files-found: error
retention-days: 30 retention-days: 30
- name: Publish GitHub Release assets
if: startsWith(github.ref, 'refs/tags/v')
uses: softprops/action-gh-release@v2
with:
generate_release_notes: true
fail_on_unmatched_files: true
files: |
dist/awatch-rus-linux_x86_64-release-binaries.tar.gz
dist/awatch-rus-linux_x86_64-release-binaries.tar.gz.sha256
dist/awatch-rus-linux_x86_64/BINARIES.txt
dist/awatch-rus-linux_x86_64/SHA256SUMS.txt
dist/awatch-rus-linux_x86_64/BUILD_MANIFEST.json
+2 -70
View File
@@ -24,6 +24,7 @@ use sha2::{Digest, Sha256};
use tiny_http::{Header, Method, Request, Response, Server, StatusCode}; use tiny_http::{Header, Method, Request, Response, Server, StatusCode};
mod executive_actions; mod executive_actions;
mod portal_roles;
mod production; mod production;
mod risk_narrative; mod risk_narrative;
mod workforce_kpi_explain; mod workforce_kpi_explain;
@@ -31,6 +32,7 @@ mod workforce_kpi_explain;
use executive_actions::{ use executive_actions::{
actions_from_center, build_action_center_from_report, filter_actions_for_role, actions_from_center, build_action_center_from_report, filter_actions_for_role,
}; };
use portal_roles::PortalRole;
use production::{ use production::{
build_healthz, build_readyz, build_version, http_request_metadata, is_limited_api_route, build_healthz, build_readyz, build_version, http_request_metadata, is_limited_api_route,
log_http_request, mark_request_started, record_http_metric, record_ingestion_accepted, log_http_request, mark_request_started, record_http_metric, record_ingestion_accepted,
@@ -75,76 +77,6 @@ unsafe extern "C" {
type SnapshotCache = Arc<Mutex<Option<CachedSnapshot>>>; type SnapshotCache = Arc<Mutex<Option<CachedSnapshot>>>;
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
enum PortalRole {
Executive,
Manager,
Security,
Forensics,
Admin,
}
impl PortalRole {
fn parse(value: &str) -> Option<Self> {
match value.trim().to_ascii_lowercase().as_str() {
"executive" | "owner" | "rukovoditel" | "руководитель" => {
Some(Self::Executive)
}
"manager" | "workforce" | "руководитель_подразделения" => {
Some(Self::Manager)
}
"security" | "ib" | "soc" | "безопасность" => Some(Self::Security),
"forensics" | "investigation" | "расследования" => Some(Self::Forensics),
"admin" | "operations" | "operator" | "эксплуатация" => Some(Self::Admin),
_ => None,
}
}
fn as_str(self) -> &'static str {
match self {
Self::Executive => "executive",
Self::Manager => "manager",
Self::Security => "security",
Self::Forensics => "forensics",
Self::Admin => "admin",
}
}
fn label_ru(self) -> &'static str {
match self {
Self::Executive => "Руководитель",
Self::Manager => "Руководитель подразделения",
Self::Security => "Безопасность",
Self::Forensics => "Расследования",
Self::Admin => "Администратор",
}
}
fn allowed_scopes(self) -> &'static [&'static str] {
match self {
Self::Executive => &["executive", "workforce"],
Self::Manager => &["executive", "workforce"],
Self::Security => &["security", "incidents", "ueba", "pfsense"],
Self::Forensics => &["forensics", "incidents", "ueba"],
Self::Admin => &[
"executive",
"workforce",
"security",
"forensics",
"incidents",
"ueba",
"pfsense",
"admin",
],
}
}
fn can_access(self, scope: &str) -> bool {
self.allowed_scopes().contains(&scope)
}
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct CachedSnapshot { struct CachedSnapshot {
created: Instant, created: Instant,
@@ -0,0 +1,77 @@
//! Portal role model and access-scope contract.
//!
//! CONTRACT: role aliases, serialized values and allowed scopes are part of
//! the portal API/security boundary. Keep changes explicit and covered by
//! existing role-gate tests in `main.rs`.
use serde::Serialize;
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub(crate) enum PortalRole {
Executive,
Manager,
Security,
Forensics,
Admin,
}
impl PortalRole {
pub(crate) fn parse(value: &str) -> Option<Self> {
match value.trim().to_ascii_lowercase().as_str() {
"executive" | "owner" | "rukovoditel" | "руководитель" => {
Some(Self::Executive)
}
"manager" | "workforce" | "руководитель_подразделения" => {
Some(Self::Manager)
}
"security" | "ib" | "soc" | "безопасность" => Some(Self::Security),
"forensics" | "investigation" | "расследования" => Some(Self::Forensics),
"admin" | "operations" | "operator" | "эксплуатация" => Some(Self::Admin),
_ => None,
}
}
pub(crate) fn as_str(self) -> &'static str {
match self {
Self::Executive => "executive",
Self::Manager => "manager",
Self::Security => "security",
Self::Forensics => "forensics",
Self::Admin => "admin",
}
}
pub(crate) fn label_ru(self) -> &'static str {
match self {
Self::Executive => "Руководитель",
Self::Manager => "Руководитель подразделения",
Self::Security => "Безопасность",
Self::Forensics => "Расследования",
Self::Admin => "Администратор",
}
}
pub(crate) fn allowed_scopes(self) -> &'static [&'static str] {
match self {
Self::Executive => &["executive", "workforce"],
Self::Manager => &["executive", "workforce"],
Self::Security => &["security", "incidents", "ueba", "pfsense"],
Self::Forensics => &["forensics", "incidents", "ueba"],
Self::Admin => &[
"executive",
"workforce",
"security",
"forensics",
"incidents",
"ueba",
"pfsense",
"admin",
],
}
}
pub(crate) fn can_access(self, scope: &str) -> bool {
self.allowed_scopes().contains(&scope)
}
}