| // Copyright (c) HashiCorp, Inc. |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| package http |
| |
| import ( |
| "encoding/json" |
| "errors" |
| "reflect" |
| "testing" |
| "time" |
| |
| "github.com/hashicorp/vault/api" |
| "github.com/hashicorp/vault/sdk/helper/jsonutil" |
| "github.com/hashicorp/vault/vault" |
| ) |
| |
| // Test wrapping functionality |
| func TestHTTP_Wrapping(t *testing.T) { |
| cluster := vault.NewTestCluster(t, &vault.CoreConfig{}, &vault.TestClusterOptions{ |
| HandlerFunc: Handler, |
| }) |
| cluster.Start() |
| defer cluster.Cleanup() |
| |
| cores := cluster.Cores |
| |
| // make it easy to get access to the active |
| core := cores[0].Core |
| vault.TestWaitActive(t, core) |
| |
| client := cores[0].Client |
| client.SetToken(cluster.RootToken) |
| |
| // Write a value that we will use with wrapping for lookup |
| _, err := client.Logical().Write("secret/foo", map[string]interface{}{ |
| "zip": "zap", |
| }) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| // Set a wrapping lookup function for reads on that path |
| client.SetWrappingLookupFunc(func(operation, path string) string { |
| if operation == "GET" && path == "secret/foo" { |
| return "5m" |
| } |
| |
| return api.DefaultWrappingLookupFunc(operation, path) |
| }) |
| |
| // First test: basic things that should fail, lookup edition |
| // Root token isn't a wrapping token |
| _, err = client.Logical().Write("sys/wrapping/lookup", nil) |
| if err == nil { |
| t.Fatal("expected error") |
| } |
| // Not supplied |
| _, err = client.Logical().Write("sys/wrapping/lookup", map[string]interface{}{ |
| "foo": "bar", |
| }) |
| if err == nil { |
| t.Fatal("expected error") |
| } |
| // Nonexistent token isn't a wrapping token |
| _, err = client.Logical().Write("sys/wrapping/lookup", map[string]interface{}{ |
| "token": "bar", |
| }) |
| if err == nil { |
| t.Fatal("expected error") |
| } |
| |
| // Second: basic things that should fail, unwrap edition |
| // Root token isn't a wrapping token |
| _, err = client.Logical().Unwrap(cluster.RootToken) |
| if err == nil { |
| t.Fatal("expected error") |
| } |
| // Root token isn't a wrapping token |
| _, err = client.Logical().Write("sys/wrapping/unwrap", nil) |
| if err == nil { |
| t.Fatal("expected error") |
| } |
| // Not supplied |
| _, err = client.Logical().Write("sys/wrapping/unwrap", map[string]interface{}{ |
| "foo": "bar", |
| }) |
| if err == nil { |
| t.Fatal("expected error") |
| } |
| // Nonexistent token isn't a wrapping token |
| _, err = client.Logical().Write("sys/wrapping/unwrap", map[string]interface{}{ |
| "token": "bar", |
| }) |
| if err == nil { |
| t.Fatal("expected error") |
| } |
| |
| // |
| // Test lookup |
| // |
| |
| // Create a wrapping token |
| secret, err := client.Logical().Read("secret/foo") |
| if err != nil { |
| t.Fatal(err) |
| } |
| if secret == nil || secret.WrapInfo == nil { |
| t.Fatal("secret or wrap info is nil") |
| } |
| wrapInfo := secret.WrapInfo |
| |
| // Test this twice to ensure no ill effect to the wrapping token as a result of the lookup |
| for i := 0; i < 2; i++ { |
| secret, err = client.Logical().Write("sys/wrapping/lookup", map[string]interface{}{ |
| "token": wrapInfo.Token, |
| }) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if secret == nil || secret.Data == nil { |
| t.Fatal("secret or secret data is nil") |
| } |
| creationTTL, _ := secret.Data["creation_ttl"].(json.Number).Int64() |
| if int(creationTTL) != wrapInfo.TTL { |
| t.Fatalf("mismatched ttls: %d vs %d", creationTTL, wrapInfo.TTL) |
| } |
| if secret.Data["creation_time"].(string) != wrapInfo.CreationTime.Format(time.RFC3339Nano) { |
| t.Fatalf("mismatched creation times: %q vs %q", secret.Data["creation_time"].(string), wrapInfo.CreationTime.Format(time.RFC3339Nano)) |
| } |
| } |
| |
| // |
| // Test unwrap |
| // |
| |
| // Create a wrapping token |
| secret, err = client.Logical().Read("secret/foo") |
| if err != nil { |
| t.Fatal(err) |
| } |
| if secret == nil || secret.WrapInfo == nil { |
| t.Fatal("secret or wrap info is nil") |
| } |
| wrapInfo = secret.WrapInfo |
| |
| // Test unwrap via the client token |
| client.SetToken(wrapInfo.Token) |
| secret, err = client.Logical().Write("sys/wrapping/unwrap", nil) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if secret.Warnings != nil { |
| t.Fatalf("Warnings found: %v", secret.Warnings) |
| } |
| if secret == nil || secret.Data == nil { |
| t.Fatal("secret or secret data is nil") |
| } |
| ret1 := secret |
| // Should be expired and fail |
| _, err = client.Logical().Write("sys/wrapping/unwrap", nil) |
| if err == nil { |
| t.Fatal("expected err") |
| } |
| |
| // Create a wrapping token |
| client.SetToken(cluster.RootToken) |
| secret, err = client.Logical().Read("secret/foo") |
| if err != nil { |
| t.Fatal(err) |
| } |
| if secret == nil || secret.WrapInfo == nil { |
| t.Fatal("secret or wrap info is nil") |
| } |
| wrapInfo = secret.WrapInfo |
| |
| // Test as a separate token |
| secret, err = client.Logical().Write("sys/wrapping/unwrap", map[string]interface{}{ |
| "token": wrapInfo.Token, |
| }) |
| if err != nil { |
| t.Fatal(err) |
| } |
| ret2 := secret |
| // Should be expired and fail |
| _, err = client.Logical().Write("sys/wrapping/unwrap", map[string]interface{}{ |
| "token": wrapInfo.Token, |
| }) |
| if err == nil { |
| t.Fatal("expected err") |
| } |
| |
| // Create a wrapping token |
| secret, err = client.Logical().Read("secret/foo") |
| if err != nil { |
| t.Fatal(err) |
| } |
| if secret == nil || secret.WrapInfo == nil { |
| t.Fatal("secret or wrap info is nil") |
| } |
| wrapInfo = secret.WrapInfo |
| |
| // Read response directly |
| client.SetToken(wrapInfo.Token) |
| secret, err = client.Logical().Read("cubbyhole/response") |
| if err != nil { |
| t.Fatal(err) |
| } |
| ret3 := secret |
| // Should be expired and fail |
| _, err = client.Logical().Write("cubbyhole/response", nil) |
| if err == nil { |
| t.Fatal("expected err") |
| } |
| |
| // Create a wrapping token |
| client.SetToken(cluster.RootToken) |
| secret, err = client.Logical().Read("secret/foo") |
| if err != nil { |
| t.Fatal(err) |
| } |
| if secret == nil || secret.WrapInfo == nil { |
| t.Fatal("secret or wrap info is nil") |
| } |
| wrapInfo = secret.WrapInfo |
| |
| // Read via Unwrap method |
| secret, err = client.Logical().Unwrap(wrapInfo.Token) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if secret.Warnings != nil { |
| t.Fatalf("Warnings found: %v", secret.Warnings) |
| } |
| ret4 := secret |
| // Should be expired and fail |
| _, err = client.Logical().Unwrap(wrapInfo.Token) |
| if err == nil { |
| t.Fatal("expected err") |
| } |
| |
| if !reflect.DeepEqual(ret1.Data, map[string]interface{}{ |
| "zip": "zap", |
| }) { |
| t.Fatalf("ret1 data did not match expected: %#v", ret1.Data) |
| } |
| if !reflect.DeepEqual(ret2.Data, map[string]interface{}{ |
| "zip": "zap", |
| }) { |
| t.Fatalf("ret2 data did not match expected: %#v", ret2.Data) |
| } |
| var ret3Secret api.Secret |
| err = jsonutil.DecodeJSON([]byte(ret3.Data["response"].(string)), &ret3Secret) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if !reflect.DeepEqual(ret3Secret.Data, map[string]interface{}{ |
| "zip": "zap", |
| }) { |
| t.Fatalf("ret3 data did not match expected: %#v", ret3Secret.Data) |
| } |
| if !reflect.DeepEqual(ret4.Data, map[string]interface{}{ |
| "zip": "zap", |
| }) { |
| t.Fatalf("ret4 data did not match expected: %#v", ret4.Data) |
| } |
| |
| // |
| // Custom wrapping |
| // |
| |
| client.SetToken(cluster.RootToken) |
| data := map[string]interface{}{ |
| "zip": "zap", |
| "three": json.Number("2"), |
| } |
| |
| // Don't set a request TTL on that path, should fail |
| client.SetWrappingLookupFunc(func(operation, path string) string { |
| return "" |
| }) |
| secret, err = client.Logical().Write("sys/wrapping/wrap", data) |
| if err == nil { |
| t.Fatal("expected error") |
| } |
| |
| // Re-set the lookup function |
| client.SetWrappingLookupFunc(func(operation, path string) string { |
| if operation == "GET" && path == "secret/foo" { |
| return "5m" |
| } |
| |
| return api.DefaultWrappingLookupFunc(operation, path) |
| }) |
| secret, err = client.Logical().Write("sys/wrapping/wrap", data) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if secret.Warnings != nil { |
| t.Fatalf("Warnings found: %v", secret.Warnings) |
| } |
| secret, err = client.Logical().Unwrap(secret.WrapInfo.Token) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if secret.Warnings != nil { |
| t.Fatalf("Warnings found: %v", secret.Warnings) |
| } |
| if !reflect.DeepEqual(data, secret.Data) { |
| t.Fatalf("custom wrap did not match expected: %#v", secret.Data) |
| } |
| |
| // |
| // Test rewrap |
| // |
| |
| // Create a wrapping token |
| secret, err = client.Logical().Read("secret/foo") |
| if err != nil { |
| t.Fatal(err) |
| } |
| if secret == nil || secret.WrapInfo == nil { |
| t.Fatal("secret or wrap info is nil") |
| } |
| wrapInfo = secret.WrapInfo |
| |
| // Check for correct CreationPath before rewrap |
| if wrapInfo.CreationPath != "secret/foo" { |
| t.Fatalf("error on wrapInfo.CreationPath: expected: secret/foo, got: %s", wrapInfo.CreationPath) |
| } |
| |
| // Test rewrapping |
| secret, err = client.Logical().Write("sys/wrapping/rewrap", map[string]interface{}{ |
| "token": wrapInfo.Token, |
| }) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if secret.Warnings != nil { |
| t.Fatalf("Warnings found: %v", secret.Warnings) |
| } |
| |
| // Check for correct Creation path after rewrap |
| if wrapInfo.CreationPath != "secret/foo" { |
| t.Fatalf("error on wrapInfo.CreationPath: expected: secret/foo, got: %s", wrapInfo.CreationPath) |
| } |
| |
| // Should be expired and fail |
| _, err = client.Logical().Write("sys/wrapping/unwrap", map[string]interface{}{ |
| "token": wrapInfo.Token, |
| }) |
| if err == nil { |
| t.Fatal("expected err") |
| } |
| |
| // Attempt unwrapping the rewrapped token |
| wrapToken := secret.WrapInfo.Token |
| secret, err = client.Logical().Unwrap(wrapToken) |
| if err != nil { |
| t.Fatal(err) |
| } |
| // Should be expired and fail |
| _, err = client.Logical().Unwrap(wrapToken) |
| if err == nil { |
| t.Fatal("expected err") |
| } |
| |
| if !reflect.DeepEqual(secret.Data, map[string]interface{}{ |
| "zip": "zap", |
| }) { |
| t.Fatalf("secret data did not match expected: %#v", secret.Data) |
| } |
| |
| // Ensure that wrapping lookup without a client token responds correctly |
| client.ClearToken() |
| secret, err = client.Logical().Read("sys/wrapping/lookup") |
| if secret != nil { |
| t.Fatalf("expected no response: %#v", secret) |
| } |
| |
| if err == nil { |
| t.Fatal("expected error") |
| } |
| |
| var respError *api.ResponseError |
| if errors.As(err, &respError); respError.StatusCode != 403 { |
| t.Fatalf("expected 403 response, actual: %d", respError.StatusCode) |
| } |
| } |