Skip to main content

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:

FingerprintAEA6 FE24 2C86 33F0 10C6 D4ED 9099 FD4F C1DC 23C4
Key ID9099FD4FC1DC23C4
Public certificatehttps://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:

ResourcesScope
Every resource on this pagepolicy:write
Data sources onlypolicy: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.

ResourceWhat it manages
fremforge_auth_policyAuthentication policy: PAT lifetime cap (0 = no cap), PAT scopes, signed commits, keyless signers, SSH on or off.
fremforge_ip_allowlistThe 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_accessWhether fremforge operators may manage the org’s users.
fremforge_email_domain_policyWhether the email-domain allowlist is enforced.
fremforge_ssh_caThe SSH certificate authority. require_cert = true needs a CA key in the same configuration.
fremforge_actions_policyActions only on protected branches.
fremforge_artifact_retentionActions artifact retention in days.
fremforge_sla_policyRemediation windows per severity (0 = none).
fremforge_push_protectionWhat a push does when the secret scanner cannot run.
fremforge_immutabilityImmutable 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_defaultsThe 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_settingsScanner settings, one resource per scanner.
fremforge_image_signature_policyWhich image signatures are trusted at pull, and whether an unsigned pull is refused. Roll out with mode = "shadow".
fremforge_dependency_proxyThe org’s dependency proxy settings.
fremforge_package_signature_policyThe signature rule of the package policy (requirement and trusted signers).
fremforge_package_access_defaultThe org’s default package access mode.

Every singleton is also a data source of the same name.

Collections have a real create and delete:

ResourceImport 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.

With provider 0.1.0, a 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-exitcode exits 2 when a setting you manage was changed outside your configuration; apply puts it back.
  • Import: import { to = fremforge_auth_policy.this; id = "acme" }, then tofu plan -generate-config-out=generated.tf writes 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_access removes 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.