Files
KArtSell.Aegis/docs/SECRETS_LOCAL_DEVELOPMENT.md
T
kjh2064 03da896a6d
ci / backend (push) Failing after 0s
Build & Test with Secrets / build (push) Failing after 2s
ci / static (push) Failing after 7s
Build & Test with Secrets / security-scan (push) Successful in 5s
ci / frontend (push) Failing after 1m3s
Build & Test with Secrets / frontend (push) Failing after 1m1s
Build & Test with Secrets / notification (push) Failing after 1s
Implement Secrets Management System: Gitea Actions + User-Secrets (AGENTS.md v16.0)
## Changes

### Security Infrastructure
- **Program.cs**: ResolveSecret() helper for secure secret resolution
  - Priority: environment variables (CI/CD) → user-secrets (local) → appsettings (fallback)
  - Validates all required secrets at startup (fail-fast)

- **ExternalApiOptions.cs**: Type-safe configuration for external APIs
  - KRX OpenAPI (Korea Exchange market data)
  - OpenDart API (financial disclosures)
  - KIS API (trading & orders)
  - Injected via IOptions<T> dependency injection

- **appsettings.json**: Safe placeholders (${VAR_NAME}) instead of hardcoded secrets
  - Never stores actual credentials
  - Production uses environment variable substitution

### CI/CD Integration
- **.gitea/workflows/secrets-injection.yml**: Automated secret injection
  - Receives secrets from Gitea Actions Secrets
  - Injects as environment variables at build time
  - Masks secrets in logs
  - No secrets stored in artifacts

### Local Development
- **docs/SECRETS_LOCAL_DEVELOPMENT.md**: Complete setup guide
  - One-time user-secrets initialization
  - How to store/update secrets locally
  - Troubleshooting for common issues

- **SECRETS_CONFIGURATION_SUMMARY.md**: Architecture & security properties
  - Secret resolution priority
  - Usage patterns in application code
  - Security audit checklist
  - Rotation procedures

## Security Properties
 Secrets never hardcoded in code
 Secrets never committed to git
 Secrets never logged or exposed in traces
 Secrets never stored in CI artifacts
 Local isolation via ~/.microsoft/usersecrets/
 CI/CD isolation via Gitea Actions Secrets (encrypted)
 Rotation support (update secret → next build uses new value)

## Compliance
- Follows AGENTS.md v16.0 security guardrails
- No magic numbers or hardcoded API keys
- All external API keys managed through centralized options
- Type-safe dependency injection eliminates string-based configuration

## Next Steps
1. Local dev: Run `dotnet user-secrets init` and configure
2. CI/CD: Add secrets to Gitea Actions Secrets
3. Verify: `dotnet run` should work without "secret is required" errors

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-08-02 14:00:17 +09:00

265 lines
6.4 KiB
Markdown

# 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/<PROJECT_GUID>/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
```
---
## 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