From 6d4a8904f250a859061d54554b1148cb05d0ef97 Mon Sep 17 00:00:00 2001 From: Ricardo Carneiro Date: Sat, 24 Jan 2026 21:39:20 -0300 Subject: [PATCH] =?UTF-8?q?feat:=20Docker=20Secrets=20para=20credenciais?= =?UTF-8?q?=20sens=C3=ADveis?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Criado DockerSecretsConfigurationProvider para ler secrets de /run/secrets/ - Removidas credenciais sensíveis do appsettings.Production.json - Adicionado indicador visual no rodapé (✓/✗) para verificar se secrets foram carregados - Atualizado deploy.yml para usar Docker Secrets no Swarm - Criado script create-docker-secrets.sh para gerenciar secrets - Criado template secrets.env.template para facilitar configuração - Documentação completa em DOCKER_SECRETS_SETUP.md Secrets gerenciados: - stripe_secret_key - stripe_webhook_secret - mongodb_connection_string - google_client_id / google_client_secret - microsoft_client_id / microsoft_client_secret IMPORTANTE: Após este deploy, é necessário criar os secrets no Swarm e recriar o service. Consulte DOCKER_SECRETS_SETUP.md. Co-Authored-By: Claude Opus 4.5 --- .claude/settings.local.json | 5 +- .github/workflows/deploy.yml | 71 ++-- .gitignore | 2 + .../DockerSecretsConfigurationProvider.cs | 111 +++++++ DOCKER_SECRETS_SETUP.md | 195 +++++++++++ Program.cs | 5 + Scripts/create-docker-secrets.sh | 309 ++++++++++++++++++ Scripts/secrets.env.template | 28 ++ Views/Shared/_Layout.cshtml | 22 ++ appsettings.Production.json | 23 +- appsettings.json | 4 +- 11 files changed, 744 insertions(+), 31 deletions(-) create mode 100644 Configuration/DockerSecretsConfigurationProvider.cs create mode 100644 DOCKER_SECRETS_SETUP.md create mode 100644 Scripts/create-docker-secrets.sh create mode 100644 Scripts/secrets.env.template diff --git a/.claude/settings.local.json b/.claude/settings.local.json index f69a6ca..cb80799 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -33,7 +33,10 @@ "Bash(chmod +x /mnt/c/vscode/qrrapido/Scripts/update-plans.sh)", "Bash(netstat -tln)", "Bash(npm install)", - "Bash(npm install:*)" + "Bash(npm install:*)", + "Bash(git add:*)", + "Bash(git commit:*)", + "Bash(git push:*)" ], "deny": [] } diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index c358d50..5a0d017 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -158,7 +158,7 @@ jobs: # Se a conexão funcionou, continua com o deploy echo "=== Iniciando Deploy no Docker Swarm ===" - # Deploy via Docker Swarm (igual ao BCards) + # Deploy via Docker Swarm with Secrets ssh -o StrictHostKeyChecking=no ubuntu@141.148.162.114 << 'EOF' # Puxa nova imagem docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest @@ -170,29 +170,52 @@ jobs: # Cria a rede overlay se não existir docker network create --driver overlay --attachable qrrapido-network || echo "Rede já existe" - # Atualiza o service ou cria se não existir - docker service update \ - --image ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest \ - --env-add Serilog__OpenSearchUrl="http://141.148.162.114:19201" \ - --env-add Serilog__OpenSearchFallback="http://129.146.116.218:19202" \ - qrrapido-prod \ - || \ - docker service create \ - --name qrrapido-prod \ - --replicas 2 \ - --network qrrapido-network \ - --publish published=5001,target=8080 \ - --mount type=bind,source=/app/keys,target=/app/keys \ - --env ASPNETCORE_ENVIRONMENT=Production \ - --env ASPNETCORE_URLS=http://+:8080 \ - --env Serilog__OpenSearchUrl="http://141.148.162.114:19201" \ - --env Serilog__OpenSearchFallback="http://129.146.116.218:19202" \ - --update-delay 30s \ - --update-parallelism 1 \ - --update-order start-first \ - --restart-condition on-failure \ - --restart-max-attempts 3 \ - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest + # Verifica se os secrets existem (devem ser criados manualmente uma vez) + echo "Verificando Docker Secrets..." + docker secret ls | grep -q "stripe_secret_key" || echo "AVISO: Secret stripe_secret_key não existe!" + docker secret ls | grep -q "stripe_webhook_secret" || echo "AVISO: Secret stripe_webhook_secret não existe!" + docker secret ls | grep -q "mongodb_connection_string" || echo "AVISO: Secret mongodb_connection_string não existe!" + docker secret ls | grep -q "google_client_id" || echo "AVISO: Secret google_client_id não existe!" + docker secret ls | grep -q "google_client_secret" || echo "AVISO: Secret google_client_secret não existe!" + docker secret ls | grep -q "microsoft_client_id" || echo "AVISO: Secret microsoft_client_id não existe!" + docker secret ls | grep -q "microsoft_client_secret" || echo "AVISO: Secret microsoft_client_secret não existe!" + + # Verifica se o service existe + if docker service inspect qrrapido-prod > /dev/null 2>&1; then + echo "Service existe, atualizando imagem..." + docker service update \ + --image ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest \ + --env-add Serilog__OpenSearchUrl="http://141.148.162.114:19201" \ + --env-add Serilog__OpenSearchFallback="http://129.146.116.218:19202" \ + --with-registry-auth \ + qrrapido-prod + else + echo "Service não existe, criando com secrets..." + docker service create \ + --name qrrapido-prod \ + --replicas 2 \ + --network qrrapido-network \ + --publish published=5001,target=8080 \ + --mount type=bind,source=/app/keys,target=/app/keys \ + --secret stripe_secret_key \ + --secret stripe_webhook_secret \ + --secret mongodb_connection_string \ + --secret google_client_id \ + --secret google_client_secret \ + --secret microsoft_client_id \ + --secret microsoft_client_secret \ + --env ASPNETCORE_ENVIRONMENT=Production \ + --env ASPNETCORE_URLS=http://+:8080 \ + --env Serilog__OpenSearchUrl="http://141.148.162.114:19201" \ + --env Serilog__OpenSearchFallback="http://129.146.116.218:19202" \ + --update-delay 30s \ + --update-parallelism 1 \ + --update-order start-first \ + --restart-condition on-failure \ + --restart-max-attempts 3 \ + --with-registry-auth \ + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest + fi # Aguarda o service estar estável echo "Aguardando service estabilizar..." diff --git a/.gitignore b/.gitignore index 3a285cd..1674e3b 100644 --- a/.gitignore +++ b/.gitignore @@ -362,6 +362,8 @@ temp/ *.env .env.local .env.production +secrets.env +scripts/secrets.env # macOS .DS_Store diff --git a/Configuration/DockerSecretsConfigurationProvider.cs b/Configuration/DockerSecretsConfigurationProvider.cs new file mode 100644 index 0000000..bd43b12 --- /dev/null +++ b/Configuration/DockerSecretsConfigurationProvider.cs @@ -0,0 +1,111 @@ +using Microsoft.Extensions.Configuration; + +namespace QRRapidoApp.Configuration +{ + /// + /// Configuration provider that reads Docker Secrets from /run/secrets/ + /// Secrets are mounted as files in Swarm mode. + /// + public class DockerSecretsConfigurationProvider : ConfigurationProvider + { + private readonly string _secretsPath; + private readonly Dictionary _secretKeyMappings; + + public DockerSecretsConfigurationProvider(string secretsPath, Dictionary secretKeyMappings) + { + _secretsPath = secretsPath; + _secretKeyMappings = secretKeyMappings; + } + + public override void Load() + { + Data = new Dictionary(StringComparer.OrdinalIgnoreCase); + + if (!Directory.Exists(_secretsPath)) + { + return; + } + + var secretsLoaded = 0; + + foreach (var mapping in _secretKeyMappings) + { + var secretFileName = mapping.Key; + var configKey = mapping.Value; + var secretFilePath = Path.Combine(_secretsPath, secretFileName); + + if (File.Exists(secretFilePath)) + { + try + { + var secretValue = File.ReadAllText(secretFilePath).Trim(); + if (!string.IsNullOrEmpty(secretValue)) + { + Data[configKey] = secretValue; + secretsLoaded++; + } + } + catch (Exception) + { + // Silently ignore read errors - will fall back to other config sources + } + } + } + + // Set indicator that secrets were loaded from Docker + if (secretsLoaded > 0) + { + Data["App:SecretsLoaded"] = "true"; + } + } + } + + public class DockerSecretsConfigurationSource : IConfigurationSource + { + public string SecretsPath { get; set; } = "/run/secrets"; + public Dictionary SecretKeyMappings { get; set; } = new(); + + public IConfigurationProvider Build(IConfigurationBuilder builder) + { + return new DockerSecretsConfigurationProvider(SecretsPath, SecretKeyMappings); + } + } + + public static class DockerSecretsConfigurationExtensions + { + /// + /// Adds Docker Secrets as a configuration source. + /// Maps secret file names to configuration keys. + /// + public static IConfigurationBuilder AddDockerSecrets( + this IConfigurationBuilder builder, + Action? configure = null) + { + var source = new DockerSecretsConfigurationSource + { + // Default mappings for QRRapido secrets + SecretKeyMappings = new Dictionary + { + // Stripe + ["stripe_secret_key"] = "Stripe:SecretKey", + ["stripe_webhook_secret"] = "Stripe:WebhookSecret", + + // MongoDB + ["mongodb_connection_string"] = "ConnectionStrings:MongoDB", + + // OAuth - Google + ["google_client_id"] = "Authentication:Google:ClientId", + ["google_client_secret"] = "Authentication:Google:ClientSecret", + + // OAuth - Microsoft + ["microsoft_client_id"] = "Authentication:Microsoft:ClientId", + ["microsoft_client_secret"] = "Authentication:Microsoft:ClientSecret", + } + }; + + configure?.Invoke(source); + builder.Add(source); + return builder; + } + } +} diff --git a/DOCKER_SECRETS_SETUP.md b/DOCKER_SECRETS_SETUP.md new file mode 100644 index 0000000..8f4851c --- /dev/null +++ b/DOCKER_SECRETS_SETUP.md @@ -0,0 +1,195 @@ +# Docker Secrets Setup - QR Rapido + +Este documento descreve como configurar Docker Secrets para o QR Rapido em ambiente de produção. + +## Por que usar Docker Secrets? + +- Credenciais sensíveis não ficam expostas no código ou em arquivos de configuração +- Secrets são criptografados em repouso e em trânsito +- Apenas containers autorizados têm acesso +- Facilita rotação de credenciais sem rebuild de imagens + +## Secrets Necessários + +| Secret Name | Descrição | Exemplo | +|-------------|-----------|---------| +| `stripe_secret_key` | Stripe API Secret Key | `sk_live_xxx` | +| `stripe_webhook_secret` | Stripe Webhook Signing Secret | `whsec_xxx` | +| `mongodb_connection_string` | String de conexão MongoDB completa | `mongodb://user:pass@host:port/db?options` | +| `google_client_id` | Google OAuth Client ID | `xxx.apps.googleusercontent.com` | +| `google_client_secret` | Google OAuth Client Secret | `GOCSPX-xxx` | +| `microsoft_client_id` | Microsoft OAuth Client ID | `guid` | +| `microsoft_client_secret` | Microsoft OAuth Client Secret | `xxx` | + +## Criando os Secrets (Uma única vez) + +Conecte no servidor manager do Swarm e execute os comandos abaixo. + +### Opção 1: Via linha de comando (menos seguro - fica no histórico) + +```bash +# Stripe +echo "sk_live_SUBSTITUA_PELA_SUA_CHAVE" | docker secret create stripe_secret_key - +echo "whsec_SUBSTITUA_PELO_SEU_SECRET" | docker secret create stripe_webhook_secret - + +# MongoDB +echo "mongodb://admin:SENHA@129.146.116.218:27017,141.148.162.114:27017/QrRapido?replicaSet=rs0&authSource=admin" | docker secret create mongodb_connection_string - + +# Google OAuth +echo "SEU_GOOGLE_CLIENT_ID.apps.googleusercontent.com" | docker secret create google_client_id - +echo "GOCSPX-SEU_CLIENT_SECRET" | docker secret create google_client_secret - + +# Microsoft OAuth +echo "SEU_MICROSOFT_CLIENT_ID" | docker secret create microsoft_client_id - +echo "SEU_MICROSOFT_CLIENT_SECRET" | docker secret create microsoft_client_secret - +``` + +### Opção 2: Via arquivo (mais seguro) + +```bash +# Cria arquivo temporário, insere o valor, cria o secret, e remove o arquivo +nano /tmp/stripe_secret && docker secret create stripe_secret_key /tmp/stripe_secret && rm /tmp/stripe_secret +``` + +Repita para cada secret. + +## Verificando Secrets Criados + +```bash +# Lista todos os secrets +docker secret ls + +# Deve mostrar: +# ID NAME CREATED +# xxx stripe_secret_key X minutes ago +# xxx stripe_webhook_secret X minutes ago +# xxx mongodb_connection_string X minutes ago +# xxx google_client_id X minutes ago +# xxx google_client_secret X minutes ago +# xxx microsoft_client_id X minutes ago +# xxx microsoft_client_secret X minutes ago +``` + +## Recriando o Service com Secrets + +Se o service já existe sem os secrets, você precisa recriá-lo: + +```bash +# Remove o service existente +docker service rm qrrapido-prod + +# O próximo deploy vai criar o service com os secrets automaticamente +``` + +Ou manualmente: + +```bash +docker service create \ + --name qrrapido-prod \ + --replicas 2 \ + --network qrrapido-network \ + --publish published=5001,target=8080 \ + --mount type=bind,source=/app/keys,target=/app/keys \ + --secret stripe_secret_key \ + --secret stripe_webhook_secret \ + --secret mongodb_connection_string \ + --secret google_client_id \ + --secret google_client_secret \ + --secret microsoft_client_id \ + --secret microsoft_client_secret \ + --env ASPNETCORE_ENVIRONMENT=Production \ + --env ASPNETCORE_URLS=http://+:8080 \ + --update-delay 30s \ + --update-parallelism 1 \ + --update-order start-first \ + --restart-condition on-failure \ + --restart-max-attempts 3 \ + registry.redecarneir.us/qrrapido:latest +``` + +## Atualizando um Secret + +Docker Secrets são imutáveis. Para atualizar: + +```bash +# 1. Cria novo secret com nome diferente +echo "novo_valor" | docker secret create stripe_secret_key_v2 - + +# 2. Atualiza o service para usar o novo secret +docker service update \ + --secret-rm stripe_secret_key \ + --secret-add stripe_secret_key_v2 \ + qrrapido-prod + +# 3. Remove o secret antigo +docker secret rm stripe_secret_key + +# 4. (Opcional) Renomeia o novo secret +# Infelizmente não é possível renomear, então mantenha a convenção de versões +# ou recrie o service com o nome correto +``` + +## Verificando se os Secrets estão funcionando + +No rodapé da página, você verá: + +- **QR Rapido v1.0.0 | Production ✓** - Secrets carregados corretamente +- **QR Rapido v1.0.0 | Production ✗** - Secrets NÃO carregados (problema!) + +Você também pode verificar os logs: + +```bash +docker service logs qrrapido-prod --tail 50 | grep -i secret +``` + +## Como funciona internamente + +1. O Docker monta cada secret como arquivo em `/run/secrets/` +2. O `DockerSecretsConfigurationProvider` lê esses arquivos na inicialização +3. Os valores sobrescrevem as configurações do `appsettings.json` +4. A variável `App:SecretsLoaded` é definida como `true` quando pelo menos um secret é carregado + +## Testando Localmente + +Para simular Docker Secrets localmente (sem Swarm): + +```bash +# Cria diretório de secrets +mkdir -p /tmp/secrets + +# Cria arquivos de teste +echo "sk_test_xxx" > /tmp/secrets/stripe_secret_key +echo "whsec_test" > /tmp/secrets/stripe_webhook_secret + +# Executa o container com os secrets montados +docker run -d \ + -v /tmp/secrets:/run/secrets:ro \ + -p 5001:8080 \ + -e ASPNETCORE_ENVIRONMENT=Development \ + qrrapido:latest +``` + +## Troubleshooting + +### Secret não está sendo lido + +1. Verifique se o container tem acesso ao secret: + ```bash + docker exec -it $(docker ps -q -f name=qrrapido) ls -la /run/secrets/ + ``` + +2. Verifique o conteúdo (apenas para debug): + ```bash + docker exec -it $(docker ps -q -f name=qrrapido) cat /run/secrets/stripe_secret_key + ``` + +### Service não inicia + +Verifique se todos os secrets existem antes de criar o service: +```bash +docker secret ls +``` + +### Erro "secret not found" + +O secret precisa existir ANTES de criar/atualizar o service que o usa. diff --git a/Program.cs b/Program.cs index 7ea6452..6f12c37 100644 --- a/Program.cs +++ b/Program.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.StaticFiles; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Localization; using MongoDB.Driver; +using QRRapidoApp.Configuration; using QRRapidoApp.Data; using QRRapidoApp.Middleware; using QRRapidoApp.Providers; @@ -42,6 +43,10 @@ Environment.SetEnvironmentVariable("ASPNETCORE_HOSTINGSTARTUP__STATICWEBASSETS__ var builder = WebApplication.CreateBuilder(options); +// Add Docker Secrets as configuration source (for Swarm deployments) +// Secrets override values from appsettings.json +builder.Configuration.AddDockerSecrets(); + // Configure Serilog var loggerConfig = new LoggerConfiguration() .ReadFrom.Configuration(builder.Configuration) diff --git a/Scripts/create-docker-secrets.sh b/Scripts/create-docker-secrets.sh new file mode 100644 index 0000000..a146cfc --- /dev/null +++ b/Scripts/create-docker-secrets.sh @@ -0,0 +1,309 @@ +#!/bin/bash +# ============================================================================= +# Script para criar/recriar Docker Secrets no Swarm +# QR Rapido - Produção +# ============================================================================= +# +# USO: +# ./create-docker-secrets.sh # Modo interativo (pede cada valor) +# ./create-docker-secrets.sh --from-env # Lê de variáveis de ambiente +# ./create-docker-secrets.sh --from-file secrets.env # Lê de arquivo .env +# ./create-docker-secrets.sh --delete # Remove todos os secrets +# ./create-docker-secrets.sh --list # Lista secrets existentes +# +# IMPORTANTE: Execute este script no servidor MANAGER do Docker Swarm +# ============================================================================= + +set -e + +# Cores para output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Lista de secrets necessários +SECRETS=( + "stripe_secret_key" + "stripe_webhook_secret" + "mongodb_connection_string" + "google_client_id" + "google_client_secret" + "microsoft_client_id" + "microsoft_client_secret" +) + +# Descrições dos secrets +declare -A SECRET_DESCRIPTIONS=( + ["stripe_secret_key"]="Stripe API Secret Key (sk_live_xxx)" + ["stripe_webhook_secret"]="Stripe Webhook Signing Secret (whsec_xxx)" + ["mongodb_connection_string"]="MongoDB Connection String completa" + ["google_client_id"]="Google OAuth Client ID" + ["google_client_secret"]="Google OAuth Client Secret" + ["microsoft_client_id"]="Microsoft OAuth Client ID (GUID)" + ["microsoft_client_secret"]="Microsoft OAuth Client Secret" +) + +# Função para exibir uso +show_usage() { + echo -e "${BLUE}=== QR Rapido - Docker Secrets Manager ===${NC}" + echo "" + echo "Uso: $0 [opção]" + echo "" + echo "Opções:" + echo " (sem opção) Modo interativo - pede cada valor" + echo " --from-env Lê valores de variáveis de ambiente" + echo " --from-file F Lê valores de arquivo .env" + echo " --delete Remove todos os secrets" + echo " --list Lista secrets existentes" + echo " --check Verifica quais secrets estão faltando" + echo " --help Exibe esta ajuda" + echo "" + echo "Variáveis de ambiente esperadas (para --from-env):" + for secret in "${SECRETS[@]}"; do + local env_var=$(echo "$secret" | tr '[:lower:]' '[:upper:]') + echo " $env_var" + done +} + +# Função para verificar se está no Swarm +check_swarm() { + if ! docker info 2>/dev/null | grep -q "Swarm: active"; then + echo -e "${RED}ERRO: Docker Swarm não está ativo neste nó.${NC}" + echo "Execute este script no servidor manager do Swarm." + exit 1 + fi + + if ! docker info 2>/dev/null | grep -q "Is Manager: true"; then + echo -e "${RED}ERRO: Este nó não é um manager do Swarm.${NC}" + echo "Execute este script no servidor manager do Swarm." + exit 1 + fi +} + +# Função para listar secrets +list_secrets() { + echo -e "${BLUE}=== Secrets existentes ===${NC}" + docker secret ls + echo "" + echo -e "${BLUE}=== Status dos secrets do QR Rapido ===${NC}" + for secret in "${SECRETS[@]}"; do + if docker secret inspect "$secret" > /dev/null 2>&1; then + echo -e " ${GREEN}✓${NC} $secret" + else + echo -e " ${RED}✗${NC} $secret (não existe)" + fi + done +} + +# Função para verificar secrets faltantes +check_secrets() { + echo -e "${BLUE}=== Verificando secrets do QR Rapido ===${NC}" + local missing=0 + for secret in "${SECRETS[@]}"; do + if docker secret inspect "$secret" > /dev/null 2>&1; then + echo -e " ${GREEN}✓${NC} $secret" + else + echo -e " ${RED}✗${NC} $secret - ${SECRET_DESCRIPTIONS[$secret]}" + missing=$((missing + 1)) + fi + done + + if [ $missing -gt 0 ]; then + echo "" + echo -e "${YELLOW}$missing secret(s) faltando.${NC}" + echo "Execute: $0 (modo interativo) para criar os secrets faltantes." + return 1 + else + echo "" + echo -e "${GREEN}Todos os secrets estão configurados!${NC}" + return 0 + fi +} + +# Função para deletar secrets +delete_secrets() { + echo -e "${YELLOW}=== Removendo secrets do QR Rapido ===${NC}" + echo -e "${RED}ATENÇÃO: Isso vai remover TODOS os secrets listados!${NC}" + read -p "Tem certeza? (digite 'sim' para confirmar): " confirm + + if [ "$confirm" != "sim" ]; then + echo "Operação cancelada." + exit 0 + fi + + for secret in "${SECRETS[@]}"; do + if docker secret inspect "$secret" > /dev/null 2>&1; then + echo -n "Removendo $secret... " + if docker secret rm "$secret" 2>/dev/null; then + echo -e "${GREEN}OK${NC}" + else + echo -e "${RED}ERRO (pode estar em uso por um service)${NC}" + fi + else + echo -e " $secret - ${YELLOW}não existe${NC}" + fi + done + + echo "" + echo -e "${YELLOW}IMPORTANTE: Se algum secret estava em uso, você precisa:${NC}" + echo "1. Parar o service: docker service rm qrrapido-prod" + echo "2. Remover os secrets: $0 --delete" + echo "3. Recriar os secrets: $0" + echo "4. Recriar o service (via CI/CD ou manualmente)" +} + +# Função para criar um secret +create_secret() { + local name=$1 + local value=$2 + + # Verifica se já existe + if docker secret inspect "$name" > /dev/null 2>&1; then + echo -e " ${YELLOW}!${NC} $name já existe (pulando)" + return 0 + fi + + # Cria o secret + echo -n " Criando $name... " + if echo -n "$value" | docker secret create "$name" - > /dev/null 2>&1; then + echo -e "${GREEN}OK${NC}" + return 0 + else + echo -e "${RED}ERRO${NC}" + return 1 + fi +} + +# Função para modo interativo +interactive_mode() { + echo -e "${BLUE}=== Modo Interativo ===${NC}" + echo "Você será perguntado sobre cada secret." + echo "Pressione ENTER para pular secrets que já existem." + echo "" + + for secret in "${SECRETS[@]}"; do + # Verifica se já existe + if docker secret inspect "$secret" > /dev/null 2>&1; then + echo -e "${GREEN}✓${NC} $secret já existe" + read -p " Deseja recriar? (s/N): " recreate + if [ "$recreate" != "s" ] && [ "$recreate" != "S" ]; then + continue + fi + echo -n " Removendo secret antigo... " + docker secret rm "$secret" > /dev/null 2>&1 || true + echo "OK" + fi + + echo "" + echo -e "${BLUE}$secret${NC}" + echo " Descrição: ${SECRET_DESCRIPTIONS[$secret]}" + + # Lê o valor (sem mostrar na tela) + read -s -p " Valor: " value + echo "" + + if [ -z "$value" ]; then + echo -e " ${YELLOW}Pulando (valor vazio)${NC}" + continue + fi + + create_secret "$secret" "$value" + done + + echo "" + echo -e "${GREEN}=== Concluído ===${NC}" + list_secrets +} + +# Função para ler de variáveis de ambiente +from_env_mode() { + echo -e "${BLUE}=== Lendo de variáveis de ambiente ===${NC}" + + local errors=0 + for secret in "${SECRETS[@]}"; do + local env_var=$(echo "$secret" | tr '[:lower:]' '[:upper:]') + local value="${!env_var}" + + if [ -z "$value" ]; then + echo -e " ${RED}✗${NC} $env_var não está definida" + errors=$((errors + 1)) + else + create_secret "$secret" "$value" + fi + done + + if [ $errors -gt 0 ]; then + echo "" + echo -e "${RED}$errors variável(is) de ambiente não encontrada(s).${NC}" + exit 1 + fi + + echo "" + echo -e "${GREEN}=== Concluído ===${NC}" +} + +# Função para ler de arquivo +from_file_mode() { + local file=$1 + + if [ ! -f "$file" ]; then + echo -e "${RED}ERRO: Arquivo não encontrado: $file${NC}" + exit 1 + fi + + echo -e "${BLUE}=== Lendo de arquivo: $file ===${NC}" + + # Carrega as variáveis do arquivo + set -a + source "$file" + set +a + + # Usa o modo from_env + from_env_mode +} + +# ============================================================================= +# MAIN +# ============================================================================= + +# Verifica se está no Swarm (exceto para --help) +if [ "$1" != "--help" ] && [ "$1" != "-h" ]; then + check_swarm +fi + +# Processa argumentos +case "$1" in + --help|-h) + show_usage + ;; + --list) + list_secrets + ;; + --check) + check_secrets + ;; + --delete) + delete_secrets + ;; + --from-env) + from_env_mode + ;; + --from-file) + if [ -z "$2" ]; then + echo -e "${RED}ERRO: Especifique o arquivo .env${NC}" + echo "Uso: $0 --from-file secrets.env" + exit 1 + fi + from_file_mode "$2" + ;; + "") + interactive_mode + ;; + *) + echo -e "${RED}Opção desconhecida: $1${NC}" + show_usage + exit 1 + ;; +esac diff --git a/Scripts/secrets.env.template b/Scripts/secrets.env.template new file mode 100644 index 0000000..af3d261 --- /dev/null +++ b/Scripts/secrets.env.template @@ -0,0 +1,28 @@ +# ============================================================================= +# Template de Secrets para QR Rapido +# ============================================================================= +# +# INSTRUÇÕES: +# 1. Copie este arquivo para 'secrets.env' (ou outro nome) +# 2. Preencha os valores abaixo +# 3. Execute: ./create-docker-secrets.sh --from-file secrets.env +# 4. DELETE o arquivo após criar os secrets (contém dados sensíveis!) +# +# NUNCA faça commit de arquivos .env com valores reais! +# ============================================================================= + +# Stripe - Obter em https://dashboard.stripe.com/apikeys +STRIPE_SECRET_KEY=sk_live_SUBSTITUA_AQUI +STRIPE_WEBHOOK_SECRET=whsec_SUBSTITUA_AQUI + +# MongoDB - Connection string completa com usuário e senha +# Formato: mongodb://user:password@host1:port,host2:port/database?options +MONGODB_CONNECTION_STRING=mongodb://admin:SENHA_AQUI@129.146.116.218:27017,141.148.162.114:27017/QrRapido?replicaSet=rs0&authSource=admin + +# Google OAuth - Obter em https://console.cloud.google.com/apis/credentials +GOOGLE_CLIENT_ID=SUBSTITUA_AQUI.apps.googleusercontent.com +GOOGLE_CLIENT_SECRET=GOCSPX-SUBSTITUA_AQUI + +# Microsoft OAuth - Obter em https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps +MICROSOFT_CLIENT_ID=SUBSTITUA_AQUI +MICROSOFT_CLIENT_SECRET=SUBSTITUA_AQUI diff --git a/Views/Shared/_Layout.cshtml b/Views/Shared/_Layout.cshtml index fc0df62..6906218 100644 --- a/Views/Shared/_Layout.cshtml +++ b/Views/Shared/_Layout.cshtml @@ -1,15 +1,23 @@ @using QRRapidoApp.Services @using Microsoft.AspNetCore.Http.Extensions @using Microsoft.Extensions.Localization +@using Microsoft.Extensions.Configuration @inject AdDisplayService AdService @inject IStringLocalizer Localizer @inject Microsoft.AspNetCore.Hosting.IWebHostEnvironment HostEnvironment +@inject IConfiguration Configuration @{ var isDevelopment = HostEnvironment?.IsDevelopment() ?? false; var isPremiumUser = false; var currentCulture = System.Globalization.CultureInfo.CurrentUICulture?.Name ?? "pt-BR"; + // App info for footer + var appName = Configuration["App:Name"] ?? "QR Rapido"; + var appVersion = Configuration["App:Version"] ?? "1.0.0"; + var appEnvironment = Configuration["App:Environment"] ?? HostEnvironment?.EnvironmentName ?? "Unknown"; + var secretsLoaded = Configuration.GetValue("App:SecretsLoaded"); + if (User?.Identity?.IsAuthenticated == true) { var userId = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value; @@ -421,6 +429,20 @@
© 2024 QR Rapido. @Localizer["AllRightsReserved"]
+
+ + @appName v@appVersion | + @appEnvironment + @if (secretsLoaded) + { + + } + else if (appEnvironment == "Production") + { + + } + +
diff --git a/appsettings.Production.json b/appsettings.Production.json index c41cb6b..f9a44f0 100644 --- a/appsettings.Production.json +++ b/appsettings.Production.json @@ -1,12 +1,15 @@ { "ApplicationName": "QRRapido-Prod", - "Environment": "Prod", + "App": { + "Environment": "Production", + "SecretsLoaded": false + }, "Support": { "TelegramUrl": "https://t.me/jobmakerbr", "FormspreeUrl": "https://formspree.io/f/xpwynqpj" }, "ConnectionStrings": { - "MongoDB": "mongodb://admin:c4rn31r0@129.146.116.218:27017,141.148.162.114:27017/QrRapido?replicaSet=rs0&authSource=admin" + "MongoDB": "LOADED_FROM_DOCKER_SECRET" }, "Serilog": { "SeqUrl": "http://172.17.0.1:5341", @@ -22,10 +25,20 @@ } } }, + "Authentication": { + "Google": { + "ClientId": "LOADED_FROM_DOCKER_SECRET", + "ClientSecret": "LOADED_FROM_DOCKER_SECRET" + }, + "Microsoft": { + "ClientId": "LOADED_FROM_DOCKER_SECRET", + "ClientSecret": "LOADED_FROM_DOCKER_SECRET" + } + }, "Stripe": { "PublishableKey": "pk_live_51Rs42SB6bFjHQirAXIhK2fetsfH7MDYWeTT5jiRGYpIS7g5fCCT0XzLK1tIOdxUYXG4gwN4OEAzuVFw9GTmvq7iM00iJmUSZWB", - "SecretKey": "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", - "WebhookSecret": "whsec_gYri7qNVHc18sREu2eXnjdRzklwRf3hI" + "SecretKey": "LOADED_FROM_DOCKER_SECRET", + "WebhookSecret": "LOADED_FROM_DOCKER_SECRET" }, "ResourceMonitoring": { "Enabled": true, @@ -77,4 +90,4 @@ "QRRapidoApp": "Information" } } -} \ No newline at end of file +} diff --git a/appsettings.json b/appsettings.json index 6a3aff2..2f3af63 100644 --- a/appsettings.json +++ b/appsettings.json @@ -5,7 +5,9 @@ "TaglinePT": "Gere QR codes em segundos!", "TaglineES": "¡Genera códigos QR en segundos!", "TaglineEN": "Generate QR codes in seconds!", - "Version": "1.0.0" + "Version": "1.0.0", + "Environment": "Development", + "SecretsLoaded": false }, "ConnectionStrings": { "MongoDB": "mongodb://localhost:27017/QrRapido"