Modify language for reporting signing state

Be more explicit about the signing status of fetched plugins and provide documentation about the different signing options.
This commit is contained in:
Paul Tyng 2020-05-12 13:58:12 -04:00
parent df39e0a806
commit 22ef5cc99c
8 changed files with 119 additions and 51 deletions

View File

@ -566,15 +566,29 @@ func (c *InitCommand) getProviders(earlyConfig *earlyconfig.Config, state *state
}
},
FetchPackageSuccess: func(provider addrs.Provider, version getproviders.Version, localDir string, authResult *getproviders.PackageAuthenticationResult) {
var warning string
if authResult != nil {
warning = authResult.Warning
var keyID string
if authResult != nil && authResult.ThirdPartySigned() {
keyID = authResult.KeyID
}
if warning != "" {
warning = c.Colorize().Color(fmt.Sprintf("\n [reset][yellow]Warning: %s[reset]", warning))
if keyID != "" {
keyID = c.Colorize().Color(fmt.Sprintf(", key ID [reset][bold]%s[reset]", keyID))
}
c.Ui.Info(fmt.Sprintf("- Installed %s v%s (%s)%s", provider.ForDisplay(), version, authResult, warning))
c.Ui.Info(fmt.Sprintf("- Installed %s v%s (%s%s)", provider.ForDisplay(), version, authResult, keyID))
},
ProvidersFetched: func(authResults map[addrs.Provider]*getproviders.PackageAuthenticationResult) {
thirdPartySigned := false
for _, authResult := range authResults {
if authResult.ThirdPartySigned() {
thirdPartySigned = true
break
}
}
if thirdPartySigned {
c.Ui.Info(fmt.Sprintf("\nPartner and community providers are signed by their developers.\n" +
"If you'd like to know more about provider signing, you can read about it here:\n" +
"https://www.terraform.io/docs/plugins/signing.html"))
}
},
}

View File

@ -26,13 +26,13 @@ const (
// PackageAuthenticationResult is returned from a PackageAuthentication
// implementation. It is a mostly-opaque type intended for use in UI, which
// implements Stringer and includes an optional Warning field.
// implements Stringer.
//
// A failed PackageAuthentication attempt will return an "unauthenticated"
// result, which is represented by nil.
type PackageAuthenticationResult struct {
result packageAuthenticationResult
Warning string
KeyID string
}
func (t *PackageAuthenticationResult) String() string {
@ -41,12 +41,25 @@ func (t *PackageAuthenticationResult) String() string {
}
return []string{
"verified checksum",
"official provider",
"partner provider",
"community provider",
"signed by HashiCorp",
"signed by a HashiCorp partner",
"self-signed",
}[t.result]
}
// ThirdPartySigned returns whether the package was authenticated as signed by a party
// other than HashiCorp.
func (t *PackageAuthenticationResult) ThirdPartySigned() bool {
if t == nil {
return false
}
if t.result == partnerProvider || t.result == communityProvider {
return true
}
return false
}
// SigningKey represents a key used to sign packages from a registry, along
// with an optional trust signature from the registry operator. These are
// both in ASCII armored OpenPGP format.
@ -234,7 +247,7 @@ func NewSignatureAuthentication(document, signature []byte, keys []SigningKey) P
func (s signatureAuthentication) AuthenticatePackage(location PackageLocation) (*PackageAuthenticationResult, error) {
// Find the key that signed the checksum file. This can fail if there is no
// valid signature for any of the provided keys.
signingKey, err := s.findSigningKey()
signingKey, keyID, err := s.findSigningKey()
if err != nil {
return nil, err
}
@ -247,7 +260,7 @@ func (s signatureAuthentication) AuthenticatePackage(location PackageLocation) (
}
_, err = openpgp.CheckDetachedSignature(hashicorpKeyring, bytes.NewReader(s.Document), bytes.NewReader(s.Signature))
if err == nil {
return &PackageAuthenticationResult{result: officialProvider}, nil
return &PackageAuthenticationResult{result: officialProvider, KeyID: keyID}, nil
}
// If the signing key has a trust signature, attempt to verify it with the
@ -273,14 +286,12 @@ func (s signatureAuthentication) AuthenticatePackage(location PackageLocation) (
return nil, fmt.Errorf("error verifying trust signature: %s", err)
}
return &PackageAuthenticationResult{result: partnerProvider}, nil
return &PackageAuthenticationResult{result: partnerProvider, KeyID: keyID}, nil
}
// We have a valid signature, but it's not from the HashiCorp key, and it
// also isn't a trusted partner. This is a community provider.
// FIXME: we may want to add a more detailed warning here explaining the
// difference between partner and community providers.
return &PackageAuthenticationResult{result: communityProvider}, nil
return &PackageAuthenticationResult{result: communityProvider, KeyID: keyID}, nil
}
// findSigningKey attempts to verify the signature using each of the keys
@ -289,11 +300,11 @@ func (s signatureAuthentication) AuthenticatePackage(location PackageLocation) (
//
// Note: currently the registry only returns one key, but this may change in
// the future.
func (s signatureAuthentication) findSigningKey() (*SigningKey, error) {
func (s signatureAuthentication) findSigningKey() (*SigningKey, string, error) {
for _, key := range s.Keys {
keyring, err := openpgp.ReadArmoredKeyRing(strings.NewReader(key.ASCIIArmor))
if err != nil {
return nil, fmt.Errorf("error decoding signing key: %s", err)
return nil, "", fmt.Errorf("error decoding signing key: %s", err)
}
entity, err := openpgp.CheckDetachedSignature(keyring, bytes.NewReader(s.Document), bytes.NewReader(s.Signature))
@ -306,16 +317,21 @@ func (s signatureAuthentication) findSigningKey() (*SigningKey, error) {
// Any other signature error is terminal.
if err != nil {
return nil, fmt.Errorf("error checking signature: %s", err)
return nil, "", fmt.Errorf("error checking signature: %s", err)
}
keyID := "n/a"
if entity.PrimaryKey != nil {
keyID = entity.PrimaryKey.KeyIdString()
}
log.Printf("[DEBUG] Provider signed by %s", entityString(entity))
return &key, nil
return &key, keyID, nil
}
// If none of the provided keys issued the signature, this package is
// unsigned. This is currently a terminal authentication error.
return nil, fmt.Errorf("authentication signature from unknown issuer")
return nil, "", fmt.Errorf("authentication signature from unknown issuer")
}
// entityString extracts the key ID and identity name(s) from an openpgp.Entity

View File

@ -26,15 +26,15 @@ func TestPackageAuthenticationResult(t *testing.T) {
},
{
&PackageAuthenticationResult{result: officialProvider},
"official provider",
"signed by HashiCorp",
},
{
&PackageAuthenticationResult{result: partnerProvider},
"partner provider",
"signed by a HashiCorp partner",
},
{
&PackageAuthenticationResult{result: communityProvider},
"community provider",
"self-signed",
},
}
for _, test := range tests {
@ -270,7 +270,10 @@ func TestSignatureAuthentication_success(t *testing.T) {
ASCIIArmor: HashicorpPublicKey,
},
},
PackageAuthenticationResult{result: officialProvider},
PackageAuthenticationResult{
result: officialProvider,
KeyID: testHashiCorpPublicKeyID,
},
},
"partner provider": {
testAuthorSignatureGoodBase64,
@ -280,7 +283,10 @@ func TestSignatureAuthentication_success(t *testing.T) {
TrustSignature: testAuthorKeyTrustSignatureArmor,
},
},
PackageAuthenticationResult{result: partnerProvider},
PackageAuthenticationResult{
result: partnerProvider,
KeyID: testAuthorKeyID,
},
},
"community provider": {
testAuthorSignatureGoodBase64,
@ -289,7 +295,10 @@ func TestSignatureAuthentication_success(t *testing.T) {
ASCIIArmor: testAuthorKeyArmor,
},
},
PackageAuthenticationResult{result: communityProvider},
PackageAuthenticationResult{
result: communityProvider,
KeyID: testAuthorKeyID,
},
},
"multiple signing keys": {
testAuthorSignatureGoodBase64,
@ -301,7 +310,10 @@ func TestSignatureAuthentication_success(t *testing.T) {
ASCIIArmor: testAuthorKeyArmor,
},
},
PackageAuthenticationResult{result: communityProvider},
PackageAuthenticationResult{
result: communityProvider,
KeyID: testAuthorKeyID,
},
},
}
@ -408,6 +420,8 @@ func TestSignatureAuthentication_failure(t *testing.T) {
}
}
const testAuthorKeyID = `37A6AB3BCF2C170A`
// testAuthorKeyArmor is test key ID 5BFEEC4317E746008621970637A6AB3BCF2C170A.
const testAuthorKeyArmor = `-----BEGIN PGP PUBLIC KEY BLOCK-----
@ -500,6 +514,9 @@ const testSignatureBadBase64 = `iQEzBAABCAAdFiEEW/7sQxfnRgCGIZcGN6arO88s` +
`rkTahBtV9yuUUd1D+oRTTTdP0bj3A+3xxXmKTBhRuvurydPTicKuWzeILIJkcwp7Kl5UbI2N` +
`n1ayZdaCIw/r4w==`
// testHashiCorpPublicKeyID is the Key ID of the HashiCorpPublicKey.
const testHashiCorpPublicKeyID = `51852D87348FFC4C`
// testHashicorpSignatureGoodBase64 is a signature of testShaSums signed with
// HashicorpPublicKey, which represents the SHA256SUMS.sig file downloaded for
// an official release.

View File

@ -249,6 +249,7 @@ NeedProvider:
// Step 3: For each provider version we've decided we need to install,
// install its package into our target cache (possibly via the global cache).
authResults := map[addrs.Provider]*getproviders.PackageAuthenticationResult{} // record auth results for all successfully fetched providers
targetPlatform := i.targetDir.targetPlatform // we inherit this to behave correctly in unit tests
for provider, version := range need {
if i.globalCacheDir != nil {
@ -348,12 +349,18 @@ NeedProvider:
continue
}
}
authResults[provider] = authResult
selected[provider] = version
if cb := evts.FetchPackageSuccess; cb != nil {
cb(provider, version, new.PackageDir, authResult)
}
}
// Emit final event for fetching if any were successfully fetched
if cb := evts.ProvidersFetched; cb != nil && len(authResults) > 0 {
cb(authResults)
}
// We'll remember our selections in a lock file inside the target directory,
// so callers can recover those exact selections later by calling
// SelectedPackages on the same installer.

View File

@ -103,6 +103,10 @@ type InstallerEvents struct {
FetchPackageSuccess func(provider addrs.Provider, version getproviders.Version, localDir string, authResult *getproviders.PackageAuthenticationResult)
FetchPackageFailure func(provider addrs.Provider, version getproviders.Version, err error)
// The ProvidersFetched event is called after all fetch operations if at
// least one provider was fetched successfully.
ProvidersFetched func(authResults map[addrs.Provider]*getproviders.PackageAuthenticationResult)
// HashPackageFailure is called if the installer is unable to determine
// the hash of the contents of an installed package after installation.
// In that case, the selection will not be recorded in the target cache

View File

@ -0,0 +1,26 @@
---
layout: "registry"
page_title: "Plugin Signing"
sidebar_current: "docs-plugins-signing"
description: |-
Terraform plugin signing trust levels
---
# Plugin Signing
~> **Note** Currently only provider plugins fetched from a registry are authenticated.
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:
* **Signed by HashiCorp** - are built, signed, and supported by HashiCorp.
* **Signed by Trusted Partners** - are built, signed, and supported by a third party. HashiCorp has
verified the ownership of the private key and we provide a chain of trust to the CLI to verify this
programatically.
* **Self-signed** - are built, signed, and supported by a third party. HashiCorp does not provide a
verification or chain of trust for the signature. You may obtain and validate fingerprints manually
if you want to ensure you are using a binary you can trust.
Terraform does **NOT** support fetching and using unsigned binaries, but you can manually install
unsigned binaries. You should take extreme care when doing so as no programatic authentication is performed.
Usage of plugins from the registry is subject to the Registry's [Terms of Use](https://registry.terraform.io/terms).

View File

@ -1,20 +0,0 @@
---
layout: "registry"
page_title: "Terraform Registry - Provider Tiers
sidebar_current: "docs-registry-provider-tiers
description: |-
Published Provider tiers in the Terraform Registry
---
# Provider Tiers
There are three tiers of providers in the Terraform Registry:
* **Official Providers** - are built, signed, and supported by HashiCorp. Official Providers can typically be used without providing
provider source information in your Terraform configuration.
* **Partner Providers** - are built, signed, and supported by a third party. HashiCorp has verified the ownership of the private
key and we provide a chain of trust to the CLI to verify this programatically. To use Partner Providers in your Terraform
configuration, you need to specify the provider source, typically this is the namespace and name to download from the registry.
* **Community Providers** - are built, signed, and supported by a third party. HashiCorp does not provide a verification or chain
of trust for the signing. You will want to obtain and validate fingerprints manually if you want to ensure you are using a
binary you can trust.

View File

@ -424,6 +424,10 @@
<a href="/docs/plugins/basics.html">Basics</a>
</li>
<li<%= sidebar_current("docs-plugins-signing") %>>
<a href="/docs/plugins/signing.html">Signing</a>
</li>
<li<%= sidebar_current("docs-plugins-provider") %>>
<a href="/docs/plugins/provider.html">Provider</a>
</li>