github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/command/e2etest/providers_tamper_test.go (about)

     1  package e2etest
     2  
     3  import (
     4  	"io/ioutil"
     5  	"os"
     6  	"path/filepath"
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/hashicorp/terraform/internal/e2e"
    11  	"github.com/hashicorp/terraform/internal/getproviders"
    12  )
    13  
    14  // TestProviderTampering tests various ways that the provider plugins in the
    15  // local cache directory might be modified after an initial "terraform init",
    16  // which other Terraform commands which use those plugins should catch and
    17  // report early.
    18  func TestProviderTampering(t *testing.T) {
    19  	// General setup: we'll do a one-off init of a test directory as our
    20  	// starting point, and then we'll clone that result for each test so
    21  	// that we can save the cost of a repeated re-init with the same
    22  	// provider.
    23  	t.Parallel()
    24  
    25  	// This test reaches out to releases.hashicorp.com to download the
    26  	// null provider, so it can only run if network access is allowed.
    27  	skipIfCannotAccessNetwork(t)
    28  
    29  	fixturePath := filepath.Join("testdata", "provider-tampering-base")
    30  	tf := e2e.NewBinary(t, terraformBin, fixturePath)
    31  
    32  	stdout, stderr, err := tf.Run("init")
    33  	if err != nil {
    34  		t.Fatalf("unexpected init error: %s\nstderr:\n%s", err, stderr)
    35  	}
    36  	if !strings.Contains(stdout, "Installing hashicorp/null v") {
    37  		t.Errorf("null provider download message is missing from init output:\n%s", stdout)
    38  		t.Logf("(this can happen if you have a copy of the plugin in one of the global plugin search dirs)")
    39  	}
    40  
    41  	seedDir := tf.WorkDir()
    42  	const providerVersion = "3.1.0" // must match the version in the fixture config
    43  	pluginDir := filepath.Join(".terraform", "providers", "registry.terraform.io", "hashicorp", "null", providerVersion, getproviders.CurrentPlatform.String())
    44  	pluginExe := filepath.Join(pluginDir, "terraform-provider-null_v"+providerVersion+"_x5")
    45  	if getproviders.CurrentPlatform.OS == "windows" {
    46  		pluginExe += ".exe" // ugh
    47  	}
    48  
    49  	// filepath.Join here to make sure we get the right path separator
    50  	// for whatever OS we're running these tests on.
    51  	providerCacheDir := filepath.Join(".terraform", "providers")
    52  
    53  	t.Run("cache dir totally gone", func(t *testing.T) {
    54  		tf := e2e.NewBinary(t, terraformBin, seedDir)
    55  		workDir := tf.WorkDir()
    56  
    57  		err := os.RemoveAll(filepath.Join(workDir, ".terraform"))
    58  		if err != nil {
    59  			t.Fatal(err)
    60  		}
    61  
    62  		stdout, stderr, err := tf.Run("plan")
    63  		if err == nil {
    64  			t.Fatalf("unexpected plan success\nstdout:\n%s", stdout)
    65  		}
    66  		if want := `registry.terraform.io/hashicorp/null: there is no package for registry.terraform.io/hashicorp/null 3.1.0 cached in ` + providerCacheDir; !strings.Contains(stderr, want) {
    67  			t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr)
    68  		}
    69  		if want := `terraform init`; !strings.Contains(stderr, want) {
    70  			t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr)
    71  		}
    72  
    73  		// Running init as suggested resolves the problem
    74  		_, stderr, err = tf.Run("init")
    75  		if err != nil {
    76  			t.Fatalf("unexpected init error: %s\nstderr:\n%s", err, stderr)
    77  		}
    78  		_, stderr, err = tf.Run("plan")
    79  		if err != nil {
    80  			t.Fatalf("unexpected plan error: %s\nstderr:\n%s", err, stderr)
    81  		}
    82  	})
    83  	t.Run("cache dir totally gone, explicit backend", func(t *testing.T) {
    84  		tf := e2e.NewBinary(t, terraformBin, seedDir)
    85  		workDir := tf.WorkDir()
    86  
    87  		err := ioutil.WriteFile(filepath.Join(workDir, "backend.tf"), []byte(localBackendConfig), 0600)
    88  		if err != nil {
    89  			t.Fatal(err)
    90  		}
    91  
    92  		err = os.RemoveAll(filepath.Join(workDir, ".terraform"))
    93  		if err != nil {
    94  			t.Fatal(err)
    95  		}
    96  
    97  		stdout, stderr, err := tf.Run("plan")
    98  		if err == nil {
    99  			t.Fatalf("unexpected plan success\nstdout:\n%s", stdout)
   100  		}
   101  		if want := `Initial configuration of the requested backend "local"`; !strings.Contains(stderr, want) {
   102  			t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr)
   103  		}
   104  		if want := `terraform init`; !strings.Contains(stderr, want) {
   105  			t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr)
   106  		}
   107  
   108  		// Running init as suggested resolves the problem
   109  		_, stderr, err = tf.Run("init")
   110  		if err != nil {
   111  			t.Fatalf("unexpected init error: %s\nstderr:\n%s", err, stderr)
   112  		}
   113  		_, stderr, err = tf.Run("plan")
   114  		if err != nil {
   115  			t.Fatalf("unexpected plan error: %s\nstderr:\n%s", err, stderr)
   116  		}
   117  	})
   118  	t.Run("null plugin package modified before plan", func(t *testing.T) {
   119  		tf := e2e.NewBinary(t, terraformBin, seedDir)
   120  		workDir := tf.WorkDir()
   121  
   122  		err := ioutil.WriteFile(filepath.Join(workDir, pluginExe), []byte("tamper"), 0600)
   123  		if err != nil {
   124  			t.Fatal(err)
   125  		}
   126  
   127  		stdout, stderr, err := tf.Run("plan")
   128  		if err == nil {
   129  			t.Fatalf("unexpected plan success\nstdout:\n%s", stdout)
   130  		}
   131  		if want := `registry.terraform.io/hashicorp/null: the cached package for registry.terraform.io/hashicorp/null 3.1.0 (in ` + providerCacheDir + `) does not match any of the checksums recorded in the dependency lock file`; !strings.Contains(stderr, want) {
   132  			t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr)
   133  		}
   134  		if want := `terraform init`; !strings.Contains(stderr, want) {
   135  			t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr)
   136  		}
   137  	})
   138  	t.Run("version constraint changed in config before plan", func(t *testing.T) {
   139  		tf := e2e.NewBinary(t, terraformBin, seedDir)
   140  		workDir := tf.WorkDir()
   141  
   142  		err := ioutil.WriteFile(filepath.Join(workDir, "provider-tampering-base.tf"), []byte(`
   143  			terraform {
   144  				required_providers {
   145  					null = {
   146  						source  = "hashicorp/null"
   147  						version = "1.0.0"
   148  					}
   149  				}
   150  			}
   151  		`), 0600)
   152  		if err != nil {
   153  			t.Fatal(err)
   154  		}
   155  
   156  		stdout, stderr, err := tf.Run("plan")
   157  		if err == nil {
   158  			t.Fatalf("unexpected plan success\nstdout:\n%s", stdout)
   159  		}
   160  		if want := `provider registry.terraform.io/hashicorp/null: locked version selection 3.1.0 doesn't match the updated version constraints "1.0.0"`; !strings.Contains(stderr, want) {
   161  			t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr)
   162  		}
   163  		if want := `terraform init -upgrade`; !strings.Contains(stderr, want) {
   164  			t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr)
   165  		}
   166  	})
   167  	t.Run("lock file modified before plan", func(t *testing.T) {
   168  		tf := e2e.NewBinary(t, terraformBin, seedDir)
   169  		workDir := tf.WorkDir()
   170  
   171  		// NOTE: We're just emptying out the lock file here because that's
   172  		// good enough for what we're trying to assert. The leaf codepath
   173  		// that generates this family of errors has some different variations
   174  		// of this error message for otehr sorts of inconsistency, but those
   175  		// are tested more thoroughly over in the "configs" package, which is
   176  		// ultimately responsible for that logic.
   177  		err := ioutil.WriteFile(filepath.Join(workDir, ".terraform.lock.hcl"), []byte(``), 0600)
   178  		if err != nil {
   179  			t.Fatal(err)
   180  		}
   181  
   182  		stdout, stderr, err := tf.Run("plan")
   183  		if err == nil {
   184  			t.Fatalf("unexpected plan success\nstdout:\n%s", stdout)
   185  		}
   186  		if want := `provider registry.terraform.io/hashicorp/null: required by this configuration but no version is selected`; !strings.Contains(stderr, want) {
   187  			t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr)
   188  		}
   189  		if want := `terraform init`; !strings.Contains(stderr, want) {
   190  			t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr)
   191  		}
   192  	})
   193  	t.Run("lock file modified after plan", func(t *testing.T) {
   194  		tf := e2e.NewBinary(t, terraformBin, seedDir)
   195  		workDir := tf.WorkDir()
   196  
   197  		_, stderr, err := tf.Run("plan", "-out", "tfplan")
   198  		if err != nil {
   199  			t.Fatalf("unexpected plan failure\nstderr:\n%s", stderr)
   200  		}
   201  
   202  		err = os.Remove(filepath.Join(workDir, ".terraform.lock.hcl"))
   203  		if err != nil {
   204  			t.Fatal(err)
   205  		}
   206  
   207  		stdout, stderr, err := tf.Run("apply", "tfplan")
   208  		if err == nil {
   209  			t.Fatalf("unexpected apply success\nstdout:\n%s", stdout)
   210  		}
   211  		if want := `provider registry.terraform.io/hashicorp/null: required by this configuration but no version is selected`; !strings.Contains(stderr, want) {
   212  			t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr)
   213  		}
   214  		if want := `Create a new plan from the updated configuration.`; !strings.Contains(stderr, want) {
   215  			t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr)
   216  		}
   217  	})
   218  	t.Run("plugin cache dir entirely removed after plan", func(t *testing.T) {
   219  		tf := e2e.NewBinary(t, terraformBin, seedDir)
   220  		workDir := tf.WorkDir()
   221  
   222  		_, stderr, err := tf.Run("plan", "-out", "tfplan")
   223  		if err != nil {
   224  			t.Fatalf("unexpected plan failure\nstderr:\n%s", stderr)
   225  		}
   226  
   227  		err = os.RemoveAll(filepath.Join(workDir, ".terraform"))
   228  		if err != nil {
   229  			t.Fatal(err)
   230  		}
   231  
   232  		stdout, stderr, err := tf.Run("apply", "tfplan")
   233  		if err == nil {
   234  			t.Fatalf("unexpected apply success\nstdout:\n%s", stdout)
   235  		}
   236  		if want := `registry.terraform.io/hashicorp/null: there is no package for registry.terraform.io/hashicorp/null 3.1.0 cached in ` + providerCacheDir; !strings.Contains(stderr, want) {
   237  			t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr)
   238  		}
   239  	})
   240  	t.Run("null plugin package modified after plan", func(t *testing.T) {
   241  		tf := e2e.NewBinary(t, terraformBin, seedDir)
   242  		workDir := tf.WorkDir()
   243  
   244  		_, stderr, err := tf.Run("plan", "-out", "tfplan")
   245  		if err != nil {
   246  			t.Fatalf("unexpected plan failure\nstderr:\n%s", stderr)
   247  		}
   248  
   249  		err = ioutil.WriteFile(filepath.Join(workDir, pluginExe), []byte("tamper"), 0600)
   250  		if err != nil {
   251  			t.Fatal(err)
   252  		}
   253  
   254  		stdout, stderr, err := tf.Run("apply", "tfplan")
   255  		if err == nil {
   256  			t.Fatalf("unexpected apply success\nstdout:\n%s", stdout)
   257  		}
   258  		if want := `registry.terraform.io/hashicorp/null: the cached package for registry.terraform.io/hashicorp/null 3.1.0 (in ` + providerCacheDir + `) does not match any of the checksums recorded in the dependency lock file`; !strings.Contains(stderr, want) {
   259  			t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr)
   260  		}
   261  	})
   262  }
   263  
   264  const localBackendConfig = `
   265  terraform {
   266    backend "local" {
   267      path = "terraform.tfstate"
   268    }
   269  }
   270  `