mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-30 10:12:35 +00:00 
			
		
		
		
	Vault Raw Read Support (CLI & Client) (#14945)
* Expose raw request from client.Logical() Not all Vault API endpoints return well-formatted JSON objects. Sometimes, in the case of the PKI secrets engine, they're not even printable (/pki/ca returns a binary (DER-encoded) certificate). While this endpoint isn't authenticated, in general the API caller would either need to use Client.RawRequestWithContext(...) directly (which the docs advise against), or setup their own net/http client and re-create much of Client and/or Client.Logical. Instead, exposing the raw Request (via the new ReadRawWithData(...)) allows callers to directly consume these non-JSON endpoints like they would nearly any other endpoint. Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Add raw formatter for direct []byte data As mentioned in the previous commit, some API endpoints return non-JSON data. We get as far as fetching this data (via client.Logical().Read), but parsing it as an api.Secret fails (as in this case, it is non-JSON). Given that we intend to update `vault read` to support such endpoints, we'll need a "raw" formatter that accepts []byte-encoded data and simply writes it to the UI. Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Add support for reading raw API endpoints Some endpoints, such as `pki/ca` and `pki/ca/pem` return non-JSON objects. When calling `vault read` on these endpoints, an error is returned because they cannot be parsed as api.Secret instances: > Error reading pki/ca/pem: invalid character '-' in numeric literal Indeed, we go to all the trouble of (successfully) fetching this value, only to be unable to Unmarshal into a Secrets value. Instead, add support for a new -format=raw option, allowing these endpoints to be consumed by callers of `vault read` directly. Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Add changelog entry Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Remove panic Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>
This commit is contained in:
		| @@ -65,23 +65,7 @@ func (c *Logical) ReadWithDataWithContext(ctx context.Context, path string, data | |||||||
| 	ctx, cancelFunc := c.c.withConfiguredTimeout(ctx) | 	ctx, cancelFunc := c.c.withConfiguredTimeout(ctx) | ||||||
| 	defer cancelFunc() | 	defer cancelFunc() | ||||||
|  |  | ||||||
| 	r := c.c.NewRequest(http.MethodGet, "/v1/"+path) | 	resp, err := c.readRawWithDataWithContext(ctx, path, data) | ||||||
|  |  | ||||||
| 	var values url.Values |  | ||||||
| 	for k, v := range data { |  | ||||||
| 		if values == nil { |  | ||||||
| 			values = make(url.Values) |  | ||||||
| 		} |  | ||||||
| 		for _, val := range v { |  | ||||||
| 			values.Add(k, val) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if values != nil { |  | ||||||
| 		r.Params = values |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	resp, err := c.c.rawRequestWithContext(ctx, r) |  | ||||||
| 	if resp != nil { | 	if resp != nil { | ||||||
| 		defer resp.Body.Close() | 		defer resp.Body.Close() | ||||||
| 	} | 	} | ||||||
| @@ -106,6 +90,41 @@ func (c *Logical) ReadWithDataWithContext(ctx context.Context, path string, data | |||||||
| 	return ParseSecret(resp.Body) | 	return ParseSecret(resp.Body) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (c *Logical) ReadRaw(path string) (*Response, error) { | ||||||
|  | 	return c.ReadRawWithData(path, nil) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *Logical) ReadRawWithData(path string, data map[string][]string) (*Response, error) { | ||||||
|  | 	return c.ReadRawWithDataWithContext(context.Background(), path, data) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *Logical) ReadRawWithDataWithContext(ctx context.Context, path string, data map[string][]string) (*Response, error) { | ||||||
|  | 	ctx, cancelFunc := c.c.withConfiguredTimeout(ctx) | ||||||
|  | 	defer cancelFunc() | ||||||
|  |  | ||||||
|  | 	return c.readRawWithDataWithContext(ctx, path, data) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *Logical) readRawWithDataWithContext(ctx context.Context, path string, data map[string][]string) (*Response, error) { | ||||||
|  | 	r := c.c.NewRequest(http.MethodGet, "/v1/"+path) | ||||||
|  |  | ||||||
|  | 	var values url.Values | ||||||
|  | 	for k, v := range data { | ||||||
|  | 		if values == nil { | ||||||
|  | 			values = make(url.Values) | ||||||
|  | 		} | ||||||
|  | 		for _, val := range v { | ||||||
|  | 			values.Add(k, val) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if values != nil { | ||||||
|  | 		r.Params = values | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return c.c.RawRequestWithContext(ctx, r) | ||||||
|  | } | ||||||
|  |  | ||||||
| func (c *Logical) List(path string) (*Secret, error) { | func (c *Logical) List(path string) (*Secret, error) { | ||||||
| 	return c.ListWithContext(context.Background(), path) | 	return c.ListWithContext(context.Background(), path) | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								changelog/14945.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								changelog/14945.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | ```release-note:improvement | ||||||
|  | cli: Support the -format=raw option, to read non-JSON Vault endpoints and original response bodies. | ||||||
|  | ``` | ||||||
| @@ -70,6 +70,7 @@ var Formatters = map[string]Formatter{ | |||||||
| 	"yaml":   YamlFormatter{}, | 	"yaml":   YamlFormatter{}, | ||||||
| 	"yml":    YamlFormatter{}, | 	"yml":    YamlFormatter{}, | ||||||
| 	"pretty": PrettyFormatter{}, | 	"pretty": PrettyFormatter{}, | ||||||
|  | 	"raw":    RawFormatter{}, | ||||||
| } | } | ||||||
|  |  | ||||||
| func Format(ui cli.Ui) string { | func Format(ui cli.Ui) string { | ||||||
| @@ -125,6 +126,27 @@ func (j JsonFormatter) Output(ui cli.Ui, secret *api.Secret, data interface{}) e | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // An output formatter for raw output of the original request object | ||||||
|  | type RawFormatter struct{} | ||||||
|  |  | ||||||
|  | func (r RawFormatter) Format(data interface{}) ([]byte, error) { | ||||||
|  | 	byte_data, ok := data.([]byte) | ||||||
|  | 	if !ok { | ||||||
|  | 		return nil, fmt.Errorf("unable to type assert to []byte: %T; please share the command run", data) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return byte_data, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r RawFormatter) Output(ui cli.Ui, secret *api.Secret, data interface{}) error { | ||||||
|  | 	b, err := r.Format(data) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	ui.Output(string(b)) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
| // An output formatter for yaml output format of an object | // An output formatter for yaml output format of an object | ||||||
| type YamlFormatter struct{} | type YamlFormatter struct{} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -91,6 +91,7 @@ func (c *ReadCommand) Run(args []string) int { | |||||||
| 		return 1 | 		return 1 | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if Format(c.UI) != "raw" { | ||||||
| 		secret, err := client.Logical().ReadWithData(path, data) | 		secret, err := client.Logical().ReadWithData(path, data) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			c.UI.Error(fmt.Sprintf("Error reading %s: %s", path, err)) | 			c.UI.Error(fmt.Sprintf("Error reading %s: %s", path, err)) | ||||||
| @@ -106,4 +107,24 @@ func (c *ReadCommand) Run(args []string) int { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		return OutputSecret(c.UI, secret) | 		return OutputSecret(c.UI, secret) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	resp, err := client.Logical().ReadRawWithData(path, data) | ||||||
|  | 	if err != nil { | ||||||
|  | 		c.UI.Error(fmt.Sprintf("Error reading: %s: %s", path, err)) | ||||||
|  | 		return 2 | ||||||
|  | 	} | ||||||
|  | 	if resp == nil || resp.Body == nil { | ||||||
|  | 		c.UI.Error(fmt.Sprintf("No value found at %s", path)) | ||||||
|  | 		return 2 | ||||||
|  | 	} | ||||||
|  | 	defer resp.Body.Close() | ||||||
|  |  | ||||||
|  | 	contents, err := io.ReadAll(resp.Body) | ||||||
|  | 	if err != nil { | ||||||
|  | 		c.UI.Error(fmt.Sprintf("Error reading: %s: %s", path, err)) | ||||||
|  | 		return 2 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return OutputData(c.UI, contents) | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Alexander Scheel
					Alexander Scheel