# Local Development: User Secrets Configuration This guide explains how to safely manage secrets locally without storing them in version control. ## Overview - **Production/CI:** Secrets stored in Gitea Actions Secrets → injected as environment variables at build/deploy time - **Local Dev:** Secrets stored in user-secrets → NOT checked into git - **Code:** Never hardcodes secrets; reads from environment or IOptions --- ## Setup User Secrets (One-Time) ### 1. Initialize User Secrets Store ```bash cd src/KArtSell.Host dotnet user-secrets init ``` This creates `~/.microsoft/usersecrets//secrets.json` (not in git). ### 2. Store Secrets Locally ```powershell # PowerShell (Windows) cd src/KArtSell.Host # PostgreSQL connection string dotnet user-secrets set "ConnectionStrings:Postgres" "Host=localhost;Port=5432;Database=kartsell;Username=kartsell;Password=kartsell" # KRX API Key dotnet user-secrets set "ExternalApis:KrxOpenApi:ApiKey" "your-krx-api-key-here" # OpenDart API Key (optional) dotnet user-secrets set "ExternalApis:OpenDart:ApiKey" "your-opendart-key-here" # KIS API Keys (optional) dotnet user-secrets set "ExternalApis:Kis:ApiKey" "your-kis-api-key" dotnet user-secrets set "ExternalApis:Kis:SecretKey" "your-kis-secret-key" ``` **Bash/macOS:** ```bash cd src/KArtSell.Host dotnet user-secrets set "ConnectionStrings:Postgres" "Host=localhost;Port=5432;Database=kartsell;Username=kartsell;Password=kartsell" dotnet user-secrets set "ExternalApis:KrxOpenApi:ApiKey" "your-krx-api-key-here" ``` ### 3. Verify Secrets Are Set ```bash cd src/KArtSell.Host dotnet user-secrets list ``` **Expected Output:** ``` ConnectionStrings:Postgres = Host=localhost;Port=5432;... ExternalApis:KrxOpenApi:ApiKey = your-krx-api-key-here ExternalApis:OpenDart:ApiKey = your-opendart-key-here ExternalApis:Kis:ApiKey = your-kis-api-key ExternalApis:Kis:SecretKey = your-kis-secret-key ``` --- ## Build Issues & Solutions ### .NET SDK Version Mismatch **Problem:** `global.json` requires .NET 10.0.100 GA, but only preview version installed ``` Requested SDK version: 10.0.100 Install the [10.0.100] .NET SDK or update global.json to match an installed SDK. ``` **Solution:** Use NuGet.config to resolve package source conflicts ```bash # NuGet.config at project root handles Telerik source override # (Telerik was configured in .sln but not actually used in code) # This prevents NU1507 "warning-as-error" during restore ``` The project includes `NuGet.config` which: - Configures only nuget.org as package source - Removes transitive Telerik source (build-only artifact) - Works with both GA and preview .NET 10 SDKs ### Building Locally ```bash cd C:\Job_Roomz\KArtSell.Aegis # Release build (optimized binaries) dotnet build KArtSell.sln -c Release # Development mode (with appsettings.Development.json) $env:ASPNETCORE_ENVIRONMENT = "Development" $env:KARTSELL_POSTGRES = "Host=127.0.0.1;Port=5432;Database=kartselldb_test;Username=kartsell_test;Password=kartsell4321@!_test" $env:KRX_OPENAPI = "stub-key-for-testing" dotnet run --project src/KArtSell.Host -c Release --no-build ``` Host listens on: `http://127.0.0.1:5002` --- ## How It Works ### Development (dotnet run) ``` User Secrets → appsettings.json (placeholder) → Program.cs (ResolveSecret) ↓ ↓ ↓ (highest (if ${VAR}) (merged together) priority) ``` When you run `dotnet run`, ASP.NET Core: 1. Loads appsettings.json (has `${KARTSELL_POSTGRES}` placeholders) 2. Overlays user-secrets (if in Development) 3. Overlays environment variables (highest priority) Result: `Program.cs` sees actual values, not placeholders. ### CI/CD (Gitea Actions) ``` Gitea Secrets (env injection) → appsettings.json → Program.cs ↓ ↓ ↓ ${{ secrets.* }} (placeholder) (resolved to actual) ``` Gitea Actions: 1. Sets `KARTSELL_POSTGRES` and `KRX_API_KEY` as environment variables 2. Code reads from environment (highest priority in ResolveSecret) 3. Never stores secrets in build artifacts --- ## Verify Setup Works ### 1. Start PostgreSQL (SSH Tunnel) ```bash ssh -L 5432:127.0.0.1:5432 kjh2064@178.104.200.7 ``` Keep this running in a separate terminal. ### 2. Run Application ```bash cd src/KArtSell.Host dotnet run -c Release ``` **Expected:** - Application starts without "KARTSELL_POSTGRES is required" error - Logs show database connection successful - Hangfire dashboard accessible at http://localhost:5000/hangfire ### 3. Verify API Works ```bash curl http://localhost:5000/health # Expected: 200 OK ``` --- ## Troubleshooting ### Issue: "ConnectionStrings:Postgres is required" **Cause:** User secrets not set or not loaded **Fix:** ```bash # Check if secrets are set dotnet user-secrets list # If empty, re-set them dotnet user-secrets set "ConnectionStrings:Postgres" "Host=localhost;Port=5432;Database=kartsell;Username=kartsell;Password=kartsell" # If using different terminal, make sure you're in src/KArtSell.Host directory ``` ### Issue: "KRX_API_KEY is required" **Cause:** API key not configured **Fix:** ```bash # Set KRX API key dotnet user-secrets set "ExternalApis:KrxOpenApi:ApiKey" "your-api-key" # Or set via environment variable (overrides user-secrets) $env:KRX_API_KEY = "your-api-key" # PowerShell export KRX_API_KEY="your-api-key" # Bash ``` ### Issue: Secrets Showing in Logs **Never should happen** — ResolveSecret does not log secret values. If you see secrets in logs: 1. Check application doesn't log Configuration 2. Check Serilog is not in Verbose mode 3. Report as security issue --- ## Best Practices ### ✅ DO - Store secrets in user-secrets locally - Use environment variables in CI/CD (via Gitea Secrets) - Commit **only** appsettings.json with placeholders - Keep `.gitignore` excluding `secrets.json` - Rotate API keys quarterly ### ❌ DON'T - Commit secrets to git (even accidentally) - Store credentials in appsettings.Development.json - Commit `.env` files - Log secrets in any log level - Share API keys via chat/email --- ## Adding New Secrets When adding a new API (e.g., new data provider): 1. **Add to ExternalApiOptions.cs:** ```csharp public class NewProviderSettings { public string ApiKey { get; set; } = string.Empty; public string BaseUrl { get; set; } = "https://api.provider.com"; } ``` 2. **Add to appsettings.json:** ```json "ExternalApis": { "NewProvider": { "ApiKey": "${NEW_PROVIDER_API_KEY}", "BaseUrl": "https://api.provider.com" } } ``` 3. **Set locally:** ```bash dotnet user-secrets set "ExternalApis:NewProvider:ApiKey" "your-key" ``` 4. **Add to Gitea Secrets:** - Go to: https://gitea.taxbaik.com/kjh2064/KArtSell.Aegis/settings/actions/secrets - Click "+ New Secret" - Name: `NEW_PROVIDER_API_KEY` - Value: actual key 5. **Add to CI/CD workflow:** ```yaml env: NEW_PROVIDER_API_KEY: ${{ secrets.NEW_PROVIDER_API_KEY }} ``` --- ## Rotating Secrets ### Local Secrets ```bash cd src/KArtSell.Host # Update the secret dotnet user-secrets set "ExternalApis:KrxOpenApi:ApiKey" "new-api-key" # Restart application # (no need to commit, secrets are local) ``` ### Production Secrets (Gitea) 1. Go to: https://gitea.taxbaik.com/kjh2064/KArtSell.Aegis/settings/actions/secrets 2. Click on secret → "Update" 3. Enter new value 4. Save 5. Next CI/CD run uses new secret automatically --- ## See Also - `docs/CLAUDE.md` — Project instructions and architecture - `GATE_3_EXECUTION_GUIDE.md` — Setting up Gate 3 shadow run (uses same secrets) - `.gitea/workflows/secrets-injection.yml` — CI/CD workflow with secret injection