Add basic-auth / header addition to OTS-CLI
closes #134 Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
parent
5c615fbfa5
commit
a83ae9ceb3
2 changed files with 74 additions and 1 deletions
|
@ -64,6 +64,10 @@ Both commands can be used in scripts:
|
||||||
- `fetch` prints the secret to `STDOUT` and stores files to the given directory
|
- `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
|
- 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!)
|
### 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:
|
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:
|
||||||
|
|
|
@ -4,14 +4,25 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"mime"
|
"mime"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/Luzifer/ots/pkg/client"
|
"github.com/Luzifer/ots/pkg/client"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
authRoundTripper struct {
|
||||||
|
http.RoundTripper
|
||||||
|
|
||||||
|
headers http.Header
|
||||||
|
user, pass string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
var createCmd = &cobra.Command{
|
var createCmd = &cobra.Command{
|
||||||
Use: "create [-f file]... [--instance url] [--secret-from file]",
|
Use: "create [-f file]... [--instance url] [--secret-from file]",
|
||||||
Short: "Create a new encrypted secret in the given OTS instance",
|
Short: "Create a new encrypted secret in the given OTS instance",
|
||||||
|
@ -23,15 +34,21 @@ var createCmd = &cobra.Command{
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
createCmd.Flags().Duration("expire", 0, "When to expire the secret (0 to use server-default)")
|
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().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().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().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)
|
rootCmd.AddCommand(createCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func createRunE(cmd *cobra.Command, _ []string) error {
|
func createRunE(cmd *cobra.Command, _ []string) (err error) {
|
||||||
var secret client.Secret
|
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
|
// Read the secret content
|
||||||
logrus.Info("reading secret content...")
|
logrus.Info("reading secret content...")
|
||||||
secretSourceName, err := cmd.Flags().GetString("secret-from")
|
secretSourceName, err := cmd.Flags().GetString("secret-from")
|
||||||
|
@ -103,3 +120,55 @@ func createRunE(cmd *cobra.Command, _ []string) error {
|
||||||
|
|
||||||
return nil
|
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)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("executing round-trip: %w", err)
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue