{"id":51232391,"url":"https://github.com/mackee/localfront","last_synced_at":"2026-06-28T17:30:27.751Z","repository":{"id":365204812,"uuid":"1271031928","full_name":"mackee/localfront","owner":"mackee","description":"A CloudFront emulator for local testing","archived":false,"fork":false,"pushed_at":"2026-06-16T09:30:59.000Z","size":256,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-16T11:14:37.821Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mackee.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-16T09:13:52.000Z","updated_at":"2026-06-16T09:30:58.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/mackee/localfront","commit_stats":null,"previous_names":["mackee/localfront"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/mackee/localfront","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mackee%2Flocalfront","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mackee%2Flocalfront/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mackee%2Flocalfront/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mackee%2Flocalfront/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mackee","download_url":"https://codeload.github.com/mackee/localfront/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mackee%2Flocalfront/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34898561,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-28T02:00:05.809Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-28T17:30:27.216Z","updated_at":"2026-06-28T17:30:27.744Z","avatar_url":"https://github.com/mackee.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# localfront\n\n**A local Amazon CloudFront emulator, driven by CloudFormation templates.**\n\nlocalfront reads the same `AWS::CloudFront::*` resources you deploy to AWS — from a hand-written template or `cdk synth` output — and runs a working data plane on your machine: a reverse proxy with cache behaviors, CloudFront Functions, KeyValueStore lookups, and signed URL / signed cookie verification, serving S3 origins from an external S3-compatible object store.\n\n\u003e **Status: Proof of Concept.** This README defines the PoC scope. Behavior, defaults, and file formats may change without notice.\n\n## Why\n\nCloudFront behavior — path-based routing across cache behaviors, CloudFront Functions, signed URLs, error-page fallbacks — can usually only be verified against a real distribution. Deploys take minutes and are awkward to run in CI. localfront provides a faithful-enough local stand-in that starts instantly from the template you already have.\n\nlocalfront deliberately has **no management API**. Configuration is a CloudFormation template, hot-reloaded on change:\n\n- **CloudFormation users** — point localfront at your template.\n- **CDK users** — point it at the synthesized template in `cdk.out/`. Resource types localfront doesn't know are skipped with a warning, so full-app templates load as-is.\n- **Terraform users** — a companion converter is planned as a **separate project**: it consumes a Terraform plan (`terraform show -json \u003cplanfile\u003e`), so all HCL expressions are already resolved by Terraform itself, and emits a CloudFormation template covering the `aws_cloudfront_*` resources (intra-plan references become `Ref` / `GetAtt`). No general-purpose Terraform→CFN converter exists; restricting scope to CloudFront resources is what makes it tractable.\n\n## PoC scope\n\n### Configuration: CloudFormation template\n\n- Loads one or more templates (JSON / YAML) and **hot-reloads** them on file change.\n- Supported resource types: `AWS::CloudFront::Distribution`, `AWS::CloudFront::Function`, `AWS::CloudFront::KeyValueStore`, `AWS::CloudFront::CachePolicy`, `AWS::CloudFront::OriginRequestPolicy`, `AWS::CloudFront::ResponseHeadersPolicy`, `AWS::CloudFront::PublicKey`, `AWS::CloudFront::KeyGroup`, `AWS::CloudFront::OriginAccessControl`. Unknown resource types are skipped with a warning.\n- AWS **managed policies** are built in under their well-known IDs (`Managed-CachingOptimized`, `Managed-CachingDisabled`, `Managed-AllViewer`, `Managed-CORS-S3Origin`, …). Legacy `ForwardedValues` is also accepted.\n- Intrinsic functions: a pragmatic subset — `Ref` and `Fn::GetAtt` between resources in the loaded templates, `Fn::Sub`, `Fn::Join`, `Fn::FindInMap`, and `Parameters` (defaults, overridable with `--parameter key=value`). Anything unresolvable (e.g. `Fn::ImportValue`) fails template loading with a clear error.\n- KeyValueStore contents are seeded from the resource's `ImportSource` (fetched from the configured S3-compatible store) or from a local JSON file via `--kvs-seed \u003cstore\u003e=\u003cfile.json\u003e`. A `--kvs-seed` replaces the store's contents and skips the `ImportSource` fetch entirely, so it works offline without `--s3-endpoint`.\n- localfront is **stateless**: the templates, seed files, and the object store are the entire configuration. There is no state directory and no write API.\n\n### Data plane (the actual proxy)\n\n- **Cache behaviors** — path pattern matching with CloudFront's precedence rules, the default behavior, allowed / cached methods, and per-behavior `Compress` (gzip / brotli).\n- **Origins** — custom HTTP(S) origins (origin path, custom headers, protocol policy), and S3 origins backed by an external S3-compatible object store (see [S3 origins](#s3-origins-external-object-storage)).\n- **Default root object** and **custom error responses** — including the classic SPA fallback (`403/404 → /index.html` with status 200).\n- **CloudFront Functions** at viewer-request / viewer-response, with cloudfront-js 2.0 semantics and KVS bindings.\n- **Signed URLs \u0026 signed cookies** — canned and custom policies, verified against the trusted key groups of restricted behaviors. A canned signature covers the exact resource URL, so localfront reconstructs it from `--public-host` (required; env `LOCALFRONT_PUBLIC_HOST`) — the host (optionally `host:port`) you signed the URLs for. The port, when present, is part of the signed resource (real CloudFront only serves on 443/80, so its signed URLs never carry one — sign your local URLs for the address you actually reach localfront at). A custom policy whose `Resource` includes a host (e.g. `https://*.tenants.example.com/*`) has that host matched too, against the same `--public-host`; leave `--public-host` empty to match each request's own `Host` instead — the wildcard-subdomain case, where every tenant arrives on a different host.\n- **Host-based routing** — distributions are matched by their aliases (CNAMEs); each also gets a stable generated ID derived from its template logical ID, reachable as `\u003cdistribution-id\u003e.cloudfront.localhost`.\n- **CloudFront request/response headers** — `X-Amz-Cf-Id`, `Via`, `X-Cache`, `X-Forwarded-For`, and the `CloudFront-Viewer-*` / device-detection headers selected by the origin request policy. Viewer header values (country, device, …) can be overridden per request to test geo- or device-dependent code.\n- **Pass-through of conditional and range requests** (`If-None-Match` / `If-Modified-Since`, `Range`, `HEAD`) to origins.\n\n### S3 origins (external object storage)\n\nlocalfront does not embed an object store. S3 origins are served by an **external S3-compatible storage** — [RustFS](https://github.com/rustfs/rustfs) is the reference companion (other S3-compatible stores such as MinIO should work as well). At request time, localfront acts as an **S3 API client**: it resolves the bucket from the origin's domain name and fetches objects with SigV4-signed `GetObject` / `HeadObject` calls against the configured endpoint, mapping S3 errors (`NoSuchKey`, `AccessDenied`, …) to the status codes CloudFront would return — which then feed custom error responses as usual.\n\n- Origin domain names of the form `\u003cbucket\u003e.s3.\u003cregion\u003e.amazonaws.com` are resolved to `\u003cs3-endpoint\u003e/\u003cbucket\u003e` (path-style), so templates written for production work unchanged.\n- Conditional and range requests are forwarded as S3 request parameters (`Range`, `If-None-Match`, …).\n- `AWS::CloudFront::OriginAccessControl` resources are accepted but not enforced — localfront always accesses the store with the credentials given at startup; bucket policies on the store side are not consulted.\n- Assets are uploaded directly to the store with the usual tooling (`aws s3 sync --endpoint-url …`); localfront is not involved in uploads.\n\n## Caching\n\nThe PoC **does not cache**. Cache policies are still interpreted — they determine the cache key and therefore which headers, cookies, and query strings reach your origin — but every request is forwarded and answered with `X-Cache: Miss from localfront`. This keeps local behavior deterministic in tests. An optional in-memory cache (honoring cache policies and TTLs, with `X-Cache: Hit` and a purge command) is on the roadmap.\n\n## Not supported\n\n**Intentionally out of scope, now and later:**\n\n| Feature | Reason |\n| --- | --- |\n| **Lambda@Edge** | Deliberate. Emulating Lambda runtimes and replication is a different product; CloudFront Functions + KVS cover most local-development use cases. |\n| **Management APIs** | By design. localfront reads template files; it does not implement the CloudFront management API, the CloudFormation API, or the `cloudfront-keyvaluestore` data API. Driving localfront directly from Terraform or AWS SDKs is out of scope — use the template workflow above. |\n\n**Accepted but ignored** — template properties that load cleanly but have no effect:\n\n- Price class, IPv6 / HTTP/2 / HTTP/3 flags\n- Viewer certificate (ACM / minimum protocol version) — the PoC serves plain HTTP only\n- Viewer protocol policy (never redirects to HTTPS locally)\n- Geo restrictions (stored, not enforced)\n- Origin Shield\n- Logging configuration\n- Web ACL (WAF) association\n- Tags\n\n**Not implemented** — template loading fails with a clear error:\n\n- Origin group failover (planned, see roadmap)\n- Field-level encryption\n- Real-time logs\n- Continuous deployment / `AWS::CloudFront::ContinuousDeploymentPolicy`\n- VPC origins, Anycast static IPs, multi-tenant (SaaS Manager) distributions\n\n## Quick start\n\n\u003e The CLI shown below is the planned interface; defaults may change.\n\n```console\n$ docker run -d -p 9000:9000 rustfs/rustfs        # S3-compatible origin storage\n$ go install github.com/mackee/localfront/cmd/localfront@latest\n$ localfront serve --template ./template.yaml \\\n    --listen        :8080 \\\n    --public-host   assets.example.test:8080 \\\n    --s3-endpoint   http://localhost:9000 \\\n    --s3-access-key rustfsadmin \\\n    --s3-secret-key rustfsadmin\ndata plane   http://localhost:8080\ntemplate     ./template.yaml (hot reload)\ndistribution E... [AssetsDistribution]\n  http://assets.example.test -\u003e localhost:8080\n```\n\nRepeatable flags: `--template`, `--parameter KEY=VALUE` (overrides template parameter defaults), and `--kvs-seed STORE=FILE` (substitutes a local JSON for a KeyValueStore's `ImportSource`). `--listen` defaults to `:8080`; `--log-level` accepts `debug|info|warn|error`. `--public-host` is required (env: `LOCALFRONT_PUBLIC_HOST`).\n\n`--access-log FILE` (env: `LOCALFRONT_ACCESS_LOG`) writes per-request access logs in CloudFront's Standard log format — 33 tab-separated columns with the `#Version:` / `#Fields:` preamble — so the same ETL that consumes the S3-delivered logs in production also consumes the file localfront produces. Default is `-` (stdout); pass an empty string (`--access-log ''` or `LOCALFRONT_ACCESS_LOG=''`) to disable. The startup banner and operational slog output go to stderr, leaving stdout clean for log consumers.\n\n### Container image (GHCR)\n\nPre-built images are published to `ghcr.io/mackee/localfront` (linux/amd64 + linux/arm64) on every release tag. The image is `gcr.io/distroless/static-debian12:nonroot`-based — about 16 MB, no shell or package manager. Every flag is also reachable as `LOCALFRONT_*`, so the binary can be fully configured by environment:\n\n```console\n$ docker run --rm -p 8080:8080 \\\n    -v $PWD/template.yaml:/etc/localfront/template.yaml:ro \\\n    -e LOCALFRONT_TEMPLATE=/etc/localfront/template.yaml \\\n    -e LOCALFRONT_PUBLIC_HOST=assets.example.test:8080 \\\n    -e LOCALFRONT_S3_ENDPOINT=http://host.docker.internal:9000 \\\n    -e LOCALFRONT_S3_ACCESS_KEY=rustfsadmin \\\n    -e LOCALFRONT_S3_SECRET_KEY=rustfsadmin \\\n    --tmpfs /tmp \\\n    ghcr.io/mackee/localfront:latest\n```\n\n`/tmp` must be writable: localfront sandboxes each CloudFront Function in its own tempdir under `os.TempDir()`. The image declares `VOLUME /tmp`, so `--tmpfs /tmp` (or a named volume) keeps the rest of the root filesystem read-only-able.\n\nCompose example:\n\n```yaml\nservices:\n  localfront:\n    image: ghcr.io/mackee/localfront:latest\n    ports: [\"8080:8080\"]\n    environment:\n      LOCALFRONT_TEMPLATE: /etc/localfront/template.yaml\n      LOCALFRONT_PUBLIC_HOST: assets.example.test:8080\n      LOCALFRONT_S3_ENDPOINT: http://rustfs:9000\n      LOCALFRONT_S3_ACCESS_KEY: rustfsadmin\n      LOCALFRONT_S3_SECRET_KEY: rustfsadmin\n    volumes:\n      - ./template.yaml:/etc/localfront/template.yaml:ro\n      - ./seeds:/etc/localfront/seeds:ro\n    tmpfs:\n      - /tmp\n    depends_on: [rustfs]\n  rustfs:\n    image: rustfs/rustfs\n    ports: [\"9000:9000\"]\n```\n\nFor repeatable flags via environment, separate entries with commas: `LOCALFRONT_TEMPLATE=/etc/localfront/a.yaml,/etc/localfront/b.yaml`, `LOCALFRONT_KVS_SEED=storeA=/etc/localfront/seeds/a.json,storeB=/etc/localfront/seeds/b.json`.\n\n### Example template\n\n```yaml\nResources:\n  AssetsDistribution:\n    Type: AWS::CloudFront::Distribution\n    Properties:\n      DistributionConfig:\n        Enabled: true\n        DefaultRootObject: index.html\n        Aliases:\n          - assets.example.test\n        Origins:\n          - Id: s3\n            DomainName: assets.s3.us-east-1.amazonaws.com # served by the external store\n            S3OriginConfig: {}\n        DefaultCacheBehavior:\n          TargetOriginId: s3\n          ViewerProtocolPolicy: allow-all\n          CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6 # Managed-CachingOptimized\n        CustomErrorResponses:\n          - ErrorCode: 404\n            ResponseCode: 200\n            ResponsePagePath: /index.html\n```\n\n### Upload assets and send a request through the distribution\n\nUploads go straight to the object store; only viewer requests go through localfront:\n\n```console\n$ export AWS_ACCESS_KEY_ID=rustfsadmin AWS_SECRET_ACCESS_KEY=rustfsadmin\n$ aws --endpoint-url http://localhost:9000 s3 mb s3://assets\n$ aws --endpoint-url http://localhost:9000 s3 sync ./dist s3://assets\n$ curl -H 'Host: assets.example.test' http://localhost:8080/\n```\n\n### Coming from CDK or Terraform\n\n- **CDK** — synthesize and point localfront at the output: `cdk synth \u0026\u0026 localfront serve --template cdk.out/MyStack.template.json`. Non-CloudFront resources in the template are skipped with a warning.\n- **Terraform** — use the planned companion converter (separate project) to turn a plan into a template, then serve it. Until it exists, write a small template by hand mirroring your `aws_cloudfront_distribution`.\n\n## Compatibility notes\n\n- Template properties follow the current `AWS::CloudFront::*` resource specifications, so production templates load unchanged.\n- Distribution IDs are generated deterministically from template logical IDs and are stable across restarts.\n- S3 origin fetches are signed with SigV4 using the credentials given at startup.\n\n## Roadmap (post-PoC)\n\n- **Terraform plan → CloudFormation companion converter** (separate repository)\n- Optional cache emulation: in-memory cache honoring cache policies and TTLs, `X-Cache: Hit`, and a purge command\n- `localfront test-function` — run a CloudFront Function against a synthetic event from the CLI\n- Origin group failover\n- TLS termination with locally trusted certificates\n- Real-time logs (Kinesis-shaped variant of CloudFront's log delivery; Standard logs are already supported via `--access-log`)\n- Geo restriction enforcement and viewer profile presets (country / device simulation)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmackee%2Flocalfront","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmackee%2Flocalfront","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmackee%2Flocalfront/lists"}