// Test performs an acceptance test on a backend with the given test case. // // Tests are not run unless an environmental variable "TF_ACC" is // set to some non-empty value. This is to avoid test cases surprising // a user by creating real resources. // // Tests will fail unless the verbose flag (`go test -v`, or explicitly // the "-test.v" flag) is set. Because some acceptance tests take quite // long, we require the verbose flag so users are able to see progress // output. func Test(t TestT, c TestCase) { // We only run acceptance tests if an env var is set because they're // slow and generally require some outside configuration. if os.Getenv(TestEnvVar) == "" { t.Skip(fmt.Sprintf( "Acceptance tests skipped unless env '%s' set", TestEnvVar)) return } // We require verbose mode so that the user knows what is going on. if !testTesting && !testing.Verbose() { t.Fatal("Acceptance tests must be run with the -v flag on tests") return } // Run the PreCheck if we have it if c.PreCheck != nil { c.PreCheck() } // Check that something is provided if c.Backend == nil && c.Factory == nil { t.Fatal("Must provide either Backend or Factory") } // Create an in-memory Vault core core, err := vault.NewCore(&vault.CoreConfig{ Physical: physical.NewInmem(), LogicalBackends: map[string]logical.Factory{ "test": func(conf *logical.BackendConfig) (logical.Backend, error) { if c.Backend != nil { return c.Backend, nil } return c.Factory(conf) }, }, }) if err != nil { t.Fatal("error initializing core: ", err) return } // Initialize the core init, err := core.Initialize(&vault.SealConfig{ SecretShares: 1, SecretThreshold: 1, }) if err != nil { t.Fatal("error initializing core: ", err) } // Unseal the core if unsealed, err := core.Unseal(init.SecretShares[0]); err != nil { t.Fatal("error unsealing core: ", err) return } else if !unsealed { t.Fatal("vault shouldn't be sealed") return } // Create an HTTP API server and client ln, addr := http.TestServer(nil, core) defer ln.Close() clientConfig := api.DefaultConfig() clientConfig.Address = addr client, err := api.NewClient(clientConfig) if err != nil { t.Fatal("error initializing HTTP client: ", err) return } // Set the token so we're authenticated client.SetToken(init.RootToken) // Mount the backend prefix := "mnt" if err := client.Sys().Mount(prefix, "test", "acceptance test"); err != nil { t.Fatal("error mounting backend: ", err) return } // Make requests var revoke []*logical.Request for i, s := range c.Steps { log.Printf("[WARN] Executing test step %d", i+1) // Make sure to prefix the path with where we mounted the thing path := fmt.Sprintf("%s/%s", prefix, s.Path) // Create the request req := &logical.Request{ Operation: s.Operation, Path: path, Data: s.Data, } if !s.Unauthenticated { req.ClientToken = client.Token() } if s.RemoteAddr != "" { req.Connection = &logical.Connection{RemoteAddr: s.RemoteAddr} } if s.ConnState != nil { req.Connection = &logical.Connection{ConnState: s.ConnState} } // Make the request resp, err := core.HandleRequest(req) if resp != nil && resp.Secret != nil { // Revoke this secret later revoke = append(revoke, &logical.Request{ Operation: logical.WriteOperation, Path: "sys/revoke/" + resp.Secret.LeaseID, }) } if err == nil && resp.IsError() && !s.ErrorOk { err = fmt.Errorf("Erroneous response:\n\n%#v", resp) } if err == nil && s.Check != nil { // Call the test method err = s.Check(resp) } if err != nil { t.Error(fmt.Sprintf("Failed step %d: %s", i+1, err)) break } } // Revoke any secrets we might have. var failedRevokes []*logical.Secret for _, req := range revoke { log.Printf("[WARN] Revoking secret: %#v", req) req.ClientToken = client.Token() resp, err := core.HandleRequest(req) if err == nil && resp.IsError() { err = fmt.Errorf("Erroneous response:\n\n%#v", resp) } if err != nil { failedRevokes = append(failedRevokes, req.Secret) t.Error(fmt.Sprintf("[ERR] Revoke error: %s", err)) } } // Perform any rollbacks. This should no-op if there aren't any. // We set the "immediate" flag here that any backend can pick up on // to do all rollbacks immediately even if the WAL entries are new. log.Printf("[WARN] Requesting RollbackOperation") req := logical.RollbackRequest(prefix + "/") req.Data["immediate"] = true req.ClientToken = client.Token() resp, err := core.HandleRequest(req) if err == nil && resp.IsError() { err = fmt.Errorf("Erroneous response:\n\n%#v", resp) } if err != nil && err != logical.ErrUnsupportedOperation { t.Error(fmt.Sprintf("[ERR] Rollback error: %s", err)) } // If we have any failed revokes, log it. if len(failedRevokes) > 0 { for _, s := range failedRevokes { t.Error(fmt.Sprintf( "WARNING: Revoking the following secret failed. It may\n"+ "still exist. Please verify:\n\n%#v", s)) } } // Cleanup if c.Teardown != nil { c.Teardown() } }
// Test performs an acceptance test on a backend with the given test case. // // Tests are not run unless an environmental variable "VAULT_ACC" is // set to some non-empty value. This is to avoid test cases surprising // a user by creating real resources. // // Tests will fail unless the verbose flag (`go test -v`, or explicitly // the "-test.v" flag) is set. Because some acceptance tests take quite // long, we require the verbose flag so users are able to see progress // output. func Test(tt TestT, c TestCase) { // We only run acceptance tests if an env var is set because they're // slow and generally require some outside configuration. if c.AcceptanceTest && os.Getenv(TestEnvVar) == "" { tt.Skip(fmt.Sprintf( "Acceptance tests skipped unless env '%s' set", TestEnvVar)) return } // We require verbose mode so that the user knows what is going on. if c.AcceptanceTest && !testTesting && !testing.Verbose() { tt.Fatal("Acceptance tests must be run with the -v flag on tests") return } // Run the PreCheck if we have it if c.PreCheck != nil { c.PreCheck() } // Check that something is provided if c.Backend == nil && c.Factory == nil { tt.Fatal("Must provide either Backend or Factory") return } // Create an in-memory Vault core core, err := vault.NewCore(&vault.CoreConfig{ Physical: physical.NewInmem(logger), LogicalBackends: map[string]logical.Factory{ "test": func(conf *logical.BackendConfig) (logical.Backend, error) { if c.Backend != nil { return c.Backend, nil } return c.Factory(conf) }, }, DisableMlock: true, }) if err != nil { tt.Fatal("error initializing core: ", err) return } // Initialize the core init, err := core.Initialize(&vault.SealConfig{ SecretShares: 1, SecretThreshold: 1, }, nil) if err != nil { tt.Fatal("error initializing core: ", err) return } // Unseal the core if unsealed, err := core.Unseal(init.SecretShares[0]); err != nil { tt.Fatal("error unsealing core: ", err) return } else if !unsealed { tt.Fatal("vault shouldn't be sealed") return } // Create an HTTP API server and client ln, addr := http.TestServer(nil, core) defer ln.Close() clientConfig := api.DefaultConfig() clientConfig.Address = addr client, err := api.NewClient(clientConfig) if err != nil { tt.Fatal("error initializing HTTP client: ", err) return } // Set the token so we're authenticated client.SetToken(init.RootToken) // Mount the backend prefix := "mnt" mountInfo := &api.MountInput{ Type: "test", Description: "acceptance test", } if err := client.Sys().Mount(prefix, mountInfo); err != nil { tt.Fatal("error mounting backend: ", err) return } // Make requests var revoke []*logical.Request for i, s := range c.Steps { log.Printf("[WARN] Executing test step %d", i+1) // Create the request req := &logical.Request{ Operation: s.Operation, Path: s.Path, Data: s.Data, } if !s.Unauthenticated { req.ClientToken = client.Token() } if s.RemoteAddr != "" { req.Connection = &logical.Connection{RemoteAddr: s.RemoteAddr} } if s.ConnState != nil { req.Connection = &logical.Connection{ConnState: s.ConnState} } if s.PreFlight != nil { ct := req.ClientToken req.ClientToken = "" if err := s.PreFlight(req); err != nil { tt.Error(fmt.Sprintf("Failed preflight for step %d: %s", i+1, err)) break } req.ClientToken = ct } // Make sure to prefix the path with where we mounted the thing req.Path = fmt.Sprintf("%s/%s", prefix, req.Path) // Make the request resp, err := core.HandleRequest(req) if resp != nil && resp.Secret != nil { // Revoke this secret later revoke = append(revoke, &logical.Request{ Operation: logical.UpdateOperation, Path: "sys/revoke/" + resp.Secret.LeaseID, }) } // Test step returned an error. if err != nil { // But if an error is expected, do not fail the test step, // regardless of whether the error is a 'logical.ErrorResponse' // or not. Set the err to nil. If the error is a logical.ErrorResponse, // it will be handled later. if s.ErrorOk { err = nil } else { // If the error is not expected, fail right away. tt.Error(fmt.Sprintf("Failed step %d: %s", i+1, err)) break } } // If the error is a 'logical.ErrorResponse' and if error was not expected, // set the error so that this can be caught below. if resp.IsError() && !s.ErrorOk { err = fmt.Errorf("Erroneous response:\n\n%#v", resp) } // Either the 'err' was nil or if an error was expected, it was set to nil. // Call the 'Check' function if there is one. // // TODO: This works perfectly for now, but it would be better if 'Check' // function takes in both the response object and the error, and decide on // the action on its own. if err == nil && s.Check != nil { // Call the test method err = s.Check(resp) } if err != nil { tt.Error(fmt.Sprintf("Failed step %d: %s", i+1, err)) break } } // Revoke any secrets we might have. var failedRevokes []*logical.Secret for _, req := range revoke { log.Printf("[WARN] Revoking secret: %#v", req) req.ClientToken = client.Token() resp, err := core.HandleRequest(req) if err == nil && resp.IsError() { err = fmt.Errorf("Erroneous response:\n\n%#v", resp) } if err != nil { failedRevokes = append(failedRevokes, req.Secret) tt.Error(fmt.Sprintf("[ERR] Revoke error: %s", err)) } } // Perform any rollbacks. This should no-op if there aren't any. // We set the "immediate" flag here that any backend can pick up on // to do all rollbacks immediately even if the WAL entries are new. log.Printf("[WARN] Requesting RollbackOperation") req := logical.RollbackRequest(prefix + "/") req.Data["immediate"] = true req.ClientToken = client.Token() resp, err := core.HandleRequest(req) if err == nil && resp.IsError() { err = fmt.Errorf("Erroneous response:\n\n%#v", resp) } if err != nil { if !errwrap.Contains(err, logical.ErrUnsupportedOperation.Error()) { tt.Error(fmt.Sprintf("[ERR] Rollback error: %s", err)) } } // If we have any failed revokes, log it. if len(failedRevokes) > 0 { for _, s := range failedRevokes { tt.Error(fmt.Sprintf( "WARNING: Revoking the following secret failed. It may\n"+ "still exist. Please verify:\n\n%#v", s)) } } // Cleanup if c.Teardown != nil { c.Teardown() } }