Skip to content

Commit

Permalink
Add basic-auth / header addition to OTS-CLI
Browse files Browse the repository at this point in the history
closes #134

Signed-off-by: Knut Ahlers <[email protected]>
  • Loading branch information
Luzifer committed Oct 18, 2023
1 parent 5c615fb commit dec49d7
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 1 deletion.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ Both commands can be used in scripts:
- `fetch` prints the secret to `STDOUT` and stores files to the given directory
- both sends logs to `STDERR` which you can disable (`--log-level=fatal`) or ignore in your script

In case your instance needs credentials to use the `/api/create` endpoint you can pass them to OTS-CLI like you would do with curl:
- `ots-cli create --instance ... -u myuser:mypass` for basic-auth
- `ots-cli create --instance ... -H 'Authorization: Token abcde'` for token-auth (you can set any header you need, just repeat `-H ...`)

### Bash: Sharing an encrypted secret (strongly recommended!)

This is slightly more complex as you first need to encrypt your secret before sending it to the API but in this case you can be sure the server will in no case be able to access the secret. Especially if you are using ots.fyi (my public hosted instance) you should not trust me with your secret but use an encrypted secret:
Expand Down
68 changes: 67 additions & 1 deletion cmd/ots-cli/cmd_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,25 @@ import (
"fmt"
"io"
"mime"
"net/http"
"os"
"path"
"strings"

"github.com/Luzifer/ots/pkg/client"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)

type (
authRoundTripper struct {
http.RoundTripper

headers http.Header
user, pass string
}
)

var createCmd = &cobra.Command{
Use: "create [-f file]... [--instance url] [--secret-from file]",
Short: "Create a new encrypted secret in the given OTS instance",
Expand All @@ -23,15 +34,21 @@ var createCmd = &cobra.Command{

func init() {
createCmd.Flags().Duration("expire", 0, "When to expire the secret (0 to use server-default)")
createCmd.Flags().StringSliceP("header", "H", nil, "Headers to include in the request (i.e. 'Authorization: Token ...')")
createCmd.Flags().String("instance", "https://ots.fyi/", "Instance to create the secret with")
createCmd.Flags().StringSliceP("file", "f", nil, "File(s) to attach to the secret")
createCmd.Flags().String("secret-from", "-", `File to read the secret content from ("-" for STDIN)`)
createCmd.Flags().StringP("user", "u", "", "Username / Password for basic auth, specified as 'user:pass'")
rootCmd.AddCommand(createCmd)
}

func createRunE(cmd *cobra.Command, _ []string) error {
func createRunE(cmd *cobra.Command, _ []string) (err error) {
var secret client.Secret

if client.HTTPClient, err = constructHTTPClient(cmd); err != nil {
return fmt.Errorf("constructing authorized HTTP client: %w", err)
}

// Read the secret content
logrus.Info("reading secret content...")
secretSourceName, err := cmd.Flags().GetString("secret-from")
Expand Down Expand Up @@ -103,3 +120,52 @@ func createRunE(cmd *cobra.Command, _ []string) error {

return nil
}

func constructHTTPClient(cmd *cobra.Command) (*http.Client, error) {
basic, _ := cmd.Flags().GetString("user")
headers, _ := cmd.Flags().GetStringSlice("header")

if basic == "" && headers == nil {
// No authorization needed
return http.DefaultClient, nil
}

t := authRoundTripper{RoundTripper: http.DefaultTransport, headers: http.Header{}}

// Set basic auth if available
user, pass, ok := strings.Cut(basic, ":")
if ok {
t.user = user
t.pass = pass
}

// Parse and set headers if available
for _, hdr := range headers {
key, value, ok := strings.Cut(hdr, ":")
if !ok {
logrus.WithField("header", hdr).Warn("invalid header format, skipping")
continue
}
t.headers.Add(key, strings.TrimSpace(value))
}

return &http.Client{Transport: t}, nil
}

func (a authRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
if a.user != "" {
r.SetBasicAuth(a.user, a.pass)
}

for key, values := range a.headers {
if r.Header == nil {
r.Header = http.Header{}
}
for _, value := range values {
r.Header.Add(key, value)
}
}

resp, err := a.RoundTripper.RoundTrip(r)
return resp, fmt.Errorf("executing round-trip: %w", err)
}

0 comments on commit dec49d7

Please sign in to comment.