copying general purpose tools from transit backend to /sys/tools (#3391)
This commit is contained in:
parent
fb6cd052c5
commit
e4065e33d2
|
@ -1,15 +1,20 @@
|
|||
package vault
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"hash"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/structs"
|
||||
uuid "github.com/hashicorp/go-uuid"
|
||||
"github.com/hashicorp/vault/helper/consts"
|
||||
"github.com/hashicorp/vault/helper/parseutil"
|
||||
"github.com/hashicorp/vault/helper/wrapping"
|
||||
|
@ -848,6 +853,74 @@ func NewSystemBackend(core *Core) *SystemBackend {
|
|||
HelpSynopsis: strings.TrimSpace(sysHelp["plugin-reload"][0]),
|
||||
HelpDescription: strings.TrimSpace(sysHelp["plugin-reload"][1]),
|
||||
},
|
||||
&framework.Path{
|
||||
Pattern: "tools/hash" + framework.OptionalParamRegex("urlalgorithm"),
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"input": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Description: "The base64-encoded input data",
|
||||
},
|
||||
|
||||
"algorithm": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Default: "sha2-256",
|
||||
Description: `Algorithm to use (POST body parameter). Valid values are:
|
||||
|
||||
* sha2-224
|
||||
* sha2-256
|
||||
* sha2-384
|
||||
* sha2-512
|
||||
|
||||
Defaults to "sha2-256".`,
|
||||
},
|
||||
|
||||
"urlalgorithm": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Description: `Algorithm to use (POST URL parameter)`,
|
||||
},
|
||||
|
||||
"format": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Default: "hex",
|
||||
Description: `Encoding format to use. Can be "hex" or "base64". Defaults to "hex".`,
|
||||
},
|
||||
},
|
||||
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.UpdateOperation: b.pathHashWrite,
|
||||
},
|
||||
|
||||
HelpSynopsis: strings.TrimSpace(sysHelp["hash"][0]),
|
||||
HelpDescription: strings.TrimSpace(sysHelp["hash"][1]),
|
||||
},
|
||||
&framework.Path{
|
||||
Pattern: "tools/random" + framework.OptionalParamRegex("urlbytes"),
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"urlbytes": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Description: "The number of bytes to generate (POST URL parameter)",
|
||||
},
|
||||
|
||||
"bytes": &framework.FieldSchema{
|
||||
Type: framework.TypeInt,
|
||||
Default: 32,
|
||||
Description: "The number of bytes to generate (POST body parameter). Defaults to 32 (256 bits).",
|
||||
},
|
||||
|
||||
"format": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Default: "base64",
|
||||
Description: `Encoding format to use. Can be "hex" or "base64". Defaults to "base64".`,
|
||||
},
|
||||
},
|
||||
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.UpdateOperation: b.pathRandomWrite,
|
||||
},
|
||||
|
||||
HelpSynopsis: strings.TrimSpace(sysHelp["random"][0]),
|
||||
HelpDescription: strings.TrimSpace(sysHelp["random"][1]),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -2485,6 +2558,108 @@ func (b *SystemBackend) handleWrappingRewrap(
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (b *SystemBackend) pathHashWrite(
|
||||
req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
inputB64 := d.Get("input").(string)
|
||||
format := d.Get("format").(string)
|
||||
algorithm := d.Get("urlalgorithm").(string)
|
||||
if algorithm == "" {
|
||||
algorithm = d.Get("algorithm").(string)
|
||||
}
|
||||
|
||||
input, err := base64.StdEncoding.DecodeString(inputB64)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(fmt.Sprintf("unable to decode input as base64: %s", err)), logical.ErrInvalidRequest
|
||||
}
|
||||
|
||||
switch format {
|
||||
case "hex":
|
||||
case "base64":
|
||||
default:
|
||||
return logical.ErrorResponse(fmt.Sprintf("unsupported encoding format %s; must be \"hex\" or \"base64\"", format)), nil
|
||||
}
|
||||
|
||||
var hf hash.Hash
|
||||
switch algorithm {
|
||||
case "sha2-224":
|
||||
hf = sha256.New224()
|
||||
case "sha2-256":
|
||||
hf = sha256.New()
|
||||
case "sha2-384":
|
||||
hf = sha512.New384()
|
||||
case "sha2-512":
|
||||
hf = sha512.New()
|
||||
default:
|
||||
return logical.ErrorResponse(fmt.Sprintf("unsupported algorithm %s", algorithm)), nil
|
||||
}
|
||||
hf.Write(input)
|
||||
retBytes := hf.Sum(nil)
|
||||
|
||||
var retStr string
|
||||
switch format {
|
||||
case "hex":
|
||||
retStr = hex.EncodeToString(retBytes)
|
||||
case "base64":
|
||||
retStr = base64.StdEncoding.EncodeToString(retBytes)
|
||||
}
|
||||
|
||||
// Generate the response
|
||||
resp := &logical.Response{
|
||||
Data: map[string]interface{}{
|
||||
"sum": retStr,
|
||||
},
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (b *SystemBackend) pathRandomWrite(
|
||||
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
|
||||
}
|
||||
|
||||
switch format {
|
||||
case "hex":
|
||||
case "base64":
|
||||
default:
|
||||
return logical.ErrorResponse(fmt.Sprintf("unsupported encoding format %s; 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 sanitizeMountPath(path string) string {
|
||||
if !strings.HasSuffix(path, "/") {
|
||||
path += "/"
|
||||
|
@ -3031,4 +3206,12 @@ This path responds to the following HTTP methods.
|
|||
`The mount paths of the plugin backends to reload.`,
|
||||
"",
|
||||
},
|
||||
"hash": {
|
||||
"Generate a hash sum for input data",
|
||||
"Generates a hash sum of the given algorithm against the given input data.",
|
||||
},
|
||||
"random": {
|
||||
"Generate random bytes",
|
||||
"This function can be used to generate high-entropy random bytes.",
|
||||
},
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package vault
|
|||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
@ -1722,3 +1723,157 @@ func TestSystemBackend_PluginCatalog_CRUD(t *testing.T) {
|
|||
t.Fatalf("expected nil response, plugin not deleted correctly got resp: %v, err: %v", resp, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSystemBackend_ToolsHash(t *testing.T) {
|
||||
b := testSystemBackend(t)
|
||||
req := logical.TestRequest(t, logical.UpdateOperation, "tools/hash")
|
||||
req.Data = map[string]interface{}{
|
||||
"input": "dGhlIHF1aWNrIGJyb3duIGZveA==",
|
||||
}
|
||||
_, err := b.HandleRequest(req)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
doRequest := func(req *logical.Request, errExpected bool, expected string) {
|
||||
t.Helper()
|
||||
resp, err := b.HandleRequest(req)
|
||||
if err != nil && !errExpected {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp == nil {
|
||||
t.Fatal("expected non-nil response")
|
||||
}
|
||||
if errExpected {
|
||||
if !resp.IsError() {
|
||||
t.Fatalf("bad: got error response: %#v", *resp)
|
||||
}
|
||||
return
|
||||
}
|
||||
if resp.IsError() {
|
||||
t.Fatalf("bad: got error response: %#v", *resp)
|
||||
}
|
||||
sum, ok := resp.Data["sum"]
|
||||
if !ok {
|
||||
t.Fatal("no sum key found in returned data")
|
||||
}
|
||||
if sum.(string) != expected {
|
||||
t.Fatal("mismatched hashes")
|
||||
}
|
||||
}
|
||||
|
||||
// Test defaults -- sha2-256
|
||||
doRequest(req, false, "9ecb36561341d18eb65484e833efea61edc74b84cf5e6ae1b81c63533e25fc8f")
|
||||
|
||||
// Test algorithm selection in the path
|
||||
req.Path = "tools/hash/sha2-224"
|
||||
doRequest(req, false, "ea074a96cabc5a61f8298a2c470f019074642631a49e1c5e2f560865")
|
||||
|
||||
// Reset and test algorithm selection in the data
|
||||
req.Path = "tools/hash"
|
||||
req.Data["algorithm"] = "sha2-224"
|
||||
doRequest(req, false, "ea074a96cabc5a61f8298a2c470f019074642631a49e1c5e2f560865")
|
||||
|
||||
req.Data["algorithm"] = "sha2-384"
|
||||
doRequest(req, false, "15af9ec8be783f25c583626e9491dbf129dd6dd620466fdf05b3a1d0bb8381d30f4d3ec29f923ff1e09a0f6b337365a6")
|
||||
|
||||
req.Data["algorithm"] = "sha2-512"
|
||||
doRequest(req, false, "d9d380f29b97ad6a1d92e987d83fa5a02653301e1006dd2bcd51afa59a9147e9caedaf89521abc0f0b682adcd47fb512b8343c834a32f326fe9bef00542ce887")
|
||||
|
||||
// Test returning as base64
|
||||
req.Data["format"] = "base64"
|
||||
doRequest(req, false, "2dOA8puXrWodkumH2D+loCZTMB4QBt0rzVGvpZqRR+nK7a+JUhq8DwtoKtzUf7USuDQ8g0oy8yb+m+8AVCzohw==")
|
||||
|
||||
// Test bad input/format/algorithm
|
||||
req.Data["format"] = "base92"
|
||||
doRequest(req, true, "")
|
||||
|
||||
req.Data["format"] = "hex"
|
||||
req.Data["algorithm"] = "foobar"
|
||||
doRequest(req, true, "")
|
||||
|
||||
req.Data["algorithm"] = "sha2-256"
|
||||
req.Data["input"] = "foobar"
|
||||
doRequest(req, true, "")
|
||||
}
|
||||
|
||||
func TestSystemBackend_ToolsRandom(t *testing.T) {
|
||||
b := testSystemBackend(t)
|
||||
req := logical.TestRequest(t, logical.UpdateOperation, "tools/random")
|
||||
|
||||
_, err := b.HandleRequest(req)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
doRequest := func(req *logical.Request, errExpected bool, format string, numBytes int) {
|
||||
t.Helper()
|
||||
getResponse := func() []byte {
|
||||
resp, err := b.HandleRequest(req)
|
||||
if err != nil && !errExpected {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp == nil {
|
||||
t.Fatal("expected non-nil response")
|
||||
}
|
||||
if errExpected {
|
||||
if !resp.IsError() {
|
||||
t.Fatalf("bad: got error response: %#v", *resp)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if resp.IsError() {
|
||||
t.Fatalf("bad: got error response: %#v", *resp)
|
||||
}
|
||||
if _, ok := resp.Data["random_bytes"]; !ok {
|
||||
t.Fatal("no random_bytes found in response")
|
||||
}
|
||||
|
||||
outputStr := resp.Data["random_bytes"].(string)
|
||||
var outputBytes []byte
|
||||
switch format {
|
||||
case "base64":
|
||||
outputBytes, err = base64.StdEncoding.DecodeString(outputStr)
|
||||
case "hex":
|
||||
outputBytes, err = hex.DecodeString(outputStr)
|
||||
default:
|
||||
t.Fatal("unknown format")
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return outputBytes
|
||||
}
|
||||
|
||||
rand1 := getResponse()
|
||||
// Expected error
|
||||
if rand1 == nil {
|
||||
return
|
||||
}
|
||||
rand2 := getResponse()
|
||||
if len(rand1) != numBytes || len(rand2) != numBytes {
|
||||
t.Fatal("length of output random bytes not what is exepcted")
|
||||
}
|
||||
if reflect.DeepEqual(rand1, rand2) {
|
||||
t.Fatal("found identical ouputs")
|
||||
}
|
||||
}
|
||||
|
||||
// Test defaults
|
||||
doRequest(req, false, "base64", 32)
|
||||
|
||||
// Test size selection in the path
|
||||
req.Path = "tools/random/24"
|
||||
req.Data["format"] = "hex"
|
||||
doRequest(req, false, "hex", 24)
|
||||
|
||||
// Test bad input/format
|
||||
req.Path = "tools/random"
|
||||
req.Data["format"] = "base92"
|
||||
doRequest(req, true, "", 0)
|
||||
|
||||
req.Data["format"] = "hex"
|
||||
req.Data["bytes"] = -1
|
||||
doRequest(req, true, "", 0)
|
||||
}
|
||||
|
|
|
@ -95,6 +95,20 @@ path "sys/wrapping/lookup" {
|
|||
path "sys/wrapping/unwrap" {
|
||||
capabilities = ["update"]
|
||||
}
|
||||
|
||||
# Allow general purpose tools
|
||||
path "sys/tools/hash" {
|
||||
capabilities = ["update"]
|
||||
}
|
||||
path "sys/tools/hash/*" {
|
||||
capabilities = ["update"]
|
||||
}
|
||||
path "sys/tools/random" {
|
||||
capabilities = ["update"]
|
||||
}
|
||||
path "sys/tools/random/*" {
|
||||
capabilities = ["update"]
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
---
|
||||
layout: "api"
|
||||
page_title: "/sys/tools - HTTP API"
|
||||
sidebar_current: "docs-http-system-tools"
|
||||
description: |-
|
||||
This is the API documentation for a general set of crypto tools.
|
||||
---
|
||||
|
||||
# `/sys/tools`
|
||||
|
||||
The `/sys/tools` endpoints are a general set of tools.
|
||||
|
||||
## Generate Random Bytes
|
||||
|
||||
This endpoint returns high-quality random bytes of the specified length.
|
||||
|
||||
| Method | Path | Produces |
|
||||
| :------- | :--------------------------- | :--------------------- |
|
||||
| `POST` | `/sys/tools/random(/:bytes)` | `200 application/json` |
|
||||
|
||||
### Parameters
|
||||
|
||||
- `bytes` `(int: 32)` – Specifies the number of bytes to return. This value can
|
||||
be specified either in the request body, or as a part of the URL.
|
||||
|
||||
- `format` `(string: "base64")` – Specifies the output encoding. Valid options
|
||||
are `hex` or `base64`.
|
||||
|
||||
### Sample Payload
|
||||
|
||||
```json
|
||||
{
|
||||
"format": "hex"
|
||||
}
|
||||
```
|
||||
|
||||
### Sample Request
|
||||
|
||||
```
|
||||
$ curl \
|
||||
--header "X-Vault-Token: ..." \
|
||||
--request POST \
|
||||
--data @payload.json \
|
||||
https://vault.rocks/v1/sys/tools/random/164
|
||||
```
|
||||
|
||||
### Sample Response
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"random_bytes": "dGhlIHF1aWNrIGJyb3duIGZveAo="
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Hash Data
|
||||
|
||||
This endpoint returns the cryptographic hash of given data using the specified
|
||||
algorithm.
|
||||
|
||||
| Method | Path | Produces |
|
||||
| :------- | :--------------------------- | :--------------------- |
|
||||
| `POST` | `/sys/tools/hash(/:algorithm)` | `200 application/json` |
|
||||
|
||||
### Parameters
|
||||
|
||||
- `algorithm` `(string: "sha2-256")` – Specifies the hash algorithm to use. This
|
||||
can also be specified as part of the URL. Currently-supported algorithms are:
|
||||
|
||||
- `sha2-224`
|
||||
- `sha2-256`
|
||||
- `sha2-384`
|
||||
- `sha2-512`
|
||||
|
||||
- `input` `(string: <required>)` – Specifies the **base64 encoded** input data.
|
||||
|
||||
- `format` `(string: "hex")` – Specifies the output encoding. This can be either
|
||||
`hex` or `base64`.
|
||||
|
||||
### Sample Payload
|
||||
|
||||
```json
|
||||
{
|
||||
"input": "adba32=="
|
||||
}
|
||||
```
|
||||
|
||||
### Sample Request
|
||||
|
||||
```
|
||||
$ curl \
|
||||
--header "X-Vault-Token: ..." \
|
||||
--request POST \
|
||||
--data @payload.json \
|
||||
https://vault.rocks/v1/sys/tools/hash/sha2-512
|
||||
```
|
||||
|
||||
### Sample Response
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"sum": "dGhlIHF1aWNrIGJyb3duIGZveAo="
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -243,6 +243,9 @@
|
|||
<li<%= sidebar_current("docs-http-system-step-down") %>>
|
||||
<a href="/api/system/step-down.html"><tt>/sys/step-down</tt></a>
|
||||
</li>
|
||||
<li<%= sidebar_current("docs-http-system-tools") %>>
|
||||
<a href="/api/system/tools.html"><tt>/sys/tools</tt></a>
|
||||
</li>
|
||||
<li<%= sidebar_current("docs-http-system-unseal") %>>
|
||||
<a href="/api/system/unseal.html"><tt>/sys/unseal</tt></a>
|
||||
</li>
|
||||
|
|
Loading…
Reference in New Issue