fix(security): remove MinIO hardcoded credentials & add presigned URL support
- Remove hardcoded minioadmin/minioadmin_secret fallback from docker-compose.yml, require MINIO_ACCESS_KEY/MINIO_SECRET_KEY env vars (fail-fast with :? syntax) - Align docker-compose.yml env var names with .env.example (MINIO_ACCESS_KEY/SECRET_KEY) - Update CI e2e workflow to use GitHub vars with non-default fallbacks - Update .env.test to use non-default test credentials - Add @aws-sdk/s3-request-presigner and getPresignedUploadUrl() method to MinioMediaStorageService for properly signed client-side uploads - Remove hardcoded credentials from dev-environment docs Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -16,8 +16,8 @@ TYPESENSE_API_KEY=ts_dev_key_change_me
|
||||
# MinIO
|
||||
MINIO_ENDPOINT=localhost
|
||||
MINIO_PORT=9000
|
||||
MINIO_ACCESS_KEY=minioadmin
|
||||
MINIO_SECRET_KEY=minioadmin_secret
|
||||
MINIO_ACCESS_KEY=test_minio_user
|
||||
MINIO_SECRET_KEY=test_minio_secret_key_32chars!!
|
||||
MINIO_BUCKET=goodgo-uploads
|
||||
|
||||
# Auth (deterministic secrets for test reproducibility)
|
||||
|
||||
8
.github/workflows/e2e.yml
vendored
8
.github/workflows/e2e.yml
vendored
@@ -60,8 +60,8 @@ jobs:
|
||||
ports:
|
||||
- 9000:9000
|
||||
env:
|
||||
MINIO_ROOT_USER: minioadmin
|
||||
MINIO_ROOT_PASSWORD: minioadmin_secret
|
||||
MINIO_ROOT_USER: ${{ vars.CI_MINIO_ACCESS_KEY || 'ci_minio_user' }}
|
||||
MINIO_ROOT_PASSWORD: ${{ vars.CI_MINIO_SECRET_KEY || 'ci_minio_secret_key_32chars!!' }}
|
||||
options: >-
|
||||
--health-cmd "curl -sf http://localhost:9000/minio/health/live || exit 1"
|
||||
--health-interval 10s
|
||||
@@ -77,8 +77,8 @@ jobs:
|
||||
TYPESENSE_API_KEY: ts_ci_key
|
||||
MINIO_ENDPOINT: localhost
|
||||
MINIO_PORT: 9000
|
||||
MINIO_ACCESS_KEY: minioadmin
|
||||
MINIO_SECRET_KEY: minioadmin_secret
|
||||
MINIO_ACCESS_KEY: ${{ vars.CI_MINIO_ACCESS_KEY || 'ci_minio_user' }}
|
||||
MINIO_SECRET_KEY: ${{ vars.CI_MINIO_SECRET_KEY || 'ci_minio_secret_key_32chars!!' }}
|
||||
MINIO_BUCKET: goodgo-uploads
|
||||
NODE_ENV: test
|
||||
JWT_SECRET: e2e-test-jwt-secret-key
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.1026.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.1026.0",
|
||||
"@goodgo/mcp-servers": "workspace:*",
|
||||
"@nestjs/common": "^11.0.0",
|
||||
"@nestjs/core": "^11.0.0",
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
HeadBucketCommand,
|
||||
CreateBucketCommand,
|
||||
} from '@aws-sdk/client-s3';
|
||||
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
||||
import { Injectable, type OnModuleInit } from '@nestjs/common';
|
||||
import { type LoggerService } from '@modules/shared/infrastructure/logger.service';
|
||||
|
||||
@@ -15,6 +16,7 @@ export const MEDIA_STORAGE_SERVICE = Symbol('MEDIA_STORAGE_SERVICE');
|
||||
export interface IMediaStorageService {
|
||||
upload(buffer: Buffer, originalName: string, mimeType: string, folder: string): Promise<string>;
|
||||
delete(fileUrl: string): Promise<void>;
|
||||
getPresignedUploadUrl(objectKey: string, mimeType: string, expiresInSeconds?: number): Promise<string>;
|
||||
}
|
||||
|
||||
function requireEnv(key: string): string {
|
||||
@@ -106,6 +108,15 @@ export class MinioMediaStorageService implements IMediaStorageService, OnModuleI
|
||||
}
|
||||
}
|
||||
|
||||
async getPresignedUploadUrl(objectKey: string, mimeType: string, expiresInSeconds = 300): Promise<string> {
|
||||
const command = new PutObjectCommand({
|
||||
Bucket: this.bucket,
|
||||
Key: objectKey,
|
||||
ContentType: mimeType,
|
||||
});
|
||||
return getSignedUrl(this.s3, command, { expiresIn: expiresInSeconds });
|
||||
}
|
||||
|
||||
async delete(fileUrl: string): Promise<void> {
|
||||
try {
|
||||
const urlObj = new URL(fileUrl);
|
||||
|
||||
@@ -68,8 +68,8 @@ services:
|
||||
- '${MINIO_CONSOLE_PORT:-9001}:9001'
|
||||
command: server /data --console-address ":9001"
|
||||
environment:
|
||||
MINIO_ROOT_USER: ${MINIO_USER:-minioadmin}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_PASSWORD:-minioadmin_secret}
|
||||
MINIO_ROOT_USER: ${MINIO_ACCESS_KEY:?MINIO_ACCESS_KEY is required}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_SECRET_KEY:?MINIO_SECRET_KEY is required}
|
||||
volumes:
|
||||
- minio_data:/data
|
||||
healthcheck:
|
||||
|
||||
@@ -130,7 +130,7 @@ curl http://localhost:8108/health
|
||||
### MinIO
|
||||
|
||||
- **API**: `http://localhost:9000`
|
||||
- **Console**: `http://localhost:9001` (login: `minioadmin` / `minioadmin_secret`)
|
||||
- **Console**: `http://localhost:9001` (login with `MINIO_ACCESS_KEY` / `MINIO_SECRET_KEY` from your `.env`)
|
||||
|
||||
### AI Services
|
||||
|
||||
|
||||
29
pnpm-lock.yaml
generated
29
pnpm-lock.yaml
generated
@@ -72,6 +72,9 @@ importers:
|
||||
'@aws-sdk/client-s3':
|
||||
specifier: ^3.1026.0
|
||||
version: 3.1026.0
|
||||
'@aws-sdk/s3-request-presigner':
|
||||
specifier: ^3.1026.0
|
||||
version: 3.1026.0
|
||||
'@goodgo/mcp-servers':
|
||||
specifier: workspace:*
|
||||
version: link:../../libs/mcp-servers
|
||||
@@ -485,6 +488,10 @@ packages:
|
||||
resolution: {integrity: sha512-6Q8B1dcx6BBqUTY1Mc/eROKA0FImEEY5VPSd6AGPEUf0ErjExz4snVqa9kNJSoVDV1rKaNf3qrWojgcKW+SdDg==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/s3-request-presigner@3.1026.0':
|
||||
resolution: {integrity: sha512-PBVt/zb4YsJMcyB/HbGmID4RP00dTkdQGkNQiw1i6oXQ/U8hnPEI8+IvTKR4+5YEQ8Cq4QmtIV0mzv070L+oOg==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/signature-v4-multi-region@3.996.16':
|
||||
resolution: {integrity: sha512-EMdXYB4r/k5RWq86fugjRhid5JA+Z6MpS7n4sij4u5/C+STrkvuf9aFu41rJA9MjUzxCLzv8U2XL8cH2GSRYpQ==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
@@ -505,6 +512,10 @@ packages:
|
||||
resolution: {integrity: sha512-2nUQ+2ih7CShuKHpGSIYvvAIOHy52dOZguYG36zptBukhw6iFwcvGfG0tes0oZFWQqEWvgZe9HLWaNlvXGdOrg==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/util-format-url@3.972.9':
|
||||
resolution: {integrity: sha512-fNJXHrs0ZT7Wx0KGIqKv7zLxlDXt2vqjx9z6oKUQFmpE5o4xxnSryvVHfHpIifYHWKz94hFccIldJ0YSZjlCBw==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/util-locate-window@3.965.5':
|
||||
resolution: {integrity: sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
@@ -6273,6 +6284,17 @@ snapshots:
|
||||
'@smithy/types': 4.14.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/s3-request-presigner@3.1026.0':
|
||||
dependencies:
|
||||
'@aws-sdk/signature-v4-multi-region': 3.996.16
|
||||
'@aws-sdk/types': 3.973.7
|
||||
'@aws-sdk/util-format-url': 3.972.9
|
||||
'@smithy/middleware-endpoint': 4.4.29
|
||||
'@smithy/protocol-http': 5.3.13
|
||||
'@smithy/smithy-client': 4.12.9
|
||||
'@smithy/types': 4.14.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/signature-v4-multi-region@3.996.16':
|
||||
dependencies:
|
||||
'@aws-sdk/middleware-sdk-s3': 3.972.28
|
||||
@@ -6311,6 +6333,13 @@ snapshots:
|
||||
'@smithy/util-endpoints': 3.3.4
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/util-format-url@3.972.9':
|
||||
dependencies:
|
||||
'@aws-sdk/types': 3.973.7
|
||||
'@smithy/querystring-builder': 4.2.13
|
||||
'@smithy/types': 4.14.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/util-locate-window@3.965.5':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
Reference in New Issue
Block a user