mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-25 16:31:10 -06:00
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:
parent
df39e0a806
commit
22ef5cc99c
@ -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"))
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
result packageAuthenticationResult
|
||||
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
|
||||
|
@ -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.
|
||||
|
@ -249,7 +249,8 @@ 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).
|
||||
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 {
|
||||
if i.globalCacheDir != nil {
|
||||
// Step 3a: If our global cache already has this version available then
|
||||
@ -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.
|
||||
|
@ -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
|
||||
|
26
website/docs/plugins/signing.html.md
Normal file
26
website/docs/plugins/signing.html.md
Normal 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).
|
@ -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>
|
||||
</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>
|
||||
|
Loading…
Reference in New Issue
Block a user