The fremforge Terraform and OpenTofu provider
The fremforge provider manages your org’s fremforge settings from OpenTofu or HashiCorp Terraform: authentication and access policy, the IP allowlist, scan and scanner policy, immutable releases, image and package signature rules, the dependency proxy, package access and registry robots.
Repositories, teams and branch protection rules are Forgejo objects and stay with the community svalabs/forgejo provider, as described in Configuration as code. The two providers work side by side in one configuration.
Install
The provider is published on frem.sh itself, so both CLIs install it with no extra configuration:
terraform {
required_providers {
fremforge = {
source = "frem.sh/fremforge/fremforge"
version = "~> 0.1"
}
}
}tofu init (or terraform init) finds the registry at https://frem.sh/.well-known/terraform.json, downloads the zip for your platform from cli.frem.sh and checks it against SHA256SUMS, whose OpenPGP signature it verifies with the fremforge release key. Builds exist for Linux and macOS (amd64, arm64) and Windows (amd64).
The release key:
| Fingerprint | AEA6 FE24 2C86 33F0 10C6 D4ED 9099 FD4F C1DC 23C4 |
| Key ID | 9099FD4FC1DC23C4 |
| Public certificate | https://frem.sh/_app/terraform/signing-key.asc |
The private key never leaves the fremforge platform: each release is signed by the platform for the provider’s release build only.
Configure
provider "fremforge" {
# endpoint = "https://frem.sh/_app/api/v1" # the default
}The provider reads its token from FREMFORGE_TOKEN. Use an org API token of the org you manage, minted under Org admin → API tokens (frem.sh/<org>/_admin/api-tokens), with the scopes your resources need. Keep a read-only token for plan and the write scope for the job that runs apply:
| Resources | Scope |
|---|---|
| Every resource on this page | policy:write |
| Data sources only | policy:read |
fremforge_push_protection (read) | findings:read |
Resources
Org settings are singletons: every org always has them and the API has no delete. Every argument is optional. Only the arguments you set are written; the others are read back into state and left alone, so a change someone makes in the console to a setting you do not manage is not drift. Import a singleton by its org slug.
| Resource | What it manages |
|---|---|
fremforge_auth_policy | Authentication policy: PAT lifetime cap (0 = no cap), PAT scopes, signed commits, keyless signers, SSH on or off. |
fremforge_ip_allowlist | The org IP allowlist. An enforcing list that does not contain the address the CLI runs from is refused by the API; roll a list out with audit_only = true first. |
fremforge_operator_access | Whether fremforge operators may manage the org’s users. |
fremforge_email_domain_policy | Whether the email-domain allowlist is enforced. |
fremforge_ssh_ca | The SSH certificate authority. require_cert = true needs a CA key in the same configuration. |
fremforge_actions_policy | Actions only on protected branches. |
fremforge_artifact_retention | Actions artifact retention in days. |
fremforge_sla_policy | Remediation windows per severity (0 = none). |
fremforge_push_protection | What a push does when the secret scanner cannot run. |
fremforge_immutability | Immutable releases and container tags. The plan warns when releases turns on: it cannot be undone for the releases published while it is on. |
fremforge_branch_protection_defaults | The defaults fremforge applies to new repositories (not a Forgejo rule). |
fremforge_sast_settings, fremforge_dependency_scan_settings, fremforge_image_scan_settings, fremforge_secret_scan_settings, fremforge_license_settings | Scanner settings, one resource per scanner. |
fremforge_image_signature_policy | Which image signatures are trusted at pull, and whether an unsigned pull is refused. Roll out with mode = "shadow". |
fremforge_dependency_proxy | The org’s dependency proxy settings. |
fremforge_package_signature_policy | The signature rule of the package policy (requirement and trusted signers). |
fremforge_package_access_default | The org’s default package access mode. |
Every singleton is also a data source of the same name.
Collections have a real create and delete:
| Resource | Import ID |
|---|---|
fremforge_email_domain | <org>/<pattern> or <org>/<id> |
fremforge_scan_policy (one per scanner; iac_scan from 0.4.0) | <org>/<scanner> |
fremforge_package_access | <org>/<type>/<name> |
fremforge_package_access_team_grant | <org>/<type>/<team>/<name> |
fremforge_package_access_actions_grant | <org>/<type>/<repository>/<name> |
fremforge_package_protection_rule | <org>/<rule id> |
fremforge_registry_robot | <org>/<robot id> |
fremforge_secret_custom_pattern (from 0.3.0; repository from 0.7.0) | <org>/<pattern id> |
fremforge_iac_custom_check (from 0.4.0) | <org>/<check id> |
A package name may contain / (an npm scope, a container path), which is why it comes last in the import IDs.
Example
resource "fremforge_auth_policy" "this" {
org = "acme"
max_pat_lifetime_days = 30
allow_user_pats = false
ssh_disabled = true
}
resource "fremforge_ssh_ca" "this" {
org = "acme"
ca_keys = [file("ssh-ca.pub")]
require_cert = true
}
resource "fremforge_scan_policy" "images" {
org = "acme"
scanner = "image_scan"
mode = "enforce"
block_severity = "high"
}Removed settings
Two settings were removed from fremforge on 2026-10-14, because they were saved but never applied: principal_policy on fremforge_ssh_ca and default_template_repo on fremforge_branch_protection_defaults. Since provider 0.1.1 both attributes are deprecated: the provider no longer sends them or reads them, so a configuration that still sets them keeps working and plans clean, and tofu plan prints a deprecation warning. Remove them from your configuration; nothing changes when you do. See Branch protection and SSH certificate authority.
fremforge_ssh_ca block whose only argument is principal_policy fails with HTTP 400 (no-fields), and a non-empty default_template_repo is refused with removed-setting. Upgrade with tofu init -upgrade (or terraform init -upgrade); version = "~> 0.1" picks up 0.1.1.Registry robot tokens never go to state
A registry robot’s token is shown by the API once, when it is issued. The provider never stores it in state, in any attribute. It writes the token once, when the API issues it (at create, and whenever you change rotation), to the file you name in token_file, with mode 0600, and never overwrites an existing file:
resource "fremforge_registry_robot" "ci" {
org = "acme"
name = "ci-pull"
access = "read"
token_file = "${path.module}/.robot-ci.token"
rotation = "2026-10" # change to issue a new token; the old one is revoked at once
}Without token_file the token is discarded. Move the file away before you rotate again.
Drift, import and destroy
- Drift:
plan -detailed-exitcodeexits 2 when a setting you manage was changed outside your configuration;applyputs it back. - Import:
import { to = fremforge_auth_policy.this; id = "acme" }, thentofu plan -generate-config-out=generated.tfwrites the configuration for you. - Destroy: for a singleton, destroy only removes it from state and changes nothing in the org: writing defaults would silently weaken a security setting. Collections are deleted.
fremforge_package_accessremoves the package’s settings and every grant on it.
Moving from the module
The module stays documented while you move. For each setting, add the provider resource, import it by org slug, check that plan shows no changes, then remove the module’s argument and tofu state rm its restful_resource.
How we test it
Every resource and data source runs through the real OpenTofu CLI against a recorded fremforge API on every change: only the arguments you set are written, unmanaged changes are ignored, managed drift is planned, import reads back the same state, and destroy behaves as described above.
The settings that are safe to change on a shared org also run live against a test org, internal-fremforge-iac-test, every night: apply, a clean plan, a change made through the API, a plan that shows it, apply, import into fresh state, a clean plan, destroy, a read back, and the original value restored. Those are the authentication policy, the SSH CA, the SLA policy and the dependency proxy’s metadata filter; package access, protection rules and registry robots were run live the same way before the release. The IP allowlist and the signature policies are tested only against the recorded API, because changing them on a shared org would disturb other tests.