Conversation
The API answered Access-Control-Allow-Origin: * to any origin, so any page could call it from its visitors' browsers, for instance spreading login attempts across many IPs to dodge the per-IP limit. The origins now come from CORS_ALLOWED_ORIGINS, with a one-level wildcard for the tenant subdomain; without it, outside production, only localhost is accepted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… HTTPS only Production now refuses to boot without DEVISE_JWT_SECRET_KEY, CORS_ALLOWED_ORIGINS and RAILS_ALLOWED_HOSTS instead of signing tokens with secret_key_base and skipping the Host check. assume_ssl and force_ssl are on behind the TLS proxy, which adds HSTS and marks the backoffice session cookie as Secure. /up stays out of the host check for the health checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Docker entrypoint runs db:prepare, which seeds every new database, so a deploy started with admin123 and password123. In production the seeded passwords now come from SEED_USER_PASSWORD and SEED_ADMIN_PASSWORD, the seed refuses to run without them or with a repo password, and it rotates the accounts a previous seed left with the repo passwords. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…CRLF scripts With core.autocrlf=true, git checks out bin/* with CRLF, so the shebang of bin/rails asks env for `ruby\r` and `docker build` dies at `./bin/rails assets:precompile` (exit 127). The Dockerfile already strips the CRs, but that step ran after assets:precompile, which was added above it. - Move "Adjust binfiles" (chmod +x, strip CR) back before the first RUN that executes bin/, the order `rails new` generates. Existing Windows clones keep CRLF on disk even after .gitattributes changes, so this is what makes them build without re-checking out bin/. - Force LF on bin/* and *.sh in .gitattributes (CRLF for the bin/kamal.cmd batch file), so fresh checkouts and containers that bind-mount the repo get runnable scripts. The index already stored them as LF: renormalizing changes no blobs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ticket de Jira
https://proyectofinalfrlp.atlassian.net/browse/TESIS-130
Descripción
Cierra la API desplegada, que usaba defaults pensados para desarrollo. CORS respondía
*a cualquier origen. SinDEVISE_JWT_SECRET_KEY, los JWT se firmaban consecret_key_base.force_sslyassume_sslestaban comentados, así que no había HSTS y la cookie del backoffice salía sinSecure. SinRAILS_ALLOWED_HOSTS, el Host no se validaba. Y eldb:preparedel entrypoint sembraba cada base nueva conadmin123ypassword123. Ahora producción no arranca sin sus secretos y hosts: preferimos un deploy que falla a la vista a uno que levanta abierto. Los seeds toman las contraseñas del entorno y rotan las del repo en una base ya sembrada. El detalle y las alternativas descartadas están en ADR-018; la parte del front va en el PR de proyecto-web de la misma card.config/initializers/cors.rb) aCORS_ALLOWED_ORIGINS, con comodín de un nivel para el subdominio de cada empresa (https://*.dominiovale paranorte.dominio, no paraa.b.dominio). Sin la variable, fuera de producción, aceptalocalhosten cualquier puerto. Sigue exponiendo elETagconfig/environments/production.rb) si faltanDEVISE_JWT_SECRET_KEY,CORS_ALLOWED_ORIGINSoRAILS_ALLOWED_HOSTS.SECRET_KEY_BASE_DUMMYexime alassets:precompiledel Dockerfileassume_sslyforce_ssl: HSTS y cookiesSecure, entre ellas_proyecto_api_session. El TLS y la redirección HTTP→HTTPS siguen en Caddy/upfuera de ella, para los health checksdb/seeds.rb: en producción las contraseñas salen deSEED_USER_PASSWORDySEED_ADMIN_PASSWORD, el seed se niega a correr sin ellas o con una del repo, y rota las cuentas que una siembra anterior dejó con las contraseñas del repospec/requests/cors_spec.rbyspec/db/seeds_spec.rbADR-018, cierra enADR-017elSecurey documenta las variables en.env.exampleyarchitecture.mdEvidencia visual
N/A
Cómo probar
bundle exec rspec spec/requests/cors_spec.rb spec/db/seeds_spec.rb→ 10 ejemplos en verde. La suite completa da 1556 ejemplos sin fallos.
docker build -t proyecto_api ., desde Linux o WSL: desde un checkout de Windows falla por los finales CRLF debin/, que va en una tarea aparte) y correrdocker run --rm -e SECRET_KEY_BASE=x proyecto_api ./bin/rails runner 'puts 1'→ Falla con
Missing environment variables for production: DEVISE_JWT_SECRET_KEY, CORS_ALLOWED_ORIGINS, RAILS_ALLOWED_HOSTS.DEVISE_JWT_SECRET_KEY,CORS_ALLOWED_ORIGINS=https://*.ejemplo.test,RAILS_ALLOWED_HOSTS=api.ejemplo.test,localhost,SEED_USER_PASSWORDySEED_ADMIN_PASSWORD. Después:curl -I -H 'Host: otro.test' http://localhost:<puerto>/api/v1/tenant-config?slug=norte→ 403. ConHost: api.ejemplo.test→ 200./upcon cualquier Host → 200.Strict-Transport-Security, yGET /admin/sign_indevuelve la cookie consecure.OPTIONS /api/v1/auth/loginconOrigin: https://norte.ejemplo.test→ devuelve ese origen. Conhttps://a.b.ejemplo.test,https://evil.exampleonull→ sinAccess-Control-Allow-Origin.secret_key_baseen vez deDEVISE_JWT_SECRET_KEY→ 401 enGET /api/v1/me.bin/rails runnerdentro del contenedor → ninguna cuenta validapassword123niadmin123.password123a un usuario y correrbin/rails db:seed→ Sin las variables
SEED_*falla con un mensaje claro. Con ellas, el usuario deja de validarpassword123.Impacto y consideraciones
¿Introduce breaking changes?
Sí. En producción la API no arranca sin las tres variables de abajo, y el front desplegado sólo puede llamarla desde los orígenes de
CORS_ALLOWED_ORIGINS. Hay que cargarlas en el server antes de deployar. Además, cambiarDEVISE_JWT_SECRET_KEYcierra las sesiones abiertas. En desarrollo no cambia nada: CORS aceptalocalhosty los seeds siguen con las contraseñas de siempre.¿Requiere nuevas variables de entorno?
Sí, sólo en producción:
DEVISE_JWT_SECRET_KEY: obligatoria, una por entorno (bin/rails secret)CORS_ALLOWED_ORIGINS=https://*.precision-logistics.duckdns.org: obligatoriaRAILS_ALLOWED_HOSTS=api.precision-logistics.duckdns.org: obligatoriaSEED_USER_PASSWORDySEED_ADMIN_PASSWORD: las pidedb:seed. Correrbin/rails db:seeduna vez con ellas en la demo actual rota las cuentas que siguen con las contraseñas del repo¿Afecta la arquitectura o genera un nuevo patrón?
Sí. Agrega
docs/adr/ADR-018-seguridad-del-despliegue.md, que fija variables obligatorias con arranque que falla en producción, CORS por lista con comodín de subdominio, TLS terminado en el proxy conassume_ssly seeds sin contraseñas del repo. Queda como deuda anotada queENCRYPTION_KEYtodavía cae asecret_key_base: cambiarla vuelve ilegibles las credenciales ya cifradas, así que necesita su propia migración.🤖 Generated with Claude Code