Merge pull request #24932 from hashicorp/signing-language
Modify language for reporting signing state
This commit is contained in:
commit
ef28671b34
|
@ -551,15 +551,29 @@ func (c *InitCommand) getProviders(config *configs.Config, state *states.State,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
FetchPackageSuccess: func(provider addrs.Provider, version getproviders.Version, localDir string, authResult *getproviders.PackageAuthenticationResult) {
|
FetchPackageSuccess: func(provider addrs.Provider, version getproviders.Version, localDir string, authResult *getproviders.PackageAuthenticationResult) {
|
||||||
var warning string
|
var keyID string
|
||||||
if authResult != nil {
|
if authResult != nil && authResult.ThirdPartySigned() {
|
||||||
warning = authResult.Warning
|
keyID = authResult.KeyID
|
||||||
}
|
}
|
||||||
if warning != "" {
|
if keyID != "" {
|
||||||
warning = c.Colorize().Color(fmt.Sprintf("\n [reset][yellow]Warning: %s[reset]", warning))
|
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"))
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,13 +26,13 @@ const (
|
||||||
|
|
||||||
// PackageAuthenticationResult is returned from a PackageAuthentication
|
// PackageAuthenticationResult is returned from a PackageAuthentication
|
||||||
// implementation. It is a mostly-opaque type intended for use in UI, which
|
// 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"
|
// A failed PackageAuthentication attempt will return an "unauthenticated"
|
||||||
// result, which is represented by nil.
|
// result, which is represented by nil.
|
||||||
type PackageAuthenticationResult struct {
|
type PackageAuthenticationResult struct {
|
||||||
result packageAuthenticationResult
|
result packageAuthenticationResult
|
||||||
Warning string
|
KeyID string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *PackageAuthenticationResult) String() string {
|
func (t *PackageAuthenticationResult) String() string {
|
||||||
|
@ -41,12 +41,25 @@ func (t *PackageAuthenticationResult) String() string {
|
||||||
}
|
}
|
||||||
return []string{
|
return []string{
|
||||||
"verified checksum",
|
"verified checksum",
|
||||||
"official provider",
|
"signed by HashiCorp",
|
||||||
"partner provider",
|
"signed by a HashiCorp partner",
|
||||||
"community provider",
|
"self-signed",
|
||||||
}[t.result]
|
}[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
|
// SigningKey represents a key used to sign packages from a registry, along
|
||||||
// with an optional trust signature from the registry operator. These are
|
// with an optional trust signature from the registry operator. These are
|
||||||
// both in ASCII armored OpenPGP format.
|
// 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) {
|
func (s signatureAuthentication) AuthenticatePackage(location PackageLocation) (*PackageAuthenticationResult, error) {
|
||||||
// Find the key that signed the checksum file. This can fail if there is no
|
// Find the key that signed the checksum file. This can fail if there is no
|
||||||
// valid signature for any of the provided keys.
|
// valid signature for any of the provided keys.
|
||||||
signingKey, err := s.findSigningKey()
|
signingKey, keyID, err := s.findSigningKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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))
|
_, err = openpgp.CheckDetachedSignature(hashicorpKeyring, bytes.NewReader(s.Document), bytes.NewReader(s.Signature))
|
||||||
if err == nil {
|
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
|
// 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 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
|
// 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.
|
// also isn't a trusted partner. This is a community provider.
|
||||||
// FIXME: we may want to add a more detailed warning here explaining the
|
return &PackageAuthenticationResult{result: communityProvider, KeyID: keyID}, nil
|
||||||
// difference between partner and community providers.
|
|
||||||
return &PackageAuthenticationResult{result: communityProvider}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// findSigningKey attempts to verify the signature using each of the keys
|
// 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
|
// Note: currently the registry only returns one key, but this may change in
|
||||||
// the future.
|
// the future.
|
||||||
func (s signatureAuthentication) findSigningKey() (*SigningKey, error) {
|
func (s signatureAuthentication) findSigningKey() (*SigningKey, string, error) {
|
||||||
for _, key := range s.Keys {
|
for _, key := range s.Keys {
|
||||||
keyring, err := openpgp.ReadArmoredKeyRing(strings.NewReader(key.ASCIIArmor))
|
keyring, err := openpgp.ReadArmoredKeyRing(strings.NewReader(key.ASCIIArmor))
|
||||||
if err != nil {
|
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))
|
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.
|
// Any other signature error is terminal.
|
||||||
if err != nil {
|
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))
|
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
|
// If none of the provided keys issued the signature, this package is
|
||||||
// unsigned. This is currently a terminal authentication error.
|
// 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
|
// entityString extracts the key ID and identity name(s) from an openpgp.Entity
|
||||||
|
|
|
@ -26,15 +26,15 @@ func TestPackageAuthenticationResult(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
&PackageAuthenticationResult{result: officialProvider},
|
&PackageAuthenticationResult{result: officialProvider},
|
||||||
"official provider",
|
"signed by HashiCorp",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
&PackageAuthenticationResult{result: partnerProvider},
|
&PackageAuthenticationResult{result: partnerProvider},
|
||||||
"partner provider",
|
"signed by a HashiCorp partner",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
&PackageAuthenticationResult{result: communityProvider},
|
&PackageAuthenticationResult{result: communityProvider},
|
||||||
"community provider",
|
"self-signed",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
@ -270,7 +270,10 @@ func TestSignatureAuthentication_success(t *testing.T) {
|
||||||
ASCIIArmor: HashicorpPublicKey,
|
ASCIIArmor: HashicorpPublicKey,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PackageAuthenticationResult{result: officialProvider},
|
PackageAuthenticationResult{
|
||||||
|
result: officialProvider,
|
||||||
|
KeyID: testHashiCorpPublicKeyID,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"partner provider": {
|
"partner provider": {
|
||||||
testAuthorSignatureGoodBase64,
|
testAuthorSignatureGoodBase64,
|
||||||
|
@ -280,7 +283,10 @@ func TestSignatureAuthentication_success(t *testing.T) {
|
||||||
TrustSignature: testAuthorKeyTrustSignatureArmor,
|
TrustSignature: testAuthorKeyTrustSignatureArmor,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PackageAuthenticationResult{result: partnerProvider},
|
PackageAuthenticationResult{
|
||||||
|
result: partnerProvider,
|
||||||
|
KeyID: testAuthorKeyID,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"community provider": {
|
"community provider": {
|
||||||
testAuthorSignatureGoodBase64,
|
testAuthorSignatureGoodBase64,
|
||||||
|
@ -289,7 +295,10 @@ func TestSignatureAuthentication_success(t *testing.T) {
|
||||||
ASCIIArmor: testAuthorKeyArmor,
|
ASCIIArmor: testAuthorKeyArmor,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PackageAuthenticationResult{result: communityProvider},
|
PackageAuthenticationResult{
|
||||||
|
result: communityProvider,
|
||||||
|
KeyID: testAuthorKeyID,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"multiple signing keys": {
|
"multiple signing keys": {
|
||||||
testAuthorSignatureGoodBase64,
|
testAuthorSignatureGoodBase64,
|
||||||
|
@ -301,7 +310,10 @@ func TestSignatureAuthentication_success(t *testing.T) {
|
||||||
ASCIIArmor: testAuthorKeyArmor,
|
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.
|
// testAuthorKeyArmor is test key ID 5BFEEC4317E746008621970637A6AB3BCF2C170A.
|
||||||
const testAuthorKeyArmor = `-----BEGIN PGP PUBLIC KEY BLOCK-----
|
const testAuthorKeyArmor = `-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
|
@ -500,6 +514,9 @@ const testSignatureBadBase64 = `iQEzBAABCAAdFiEEW/7sQxfnRgCGIZcGN6arO88s` +
|
||||||
`rkTahBtV9yuUUd1D+oRTTTdP0bj3A+3xxXmKTBhRuvurydPTicKuWzeILIJkcwp7Kl5UbI2N` +
|
`rkTahBtV9yuUUd1D+oRTTTdP0bj3A+3xxXmKTBhRuvurydPTicKuWzeILIJkcwp7Kl5UbI2N` +
|
||||||
`n1ayZdaCIw/r4w==`
|
`n1ayZdaCIw/r4w==`
|
||||||
|
|
||||||
|
// testHashiCorpPublicKeyID is the Key ID of the HashiCorpPublicKey.
|
||||||
|
const testHashiCorpPublicKeyID = `51852D87348FFC4C`
|
||||||
|
|
||||||
// testHashicorpSignatureGoodBase64 is a signature of testShaSums signed with
|
// testHashicorpSignatureGoodBase64 is a signature of testShaSums signed with
|
||||||
// HashicorpPublicKey, which represents the SHA256SUMS.sig file downloaded for
|
// HashicorpPublicKey, which represents the SHA256SUMS.sig file downloaded for
|
||||||
// an official release.
|
// an official release.
|
||||||
|
|
|
@ -269,7 +269,8 @@ NeedProvider:
|
||||||
|
|
||||||
// Step 3: For each provider version we've decided we need to install,
|
// 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).
|
// install its package into our target cache (possibly via the global cache).
|
||||||
targetPlatform := i.targetDir.targetPlatform // we inherit this to behave correctly in unit tests
|
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 {
|
for provider, version := range need {
|
||||||
if i.globalCacheDir != nil {
|
if i.globalCacheDir != nil {
|
||||||
// Step 3a: If our global cache already has this version available then
|
// Step 3a: If our global cache already has this version available then
|
||||||
|
@ -368,12 +369,18 @@ NeedProvider:
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
authResults[provider] = authResult
|
||||||
selected[provider] = version
|
selected[provider] = version
|
||||||
if cb := evts.FetchPackageSuccess; cb != nil {
|
if cb := evts.FetchPackageSuccess; cb != nil {
|
||||||
cb(provider, version, new.PackageDir, authResult)
|
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,
|
// We'll remember our selections in a lock file inside the target directory,
|
||||||
// so callers can recover those exact selections later by calling
|
// so callers can recover those exact selections later by calling
|
||||||
// SelectedPackages on the same installer.
|
// SelectedPackages on the same installer.
|
||||||
|
|
|
@ -103,6 +103,10 @@ type InstallerEvents struct {
|
||||||
FetchPackageSuccess func(provider addrs.Provider, version getproviders.Version, localDir string, authResult *getproviders.PackageAuthenticationResult)
|
FetchPackageSuccess func(provider addrs.Provider, version getproviders.Version, localDir string, authResult *getproviders.PackageAuthenticationResult)
|
||||||
FetchPackageFailure func(provider addrs.Provider, version getproviders.Version, err error)
|
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
|
// HashPackageFailure is called if the installer is unable to determine
|
||||||
// the hash of the contents of an installed package after installation.
|
// the hash of the contents of an installed package after installation.
|
||||||
// In that case, the selection will not be recorded in the target cache
|
// In that case, the selection will not be recorded in the target cache
|
||||||
|
|
|
@ -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).
|
|
@ -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.
|
|
|
@ -424,6 +424,10 @@
|
||||||
<a href="/docs/plugins/basics.html">Basics</a>
|
<a href="/docs/plugins/basics.html">Basics</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-plugins-signing") %>>
|
||||||
|
<a href="/docs/plugins/signing.html">Signing</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li<%= sidebar_current("docs-plugins-provider") %>>
|
<li<%= sidebar_current("docs-plugins-provider") %>>
|
||||||
<a href="/docs/plugins/provider.html">Provider</a>
|
<a href="/docs/plugins/provider.html">Provider</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
Loading…
Reference in New Issue