Files
admin/_docs/05_security/infrastructure_review.md
T
Oleksandr Bezdieniezhnykh c7b297de83
ci/woodpecker/push/01-test Pipeline failed
ci/woodpecker/push/02-build-push unknown status
refactor: remove deploy.cmd and update Dockerfile for health checks
- Deleted the deploy.cmd script as it was no longer needed.
- Updated Dockerfile to include curl for health checks and added a non-root user for improved security.
- Modified health check command to use curl for better reliability.
- Adjusted docker-compose.test.yml to reflect changes in health check configuration.
- Cleaned up appsettings.json and removed unused configuration properties.
- Removed Resource entity and related requests from the codebase as part of the architectural shift.
- Updated documentation to reflect the removal of hardware binding and related endpoints.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-13 08:47:21 +03:00

7.1 KiB

Infrastructure & Configuration Review

Date: 2026-05-13 Scope: Dockerfile, docker.test/Dockerfile, e2e/Dockerfile, docker-compose.test.yml, appsettings*.json, env/db/*.sql, .gitignore, .dockerignore. No CI/CD pipeline files (.github/workflows/, .gitlab-ci.yml, azure-pipelines.yml) are present in this workspace.

Container Security

Production image (Dockerfile)

Check Result Notes
Non-root user FAIL No USER directive — runs as root. See F-6.
Minimal base image PASS mcr.microsoft.com/dotnet/aspnet:10.0 (runtime-only) for the final stage; SDK image used only for build.
Multi-stage build PASS Build / publish / runtime stages cleanly separated.
No secrets in build args PASS Only CI_COMMIT_SHA (passed as AZAION_REVISION env at runtime) — non-sensitive.
Health check defined PASS (compose layer) docker-compose.test.yml:42-51 defines a TCP health check; the production deployment must define an equivalent. Not verified in this audit because no production compose file exists in this workspace.
Image pinned by digest WARN Base images use :10.0 tag, not @sha256:... digest. Tag floats — a poisoned upstream tag would be picked up on the next rebuild. Acceptable if the build runs from a controlled cache; otherwise pin.
EXPOSE matches Kestrel binding PASS EXPOSE 8080, ASPNETCORE_URLS=http://+:8080.

Test sidecar image (docker.test/Dockerfile)

FROM alpine:latest
CMD echo hello

This image is essentially a no-op stub. It's referenced from somewhere in the test/CI tooling but contributes nothing functional. Recommendation: either remove it (if nothing references it) or document its purpose. From a security standpoint it's inert — alpine:latest is fine for an echo and the floating tag is irrelevant for a stub. Flag as operational hygiene, not a security finding.

E2E runner image (e2e/Dockerfile)

Check Result Notes
Non-root FAIL (test-only) Same root-by-default behavior. Test runner only — no exposed network, runs in an ephemeral container per CI run. Acceptable.
Uses SDK image as final WARN (test-only) Final stage is mcr.microsoft.com/dotnet/sdk:10.0 — needed for dotnet test. SDK images carry more attack surface than runtime images, but this container is only reachable on the internal e2e-net bridge. Acceptable for test-only.

docker-compose.test.yml

Check Result Notes
Secrets via env vars (not committed prod secrets) PASS (test-only) The DB password, JWT secret, and master key are committed because this is the e2e harness only. F-10 captures the rule: these literals must never appear in a production compose file.
No port leaks PASS Only 8080:8080 is published from system-under-test; test-db is internal to the bridge.
Healthcheck for the API PASS TCP probe on 127.0.0.1:8080.
Network isolation PASS All three services share e2e-net only; no host network mode.
Image lock WARN (test-only) postgres:16-alpine floats. For test reproducibility consider pinning a digest, but not security-critical for an ephemeral test stack.

There is no production docker-compose.yml in this workspace. Any production deployment must:

  • Inject JwtConfig__Secret and both ConnectionStrings__* values from a secret manager (Vault, AWS Secrets Manager, Azure Key Vault). (ResourcesConfig__EncryptionMasterKey was the third item here pre-revert; that field has since been deleted along with the OTA feature.)
  • NOT carry over the ASPNETCORE_ENVIRONMENT=Development value used in the test compose — that environment value enables Swagger at the root path.

Environment Configuration

Azaion.AdminApi/appsettings.json

Field Value committed Risk
Logging.LogLevel.* Information / Warning OK
AllowedHosts "*" OK at the framework level — host filtering is normally enforced upstream by the reverse proxy.
ResourcesConfig.ResourcesFolder "Content" Relative path; resolves under the working directory inside the container. OK.
ResourcesConfig.EncryptionMasterKey Removed in the post-cycle-1 revert along with the OTA feature; field no longer exists in ResourcesConfig, appsettings.json, or docker-compose.test.yml. Closes F-5 automatically.
JwtConfig.{Issuer, Audience, TokenLifetimeHours} committed Public-by-design (not secrets).
JwtConfig.Secret NOT committed Correct. Supplied via env var.
ConnectionStrings.* NOT committed Correct. Supplied via env var.

Azaion.AdminApi/appsettings.Development.json

Empty except for log levels — fine.

e2e/Azaion.E2E/appsettings.test.json

Test-only. F-10 captures this; not a production concern.

Secrets Hygiene

Pattern Where Disposition
Plaintext DB passwords env/db/01_permissions.sql F-11 — operator template, not runtime. Add a header comment.
Plaintext DB / JWT / master-key in docker-compose.test.yml docker-compose.test.yml:31-37 F-10 — test-only. CI guard recommended.
Plaintext admin/uploader passwords in e2e/Azaion.E2E/appsettings.test.json as above F-10 — test-only.
.env ignored .gitignore:10 PASS
bin/, obj/, logs/, Content/ ignored .gitignore:2-3, 7, 9 PASS — keeps build artifacts and runtime data out of git.

CI/CD

No CI configuration files were found in the workspace (.github/workflows/, .gitlab-ci.yml, azure-pipelines.yml, Jenkinsfile — none present). Either:

  • CI is configured outside this repo (e.g., upstream meta-repo), in which case the security guardrails (dotnet list package --vulnerable, secret-scanning, dependency-review) need to be verified there.
  • Or there is no automated CI today, in which case this is a meta-finding: the dependency-bump (D-1) and the test suite have no automated gate. Recommend: introduce at least a dotnet build && dotnet test && dotnet list package --vulnerable job before deploy.

Network Security

Check Status Notes
HTTPS enforcement in code FAIL F-13 — assumed at reverse proxy.
HSTS not configured Acceptable if reverse proxy injects it.
CORS tight AdminCorsPolicy whitelist-only (https://admin.azaion.com, http://admin.azaion.com). The plaintext http:// origin can be removed once the SaaS UI is HTTPS-only.
Security headers not configured in app X-Frame-Options, X-Content-Type-Options, Content-Security-Policy not set in code. Reverse proxy responsibility today; document the assumption.

Self-verification

  • All Dockerfiles reviewed (Dockerfile, docker.test/Dockerfile, e2e/Dockerfile)
  • All compose files reviewed (docker-compose.test.yml — only one in repo)
  • All environment / config files reviewed (3 appsettings*.json)
  • CI/CD reviewed (none present in repo — surfaced as meta-finding)