Skip to content

Commit 0acfbc5

Browse files
committed
Add a config path to update key settings
This path can be used to update the transparency log address of existing keys.
1 parent e27e427 commit 0acfbc5

File tree

5 files changed

+197
-7
lines changed

5 files changed

+197
-7
lines changed

docs/http-api.md

+36-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Since it is possible to mount secret backends at any location, please update you
88
* [List Keys](#list-keys)
99
* [Delete Key](#delete-key)
1010
* [Export Key](#export-key)
11+
* [Update Key Configuration](#update-key-configuration)
1112
* [Decrypt Data](#decrypt-data)
1213
* [Sign Data](#sign-data)
1314
* [Verify Signed Data](#verify-signed-data)
@@ -40,7 +41,7 @@ it must be deleted first.
4041

4142
- `exportable` `(bool: false)` – Specifies if the raw key is exportable.
4243

43-
- `transparency_log_address` `(string: "")` – Specifies the [Rekor transparency log](https://github.com/sigstore/rekor) address to publish the signature.
44+
- `transparency_log_address` `(string: "")` – Specifies the [Rekor transparency log](https://github.com/sigstore/rekor) address used to publish the signatures.
4445

4546
### Sample Payload
4647

@@ -194,6 +195,40 @@ $ curl \
194195
}
195196
```
196197

198+
## Update Key Configuration
199+
200+
This endpoint allows tuning configuration values for a given key.
201+
(These values are returned during a read operation on the named key.)
202+
203+
204+
| Method | Path | Produces |
205+
|:-------|:-------------------------|:-------------------|
206+
| `POST` | `/gpg/keys/:name/config` | `204 (empty body)` |
207+
208+
### Parameters
209+
210+
- `name` `(string: <required>)` – Specifies the name of the key configure.
211+
212+
- `transparency_log_address` `(string: "")` – Specifies the [Rekor transparency log](https://github.com/sigstore/rekor) address used to publish the signatures.
213+
214+
### Sample payload
215+
216+
```json
217+
{
218+
"transparency_log_address": "https://rekor.example.com"
219+
}
220+
```
221+
222+
### Sample request
223+
224+
```
225+
$ curl \
226+
--header "X-Vault-Token: ..." \
227+
--request POST \
228+
--data @payload.json \
229+
https://vault.example.com/v1/gpg/keys/my-key/config
230+
```
231+
197232
## Sign Data
198233

199234
This endpoint returns the signature of the given data using the

gpg/backend.go

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ func Backend() *backend {
3030
pathVerify(&b),
3131
pathDecrypt(&b),
3232
pathShowSessionKey(&b),
33+
pathConfig(&b),
3334
},
3435
PathsSpecial: &logical.Paths{
3536
SealWrapStorage: []string{

gpg/path_config.go

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package gpg
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"github.com/hashicorp/vault/sdk/framework"
7+
"github.com/hashicorp/vault/sdk/helper/locksutil"
8+
"github.com/hashicorp/vault/sdk/logical"
9+
)
10+
11+
func pathConfig(b *backend) *framework.Path {
12+
return &framework.Path{
13+
Pattern: "keys/" + framework.GenericNameRegex("name") + "/config",
14+
Fields: map[string]*framework.FieldSchema{
15+
"name": {
16+
Type: framework.TypeString,
17+
Description: "Name of the key",
18+
},
19+
20+
"transparency_log_address": {
21+
Type: framework.TypeString,
22+
Description: "Address of a Rekor transparency log to publish the signatures.",
23+
},
24+
},
25+
Operations: map[logical.Operation]framework.OperationHandler{
26+
logical.UpdateOperation: &framework.PathOperation{
27+
Callback: b.pathConfigWrite,
28+
},
29+
},
30+
HelpSynopsis: pathConfigHelpSyn,
31+
HelpDescription: pathConfigHelpDesc,
32+
}
33+
}
34+
35+
func (b *backend) pathConfigWrite(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
36+
name := data.Get("name").(string)
37+
38+
lock := locksutil.LockForKey(b.keyLocks, name)
39+
lock.Lock()
40+
defer lock.Unlock()
41+
42+
entry, err := b.key(ctx, req.Storage, name)
43+
if err != nil {
44+
return nil, err
45+
}
46+
if entry == nil {
47+
return logical.ErrorResponse(fmt.Sprintf("no existing key named %s could be found", name)), logical.ErrInvalidRequest
48+
}
49+
50+
persistNeeded := false
51+
logAddress, ok := data.GetOk("transparency_log_address")
52+
if ok {
53+
entry.TransparencyLogAddress = logAddress.(string)
54+
persistNeeded = true
55+
}
56+
57+
if !persistNeeded {
58+
return nil, nil
59+
}
60+
61+
return &logical.Response{}, b.storeKeyEntry(ctx, req.Storage, name, entry)
62+
}
63+
64+
const pathConfigHelpSyn = "Configure a named GPG key"
65+
const pathConfigHelpDesc = "This path is used to configure the named key."

gpg/path_config_test.go

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package gpg
2+
3+
import (
4+
"context"
5+
"github.com/hashicorp/vault/sdk/logical"
6+
"testing"
7+
)
8+
9+
func TestGPG_SetKeyConfig(t *testing.T) {
10+
storage := &logical.InmemStorage{}
11+
b := Backend()
12+
13+
req := &logical.Request{
14+
Storage: storage,
15+
Operation: logical.UpdateOperation,
16+
Path: "keys/test",
17+
Data: map[string]interface{}{
18+
"real_name": "Vault",
19+
"email": "[email protected]",
20+
"key_bits": 2048,
21+
"generate": true,
22+
},
23+
}
24+
_, err := b.HandleRequest(context.Background(), req)
25+
if err != nil {
26+
t.Fatal(err)
27+
}
28+
29+
updateTransparencyLogAddress := func(keyName string, address string) {
30+
req := &logical.Request{
31+
Storage: storage,
32+
Operation: logical.UpdateOperation,
33+
Path: "keys/" + keyName + "/config",
34+
Data: map[string]interface{}{
35+
"transparency_log_address": address,
36+
},
37+
}
38+
_, err := b.HandleRequest(context.Background(), req)
39+
if err != nil {
40+
t.Fatal(err)
41+
}
42+
}
43+
44+
checkTransparencyLogAddress := func(keyName string, expectedAddress string) {
45+
req := &logical.Request{
46+
Storage: storage,
47+
Operation: logical.ReadOperation,
48+
Path: "keys/" + keyName,
49+
}
50+
resp, err := b.HandleRequest(context.Background(), req)
51+
if err != nil {
52+
t.Fatal(err)
53+
}
54+
55+
respLogAddress := resp.Data["transparency_log_address"]
56+
57+
if respLogAddress != expectedAddress {
58+
t.Errorf("no received the expected address %s, got %s", expectedAddress, respLogAddress)
59+
}
60+
}
61+
62+
checkTransparencyLogAddress("test", "")
63+
updateTransparencyLogAddress("test", "https://rekor.example.com")
64+
checkTransparencyLogAddress("test", "https://rekor.example.com")
65+
updateTransparencyLogAddress("test", "")
66+
checkTransparencyLogAddress("test", "")
67+
}
68+
69+
func TestGPG_AttemptToSetConfigOfAnUnknownKey(t *testing.T) {
70+
storage := &logical.InmemStorage{}
71+
b := Backend()
72+
73+
req := &logical.Request{
74+
Storage: storage,
75+
Operation: logical.UpdateOperation,
76+
Path: "keys/test/config",
77+
Data: map[string]interface{}{
78+
"transparency_log_address": "",
79+
},
80+
}
81+
resp, err := b.HandleRequest(context.Background(), req)
82+
if err == nil {
83+
t.Fatal("expected an error because the key does not exist")
84+
}
85+
if !resp.IsError() {
86+
t.Fatal("expected an response error because the key does not exist")
87+
}
88+
}

gpg/path_keys.go

+7-6
Original file line numberDiff line numberDiff line change
@@ -237,18 +237,19 @@ func (b *backend) pathKeyCreate(ctx context.Context, req *logical.Request, data
237237
}
238238
}
239239

240-
entry, err := logical.StorageEntryJSON("key/"+name, &keyEntry{
240+
return nil, b.storeKeyEntry(ctx, req.Storage, name, &keyEntry{
241241
SerializedKey: buf.Bytes(),
242242
Exportable: exportable,
243243
TransparencyLogAddress: transparencyLogAddress,
244244
})
245+
}
246+
247+
func (b *backend) storeKeyEntry(ctx context.Context, storage logical.Storage, name string, keyEntry *keyEntry) error {
248+
entry, err := logical.StorageEntryJSON("key/"+name, keyEntry)
245249
if err != nil {
246-
return nil, err
250+
return err
247251
}
248-
if err := req.Storage.Put(ctx, entry); err != nil {
249-
return nil, err
250-
}
251-
return nil, nil
252+
return storage.Put(ctx, entry)
252253
}
253254

254255
func (b *backend) pathKeyDelete(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {

0 commit comments

Comments
 (0)