github.com/hugorut/terraform@v1.1.3/src/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/hugorut/terraform/src/e2e"
    11  	"github.com/hugorut/terraform/src/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(terraformBin, fixturePath)
    31  	defer tf.Close()
    32  
    33  	stdout, stderr, err := tf.Run("init")
    34  	if err != nil {
    35  		t.Fatalf("unexpected init error: %s\nstderr:\n%s", err, stderr)
    36  	}
    37  	if !strings.Contains(stdout, "Installing hashicorp/null v") {
    38  		t.Errorf("null provider download message is missing from init output:\n%s", stdout)
    39  		t.Logf("(this can happen if you have a copy of the plugin in one of the global plugin search dirs)")
    40  	}
    41  
    42  	seedDir := tf.WorkDir()
    43  	const providerVersion = "3.1.0" // must match the version in the fixture config
    44  	pluginDir := ".terraform/providers/registry.terraform.io/hashicorp/null/" + providerVersion + "/" + getproviders.CurrentPlatform.String()
    45  	pluginExe := pluginDir + "/terraform-provider-null_v" + providerVersion + "_x5"
    46  	if getproviders.CurrentPlatform.OS == "windows" {
    47  		pluginExe += ".exe" // ugh
    48  	}
    49  
    50  	t.Run("cache dir totally gone", func(t *testing.T) {
    51  		tf := e2e.NewBinary(terraformBin, seedDir)
    52  		defer tf.Close()
    53  		workDir := tf.WorkDir()
    54  
    55  		err := os.RemoveAll(filepath.Join(workDir, ".terraform"))
    56  		if err != nil {
    57  			t.Fatal(err)
    58  		}
    59  
    60  		stdout, stderr, err := tf.Run("plan")
    61  		if err == nil {
    62  			t.Fatalf("unexpected plan success\nstdout:\n%s", stdout)
    63  		}
    64  		if want := `registry.terraform.io/hashicorp/null: there is no package for registry.terraform.io/hashicorp/null 3.1.0 cached in .terraform/providers`; !strings.Contains(stderr, want) {
    65  			t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr)
    66  		}
    67  		if want := `terraform init`; !strings.Contains(stderr, want) {
    68  			t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr)
    69  		}
    70  
    71  		// Running init as suggested resolves the problem
    72  		_, stderr, err = tf.Run("init")
    73  		if err != nil {
    74  			t.Fatalf("unexpected init error: %s\nstderr:\n%s", err, stderr)
    75  		}
    76  		_, stderr, err = tf.Run("plan")
    77  		if err != nil {
    78  			t.Fatalf("unexpected plan error: %s\nstderr:\n%s", err, stderr)
    79  		}
    80  	})
    81  	t.Run("cache dir totally gone, explicit backend", func(t *testing.T) {
    82  		tf := e2e.NewBinary(terraformBin, seedDir)
    83  		defer tf.Close()
    84  		workDir := tf.WorkDir()
    85  
    86  		err := ioutil.WriteFile(filepath.Join(workDir, "backend.tf"), []byte(localBackendConfig), 0600)
    87  		if err != nil {
    88  			t.Fatal(err)
    89  		}
    90  
    91  		err = os.RemoveAll(filepath.Join(workDir, ".terraform"))
    92  		if err != nil {
    93  			t.Fatal(err)
    94  		}
    95  
    96  		stdout, stderr, err := tf.Run("plan")
    97  		if err == nil {
    98  			t.Fatalf("unexpected plan success\nstdout:\n%s", stdout)
    99  		}
   100  		if want := `Initial configuration of the requested backend "local"`; !strings.Contains(stderr, want) {
   101  			t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr)
   102  		}
   103  		if want := `terraform init`; !strings.Contains(stderr, want) {
   104  			t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr)
   105  		}
   106  
   107  		// Running init as suggested resolves the problem
   108  		_, stderr, err = tf.Run("init")
   109  		if err != nil {
   110  			t.Fatalf("unexpected init error: %s\nstderr:\n%s", err, stderr)
   111  		}
   112  		_, stderr, err = tf.Run("plan")
   113  		if err != nil {
   114  			t.Fatalf("unexpected plan error: %s\nstderr:\n%s", err, stderr)
   115  		}
   116  	})
   117  	t.Run("null plugin package modified before plan", func(t *testing.T) {
   118  		tf := e2e.NewBinary(terraformBin, seedDir)
   119  		defer tf.Close()
   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 .terraform/providers) 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(terraformBin, seedDir)
   140  		defer tf.Close()
   141  		workDir := tf.WorkDir()
   142  
   143  		err := ioutil.WriteFile(filepath.Join(workDir, "provider-tampering-base.tf"), []byte(`
   144  			terraform {
   145  				required_providers {
   146  					null = {
   147  						source  = "hashicorp/null"
   148  						version = "1.0.0"
   149  					}
   150  				}
   151  			}
   152  		`), 0600)
   153  		if err != nil {
   154  			t.Fatal(err)
   155  		}
   156  
   157  		stdout, stderr, err := tf.Run("plan")
   158  		if err == nil {
   159  			t.Fatalf("unexpected plan success\nstdout:\n%s", stdout)
   160  		}
   161  		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) {
   162  			t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr)
   163  		}
   164  		if want := `terraform init -upgrade`; !strings.Contains(stderr, want) {
   165  			t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr)
   166  		}
   167  	})
   168  	t.Run("lock file modified before plan", func(t *testing.T) {
   169  		tf := e2e.NewBinary(terraformBin, seedDir)
   170  		defer tf.Close()
   171  		workDir := tf.WorkDir()
   172  
   173  		// NOTE: We're just emptying out the lock file here because that's
   174  		// good enough for what we're trying to assert. The leaf codepath
   175  		// that generates this family of errors has some different variations
   176  		// of this error message for otehr sorts of inconsistency, but those
   177  		// are tested more thoroughly over in the "configs" package, which is
   178  		// ultimately responsible for that logic.
   179  		err := ioutil.WriteFile(filepath.Join(workDir, ".terraform.lock.hcl"), []byte(``), 0600)
   180  		if err != nil {
   181  			t.Fatal(err)
   182  		}
   183  
   184  		stdout, stderr, err := tf.Run("plan")
   185  		if err == nil {
   186  			t.Fatalf("unexpected plan success\nstdout:\n%s", stdout)
   187  		}
   188  		if want := `provider registry.terraform.io/hashicorp/null: required by this configuration but no version is selected`; !strings.Contains(stderr, want) {
   189  			t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr)
   190  		}
   191  		if want := `terraform init`; !strings.Contains(stderr, want) {
   192  			t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr)
   193  		}
   194  	})
   195  	t.Run("lock file modified after plan", func(t *testing.T) {
   196  		tf := e2e.NewBinary(terraformBin, seedDir)
   197  		defer tf.Close()
   198  		workDir := tf.WorkDir()
   199  
   200  		_, stderr, err := tf.Run("plan", "-out", "tfplan")
   201  		if err != nil {
   202  			t.Fatalf("unexpected plan failure\nstderr:\n%s", stderr)
   203  		}
   204  
   205  		err = os.Remove(filepath.Join(workDir, ".terraform.lock.hcl"))
   206  		if err != nil {
   207  			t.Fatal(err)
   208  		}
   209  
   210  		stdout, stderr, err := tf.Run("apply", "tfplan")
   211  		if err == nil {
   212  			t.Fatalf("unexpected apply success\nstdout:\n%s", stdout)
   213  		}
   214  		if want := `provider registry.terraform.io/hashicorp/null: required by this configuration but no version is selected`; !strings.Contains(stderr, want) {
   215  			t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr)
   216  		}
   217  		if want := `Create a new plan from the updated configuration.`; !strings.Contains(stderr, want) {
   218  			t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr)
   219  		}
   220  	})
   221  	t.Run("plugin cache dir entirely removed after plan", func(t *testing.T) {
   222  		tf := e2e.NewBinary(terraformBin, seedDir)
   223  		defer tf.Close()
   224  		workDir := tf.WorkDir()
   225  
   226  		_, stderr, err := tf.Run("plan", "-out", "tfplan")
   227  		if err != nil {
   228  			t.Fatalf("unexpected plan failure\nstderr:\n%s", stderr)
   229  		}
   230  
   231  		err = os.RemoveAll(filepath.Join(workDir, ".terraform"))
   232  		if err != nil {
   233  			t.Fatal(err)
   234  		}
   235  
   236  		stdout, stderr, err := tf.Run("apply", "tfplan")
   237  		if err == nil {
   238  			t.Fatalf("unexpected apply success\nstdout:\n%s", stdout)
   239  		}
   240  		if want := `registry.terraform.io/hashicorp/null: there is no package for registry.terraform.io/hashicorp/null 3.1.0 cached in .terraform/providers`; !strings.Contains(stderr, want) {
   241  			t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr)
   242  		}
   243  	})
   244  	t.Run("null plugin package modified after plan", func(t *testing.T) {
   245  		tf := e2e.NewBinary(terraformBin, seedDir)
   246  		defer tf.Close()
   247  		workDir := tf.WorkDir()
   248  
   249  		_, stderr, err := tf.Run("plan", "-out", "tfplan")
   250  		if err != nil {
   251  			t.Fatalf("unexpected plan failure\nstderr:\n%s", stderr)
   252  		}
   253  
   254  		err = ioutil.WriteFile(filepath.Join(workDir, pluginExe), []byte("tamper"), 0600)
   255  		if err != nil {
   256  			t.Fatal(err)
   257  		}
   258  
   259  		stdout, stderr, err := tf.Run("apply", "tfplan")
   260  		if err == nil {
   261  			t.Fatalf("unexpected apply success\nstdout:\n%s", stdout)
   262  		}
   263  		if want := `registry.terraform.io/hashicorp/null: the cached package for registry.terraform.io/hashicorp/null 3.1.0 (in .terraform/providers) does not match any of the checksums recorded in the dependency lock file`; !strings.Contains(stderr, want) {
   264  			t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr)
   265  		}
   266  	})
   267  }
   268  
   269  const localBackendConfig = `
   270  terraform {
   271    backend "local" {
   272      path = "terraform.tfstate"
   273    }
   274  }
   275  `