Allow callers to choose the entropy source for the random endpoints. (#15213)
* Allow callers to choose the entropy source for the random endpoints * Put source in the URL for sys as well * changelog * docs * Fix unit tests, and add coverage * refactor to use a single common implementation * Update documentation * one more tweak * more cleanup * Readd lost test expected code * fmt
This commit is contained in:
parent
6d9888e09b
commit
bef350c916
|
@ -2,21 +2,14 @@ package transit
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
uuid "github.com/hashicorp/go-uuid"
|
||||
"github.com/hashicorp/vault/helper/random"
|
||||
"github.com/hashicorp/vault/sdk/framework"
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
)
|
||||
|
||||
const maxBytes = 128 * 1024
|
||||
|
||||
func (b *backend) pathRandom() *framework.Path {
|
||||
return &framework.Path{
|
||||
Pattern: "random" + framework.OptionalParamRegex("urlbytes"),
|
||||
Pattern: "random(/" + framework.GenericNameRegex("source") + ")?" + framework.OptionalParamRegex("urlbytes"),
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"urlbytes": {
|
||||
Type: framework.TypeString,
|
||||
|
@ -34,6 +27,12 @@ func (b *backend) pathRandom() *framework.Path {
|
|||
Default: "base64",
|
||||
Description: `Encoding format to use. Can be "hex" or "base64". Defaults to "base64".`,
|
||||
},
|
||||
|
||||
"source": {
|
||||
Type: framework.TypeString,
|
||||
Default: "platform",
|
||||
Description: `Which system to source random data from, ether "platform", "seal", or "all".`,
|
||||
},
|
||||
},
|
||||
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
|
@ -45,55 +44,8 @@ func (b *backend) pathRandom() *framework.Path {
|
|||
}
|
||||
}
|
||||
|
||||
func (b *backend) pathRandomWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
bytes := 0
|
||||
var err error
|
||||
strBytes := d.Get("urlbytes").(string)
|
||||
if strBytes != "" {
|
||||
bytes, err = strconv.Atoi(strBytes)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(fmt.Sprintf("error parsing url-set byte count: %s", err)), nil
|
||||
}
|
||||
} else {
|
||||
bytes = d.Get("bytes").(int)
|
||||
}
|
||||
format := d.Get("format").(string)
|
||||
|
||||
if bytes < 1 {
|
||||
return logical.ErrorResponse(`"bytes" cannot be less than 1`), nil
|
||||
}
|
||||
|
||||
if bytes > maxBytes {
|
||||
return logical.ErrorResponse(`"bytes" should be less than %d`, maxBytes), nil
|
||||
}
|
||||
|
||||
switch format {
|
||||
case "hex":
|
||||
case "base64":
|
||||
default:
|
||||
return logical.ErrorResponse("unsupported encoding format %q; must be \"hex\" or \"base64\"", format), nil
|
||||
}
|
||||
|
||||
randBytes, err := uuid.GenerateRandomBytes(bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var retStr string
|
||||
switch format {
|
||||
case "hex":
|
||||
retStr = hex.EncodeToString(randBytes)
|
||||
case "base64":
|
||||
retStr = base64.StdEncoding.EncodeToString(randBytes)
|
||||
}
|
||||
|
||||
// Generate the response
|
||||
resp := &logical.Response{
|
||||
Data: map[string]interface{}{
|
||||
"random_bytes": retStr,
|
||||
},
|
||||
}
|
||||
return resp, nil
|
||||
func (b *backend) pathRandomWrite(_ context.Context, _ *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
return random.HandleRandomAPI(d, b.GetRandomReader())
|
||||
}
|
||||
|
||||
const pathRandomHelpSyn = `Generate random bytes`
|
||||
|
|
|
@ -4,9 +4,11 @@ import (
|
|||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/vault/helper/random"
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
)
|
||||
|
||||
|
@ -81,24 +83,42 @@ func TestTransit_Random(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// Test defaults
|
||||
doRequest(req, false, "base64", 32)
|
||||
for _, source := range []string{"", "platform", "seal", "all"} {
|
||||
req.Data["source"] = source
|
||||
req.Data["bytes"] = 32
|
||||
req.Data["format"] = "base64"
|
||||
req.Path = "random"
|
||||
// Test defaults
|
||||
doRequest(req, false, "base64", 32)
|
||||
|
||||
// Test size selection in the path
|
||||
req.Path = "random/24"
|
||||
req.Data["format"] = "hex"
|
||||
doRequest(req, false, "hex", 24)
|
||||
// Test size selection in the path
|
||||
req.Path = "random/24"
|
||||
req.Data["format"] = "hex"
|
||||
doRequest(req, false, "hex", 24)
|
||||
|
||||
// Test bad input/format
|
||||
req.Path = "random"
|
||||
req.Data["format"] = "base92"
|
||||
doRequest(req, true, "", 0)
|
||||
if source != "" {
|
||||
// Test source selection in the path
|
||||
req.Path = fmt.Sprintf("random/%s", source)
|
||||
req.Data["format"] = "hex"
|
||||
doRequest(req, false, "hex", 32)
|
||||
|
||||
req.Data["format"] = "hex"
|
||||
req.Data["bytes"] = -1
|
||||
doRequest(req, true, "", 0)
|
||||
req.Path = fmt.Sprintf("random/%s/24", source)
|
||||
req.Data["format"] = "hex"
|
||||
doRequest(req, false, "hex", 24)
|
||||
}
|
||||
|
||||
req.Data["format"] = "hex"
|
||||
req.Data["bytes"] = maxBytes + 1
|
||||
doRequest(req, true, "", 0)
|
||||
// Test bad input/format
|
||||
req.Path = "random"
|
||||
req.Data["format"] = "base92"
|
||||
doRequest(req, true, "", 0)
|
||||
|
||||
req.Data["format"] = "hex"
|
||||
req.Data["bytes"] = -1
|
||||
doRequest(req, true, "", 0)
|
||||
|
||||
req.Data["format"] = "hex"
|
||||
req.Data["bytes"] = random.APIMaxBytes + 1
|
||||
|
||||
doRequest(req, true, "", 0)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
```release-note:improvement
|
||||
core,transit: Allow callers to choose random byte source including entropy augmentation sources for the sys/tools/random and transit/random endpoints.
|
||||
```
|
|
@ -0,0 +1,115 @@
|
|||
package random
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
|
||||
"github.com/hashicorp/go-uuid"
|
||||
"github.com/hashicorp/vault/sdk/framework"
|
||||
"github.com/hashicorp/vault/sdk/helper/xor"
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
)
|
||||
|
||||
const APIMaxBytes = 128 * 1024
|
||||
|
||||
func HandleRandomAPI(d *framework.FieldData, additionalSource io.Reader) (*logical.Response, error) {
|
||||
bytes := 0
|
||||
// Parsing is convoluted here, but allows operators to ACL both source and byte count
|
||||
maybeUrlBytes := d.Raw["urlbytes"]
|
||||
maybeSource := d.Raw["source"]
|
||||
source := "platform"
|
||||
var err error
|
||||
if maybeSource == "" {
|
||||
bytes = d.Get("bytes").(int)
|
||||
} else if maybeUrlBytes == "" && isValidSource(maybeSource.(string)) {
|
||||
source = maybeSource.(string)
|
||||
bytes = d.Get("bytes").(int)
|
||||
} else if maybeUrlBytes == "" {
|
||||
bytes, err = strconv.Atoi(maybeSource.(string))
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(fmt.Sprintf("error parsing url-set byte count: %s", err)), nil
|
||||
}
|
||||
} else {
|
||||
source = maybeSource.(string)
|
||||
bytes, err = strconv.Atoi(maybeUrlBytes.(string))
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(fmt.Sprintf("error parsing url-set byte count: %s", err)), nil
|
||||
}
|
||||
}
|
||||
format := d.Get("format").(string)
|
||||
|
||||
if bytes < 1 {
|
||||
return logical.ErrorResponse(`"bytes" cannot be less than 1`), nil
|
||||
}
|
||||
|
||||
if bytes > APIMaxBytes {
|
||||
return logical.ErrorResponse(`"bytes" should be less than %d`, APIMaxBytes), nil
|
||||
}
|
||||
|
||||
switch format {
|
||||
case "hex":
|
||||
case "base64":
|
||||
default:
|
||||
return logical.ErrorResponse("unsupported encoding format %q; must be \"hex\" or \"base64\"", format), nil
|
||||
}
|
||||
|
||||
var randBytes []byte
|
||||
var warning string
|
||||
switch source {
|
||||
case "", "platform":
|
||||
randBytes, err = uuid.GenerateRandomBytes(bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "seal":
|
||||
if rand.Reader == additionalSource {
|
||||
warning = "no seal/entropy augmentation available, using platform entropy source"
|
||||
}
|
||||
randBytes, err = uuid.GenerateRandomBytesWithReader(bytes, additionalSource)
|
||||
case "all":
|
||||
var sealBytes []byte
|
||||
sealBytes, err = uuid.GenerateRandomBytesWithReader(bytes, additionalSource)
|
||||
if err == nil {
|
||||
randBytes, err = uuid.GenerateRandomBytes(bytes)
|
||||
if err == nil {
|
||||
randBytes, err = xor.XORBytes(sealBytes, randBytes)
|
||||
}
|
||||
}
|
||||
default:
|
||||
return logical.ErrorResponse("unsupported entropy source %q; must be \"platform\" or \"seal\", or \"all\"", source), nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var retStr string
|
||||
switch format {
|
||||
case "hex":
|
||||
retStr = hex.EncodeToString(randBytes)
|
||||
case "base64":
|
||||
retStr = base64.StdEncoding.EncodeToString(randBytes)
|
||||
}
|
||||
|
||||
// Generate the response
|
||||
resp := &logical.Response{
|
||||
Data: map[string]interface{}{
|
||||
"random_bytes": retStr,
|
||||
},
|
||||
}
|
||||
if warning != "" {
|
||||
resp.Warnings = []string{warning}
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func isValidSource(s string) bool {
|
||||
switch s {
|
||||
case "", "platform", "seal", "all":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -29,7 +29,6 @@ import (
|
|||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/hashicorp/go-secure-stdlib/parseutil"
|
||||
"github.com/hashicorp/go-secure-stdlib/strutil"
|
||||
"github.com/hashicorp/go-uuid"
|
||||
"github.com/hashicorp/vault/helper/hostutil"
|
||||
"github.com/hashicorp/vault/helper/identity"
|
||||
"github.com/hashicorp/vault/helper/metricsutil"
|
||||
|
@ -3610,55 +3609,8 @@ func (b *SystemBackend) pathHashWrite(ctx context.Context, req *logical.Request,
|
|||
return resp, nil
|
||||
}
|
||||
|
||||
func (b *SystemBackend) pathRandomWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
bytes := 0
|
||||
var err error
|
||||
strBytes := d.Get("urlbytes").(string)
|
||||
if strBytes != "" {
|
||||
bytes, err = strconv.Atoi(strBytes)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(fmt.Sprintf("error parsing url-set byte count: %s", err)), nil
|
||||
}
|
||||
} else {
|
||||
bytes = d.Get("bytes").(int)
|
||||
}
|
||||
format := d.Get("format").(string)
|
||||
|
||||
if bytes < 1 {
|
||||
return logical.ErrorResponse(`"bytes" cannot be less than 1`), nil
|
||||
}
|
||||
|
||||
if bytes > maxBytes {
|
||||
return logical.ErrorResponse(`"bytes" should be less than %d`, maxBytes), nil
|
||||
}
|
||||
|
||||
switch format {
|
||||
case "hex":
|
||||
case "base64":
|
||||
default:
|
||||
return logical.ErrorResponse("unsupported encoding format %q; must be \"hex\" or \"base64\"", format), nil
|
||||
}
|
||||
|
||||
randBytes, err := uuid.GenerateRandomBytes(bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var retStr string
|
||||
switch format {
|
||||
case "hex":
|
||||
retStr = hex.EncodeToString(randBytes)
|
||||
case "base64":
|
||||
retStr = base64.StdEncoding.EncodeToString(randBytes)
|
||||
}
|
||||
|
||||
// Generate the response
|
||||
resp := &logical.Response{
|
||||
Data: map[string]interface{}{
|
||||
"random_bytes": retStr,
|
||||
},
|
||||
}
|
||||
return resp, nil
|
||||
func (b *SystemBackend) pathRandomWrite(_ context.Context, _ *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
return random.HandleRandomAPI(d, b.Core.secureRandomReader)
|
||||
}
|
||||
|
||||
func hasMountAccess(ctx context.Context, acl *ACL, path string) bool {
|
||||
|
|
|
@ -855,7 +855,7 @@ func (b *SystemBackend) toolsPaths() []*framework.Path {
|
|||
},
|
||||
|
||||
{
|
||||
Pattern: "tools/random" + framework.OptionalParamRegex("urlbytes"),
|
||||
Pattern: "tools/random(/" + framework.GenericNameRegex("source") + ")?" + framework.OptionalParamRegex("urlbytes"),
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"urlbytes": {
|
||||
Type: framework.TypeString,
|
||||
|
@ -873,6 +873,12 @@ func (b *SystemBackend) toolsPaths() []*framework.Path {
|
|||
Default: "base64",
|
||||
Description: `Encoding format to use. Can be "hex" or "base64". Defaults to "base64".`,
|
||||
},
|
||||
|
||||
"source": {
|
||||
Type: framework.TypeString,
|
||||
Default: "platform",
|
||||
Description: `Which system to source random data from, ether "platform", "seal", or "all".`,
|
||||
},
|
||||
},
|
||||
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
|
|
|
@ -652,9 +652,9 @@ $ curl \
|
|||
|
||||
This endpoint returns high-quality random bytes of the specified length.
|
||||
|
||||
| Method | Path |
|
||||
| :----- | :------------------------- |
|
||||
| `POST` | `/transit/random(/:bytes)` |
|
||||
| Method | Path |
|
||||
| :----- | :----------------------------------- |
|
||||
| `POST` | `/transit/random(/:source)(/:bytes)` |
|
||||
|
||||
### Parameters
|
||||
|
||||
|
@ -664,6 +664,11 @@ This endpoint returns high-quality random bytes of the specified length.
|
|||
- `format` `(string: "base64")` – Specifies the output encoding. Valid options
|
||||
are `hex` or `base64`.
|
||||
|
||||
- `source` `(string: "platform")` - Specifies the source of the requested bytes.
|
||||
`platform`, the default, sources bytes from the platform's entropy source.
|
||||
`seal` sources from entropy augmentation (enterprise only).
|
||||
`all` mixes bytes from all available sources.
|
||||
|
||||
### Sample Payload
|
||||
|
||||
```json
|
||||
|
|
|
@ -12,9 +12,9 @@ The `/sys/tools` endpoints are a general set of tools.
|
|||
|
||||
This endpoint returns high-quality random bytes of the specified length.
|
||||
|
||||
| Method | Path |
|
||||
| :----- | :--------------------------- |
|
||||
| `POST` | `/sys/tools/random(/:bytes)` |
|
||||
| Method | Path |
|
||||
| :----- | :------------------------------------- |
|
||||
| `POST` | `/sys/tools/random(/:source)(/:bytes)` |
|
||||
|
||||
### Parameters
|
||||
|
||||
|
@ -24,6 +24,11 @@ This endpoint returns high-quality random bytes of the specified length.
|
|||
- `format` `(string: "base64")` – Specifies the output encoding. Valid options
|
||||
are `hex` or `base64`.
|
||||
|
||||
- `source` `(string: "platform")` - Specifies the source of the requested bytes.
|
||||
`platform`, the default, sources bytes from the platform's entropy source.
|
||||
`seal` sources from entropy augmentation (enterprise only).
|
||||
`all` mixes bytes from all available sources.
|
||||
|
||||
### Sample Payload
|
||||
|
||||
```json
|
||||
|
|
Loading…
Reference in New Issue