@@ -95,23 +95,15 @@ var ErrPluginNotVerified = errors.New("plugin not verified")
95
95
96
96
// Install fetches the release from GitHub and puts the binary in the plugin directory.
97
97
// This installation process will automatically check the checksum of the downloaded zip file.
98
- // Therefore, the release must always contain a checksum file.
99
- // In addition, the release must meet the following conventions:
98
+ // The release must always contain a checksum file and meet the following conventions:
100
99
//
101
100
// - The release must be tagged with a name like v1.1.1
102
101
// - The release must contain an asset with a name like tflint-ruleset-{name}_{GOOS}_{GOARCH}.zip
103
102
// - The zip file must contain a binary named tflint-ruleset-{name} (tflint-ruleset-{name}.exe in Windows)
104
103
// - The release must contain a checksum file for the zip file with the name checksums.txt
105
104
// - The checksum file must contain a sha256 hash and filename
106
105
//
107
- // If Artifact Attestations are present, TFLint will verify the checksum file
108
- // to ensure that it has not been tampered with.
109
- //
110
- // If the following conditions are met, the checksum file will be verified
111
- // as being signed with the PGP key.
112
- //
113
- // - The release must contain a signature file for the checksum file with the name checksums.txt.sig
114
- // - The signature file must be binary OpenPGP format
106
+ // If possible, verify the signature to ensure that the checksum file has not been tampered with.
115
107
func (c * InstallConfig ) Install () (string , error ) {
116
108
dir , err := getPluginDir (c .globalConfig )
117
109
if err != nil {
@@ -138,62 +130,17 @@ func (c *InstallConfig) Install() (string, error) {
138
130
return "" , fmt .Errorf ("Failed to download checksums.txt: %s" , err )
139
131
}
140
132
141
- var skipVerify bool
133
+ var verified bool
142
134
sigchecker := NewSignatureChecker (c )
143
135
if sigchecker .HasSigningKey () {
144
- // Verify by PGP signing key
145
- log .Printf ("[DEBUG] Download checksums.txt.sig" )
146
- signatureFile , err := c .downloadToTempFile (assets ["checksums.txt.sig" ])
147
- if signatureFile != nil {
148
- defer os .Remove (signatureFile .Name ())
136
+ if err := c .verifyChecksumsSignature (sigchecker , checksumsFile , assets ); err != nil {
137
+ return "" , err
149
138
}
150
- if err != nil {
151
- return "" , fmt .Errorf ("Failed to download checksums.txt.sig: %s" , err )
152
- }
153
-
154
- if err := sigchecker .Verify (checksumsFile , signatureFile ); err != nil {
155
- return "" , fmt .Errorf ("Failed to check checksums.txt signature: %s" , err )
156
- }
157
- if _ , err := checksumsFile .Seek (0 , 0 ); err != nil {
158
- return "" , fmt .Errorf ("Failed to check checksums.txt signature: %s" , err )
159
- }
160
- log .Printf ("[DEBUG] Verified signature successfully" )
161
-
139
+ verified = true
162
140
} else {
163
- // Attempt to verify by artifact attestations.
164
- var attestations []* github.Attestation
165
- repo , err := c .fetchRepository ()
141
+ verified , err = c .tryKeylessVerifyChecksumsSignature (sigchecker , checksumsFile )
166
142
if err != nil {
167
- return "" , fmt .Errorf ("Failed to get GitHub repository metadata: %s" , err )
168
- }
169
- // If the repository is private, artifact attestations is not always available
170
- // because it requires GitHub Enterprise Cloud plan, so we skip verification here.
171
- if repo .Private != nil && * repo .Private {
172
- skipVerify = true
173
- } else {
174
- log .Printf ("[DEBUG] Download artifact attestations" )
175
- attestations , err = c .fetchArtifactAttestations (checksumsFile )
176
- if err != nil {
177
- var gerr * github.ErrorResponse
178
- // If there are no attestations, it will be ignored without errors.
179
- // However, experimental mode is enabled, enforces that attestations are present.
180
- if errors .As (err , & gerr ) && gerr .Response .StatusCode == 404 && ! IsExperimentalModeEnabled () {
181
- log .Printf ("[DEBUG] Artifact attestations not found and will be ignored: %s" , err )
182
- skipVerify = true
183
- } else {
184
- return "" , fmt .Errorf ("Failed to download artifact attestations: %s" , err )
185
- }
186
- }
187
- }
188
-
189
- if ! skipVerify {
190
- if err := sigchecker .VerifyKeyless (checksumsFile , attestations ); err != nil {
191
- return "" , fmt .Errorf ("Failed to check checksums.txt signature: %s" , err )
192
- }
193
- if _ , err := checksumsFile .Seek (0 , 0 ); err != nil {
194
- return "" , fmt .Errorf ("Failed to check checksums.txt signature: %s" , err )
195
- }
196
- log .Printf ("[DEBUG] Verified signature successfully" )
143
+ return "" , err
197
144
}
198
145
}
199
146
@@ -220,12 +167,73 @@ func (c *InstallConfig) Install() (string, error) {
220
167
}
221
168
222
169
log .Printf ("[DEBUG] Installed %s successfully" , path )
223
- if skipVerify {
170
+ if ! verified {
224
171
return path , ErrPluginNotVerified
225
172
}
226
173
return path , nil
227
174
}
228
175
176
+ // Verify checksums.txt.sig by PGP signing key.
177
+ // The release must contain a signature file for the checksum file with the name checksums.txt.sig.
178
+ // The signature file must be binary OpenPGP format.
179
+ func (c * InstallConfig ) verifyChecksumsSignature (sigchecker * SignatureChecker , checksum io.ReadSeeker , assets map [string ]* github.ReleaseAsset ) error {
180
+ log .Printf ("[DEBUG] Download checksums.txt.sig" )
181
+ signatureFile , err := c .downloadToTempFile (assets ["checksums.txt.sig" ])
182
+ if signatureFile != nil {
183
+ defer os .Remove (signatureFile .Name ())
184
+ }
185
+ if err != nil {
186
+ return fmt .Errorf ("Failed to download checksums.txt.sig: %s" , err )
187
+ }
188
+
189
+ if err := sigchecker .Verify (checksum , signatureFile ); err != nil {
190
+ return fmt .Errorf ("Failed to check checksums.txt signature: %s" , err )
191
+ }
192
+ if _ , err := checksum .Seek (0 , 0 ); err != nil {
193
+ return fmt .Errorf ("Failed to check checksums.txt signature: %s" , err )
194
+ }
195
+
196
+ log .Printf ("[DEBUG] Verified signature successfully" )
197
+ return nil
198
+ }
199
+
200
+ // Verify checksums.txt signature by artifact attestations.
201
+ func (c * InstallConfig ) tryKeylessVerifyChecksumsSignature (sigchecker * SignatureChecker , checksum io.ReadSeeker ) (bool , error ) {
202
+ repo , err := c .fetchRepository ()
203
+ if err != nil {
204
+ return false , fmt .Errorf ("Failed to get GitHub repository metadata: %s" , err )
205
+ }
206
+ // If the repository is private, artifact attestations is not always available
207
+ // because it requires GitHub Enterprise Cloud plan, so we skip verification here.
208
+ if repo .Private != nil && * repo .Private {
209
+ return false , nil
210
+ }
211
+
212
+ log .Printf ("[DEBUG] Download artifact attestations" )
213
+ attestations , err := c .fetchArtifactAttestations (checksum )
214
+ if err != nil {
215
+ var gerr * github.ErrorResponse
216
+ // If there are no attestations, it will be ignored without errors.
217
+ // However, experimental mode is enabled, enforces that attestations are present.
218
+ if errors .As (err , & gerr ) && gerr .Response .StatusCode == 404 && ! IsExperimentalModeEnabled () {
219
+ log .Printf ("[DEBUG] Artifact attestations not found and will be ignored: %s" , err )
220
+ return false , nil
221
+ } else {
222
+ return false , fmt .Errorf ("Failed to download artifact attestations: %s" , err )
223
+ }
224
+ }
225
+
226
+ if err := sigchecker .VerifyKeyless (checksum , attestations ); err != nil {
227
+ return false , fmt .Errorf ("Failed to check checksums.txt signature: %s" , err )
228
+ }
229
+ if _ , err := checksum .Seek (0 , 0 ); err != nil {
230
+ return false , fmt .Errorf ("Failed to check checksums.txt signature: %s" , err )
231
+ }
232
+
233
+ log .Printf ("[DEBUG] Verified signature successfully" )
234
+ return true , nil
235
+ }
236
+
229
237
// fetchReleaseAssets fetches assets from the GitHub release.
230
238
// The release is determined by the source path and tag name.
231
239
func (c * InstallConfig ) fetchReleaseAssets () (map [string ]* github.ReleaseAsset , error ) {
0 commit comments