diff --git a/api/output_string.go b/api/output_string.go index 03ddff4c2e..a9ed95b198 100644 --- a/api/output_string.go +++ b/api/output_string.go @@ -19,10 +19,18 @@ var LastOutputStringError *OutputStringError type OutputStringError struct { *retryablehttp.Request + unixSocket string parsingError error parsedCurlString string } +func NewOutputDomainSocketCurlStringError(req *retryablehttp.Request, socketAddr string) *OutputStringError { + return &OutputStringError{ + Request: req, + unixSocket: socketAddr, + } +} + func (d *OutputStringError) Error() string { if d.parsedCurlString == "" { d.parseRequest() @@ -46,6 +54,9 @@ func (d *OutputStringError) parseRequest() { if d.Request.Method != "GET" { d.parsedCurlString = fmt.Sprintf("%s -X %s", d.parsedCurlString, d.Request.Method) } + if d.unixSocket != "" { + d.parsedCurlString = fmt.Sprintf("%s --unix-socket %s", d.parsedCurlString, d.unixSocket) + } for k, v := range d.Request.Header { for _, h := range v { if strings.ToLower(k) == "authorization" { diff --git a/internal/clientcache/cmd/daemon/addtoken.go b/internal/clientcache/cmd/daemon/addtoken.go index 8c16818e36..bac981cb4c 100644 --- a/internal/clientcache/cmd/daemon/addtoken.go +++ b/internal/clientcache/cmd/daemon/addtoken.go @@ -109,7 +109,11 @@ func (c *AddTokenCommand) Add(ctx context.Context, apiClient *api.Client, keyrin } else { return nil, nil, errors.New("The found auth token is not in the proper format.") } - pa.AuthToken = token + if c.FlagOutputCurlString { + pa.AuthToken = "/*token*/" + } else { + pa.AuthToken = token + } default: at := c.ReadTokenFromKeyring(keyringType, tokenName) if at == nil { @@ -121,15 +125,19 @@ func (c *AddTokenCommand) Add(ctx context.Context, apiClient *api.Client, keyrin } pa.AuthTokenId = at.Id } + var opts []client.Option + if c.FlagOutputCurlString { + opts = append(opts, client.WithOutputCurlString()) + } dotPath, err := DefaultDotDirectory(ctx) if err != nil { return nil, nil, err } - return addToken(ctx, dotPath, &pa) + return addToken(ctx, dotPath, &pa, opts...) } -func addToken(ctx context.Context, daemonPath string, p *daemon.UpsertTokenRequest) (*api.Response, *api.Error, error) { +func addToken(ctx context.Context, daemonPath string, p *daemon.UpsertTokenRequest, opt ...client.Option) (*api.Response, *api.Error, error) { addr, err := daemon.SocketAddress(daemonPath) if err != nil { return nil, nil, fmt.Errorf("Error when retrieving the socket address: %w", err) @@ -143,7 +151,7 @@ func addToken(ctx context.Context, daemonPath string, p *daemon.UpsertTokenReque if err != nil { return nil, nil, fmt.Errorf("Error when making a new client: %w.", err) } - resp, err := c.Post(ctx, "/v1/tokens", p) + resp, err := c.Post(ctx, "/v1/tokens", p, opt...) if err != nil { return nil, nil, fmt.Errorf("Error when sending request to the daemon: %w.", err) } diff --git a/internal/clientcache/cmd/daemon/status.go b/internal/clientcache/cmd/daemon/status.go index b6a8064e11..3a6fe24fb1 100644 --- a/internal/clientcache/cmd/daemon/status.go +++ b/internal/clientcache/cmd/daemon/status.go @@ -96,11 +96,15 @@ func (c *StatusCommand) Status(ctx context.Context) (*api.Response, *daemon.Stat if err != nil { return nil, nil, nil, err } + var opts []client.Option + if c.FlagOutputCurlString { + opts = append(opts, client.WithOutputCurlString()) + } - return status(ctx, dotPath) + return status(ctx, dotPath, opts...) } -func status(ctx context.Context, daemonPath string) (*api.Response, *daemon.StatusResult, *api.Error, error) { +func status(ctx context.Context, daemonPath string, opt ...client.Option) (*api.Response, *daemon.StatusResult, *api.Error, error) { const op = "daemon.status" addr, err := daemon.SocketAddress(daemonPath) if err != nil { @@ -115,7 +119,7 @@ func status(ctx context.Context, daemonPath string) (*api.Response, *daemon.Stat return nil, nil, nil, err } - resp, err := c.Get(ctx, "/v1/status", nil) + resp, err := c.Get(ctx, "/v1/status", nil, opt...) if err != nil { return nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("client do failed")) } diff --git a/internal/clientcache/cmd/search/search.go b/internal/clientcache/cmd/search/search.go index 4feadcea76..8d456d1ff1 100644 --- a/internal/clientcache/cmd/search/search.go +++ b/internal/clientcache/cmd/search/search.go @@ -132,11 +132,11 @@ func (c *SearchCommand) Run(args []string) int { } func (c *SearchCommand) Search(ctx context.Context) (*api.Response, *daemon.SearchResult, *api.Error, error) { - client, err := c.Client() + cl, err := c.Client() if err != nil { return nil, nil, nil, err } - t := client.Token() + t := cl.Token() if t == "" { return nil, nil, nil, fmt.Errorf("Auth Token selected for searching is empty.") } @@ -150,15 +150,19 @@ func (c *SearchCommand) Search(ctx context.Context) (*api.Response, *daemon.Sear resource: c.flagResource, authTokenId: strings.Join(tSlice[:2], "_"), } + var opts []client.Option + if c.FlagOutputCurlString { + opts = append(opts, client.WithOutputCurlString()) + } dotPath, err := daemoncmd.DefaultDotDirectory(ctx) if err != nil { return nil, nil, nil, err } - return search(ctx, dotPath, tf) + return search(ctx, dotPath, tf, opts...) } -func search(ctx context.Context, daemonPath string, fb filterBy) (*api.Response, *daemon.SearchResult, *api.Error, error) { +func search(ctx context.Context, daemonPath string, fb filterBy, opt ...client.Option) (*api.Response, *daemon.SearchResult, *api.Error, error) { addr, err := daemon.SocketAddress(daemonPath) if err != nil { return nil, nil, nil, fmt.Errorf("Error when retrieving the socket address: %w", err) @@ -175,7 +179,7 @@ func search(ctx context.Context, daemonPath string, fb filterBy) (*api.Response, q.Add("auth_token_id", fb.authTokenId) q.Add("resource", fb.resource) q.Add("query", fb.flagQuery) - resp, err := c.Get(ctx, "/v1/search", q) + resp, err := c.Get(ctx, "/v1/search", q, opt...) if err != nil { return nil, nil, nil, fmt.Errorf("Error when sending request to the daemon: %w.", err) } diff --git a/internal/clientcache/internal/client/client.go b/internal/clientcache/internal/client/client.go index ed6afe3494..3f319fd4e8 100644 --- a/internal/clientcache/internal/client/client.go +++ b/internal/clientcache/internal/client/client.go @@ -17,7 +17,7 @@ import ( "github.com/hashicorp/go-retryablehttp" ) -const hostHeader = "api.boundary.localhost" +const hostHeader = "clientcache.boundary.localhost" type Client struct { client *retryablehttp.Client @@ -56,11 +56,19 @@ func New(ctx context.Context, address *url.URL) (*Client, error) { // Get sends a GET http request to the provided path. The vals provided are // encoded and attached to the request if present. -func (c *Client) Get(ctx context.Context, path string, vals *url.Values) (*api.Response, error) { +func (c *Client) Get(ctx context.Context, path string, vals *url.Values, opt ...Option) (*api.Response, error) { req := request(ctx, "GET", path) if vals != nil { req.URL.RawQuery = vals.Encode() } + opts, err := getOpts(opt...) + if err != nil { + return nil, err + } + if opts.withOutputCurlString { + api.LastOutputStringError = api.NewOutputDomainSocketCurlStringError(req, c.domainSocketPath) + return nil, api.LastOutputStringError + } resp, err := c.client.Do(req) if err != nil { return nil, err @@ -70,7 +78,7 @@ func (c *Client) Get(ctx context.Context, path string, vals *url.Values) (*api.R // Post sends a POST http request to the provided path. The body is marshaled // to json and added to the request body. -func (c *Client) Post(ctx context.Context, path string, body any) (*api.Response, error) { +func (c *Client) Post(ctx context.Context, path string, body any, opt ...Option) (*api.Response, error) { req := request(ctx, "POST", path) if body != nil { b, err := json.Marshal(body) @@ -79,6 +87,14 @@ func (c *Client) Post(ctx context.Context, path string, body any) (*api.Response } req.SetBody(b) } + opts, err := getOpts(opt...) + if err != nil { + return nil, err + } + if opts.withOutputCurlString { + api.LastOutputStringError = api.NewOutputDomainSocketCurlStringError(req, c.domainSocketPath) + return nil, api.LastOutputStringError + } resp, err := c.client.Do(req) if err != nil { return nil, err diff --git a/internal/clientcache/internal/client/option.go b/internal/clientcache/internal/client/option.go new file mode 100644 index 0000000000..b631362c98 --- /dev/null +++ b/internal/clientcache/internal/client/option.go @@ -0,0 +1,39 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package client + +// GetOpts - iterate the inbound Options and return a struct. +func getOpts(opt ...Option) (options, error) { + opts := getDefaultOptions() + for _, o := range opt { + if o != nil { + err := o(&opts) + if err != nil { + return opts, err + } + } + } + return opts, nil +} + +// Option - how Options are passed as arguments. +type Option func(*options) error + +// options - how options are represented. +type options struct { + withOutputCurlString bool +} + +func getDefaultOptions() options { + return options{} +} + +// WithOutputCurlString specifies that the client should return an +// OutputStringError that prints out the curl string for the request being generated. +func WithOutputCurlString() Option { + return func(o *options) error { + o.withOutputCurlString = true + return nil + } +} diff --git a/internal/clientcache/internal/client/options_test.go b/internal/clientcache/internal/client/options_test.go new file mode 100644 index 0000000000..feec5a8917 --- /dev/null +++ b/internal/clientcache/internal/client/options_test.go @@ -0,0 +1,28 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package client + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_GetOpts(t *testing.T) { + t.Parallel() + + t.Run("default", func(t *testing.T) { + opts, err := getOpts() + assert.NoError(t, err) + testOpts := options{} + assert.Equal(t, opts, testOpts) + }) + t.Run("WithOutputCurlString", func(t *testing.T) { + opts, err := getOpts(WithOutputCurlString()) + assert.NoError(t, err) + testOpts := getDefaultOptions() + testOpts.withOutputCurlString = true + assert.Equal(t, opts, testOpts) + }) +} diff --git a/internal/clientcache/internal/daemon/token_handler.go b/internal/clientcache/internal/daemon/token_handler.go index 0dade912ba..25d2d6bcb7 100644 --- a/internal/clientcache/internal/daemon/token_handler.go +++ b/internal/clientcache/internal/daemon/token_handler.go @@ -24,23 +24,23 @@ type refresher interface { // KeyringToken has keyring held auth token information. type KeyringToken struct { // The keyring type used by boundary to access the auth token - KeyringType string `json:"keyring_type"` + KeyringType string `json:"keyring_type,omitempty"` // The token identifier for the provided keyring type that holds the auth token - TokenName string `json:"token_name"` + TokenName string `json:"token_name,omitempty"` } // userTokenToAdd is the request body to this handler. type UpsertTokenRequest struct { // BoundaryAddr is a required field for all requests - BoundaryAddr string `json:"boundary_addr"` + BoundaryAddr string `json:"boundary_addr,omitempty"` // The id of the auth token asserted to be attempted to be added - AuthTokenId string `json:"auth_token_id"` + AuthTokenId string `json:"auth_token_id,omitempty"` // The raw auth token for this user. Either this field or the Keyring field // must be set but not both. - AuthToken string `json:"auth_token"` + AuthToken string `json:"auth_token,omitempty"` // Keyring is the keyring info used when adding an auth token held in // keyring to the daemon. - Keyring *KeyringToken `json:"keyring"` + Keyring *KeyringToken `json:"keyring,omitempty"` } func newTokenHandlerFunc(ctx context.Context, repo *cache.Repository, refresher refresher) (http.HandlerFunc, error) {