Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add HTTP PATCH support to KV #12687

Merged
merged 35 commits into from
Oct 13, 2021
Merged
Changes from 1 commit
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
e64bfec
handle HTTP PATCH requests as logical.PatchOperation
ccapurso Sep 20, 2021
11eaec8
update go.mod, go.sum
ccapurso Sep 20, 2021
10ebff6
a nil response for logical.PatchOperation should result in 404
ccapurso Sep 20, 2021
0d6fab2
respond with 415 for incorrect MIME type in PATCH Content-Type header
ccapurso Sep 20, 2021
1ab8d4c
add abstraction to handle PatchOperation requests
ccapurso Sep 20, 2021
dc649aa
add ACLs for patch
raskchanky Sep 20, 2021
8607481
Merge remote-tracking branch 'origin/kv-patch' into kv-patch
raskchanky Sep 20, 2021
6257873
Adding JSON Merge support to the API client
raskchanky Sep 21, 2021
8bd064d
add HTTP PATCH tests to check high level response logic
ccapurso Sep 22, 2021
8bab71d
add permission-based 'kv patch' tests in prep to add HTTP PATCH
ccapurso Sep 23, 2021
67f7a60
adding more 'kv patch' CLI command tests
ccapurso Sep 24, 2021
ecb1efe
fix TestHandler_Patch_NotFound
ccapurso Sep 27, 2021
0bfec35
Fix TestKvPatchCommand_StdinValue
ccapurso Sep 27, 2021
6b9aeca
add audit log test for HTTP PATCH
ccapurso Sep 27, 2021
4f9b112
patch CLI changes
raskchanky Sep 28, 2021
0dd646a
Merge remote-tracking branch 'origin/kv-patch' into kv-patch
raskchanky Sep 28, 2021
aeeff0e
add patch CLI tests
raskchanky Sep 29, 2021
af0e962
change JSONMergePatch func to accept a ctx
ccapurso Sep 30, 2021
07ae4ea
fix TestKVPatchCommand_RWMethodNotExists and TestKVPatchCommand_RWMet…
ccapurso Sep 30, 2021
d18f9e3
go fmt
ccapurso Sep 30, 2021
1cd8bf4
add a test to verify patching works by default with the root token
raskchanky Oct 1, 2021
a8c7e13
add changelog entry
ccapurso Oct 4, 2021
723fb6b
get vault-plugin-secrets-kv@add-patch-support
ccapurso Oct 7, 2021
f309ea8
PR feedback
raskchanky Oct 7, 2021
eb00941
Merge remote-tracking branch 'origin/kv-patch' into kv-patch
raskchanky Oct 7, 2021
de33fd6
reorder some imports; go fmt
ccapurso Oct 7, 2021
2fe0402
add doc comment for HandlePatchOperation
ccapurso Oct 7, 2021
90c5725
add [email protected] to go.mod
ccapurso Oct 7, 2021
5d04fff
remove unnecessary cancelFunc for WriteBytes
ccapurso Oct 7, 2021
29b9d69
remove default for -method
raskchanky Oct 7, 2021
8f908d5
Merge remote-tracking branch 'origin/kv-patch' into kv-patch
raskchanky Oct 7, 2021
16135ef
use stable version of json-patch; go mod tidy
ccapurso Oct 7, 2021
6fcc723
more PR feedback
raskchanky Oct 8, 2021
802fd02
Merge branch 'main' into kv-patch
ccapurso Oct 11, 2021
aee77e3
temp go get vault-plugin-secrets-kv@master until official release
ccapurso Oct 13, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
remove default for -method
raskchanky committed Oct 7, 2021
commit 29b9d6964b2d59fa33070840367154de442a72cf
15 changes: 8 additions & 7 deletions command/kv_patch.go
Original file line number Diff line number Diff line change
@@ -91,9 +91,8 @@ func (c *KVPatchCommand) Flags() *FlagSets {
})

f.StringVar(&StringVar{
Name: "method",
Target: &c.flagMethod,
Default: "patch",
Name: "method",
Target: &c.flagMethod,
Usage: `Specifies which method of patching to use. If set to "patch", then
an HTTP PATCH request will be issued. This is the default. If set to "rw",
then a read will be performed, then a local update, followed by a remote
@@ -175,7 +174,9 @@ func (c *KVPatchCommand) Run(args []string) int {
case "rw":
secret, code = c.readThenWrite(client, path, newData)
case "patch":
secret, code = c.mergePatch(client, path, newData)
secret, code = c.mergePatch(client, path, newData, false)
case "":
secret, code = c.mergePatch(client, path, newData, true)
default:
c.UI.Error(fmt.Sprintf("Unsupported method provided to -method flag: %s", c.flagMethod))
return 2
@@ -269,7 +270,7 @@ func (c *KVPatchCommand) readThenWrite(client *api.Client, path string, newData
return secret, 0
}

func (c *KVPatchCommand) mergePatch(client *api.Client, path string, newData map[string]interface{}) (*api.Secret, int) {
func (c *KVPatchCommand) mergePatch(client *api.Client, path string, newData map[string]interface{}, rwFallback bool) (*api.Secret, int) {
data := map[string]interface{}{
"data": newData,
"options": map[string]interface{}{},
@@ -282,8 +283,8 @@ func (c *KVPatchCommand) mergePatch(client *api.Client, path string, newData map
secret, err := client.Logical().JSONMergePatch(context.Background(), path, data)
if err != nil {
// If it's a 403, that probably means they don't have the patch capability in their policy. Fall back to
// the old way of doing it.
if re, ok := err.(*api.ResponseError); ok && re.StatusCode == 403 {
// the old way of doing it if the user didn't specify a -method. If they did, and it was "patch", then just error.
if re, ok := err.(*api.ResponseError); ok && re.StatusCode == 403 && rwFallback {
c.UI.Warn(fmt.Sprintf("Data was written to %s but we recommend that you add the \"patch\" capability to your ACL policy in order to use HTTP PATCH in the future.", path))
return c.readThenWrite(client, path, newData)
}
95 changes: 61 additions & 34 deletions command/kv_test.go
Original file line number Diff line number Diff line change
@@ -980,48 +980,75 @@ func TestKVPatchCommand_Methods(t *testing.T) {
}

func TestKVPatchCommand_403Fallback(t *testing.T) {
t.Parallel()
client, closer := testVaultServer(t)
defer closer()

if err := client.Sys().Mount("kv/", &api.MountInput{
Type: "kv-v2",
}); err != nil {
t.Fatalf("kv-v2 mount attempt failed - err: %#v\n", err)
cases := []struct {
name string
args []string
expected string
code int
}{
// if no -method is specified, and patch fails, it should fall back to rw and succeed
{
"unspecified",
[]string{"kv/foo", "bar=quux"},
`add the "patch" capability to your ACL policy`,
0,
},
// if -method=patch is specified, and patch fails, it should not fall back, and just error
{
"specifying patch",
[]string{"-method", "patch", "kv/foo", "bar=quux"},
"permission denied",
2,
},
}

// create a policy without patch capability
policy := `path "kv/*" { capabilities = ["create", "update", "read"] }`
secretAuth, err := createTokenForPolicy(t, client, policy)
if err != nil {
t.Fatalf("policy/token creation failed for policy %s, err: %#v\n", policy, err)
}
for _, tc := range cases {
tc := tc

kvClient, err := client.Clone()
if err != nil {
t.Fatal(err)
}
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
client, closer := testVaultServer(t)
defer closer()

if err := client.Sys().Mount("kv/", &api.MountInput{
Type: "kv-v2",
}); err != nil {
t.Fatalf("kv-v2 mount attempt failed - err: %#v\n", err)
}

kvClient.SetToken(secretAuth.ClientToken)
// create a policy without patch capability
policy := `path "kv/*" { capabilities = ["create", "update", "read"] }`
secretAuth, err := createTokenForPolicy(t, client, policy)
if err != nil {
t.Fatalf("policy/token creation failed for policy %s, err: %#v\n", policy, err)
}

// Write a value then attempt to patch it. It should fail, then fall back to the old method
_, err = kvClient.Logical().Write("kv/data/foo", map[string]interface{}{"data": map[string]interface{}{"bar": "baz"}})
if err != nil {
t.Fatal(err)
}
kvClient, err := client.Clone()
if err != nil {
t.Fatal(err)
}

ui, cmd := testKVPatchCommand(t)
cmd.client = kvClient
code := cmd.Run([]string{"kv/foo", "bar=quux"})
kvClient.SetToken(secretAuth.ClientToken)

if code != 0 {
t.Fatalf("expected code to be 0 but was %d", code)
}
// Write a value then attempt to patch it
_, err = kvClient.Logical().Write("kv/data/foo", map[string]interface{}{"data": map[string]interface{}{"bar": "baz"}})
if err != nil {
t.Fatal(err)
}

combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
expected := `add the "patch" capability to your ACL policy`
if !strings.Contains(combined, expected) {
t.Errorf("expected %q to contain %q", combined, expected)
ui, cmd := testKVPatchCommand(t)
cmd.client = kvClient
code := cmd.Run(tc.args)

if code != tc.code {
t.Fatalf("expected code to be %d but was %d", tc.code, code)
}

combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
if !strings.Contains(combined, tc.expected) {
t.Errorf("expected %q to contain %q", combined, tc.expected)
}
})
}
}