mirror of
https://github.com/getnora-io/nora.git
synced 2026-04-12 09:10:32 +00:00
style: cargo fmt
DevITWay
This commit is contained in:
414
CHANGELOG.md.bak
Normal file
414
CHANGELOG.md.bak
Normal file
@@ -0,0 +1,414 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to NORA will be documented in this file.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.2.18] - 2026-01-31
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Logo styling refinements
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.2.17] - 2026-01-31
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Copyright headers to all source files (Volkov Pavel | DevITWay)
|
||||||
|
- SPDX-License-Identifier: MIT in all .rs files
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.2.16] - 2026-01-31
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- N○RA branding: stylized O logo across dashboard
|
||||||
|
- Fixed O letter alignment in logo
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.2.15] - 2026-01-31
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Code formatting (cargo fmt)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.2.14] - 2026-01-31
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Docker dashboard now shows actual image size from manifest layers (config + layers sum)
|
||||||
|
- Previously showed only manifest file size (~500 B instead of actual image size)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.2.13] - 2026-01-31
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- npm dashboard now shows correct version count and package sizes
|
||||||
|
- Parses metadata.json for versions, dist.unpackedSize, and time.modified
|
||||||
|
- Previously showed 0 versions / 0 B for all packages
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.2.12] - 2026-01-30
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
#### Configurable Rate Limiting
|
||||||
|
- Rate limits now configurable via `config.toml` and environment variables
|
||||||
|
- New config section `[rate_limit]` with parameters: `auth_rps`, `auth_burst`, `upload_rps`, `upload_burst`, `general_rps`, `general_burst`
|
||||||
|
- Environment variables: `NORA_RATE_LIMIT_{AUTH|UPLOAD|GENERAL}_{RPS|BURST}`
|
||||||
|
|
||||||
|
#### Secrets Provider Architecture
|
||||||
|
- Trait-based secrets management (`SecretsProvider` trait)
|
||||||
|
- ENV provider as default (12-Factor App pattern)
|
||||||
|
- Protected secrets with `zeroize` (memory zeroed on drop)
|
||||||
|
- Redacted Debug impl prevents secret leakage in logs
|
||||||
|
- New config section `[secrets]` with `provider` and `clear_env` options
|
||||||
|
|
||||||
|
#### Docker Image Metadata
|
||||||
|
- Support for image metadata retrieval
|
||||||
|
|
||||||
|
#### Documentation
|
||||||
|
- Bilingual onboarding guide (EN/RU)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.2.11] - 2026-01-26
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Internationalization (i18n) support
|
||||||
|
- PyPI registry proxy
|
||||||
|
- UI improvements
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.2.10] - 2026-01-26
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Dark theme applied to all UI pages
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.2.9] - 2026-01-26
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Version bump release
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.2.8] - 2026-01-26
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Dashboard endpoint added to OpenAPI documentation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.2.7] - 2026-01-26
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Dynamic version display in UI sidebar
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.2.6] - 2026-01-26
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
#### Dashboard Metrics
|
||||||
|
- Global stats panel: downloads, uploads, artifacts, cache hit rate, storage
|
||||||
|
- Extended registry cards with artifact count, size, counters
|
||||||
|
- Activity log (last 20 events)
|
||||||
|
|
||||||
|
#### UI
|
||||||
|
- Dark theme (bg: #0f172a, cards: #1e293b)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.2.5] - 2026-01-26
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Docker push/pull: added PATCH endpoint for chunked uploads
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.2.4] - 2026-01-26
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Rate limiting: health/metrics endpoints now exempt
|
||||||
|
- Increased upload rate limits for Docker parallel requests
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.2.0] - 2026-01-25
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
#### UI: SVG Brand Icons
|
||||||
|
- Replaced emoji icons with proper SVG brand icons (Simple Icons style)
|
||||||
|
- Docker, Maven, npm, Cargo, PyPI icons now render as scalable vector graphics
|
||||||
|
- Consistent icon styling across dashboard, sidebar, and detail pages
|
||||||
|
|
||||||
|
#### Testing Infrastructure
|
||||||
|
- Unit tests for LocalStorage (8 tests): put/get, list, stat, health_check
|
||||||
|
- Unit tests for S3Storage with wiremock HTTP mocking (11 tests)
|
||||||
|
- Integration tests for auth/htpasswd (7 tests)
|
||||||
|
- Token lifecycle tests (11 tests)
|
||||||
|
- Validation tests (21 tests)
|
||||||
|
- **Total: 75 tests passing**
|
||||||
|
|
||||||
|
#### Security: Input Validation (`validation.rs`)
|
||||||
|
- Path traversal protection: rejects `../`, `..\\`, null bytes, absolute paths
|
||||||
|
- Docker image name validation per OCI distribution spec
|
||||||
|
- Content digest validation (`sha256:[64 hex]`, `sha512:[128 hex]`)
|
||||||
|
- Docker tag/reference validation
|
||||||
|
- Storage key length limits (max 1024 chars)
|
||||||
|
|
||||||
|
#### Security: Rate Limiting (`rate_limit.rs`)
|
||||||
|
- Auth endpoints: 1 req/sec, burst 5 (brute-force protection)
|
||||||
|
- Upload endpoints: 10 req/sec, burst 20
|
||||||
|
- General endpoints: 100 req/sec, burst 200
|
||||||
|
- Uses `tower_governor` 0.8 with `PeerIpKeyExtractor`
|
||||||
|
|
||||||
|
#### Observability: Request ID Tracking (`request_id.rs`)
|
||||||
|
- `X-Request-ID` header added to all responses
|
||||||
|
- Accepts upstream request ID or generates UUID v4
|
||||||
|
- Tracing spans include request_id for log correlation
|
||||||
|
|
||||||
|
#### CLI: Migrate Command (`migrate.rs`)
|
||||||
|
- `nora migrate --from local --to s3` - migrate between storage backends
|
||||||
|
- `--dry-run` flag for preview without copying
|
||||||
|
- Progress bar with indicatif
|
||||||
|
- Skips existing files in destination
|
||||||
|
- Summary statistics (migrated, skipped, failed, bytes)
|
||||||
|
|
||||||
|
#### Error Handling (`error.rs`)
|
||||||
|
- `AppError` enum with `IntoResponse` for Axum
|
||||||
|
- Automatic conversion from `StorageError` and `ValidationError`
|
||||||
|
- JSON error responses with request_id support
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- `StorageError` now uses `thiserror` derive macro
|
||||||
|
- `TokenError` now uses `thiserror` derive macro
|
||||||
|
- Storage wrapper validates keys before delegating to backend
|
||||||
|
- Docker registry handlers validate name, digest, reference inputs
|
||||||
|
- Body size limit set to 100MB default via `DefaultBodyLimit`
|
||||||
|
|
||||||
|
### Dependencies Added
|
||||||
|
- `thiserror = "2"` - typed error handling
|
||||||
|
- `tower_governor = "0.8"` - rate limiting
|
||||||
|
- `governor = "0.10"` - rate limiting backend
|
||||||
|
- `tempfile = "3"` (dev) - temporary directories for tests
|
||||||
|
- `wiremock = "0.6"` (dev) - HTTP mocking for S3 tests
|
||||||
|
|
||||||
|
### Files Added
|
||||||
|
- `src/validation.rs` - input validation module
|
||||||
|
- `src/migrate.rs` - storage migration module
|
||||||
|
- `src/error.rs` - application error types
|
||||||
|
- `src/request_id.rs` - request ID middleware
|
||||||
|
- `src/rate_limit.rs` - rate limiting configuration
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.1.0] - 2026-01-24
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Multi-protocol support: Docker Registry v2, Maven, npm, Cargo, PyPI
|
||||||
|
- Web UI dashboard
|
||||||
|
- Swagger UI (`/api-docs`)
|
||||||
|
- Storage backends: Local filesystem, S3-compatible
|
||||||
|
- Smart proxy/cache for Maven and npm
|
||||||
|
- Health checks (`/health`, `/ready`)
|
||||||
|
- Basic authentication (htpasswd with bcrypt)
|
||||||
|
- API tokens (revocable, per-user)
|
||||||
|
- Prometheus metrics (`/metrics`)
|
||||||
|
- JSON structured logging
|
||||||
|
- Environment variable configuration
|
||||||
|
- Graceful shutdown (SIGTERM/SIGINT)
|
||||||
|
- Backup/restore commands
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Журнал изменений (RU)
|
||||||
|
|
||||||
|
Все значимые изменения NORA документируются в этом файле.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.2.12] - 2026-01-30
|
||||||
|
|
||||||
|
### Добавлено
|
||||||
|
|
||||||
|
#### Настраиваемый Rate Limiting
|
||||||
|
- Rate limits настраиваются через `config.toml` и переменные окружения
|
||||||
|
- Новая секция `[rate_limit]` с параметрами: `auth_rps`, `auth_burst`, `upload_rps`, `upload_burst`, `general_rps`, `general_burst`
|
||||||
|
- Переменные окружения: `NORA_RATE_LIMIT_{AUTH|UPLOAD|GENERAL}_{RPS|BURST}`
|
||||||
|
|
||||||
|
#### Архитектура Secrets Provider
|
||||||
|
- Trait-based управление секретами (`SecretsProvider` trait)
|
||||||
|
- ENV provider по умолчанию (12-Factor App паттерн)
|
||||||
|
- Защищённые секреты с `zeroize` (память обнуляется при drop)
|
||||||
|
- Redacted Debug impl предотвращает утечку секретов в логи
|
||||||
|
- Новая секция `[secrets]` с опциями `provider` и `clear_env`
|
||||||
|
|
||||||
|
#### Docker Image Metadata
|
||||||
|
- Поддержка получения метаданных образов
|
||||||
|
|
||||||
|
#### Документация
|
||||||
|
- Двуязычный onboarding guide (EN/RU)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.2.11] - 2026-01-26
|
||||||
|
|
||||||
|
### Добавлено
|
||||||
|
- Поддержка интернационализации (i18n)
|
||||||
|
- PyPI registry proxy
|
||||||
|
- Улучшения UI
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.2.10] - 2026-01-26
|
||||||
|
|
||||||
|
### Изменено
|
||||||
|
- Тёмная тема применена ко всем страницам UI
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.2.9] - 2026-01-26
|
||||||
|
|
||||||
|
### Изменено
|
||||||
|
- Релиз с обновлением версии
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.2.8] - 2026-01-26
|
||||||
|
|
||||||
|
### Добавлено
|
||||||
|
- Dashboard endpoint добавлен в OpenAPI документацию
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.2.7] - 2026-01-26
|
||||||
|
|
||||||
|
### Добавлено
|
||||||
|
- Динамическое отображение версии в сайдбаре UI
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.2.6] - 2026-01-26
|
||||||
|
|
||||||
|
### Добавлено
|
||||||
|
|
||||||
|
#### Dashboard Metrics
|
||||||
|
- Глобальная панель статистики: downloads, uploads, artifacts, cache hit rate, storage
|
||||||
|
- Расширенные карточки реестров с количеством артефактов, размером, счётчиками
|
||||||
|
- Лог активности (последние 20 событий)
|
||||||
|
|
||||||
|
#### UI
|
||||||
|
- Тёмная тема (bg: #0f172a, cards: #1e293b)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.2.5] - 2026-01-26
|
||||||
|
|
||||||
|
### Исправлено
|
||||||
|
- Docker push/pull: добавлен PATCH endpoint для chunked uploads
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.2.4] - 2026-01-26
|
||||||
|
|
||||||
|
### Исправлено
|
||||||
|
- Rate limiting: health/metrics endpoints теперь исключены
|
||||||
|
- Увеличены лимиты upload для параллельных Docker запросов
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.2.0] - 2026-01-25
|
||||||
|
|
||||||
|
### Добавлено
|
||||||
|
|
||||||
|
#### UI: SVG иконки брендов
|
||||||
|
- Эмоджи заменены на SVG иконки брендов (стиль Simple Icons)
|
||||||
|
- Docker, Maven, npm, Cargo, PyPI теперь отображаются как векторная графика
|
||||||
|
- Единый стиль иконок на дашборде, сайдбаре и страницах деталей
|
||||||
|
|
||||||
|
#### Тестовая инфраструктура
|
||||||
|
- Unit-тесты для LocalStorage (8 тестов): put/get, list, stat, health_check
|
||||||
|
- Unit-тесты для S3Storage с HTTP-мокированием wiremock (11 тестов)
|
||||||
|
- Интеграционные тесты auth/htpasswd (7 тестов)
|
||||||
|
- Тесты жизненного цикла токенов (11 тестов)
|
||||||
|
- Тесты валидации (21 тест)
|
||||||
|
- **Всего: 75 тестов проходят**
|
||||||
|
|
||||||
|
#### Безопасность: Валидация ввода (`validation.rs`)
|
||||||
|
- Защита от path traversal: отклоняет `../`, `..\\`, null-байты, абсолютные пути
|
||||||
|
- Валидация имён Docker-образов по спецификации OCI distribution
|
||||||
|
- Валидация дайджестов (`sha256:[64 hex]`, `sha512:[128 hex]`)
|
||||||
|
- Валидация тегов и ссылок Docker
|
||||||
|
- Ограничение длины ключей хранилища (макс. 1024 символа)
|
||||||
|
|
||||||
|
#### Безопасность: Rate Limiting (`rate_limit.rs`)
|
||||||
|
- Auth endpoints: 1 req/sec, burst 5 (защита от брутфорса)
|
||||||
|
- Upload endpoints: 10 req/sec, burst 20
|
||||||
|
- Общие endpoints: 100 req/sec, burst 200
|
||||||
|
- Использует `tower_governor` 0.8 с `PeerIpKeyExtractor`
|
||||||
|
|
||||||
|
#### Наблюдаемость: Отслеживание Request ID (`request_id.rs`)
|
||||||
|
- Заголовок `X-Request-ID` добавляется ко всем ответам
|
||||||
|
- Принимает upstream request ID или генерирует UUID v4
|
||||||
|
- Tracing spans включают request_id для корреляции логов
|
||||||
|
|
||||||
|
#### CLI: Команда миграции (`migrate.rs`)
|
||||||
|
- `nora migrate --from local --to s3` - миграция между storage backends
|
||||||
|
- Флаг `--dry-run` для предпросмотра без копирования
|
||||||
|
- Прогресс-бар с indicatif
|
||||||
|
- Пропуск существующих файлов в destination
|
||||||
|
- Итоговая статистика (migrated, skipped, failed, bytes)
|
||||||
|
|
||||||
|
#### Обработка ошибок (`error.rs`)
|
||||||
|
- Enum `AppError` с `IntoResponse` для Axum
|
||||||
|
- Автоматическая конверсия из `StorageError` и `ValidationError`
|
||||||
|
- JSON-ответы об ошибках с поддержкой request_id
|
||||||
|
|
||||||
|
### Изменено
|
||||||
|
- `StorageError` теперь использует макрос `thiserror`
|
||||||
|
- `TokenError` теперь использует макрос `thiserror`
|
||||||
|
- Storage wrapper валидирует ключи перед делегированием backend
|
||||||
|
- Docker registry handlers валидируют name, digest, reference
|
||||||
|
- Лимит размера body установлен в 100MB через `DefaultBodyLimit`
|
||||||
|
|
||||||
|
### Добавлены зависимости
|
||||||
|
- `thiserror = "2"` - типизированная обработка ошибок
|
||||||
|
- `tower_governor = "0.8"` - rate limiting
|
||||||
|
- `governor = "0.10"` - backend для rate limiting
|
||||||
|
- `tempfile = "3"` (dev) - временные директории для тестов
|
||||||
|
- `wiremock = "0.6"` (dev) - HTTP-мокирование для S3 тестов
|
||||||
|
|
||||||
|
### Добавлены файлы
|
||||||
|
- `src/validation.rs` - модуль валидации ввода
|
||||||
|
- `src/migrate.rs` - модуль миграции хранилища
|
||||||
|
- `src/error.rs` - типы ошибок приложения
|
||||||
|
- `src/request_id.rs` - middleware для request ID
|
||||||
|
- `src/rate_limit.rs` - конфигурация rate limiting
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.1.0] - 2026-01-24
|
||||||
|
|
||||||
|
### Добавлено
|
||||||
|
- Мульти-протокольная поддержка: Docker Registry v2, Maven, npm, Cargo, PyPI
|
||||||
|
- Web UI дашборд
|
||||||
|
- Swagger UI (`/api-docs`)
|
||||||
|
- Storage backends: локальная файловая система, S3-совместимое хранилище
|
||||||
|
- Умный прокси/кэш для Maven и npm
|
||||||
|
- Health checks (`/health`, `/ready`)
|
||||||
|
- Базовая аутентификация (htpasswd с bcrypt)
|
||||||
|
- API токены (отзываемые, per-user)
|
||||||
|
- Prometheus метрики (`/metrics`)
|
||||||
|
- JSON структурированное логирование
|
||||||
|
- Конфигурация через переменные окружения
|
||||||
|
- Graceful shutdown (SIGTERM/SIGINT)
|
||||||
|
- Команды backup/restore
|
||||||
@@ -45,11 +45,7 @@ pub struct AuditLog {
|
|||||||
impl AuditLog {
|
impl AuditLog {
|
||||||
pub fn new(storage_path: &str) -> Self {
|
pub fn new(storage_path: &str) -> Self {
|
||||||
let path = PathBuf::from(storage_path).join("audit.jsonl");
|
let path = PathBuf::from(storage_path).join("audit.jsonl");
|
||||||
let writer = match OpenOptions::new()
|
let writer = match OpenOptions::new().create(true).append(true).open(&path) {
|
||||||
.create(true)
|
|
||||||
.append(true)
|
|
||||||
.open(&path)
|
|
||||||
{
|
|
||||||
Ok(f) => {
|
Ok(f) => {
|
||||||
info!(path = %path.display(), "Audit log initialized");
|
info!(path = %path.display(), "Audit log initialized");
|
||||||
Mutex::new(Some(f))
|
Mutex::new(Some(f))
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ use std::collections::HashMap;
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::AppState;
|
|
||||||
use crate::tokens::Role;
|
use crate::tokens::Role;
|
||||||
|
use crate::AppState;
|
||||||
|
|
||||||
/// Htpasswd-based authentication
|
/// Htpasswd-based authentication
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -247,12 +247,18 @@ async fn create_token(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let role = match req.role.as_str() {
|
let role = match req.role.as_str() {
|
||||||
"read" => Role::Read,
|
"read" => Role::Read,
|
||||||
"write" => Role::Write,
|
"write" => Role::Write,
|
||||||
"admin" => Role::Admin,
|
"admin" => Role::Admin,
|
||||||
_ => return (StatusCode::BAD_REQUEST, "Invalid role. Use: read, write, admin").into_response(),
|
_ => {
|
||||||
};
|
return (
|
||||||
match token_store.create_token(&req.username, req.ttl_days, req.description, role) {
|
StatusCode::BAD_REQUEST,
|
||||||
|
"Invalid role. Use: read, write, admin",
|
||||||
|
)
|
||||||
|
.into_response()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match token_store.create_token(&req.username, req.ttl_days, req.description, role) {
|
||||||
Ok(token) => Json(CreateTokenResponse {
|
Ok(token) => Json(CreateTokenResponse {
|
||||||
token,
|
token,
|
||||||
expires_in_days: req.ttl_days,
|
expires_in_days: req.ttl_days,
|
||||||
|
|||||||
@@ -29,7 +29,10 @@ pub async fn run_gc(storage: &Storage, dry_run: bool) -> GcResult {
|
|||||||
|
|
||||||
// 2. Collect all referenced digests from manifests
|
// 2. Collect all referenced digests from manifests
|
||||||
let referenced = collect_referenced_digests(storage).await;
|
let referenced = collect_referenced_digests(storage).await;
|
||||||
info!("Found {} referenced digests from manifests", referenced.len());
|
info!(
|
||||||
|
"Found {} referenced digests from manifests",
|
||||||
|
referenced.len()
|
||||||
|
);
|
||||||
|
|
||||||
// 3. Find orphans
|
// 3. Find orphans
|
||||||
let mut orphan_keys: Vec<String> = Vec::new();
|
let mut orphan_keys: Vec<String> = Vec::new();
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ mod backup;
|
|||||||
mod config;
|
mod config;
|
||||||
mod dashboard_metrics;
|
mod dashboard_metrics;
|
||||||
mod error;
|
mod error;
|
||||||
|
mod gc;
|
||||||
mod health;
|
mod health;
|
||||||
mod metrics;
|
mod metrics;
|
||||||
mod migrate;
|
mod migrate;
|
||||||
mod openapi;
|
mod openapi;
|
||||||
mod gc;
|
|
||||||
mod rate_limit;
|
mod rate_limit;
|
||||||
mod registry;
|
mod registry;
|
||||||
mod repo_index;
|
mod repo_index;
|
||||||
|
|||||||
@@ -51,7 +51,9 @@ async fn download(
|
|||||||
"cargo",
|
"cargo",
|
||||||
"LOCAL",
|
"LOCAL",
|
||||||
));
|
));
|
||||||
state.audit.log(AuditEntry::new("pull", "api", "", "cargo", ""));
|
state
|
||||||
|
.audit
|
||||||
|
.log(AuditEntry::new("pull", "api", "", "cargo", ""));
|
||||||
(StatusCode::OK, data).into_response()
|
(StatusCode::OK, data).into_response()
|
||||||
}
|
}
|
||||||
Err(_) => StatusCode::NOT_FOUND.into_response(),
|
Err(_) => StatusCode::NOT_FOUND.into_response(),
|
||||||
|
|||||||
@@ -312,7 +312,10 @@ async fn upload_blob(
|
|||||||
StatusCode::CREATED,
|
StatusCode::CREATED,
|
||||||
[
|
[
|
||||||
(header::LOCATION, location),
|
(header::LOCATION, location),
|
||||||
(HeaderName::from_static("docker-content-digest"), digest.to_string()),
|
(
|
||||||
|
HeaderName::from_static("docker-content-digest"),
|
||||||
|
digest.to_string(),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
.into_response()
|
.into_response()
|
||||||
@@ -489,7 +492,13 @@ async fn put_manifest(
|
|||||||
"docker",
|
"docker",
|
||||||
"LOCAL",
|
"LOCAL",
|
||||||
));
|
));
|
||||||
state.audit.log(AuditEntry::new("push", "api", &format!("{}:{}", name, reference), "docker", "manifest"));
|
state.audit.log(AuditEntry::new(
|
||||||
|
"push",
|
||||||
|
"api",
|
||||||
|
&format!("{}:{}", name, reference),
|
||||||
|
"docker",
|
||||||
|
"manifest",
|
||||||
|
));
|
||||||
state.repo_index.invalidate("docker");
|
state.repo_index.invalidate("docker");
|
||||||
|
|
||||||
let location = format!("/v2/{}/manifests/{}", name, reference);
|
let location = format!("/v2/{}/manifests/{}", name, reference);
|
||||||
|
|||||||
@@ -43,7 +43,9 @@ async fn download(State(state): State<Arc<AppState>>, Path(path): Path<String>)
|
|||||||
"maven",
|
"maven",
|
||||||
"CACHE",
|
"CACHE",
|
||||||
));
|
));
|
||||||
state.audit.log(AuditEntry::new("cache_hit", "api", "", "maven", ""));
|
state
|
||||||
|
.audit
|
||||||
|
.log(AuditEntry::new("cache_hit", "api", "", "maven", ""));
|
||||||
return with_content_type(&path, data).into_response();
|
return with_content_type(&path, data).into_response();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +62,9 @@ async fn download(State(state): State<Arc<AppState>>, Path(path): Path<String>)
|
|||||||
"maven",
|
"maven",
|
||||||
"PROXY",
|
"PROXY",
|
||||||
));
|
));
|
||||||
state.audit.log(AuditEntry::new("proxy_fetch", "api", "", "maven", ""));
|
state
|
||||||
|
.audit
|
||||||
|
.log(AuditEntry::new("proxy_fetch", "api", "", "maven", ""));
|
||||||
|
|
||||||
let storage = state.storage.clone();
|
let storage = state.storage.clone();
|
||||||
let key_clone = key.clone();
|
let key_clone = key.clone();
|
||||||
@@ -106,7 +110,9 @@ async fn upload(
|
|||||||
"maven",
|
"maven",
|
||||||
"LOCAL",
|
"LOCAL",
|
||||||
));
|
));
|
||||||
state.audit.log(AuditEntry::new("push", "api", "", "maven", ""));
|
state
|
||||||
|
.audit
|
||||||
|
.log(AuditEntry::new("push", "api", "", "maven", ""));
|
||||||
state.repo_index.invalidate("maven");
|
state.repo_index.invalidate("maven");
|
||||||
StatusCode::CREATED
|
StatusCode::CREATED
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,9 @@ async fn handle_request(State(state): State<Arc<AppState>>, Path(path): Path<Str
|
|||||||
"npm",
|
"npm",
|
||||||
"CACHE",
|
"CACHE",
|
||||||
));
|
));
|
||||||
state.audit.log(AuditEntry::new("cache_hit", "api", "", "npm", ""));
|
state
|
||||||
|
.audit
|
||||||
|
.log(AuditEntry::new("cache_hit", "api", "", "npm", ""));
|
||||||
}
|
}
|
||||||
return with_content_type(is_tarball, data).into_response();
|
return with_content_type(is_tarball, data).into_response();
|
||||||
}
|
}
|
||||||
@@ -69,7 +71,9 @@ async fn handle_request(State(state): State<Arc<AppState>>, Path(path): Path<Str
|
|||||||
"npm",
|
"npm",
|
||||||
"PROXY",
|
"PROXY",
|
||||||
));
|
));
|
||||||
state.audit.log(AuditEntry::new("proxy_fetch", "api", "", "npm", ""));
|
state
|
||||||
|
.audit
|
||||||
|
.log(AuditEntry::new("proxy_fetch", "api", "", "npm", ""));
|
||||||
}
|
}
|
||||||
|
|
||||||
let storage = state.storage.clone();
|
let storage = state.storage.clone();
|
||||||
|
|||||||
@@ -116,7 +116,9 @@ async fn download_file(
|
|||||||
"pypi",
|
"pypi",
|
||||||
"CACHE",
|
"CACHE",
|
||||||
));
|
));
|
||||||
state.audit.log(AuditEntry::new("cache_hit", "api", "", "pypi", ""));
|
state
|
||||||
|
.audit
|
||||||
|
.log(AuditEntry::new("cache_hit", "api", "", "pypi", ""));
|
||||||
|
|
||||||
let content_type = if filename.ends_with(".whl") {
|
let content_type = if filename.ends_with(".whl") {
|
||||||
"application/zip"
|
"application/zip"
|
||||||
@@ -158,7 +160,9 @@ async fn download_file(
|
|||||||
"pypi",
|
"pypi",
|
||||||
"PROXY",
|
"PROXY",
|
||||||
));
|
));
|
||||||
state.audit.log(AuditEntry::new("proxy_fetch", "api", "", "pypi", ""));
|
state
|
||||||
|
.audit
|
||||||
|
.log(AuditEntry::new("proxy_fetch", "api", "", "pypi", ""));
|
||||||
|
|
||||||
// Cache in local storage
|
// Cache in local storage
|
||||||
let storage = state.storage.clone();
|
let storage = state.storage.clone();
|
||||||
|
|||||||
@@ -36,7 +36,9 @@ async fn download(State(state): State<Arc<AppState>>, Path(path): Path<String>)
|
|||||||
state
|
state
|
||||||
.activity
|
.activity
|
||||||
.push(ActivityEntry::new(ActionType::Pull, path, "raw", "LOCAL"));
|
.push(ActivityEntry::new(ActionType::Pull, path, "raw", "LOCAL"));
|
||||||
state.audit.log(AuditEntry::new("pull", "api", "", "raw", ""));
|
state
|
||||||
|
.audit
|
||||||
|
.log(AuditEntry::new("pull", "api", "", "raw", ""));
|
||||||
|
|
||||||
// Guess content type from extension
|
// Guess content type from extension
|
||||||
let content_type = guess_content_type(&key);
|
let content_type = guess_content_type(&key);
|
||||||
@@ -74,7 +76,9 @@ async fn upload(
|
|||||||
state
|
state
|
||||||
.activity
|
.activity
|
||||||
.push(ActivityEntry::new(ActionType::Push, path, "raw", "LOCAL"));
|
.push(ActivityEntry::new(ActionType::Push, path, "raw", "LOCAL"));
|
||||||
state.audit.log(AuditEntry::new("push", "api", "", "raw", ""));
|
state
|
||||||
|
.audit
|
||||||
|
.log(AuditEntry::new("push", "api", "", "raw", ""));
|
||||||
StatusCode::CREATED.into_response()
|
StatusCode::CREATED.into_response()
|
||||||
}
|
}
|
||||||
Err(_) => StatusCode::INTERNAL_SERVER_ERROR.into_response(),
|
Err(_) => StatusCode::INTERNAL_SERVER_ERROR.into_response(),
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ impl Role {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// API Token metadata stored on disk
|
/// API Token metadata stored on disk
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct TokenInfo {
|
pub struct TokenInfo {
|
||||||
@@ -260,7 +259,9 @@ mod tests {
|
|||||||
let temp_dir = TempDir::new().unwrap();
|
let temp_dir = TempDir::new().unwrap();
|
||||||
let store = TokenStore::new(temp_dir.path());
|
let store = TokenStore::new(temp_dir.path());
|
||||||
|
|
||||||
let token = store.create_token("testuser", 30, None, Role::Write).unwrap();
|
let token = store
|
||||||
|
.create_token("testuser", 30, None, Role::Write)
|
||||||
|
.unwrap();
|
||||||
let (user, role) = store.verify_token(&token).unwrap();
|
let (user, role) = store.verify_token(&token).unwrap();
|
||||||
|
|
||||||
assert_eq!(user, "testuser");
|
assert_eq!(user, "testuser");
|
||||||
@@ -291,7 +292,9 @@ mod tests {
|
|||||||
let store = TokenStore::new(temp_dir.path());
|
let store = TokenStore::new(temp_dir.path());
|
||||||
|
|
||||||
// Create token and manually set it as expired
|
// Create token and manually set it as expired
|
||||||
let token = store.create_token("testuser", 1, None, Role::Write).unwrap();
|
let token = store
|
||||||
|
.create_token("testuser", 1, None, Role::Write)
|
||||||
|
.unwrap();
|
||||||
let token_hash = hash_token(&token);
|
let token_hash = hash_token(&token);
|
||||||
let file_path = temp_dir.path().join(format!("{}.json", &token_hash[..16]));
|
let file_path = temp_dir.path().join(format!("{}.json", &token_hash[..16]));
|
||||||
|
|
||||||
@@ -330,7 +333,9 @@ mod tests {
|
|||||||
let temp_dir = TempDir::new().unwrap();
|
let temp_dir = TempDir::new().unwrap();
|
||||||
let store = TokenStore::new(temp_dir.path());
|
let store = TokenStore::new(temp_dir.path());
|
||||||
|
|
||||||
let token = store.create_token("testuser", 30, None, Role::Write).unwrap();
|
let token = store
|
||||||
|
.create_token("testuser", 30, None, Role::Write)
|
||||||
|
.unwrap();
|
||||||
let token_hash = hash_token(&token);
|
let token_hash = hash_token(&token);
|
||||||
let hash_prefix = &token_hash[..16];
|
let hash_prefix = &token_hash[..16];
|
||||||
|
|
||||||
@@ -375,7 +380,9 @@ mod tests {
|
|||||||
let temp_dir = TempDir::new().unwrap();
|
let temp_dir = TempDir::new().unwrap();
|
||||||
let store = TokenStore::new(temp_dir.path());
|
let store = TokenStore::new(temp_dir.path());
|
||||||
|
|
||||||
let token = store.create_token("testuser", 30, None, Role::Write).unwrap();
|
let token = store
|
||||||
|
.create_token("testuser", 30, None, Role::Write)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// First verification
|
// First verification
|
||||||
store.verify_token(&token).unwrap();
|
store.verify_token(&token).unwrap();
|
||||||
@@ -391,7 +398,12 @@ mod tests {
|
|||||||
let store = TokenStore::new(temp_dir.path());
|
let store = TokenStore::new(temp_dir.path());
|
||||||
|
|
||||||
store
|
store
|
||||||
.create_token("testuser", 30, Some("CI/CD Pipeline".to_string()), Role::Admin)
|
.create_token(
|
||||||
|
"testuser",
|
||||||
|
30,
|
||||||
|
Some("CI/CD Pipeline".to_string()),
|
||||||
|
Role::Admin,
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let tokens = store.list_tokens("testuser");
|
let tokens = store.list_tokens("testuser");
|
||||||
|
|||||||
Reference in New Issue
Block a user