Skip to content

Commit cf3fc68

Browse files
committed
Modify language for reporting signing state
Be more explicit about the signing status of fetched plugins and provide documentation about the different signing options.
1 parent df39e0a commit cf3fc68

File tree

8 files changed

+119
-51
lines changed

8 files changed

+119
-51
lines changed

command/init.go

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -566,15 +566,29 @@ func (c *InitCommand) getProviders(earlyConfig *earlyconfig.Config, state *state
566566
}
567567
},
568568
FetchPackageSuccess: func(provider addrs.Provider, version getproviders.Version, localDir string, authResult *getproviders.PackageAuthenticationResult) {
569-
var warning string
570-
if authResult != nil {
571-
warning = authResult.Warning
569+
var keyID string
570+
if authResult != nil && authResult.ThirdPartySigned() {
571+
keyID = authResult.KeyID
572572
}
573-
if warning != "" {
574-
warning = c.Colorize().Color(fmt.Sprintf("\n [reset][yellow]Warning: %s[reset]", warning))
573+
if keyID != "" {
574+
keyID = c.Colorize().Color(fmt.Sprintf(", key ID [reset][bold]%s[reset]", keyID))
575575
}
576576

577-
c.Ui.Info(fmt.Sprintf("- Installed %s v%s (%s)%s", provider.ForDisplay(), version, authResult, warning))
577+
c.Ui.Info(fmt.Sprintf("- Installed %s v%s (%s%s)", provider.ForDisplay(), version, authResult, keyID))
578+
},
579+
ProvidersFetched: func(authResults map[addrs.Provider]*getproviders.PackageAuthenticationResult) {
580+
thirdPartySigned := false
581+
for _, authResult := range authResults {
582+
if authResult.ThirdPartySigned() {
583+
thirdPartySigned = true
584+
break
585+
}
586+
}
587+
if thirdPartySigned {
588+
c.Ui.Info(fmt.Sprintf("\nPartner and community providers are signed by their developers." +
589+
"\nIf you'd like to know more about provider signing, you can read about it here: " +
590+
"https://www.terraform.io/docs/signing.html"))
591+
}
578592
},
579593
}
580594

internal/getproviders/package_authentication.go

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@ const (
2626

2727
// PackageAuthenticationResult is returned from a PackageAuthentication
2828
// implementation. It is a mostly-opaque type intended for use in UI, which
29-
// implements Stringer and includes an optional Warning field.
29+
// implements Stringer.
3030
//
3131
// A failed PackageAuthentication attempt will return an "unauthenticated"
3232
// result, which is represented by nil.
3333
type PackageAuthenticationResult struct {
34-
result packageAuthenticationResult
35-
Warning string
34+
result packageAuthenticationResult
35+
KeyID string
3636
}
3737

3838
func (t *PackageAuthenticationResult) String() string {
@@ -41,12 +41,25 @@ func (t *PackageAuthenticationResult) String() string {
4141
}
4242
return []string{
4343
"verified checksum",
44-
"official provider",
45-
"partner provider",
46-
"community provider",
44+
"signed by HashiCorp",
45+
"signed by a HashiCorp partner",
46+
"self-signed",
4747
}[t.result]
4848
}
4949

50+
// ThirdPartySigned returns whether the package was authenticated as signed by a party
51+
// other than HashiCorp.
52+
func (t *PackageAuthenticationResult) ThirdPartySigned() bool {
53+
if t == nil {
54+
return false
55+
}
56+
if t.result == partnerProvider || t.result == communityProvider {
57+
return true
58+
}
59+
60+
return false
61+
}
62+
5063
// SigningKey represents a key used to sign packages from a registry, along
5164
// with an optional trust signature from the registry operator. These are
5265
// both in ASCII armored OpenPGP format.
@@ -234,7 +247,7 @@ func NewSignatureAuthentication(document, signature []byte, keys []SigningKey) P
234247
func (s signatureAuthentication) AuthenticatePackage(location PackageLocation) (*PackageAuthenticationResult, error) {
235248
// Find the key that signed the checksum file. This can fail if there is no
236249
// valid signature for any of the provided keys.
237-
signingKey, err := s.findSigningKey()
250+
signingKey, keyID, err := s.findSigningKey()
238251
if err != nil {
239252
return nil, err
240253
}
@@ -247,7 +260,7 @@ func (s signatureAuthentication) AuthenticatePackage(location PackageLocation) (
247260
}
248261
_, err = openpgp.CheckDetachedSignature(hashicorpKeyring, bytes.NewReader(s.Document), bytes.NewReader(s.Signature))
249262
if err == nil {
250-
return &PackageAuthenticationResult{result: officialProvider}, nil
263+
return &PackageAuthenticationResult{result: officialProvider, KeyID: keyID}, nil
251264
}
252265

253266
// If the signing key has a trust signature, attempt to verify it with the
@@ -273,14 +286,12 @@ func (s signatureAuthentication) AuthenticatePackage(location PackageLocation) (
273286
return nil, fmt.Errorf("error verifying trust signature: %s", err)
274287
}
275288

276-
return &PackageAuthenticationResult{result: partnerProvider}, nil
289+
return &PackageAuthenticationResult{result: partnerProvider, KeyID: keyID}, nil
277290
}
278291

279292
// We have a valid signature, but it's not from the HashiCorp key, and it
280293
// also isn't a trusted partner. This is a community provider.
281-
// FIXME: we may want to add a more detailed warning here explaining the
282-
// difference between partner and community providers.
283-
return &PackageAuthenticationResult{result: communityProvider}, nil
294+
return &PackageAuthenticationResult{result: communityProvider, KeyID: keyID}, nil
284295
}
285296

286297
// findSigningKey attempts to verify the signature using each of the keys
@@ -289,11 +300,11 @@ func (s signatureAuthentication) AuthenticatePackage(location PackageLocation) (
289300
//
290301
// Note: currently the registry only returns one key, but this may change in
291302
// the future.
292-
func (s signatureAuthentication) findSigningKey() (*SigningKey, error) {
303+
func (s signatureAuthentication) findSigningKey() (*SigningKey, string, error) {
293304
for _, key := range s.Keys {
294305
keyring, err := openpgp.ReadArmoredKeyRing(strings.NewReader(key.ASCIIArmor))
295306
if err != nil {
296-
return nil, fmt.Errorf("error decoding signing key: %s", err)
307+
return nil, "", fmt.Errorf("error decoding signing key: %s", err)
297308
}
298309

299310
entity, err := openpgp.CheckDetachedSignature(keyring, bytes.NewReader(s.Document), bytes.NewReader(s.Signature))
@@ -306,16 +317,21 @@ func (s signatureAuthentication) findSigningKey() (*SigningKey, error) {
306317

307318
// Any other signature error is terminal.
308319
if err != nil {
309-
return nil, fmt.Errorf("error checking signature: %s", err)
320+
return nil, "", fmt.Errorf("error checking signature: %s", err)
321+
}
322+
323+
keyID := "n/a"
324+
if entity.PrimaryKey != nil {
325+
keyID = entity.PrimaryKey.KeyIdString()
310326
}
311327

312328
log.Printf("[DEBUG] Provider signed by %s", entityString(entity))
313-
return &key, nil
329+
return &key, keyID, nil
314330
}
315331

316332
// If none of the provided keys issued the signature, this package is
317333
// unsigned. This is currently a terminal authentication error.
318-
return nil, fmt.Errorf("authentication signature from unknown issuer")
334+
return nil, "", fmt.Errorf("authentication signature from unknown issuer")
319335
}
320336

321337
// entityString extracts the key ID and identity name(s) from an openpgp.Entity

internal/getproviders/package_authentication_test.go

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,15 @@ func TestPackageAuthenticationResult(t *testing.T) {
2626
},
2727
{
2828
&PackageAuthenticationResult{result: officialProvider},
29-
"official provider",
29+
"signed by HashiCorp",
3030
},
3131
{
3232
&PackageAuthenticationResult{result: partnerProvider},
33-
"partner provider",
33+
"signed by a HashiCorp partner",
3434
},
3535
{
3636
&PackageAuthenticationResult{result: communityProvider},
37-
"community provider",
37+
"self-signed",
3838
},
3939
}
4040
for _, test := range tests {
@@ -270,7 +270,10 @@ func TestSignatureAuthentication_success(t *testing.T) {
270270
ASCIIArmor: HashicorpPublicKey,
271271
},
272272
},
273-
PackageAuthenticationResult{result: officialProvider},
273+
PackageAuthenticationResult{
274+
result: officialProvider,
275+
KeyID: testHashiCorpPublicKeyID,
276+
},
274277
},
275278
"partner provider": {
276279
testAuthorSignatureGoodBase64,
@@ -280,7 +283,10 @@ func TestSignatureAuthentication_success(t *testing.T) {
280283
TrustSignature: testAuthorKeyTrustSignatureArmor,
281284
},
282285
},
283-
PackageAuthenticationResult{result: partnerProvider},
286+
PackageAuthenticationResult{
287+
result: partnerProvider,
288+
KeyID: testAuthorKeyID,
289+
},
284290
},
285291
"community provider": {
286292
testAuthorSignatureGoodBase64,
@@ -289,7 +295,10 @@ func TestSignatureAuthentication_success(t *testing.T) {
289295
ASCIIArmor: testAuthorKeyArmor,
290296
},
291297
},
292-
PackageAuthenticationResult{result: communityProvider},
298+
PackageAuthenticationResult{
299+
result: communityProvider,
300+
KeyID: testAuthorKeyID,
301+
},
293302
},
294303
"multiple signing keys": {
295304
testAuthorSignatureGoodBase64,
@@ -301,7 +310,10 @@ func TestSignatureAuthentication_success(t *testing.T) {
301310
ASCIIArmor: testAuthorKeyArmor,
302311
},
303312
},
304-
PackageAuthenticationResult{result: communityProvider},
313+
PackageAuthenticationResult{
314+
result: communityProvider,
315+
KeyID: testAuthorKeyID,
316+
},
305317
},
306318
}
307319

@@ -408,6 +420,8 @@ func TestSignatureAuthentication_failure(t *testing.T) {
408420
}
409421
}
410422

423+
const testAuthorKeyID = `37A6AB3BCF2C170A`
424+
411425
// testAuthorKeyArmor is test key ID 5BFEEC4317E746008621970637A6AB3BCF2C170A.
412426
const testAuthorKeyArmor = `-----BEGIN PGP PUBLIC KEY BLOCK-----
413427
@@ -500,6 +514,9 @@ const testSignatureBadBase64 = `iQEzBAABCAAdFiEEW/7sQxfnRgCGIZcGN6arO88s` +
500514
`rkTahBtV9yuUUd1D+oRTTTdP0bj3A+3xxXmKTBhRuvurydPTicKuWzeILIJkcwp7Kl5UbI2N` +
501515
`n1ayZdaCIw/r4w==`
502516

517+
// testHashiCorpPublicKeyID is the Key ID of the HashiCorpPublicKey.
518+
const testHashiCorpPublicKeyID = `51852D87348FFC4C`
519+
503520
// testHashicorpSignatureGoodBase64 is a signature of testShaSums signed with
504521
// HashicorpPublicKey, which represents the SHA256SUMS.sig file downloaded for
505522
// an official release.

internal/providercache/installer.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,8 @@ NeedProvider:
249249

250250
// Step 3: For each provider version we've decided we need to install,
251251
// install its package into our target cache (possibly via the global cache).
252-
targetPlatform := i.targetDir.targetPlatform // we inherit this to behave correctly in unit tests
252+
authResults := map[addrs.Provider]*getproviders.PackageAuthenticationResult{} // record auth results for all successfully fetched providers
253+
targetPlatform := i.targetDir.targetPlatform // we inherit this to behave correctly in unit tests
253254
for provider, version := range need {
254255
if i.globalCacheDir != nil {
255256
// Step 3a: If our global cache already has this version available then
@@ -348,12 +349,18 @@ NeedProvider:
348349
continue
349350
}
350351
}
352+
authResults[provider] = authResult
351353
selected[provider] = version
352354
if cb := evts.FetchPackageSuccess; cb != nil {
353355
cb(provider, version, new.PackageDir, authResult)
354356
}
355357
}
356358

359+
// Emit final event for fetching if any were successfully fetched
360+
if cb := evts.ProvidersFetched; cb != nil && len(authResults) > 0 {
361+
cb(authResults)
362+
}
363+
357364
// We'll remember our selections in a lock file inside the target directory,
358365
// so callers can recover those exact selections later by calling
359366
// SelectedPackages on the same installer.

internal/providercache/installer_events.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@ type InstallerEvents struct {
103103
FetchPackageSuccess func(provider addrs.Provider, version getproviders.Version, localDir string, authResult *getproviders.PackageAuthenticationResult)
104104
FetchPackageFailure func(provider addrs.Provider, version getproviders.Version, err error)
105105

106+
// The ProvidersFetched event is called after all fetch operations if at
107+
// least one provider was fetched successfully.
108+
ProvidersFetched func(authResults map[addrs.Provider]*getproviders.PackageAuthenticationResult)
109+
106110
// HashPackageFailure is called if the installer is unable to determine
107111
// the hash of the contents of an installed package after installation.
108112
// In that case, the selection will not be recorded in the target cache
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
layout: "registry"
3+
page_title: "Plugin Signing"
4+
sidebar_current: "docs-plugins-signing"
5+
description: |-
6+
Terraform plugin signing trust levels
7+
---
8+
9+
# Plugin Signing
10+
11+
~> **Note** Currently only provider plugins fetched from a registry are authenticated.
12+
13+
Terraform providers installed from the Registry are cryptographically signed, and the signature is verified at time of installation. There are three types of provider signatures, each with different trust implications:
14+
15+
* **Signed by HashiCorp** - are built, signed, and supported by HashiCorp.
16+
* **Signed by Trusted Partners** - are built, signed, and supported by a third party. HashiCorp has
17+
verified the ownership of the private key and we provide a chain of trust to the CLI to verify this
18+
programatically.
19+
* **Self-signed** - are built, signed, and supported by a third party. HashiCorp does not provide a
20+
verification or chain of trust for the signing. You will want to obtain and validate fingerprints manually
21+
if you want to ensure you are using a binary you can trust.
22+
23+
Terraform does **NOT** support fetching and using unsigned binaries, but you can manually install
24+
unsigned binaries. You should take extreme care when doing so as no programatic authentication is performed.
25+
26+
Usage of plugins from the registry is subject to the Registry's [Terms of Use](https://registry.terraform.io/terms).

website/docs/registry/providers/tiers.html.md

Lines changed: 0 additions & 20 deletions
This file was deleted.

website/layouts/docs.erb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,10 @@
424424
<a href="/docs/plugins/basics.html">Basics</a>
425425
</li>
426426

427+
<li<%= sidebar_current("docs-plugins-signing") %>>
428+
<a href="/docs/plugins/signing.html">Signing</a>
429+
</li>
430+
427431
<li<%= sidebar_current("docs-plugins-provider") %>>
428432
<a href="/docs/plugins/provider.html">Provider</a>
429433
</li>

0 commit comments

Comments
 (0)