Lfx object store ops
Skill linuxfoundation/lfx-skills/skills/lfx-object-store-ops
Provisioning requirements for LFX object storage backends in deployed environments (prod, staging, shared dev): private S3 buckets, CloudFront with Origin Access Control, shared wildcard certificate, and IRSA write access, all in `lfx-v2-opentofu`. Written for the linuxfoundation GitHub org's ops audience — see the `-ops` skill convention in `/lfx-skills:lfx`. Fires on prompts like "provision a bucket", "object storage backend", "CloudFront for uploads", "S3 bucket opentofu", "IRSA for S3", "object store CDN", "bucket policy", "wildcard cert uploads". Requirements only; the implementing agent owns the HCL.From its SKILL.md
npx -y skills add linuxfoundation/lfx-skills --skill lfx-object-store-opsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 10 stars10 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
9.2 KB, as published. Nobody here has run it
LFX Object Store Ops
How the object storage backend is provisioned for deployed environments.
This is a requirements stub, deliberately non-prescriptive: it captures the
constraints the Ops team has agreed to, points at the existing conventions
in lfx-v2-opentofu, and leaves the HCL to the implementing agent.
Audience: linuxfoundation GitHub org members. This skill is written for
that ops audience and assumes org membership; it does not itself enforce
it (see the -ops skill convention in /lfx-skills:lfx for how the router
handles routing when membership is unconfirmed). Application-side design
(endpoints, SDK usage, chart contract, local dev) is
/lfx-skills:lfx-object-store-design.
Where
All resources live in
lfx-v2-opentofu:
- Environments are OpenTofu workspaces (
dev,staging,prod), not directories. Resource names interpolate${terraform.workspace}(sees3.tffor existing examples). - Workflow via the repo
Makefile(make init/plan/apply); read the repoREADME.mdbefore planning.
Pattern
Data-driven flat for_each, matching the postgres convention
(postgres-database-definitions.yaml + postgres.tf). No new module —
the Ops team prefers flat loops over YAML definitions; modules are reserved
for cases that would otherwise require nested loops (see
modules/eks-service-account-role/).
- New
object-store-definitions.yamlholding one entry per service bucket, including a boolean flag marking whether the bucket is public (CDN-fronted) — the design skill requires public and private files in separate buckets, so this is a per-bucket property, never mixed. - New
object-storage.tfwith onefor_eachper resource type. Bucket resources loop over every entry:aws_s3_bucket(+ versioning, SSE, lifecycle, public-access-block). CDN resources —aws_cloudfront_origin_access_control,aws_cloudfront_distribution,aws_s3_bucket_policy(OAC read policy), and the DNS alias records — loop over a filtered{ for k, v in local.object_stores : k => v if v.public }so a distribution is only ever attached to explicitly public buckets. Attaching CloudFront/OAC to a service-API-only bucket would make its private objects anonymously retrievable. Still flat 1:1 loops, no nesting.
Requirements
Buckets
- Private only. Public access block enabled; no public-read bucket
policies. The existing
lfx-one-project-logos-png-*buckets ins3.tfare a legacy anti-pattern — do not copy them. - Versioning and SSE enabled; names interpolate
${terraform.workspace}. Bucket versioning here is a data-protection feature (recovery from accidental delete/overwrite) — it is unrelated to the design skill's cache-busting query parameter (/lfx-skills:lfx-object-store-design, "Download flow"), which is an app-generated hint inpublic_url, not the S3VersionIdthis setting produces. Do not conflate the two: enabling bucket versioning does not, by itself, invalidate CDN caches or change what the cache-busting parameter needs to look like. - Lifecycle rules are mandatory alongside versioning. With versioning
on,
DeleteObjectonly adds a delete marker and every overwrite retains the prior payload as a noncurrent version — without lifecycle expiration, storage grows unboundedly and user-"deleted" content is retained indefinitely (a data-retention problem, not just a cost one). Each bucket gets anaws_s3_bucket_lifecycle_configuration(1:1, same flat loop) that: expires noncurrent versions after 30 days, removes expired object delete markers, and aborts incomplete multipart uploads after 7 days.
CloudFront
- Origin Access Control (OAC) with a
SourceArn-conditioned bucket policy. Not the legacy OAI (aws_cloudfront_origin_access_identity) orforwarded_valuespatterns — the ITXcloudfront-distribution-with-s3module is a structural reference only and must not be copied (deprecated OAI,forwarded_values, TLSv1). - Managed cache and origin-request policies that honor the per-object
Cache-Controlheader. This header is object metadata set by the service at upload (PutObject'sCacheControlfield), not an origin or bucket configuration — the design skill's baseline is a short TTL,public, max-age=86400, since the cache-busting parameter is a hint rather than an immutable version. The design skill's cache-busting query parameter (see/lfx-skills:lfx-object-store-design) must be included in the cache key — a managed policy that strips query strings (such as the defaultCachingOptimizedpolicy) will keep serving a stale object after the version parameter changes. Use a custom cache policy that forwards that parameter if the default managed policies exclude it. In that cache policy, setmin_ttl = 0— Minimum TTL is the only setting that overrides a shorter per-objectmax-ageupward, and any nonzero value would defeat the service's short-TTL intent for persisted copies of the URL.default_ttlis different: it applies only when an object carries noCache-Controlmetadata at all, never overriding a present header — keep it short (≤ 1 day) as a safety net for objects uploaded without the metadata. - One standardized
default_cache_behavior(GET/HEAD); no per-pathordered_cache_behaviorblocks — this keeps dynamic blocks out of the loop.
Certificates
- One shared wildcard certificate; distribution aliases are shaped
{bucket}.{vanity-domain}(vanity domain TBD, e.g.*.lfxuseruploads.comor*.downloads.lfx.community). This keeps ACM issuance and DNS validation — the one real nested-loop hazard — out of the per-bucket loop. - The certificate must be in
us-east-1(CloudFront requirement).
DNS
- The alias and certificate alone do not publish the hostname: each
{bucket}.{vanity-domain}name needs Route 53 alias A and AAAA records pointing at its CloudFront distribution, in the vanity domain's hosted zone. Enable IPv6 on the distribution (is_ipv6_enabled = true— it defaults to false) so the AAAA record is actually served. This is a per-public-bucket 1:1 resource — include it in the same filtered public-bucket loop as the distribution. Without it theCDN_URL_PREFIXhanded off below will not resolve.
Write access (IRSA)
- Service access via the existing IRSA mechanism: an entry in
iam-service-account-definitions.yamlwith an inline policy scoped to the service's bucket ARN(s), reconciled throughmodules/eks-service-account-role/. Seelfx-v2-opentofu/docs/service-accounts.md. - The policy needs bucket-level
s3:ListBucket(used by the service'sHeadBucketreadiness check and, if applicable, any internal listing — see/lfx-skills:lfx-object-store-design) plus object-levels3:GetObject,s3:PutObject, ands3:DeleteObjecton{bucket-arn}/*. Do not grants3:CreateBucketto a deployed role — the bucket is provisioned by this ops flow, not by the service at startup. - Bucket definition entries do not own IAM; keep the concerns separate.
Prohibitions
- No public-read buckets or bucket ACLs.
- No static credentials in deployed environments (IRSA only).
- No Cloudflare R2 or other non-AWS deployments. "S3-compatible" in the design skill is app-side portability wording only.
Handoffs
After provisioning, hand these to lfx-v2-argocd environment values:
- The IRSA role ARN.
lfx-v2-argocdcreates the ServiceAccount itself in its deployment manifests, carrying theeks.amazonaws.com/role-arnannotation; the service chart is configured withserviceAccount.create: falseandserviceAccount.nameto reference that externally created account (the chart does not render or annotate the SA in this mode). - The bucket name(s) (
S3_BUCKET, or the purpose-prefixed variants from the design skill's multi-bucket namespacing) — one per bucket — plus the region (AWS_REGION) once: it is process-wide, and all of a service's buckets are provisioned in the environment's single region. - The CDN hostname (
CDN_URL_PREFIX, e.g.https://{bucket}.{vanity-domain}) — one per CDN-fronted bucket.
Handoff boundary
The implementing agent in lfx-v2-opentofu owns the HCL, plan review, and
apply workflow under that repo's local conventions. Application-side chart
and code changes belong to the owning service repo per
/lfx-skills:lfx-object-store-design.