Description
Introduction
TFLint has a mechanism to install plugins, so you can easily extend rules by third-party plugins. Configure the plugins as follows and install them with tflint --init
:
plugin "terraform" {
enabled = true
version = "0.2.2"
source = "github.com/terraform-linters/tflint-ruleset-terraform"
}
Version constraints are not available and only a single version can be set. Although this seems to prevent the installation of unintended versions, it is not quite complete. Repojacking can allow an attacker to trick a user into installing a malicious plugin. The environment running TFLint is likely to contain sensitive info such as access tokens, so preventing such attacks is important.
Verifying a signature mitigates the impacts of this problem. The installation fails because the attacker does not know the plugin developer's signing key. Fortunately, plugins under the "terraform-linters" organization have their signatures automatically verified, so you're less likely to run into this problem.
However, things change when we adopt keyless signing. Keyless signing verifies the ID token issued by GitHub Actions instead of the plugin developer's private key. In other words, if the attacker succeeds in repojacking, they can issue valid ID tokens.
Proposal
To mitigate this problem, we introduce dependency lockfile just like Terraform.
https://developer.hashicorp.com/terraform/language/files/dependency-lock
Running tflint --init
will create a .tflint.lock.hcl
file like this:
plugin "github.com/terraform-linters/tflint-ruleset-terraform" {
version = "0.2.2"
hashes = [
"h1:FJwsuowaG5CIdZ0WQyFZH9r6kIJeRKts9+GcRsTz1+Y=",
"h1:c/ntSXrDYM1mUir2KufijYebPcwKqS9CRGd3duDSGfY=",
"h1:yre4Ph76g9H84MbuhZ2z5MuldjSA4FsrX6538O7PCcY=",
]
}
Hashes are the sha256 of the plugin binary on all platforms. When the lockfile entry exists, installed plugins are tested that their hash matches the hashes
in the lockfile. If they don't match, the installation will fail, preventing you from installing anything other than the first trusted plugin. If the entry does not exist in the lockfile, TFLint appends an entry with hashes to the lockfile.
These behaviors are almost the same as Terraform, so implement them with reference to the following documentation.
https://developer.hashicorp.com/terraform/language/files/dependency-lock#dependency-installation-behavior
https://developer.hashicorp.com/terraform/language/files/dependency-lock#understanding-lock-file-changes
A notable difference is that --upgrade
flag is not required. tflint --init
can always update the lockfile because TFLint does not support version constraints.
There are some considerations for implementation:
- File name and location
- The name and location of the lockfile are not obvious in that case, as you can specify a different file with the
--config
flag. Perhaps it's worth looking at the behavior of similar tools. For example, Bundler can specify any path with the--gemfile
flag.
- The name and location of the lockfile are not obvious in that case, as you can specify a different file with the
- Type of hash to record
- In the example above, only the
h1:
hash is recorded, but there is room for discussion on whether other hashes should be recorded. For example, Terraform recordszh:
hashes as well. - Terraform supports both
zh:
andh1:
because it neededh1:
to introduce Local Filesystem Mirrors. See also Feature request: Generate .terraform.lock.hcl including zh and h1 hash values for given platforms from required_providers block without downloading providers and modules hashicorp/terraform#27264 (comment) for details. - Given the discussion above,
zh:
might be sufficient for TFLint. On the other hand,h1:
is better in terms of flexibility. If we recordh1:
hashes, will needchecksums.txt
withh1:
hashes available for all platforms (currently onlyzh:
hashes are included).
- In the example above, only the
- Compatibility with automated update workflows
- If using an automated workflow such as Renovate, the lockfile should contain hashes for all platforms, as
tflint --init
may run on different platforms. The key is to avoid unnecessary lockfile diffs in any case. Terraform only records theh1:
hash of the platform it runs on, which should be avoided. - We may need to provide a mechanism to easily update the lockfile from Renovate.
- If using an automated workflow such as Renovate, the lockfile should contain hashes for all platforms, as
- Application to
plugin.SecureConfig
- SecureConfig allows go-plugin to verify the checksum of the binary before executing the plugin. The
hashes
in the lockfile may be available for this checksum. But in that case, we want theh1:
hash instead of thezh:
. See also Configure go-plugin'sSecureConfig
tflint-plugin-sdk#192.
- SecureConfig allows go-plugin to verify the checksum of the binary before executing the plugin. The
References
- Allow plugin version to be specified as SHA-1 hash #1486
- https://checkmarx.com/blog/attacking-the-software-supply-chain-with-a-simple-rename/
- https://developer.hashicorp.com/terraform/language/files/dependency-lock#lock-file-location
- https://github.com/hashicorp/terraform/tree/v1.3.6/internal/depsfile
- https://github.com/hashicorp/terraform/blob/v1.3.6/internal/getproviders/hash.go
- Feature request: Generate .terraform.lock.hcl including zh and h1 hash values for given platforms from required_providers block without downloading providers and modules hashicorp/terraform#27264 (comment)
- Configure go-plugin's
SecureConfig
tflint-plugin-sdk#192