Skip to content

Commit

Permalink
feat(core): allow no-cache client credentials options throughout CLI (#…
Browse files Browse the repository at this point in the history
…142)

The main goal of this is to:
	1. cache on the user's OS by default
2. allow auth without caching when desired (for example Ubuntu has known
[gnome keyring issues](zalando/go-keyring#18))
3. allow flexibility in how non-cached auth is passed into the CLI,
knowing that `stdin` isn't always an open option for all authenticated
commands
4. take inspiration from nice CLIs like [gcloud
--cred-file](https://cloud.google.com/sdk/gcloud/reference/auth/login#:~:text=complete%20the%20authorization.-,%2D%2Dcred%2Dfile%3DCRED_FILE,-Path%20to%20the)
and [gh
--with-token](https://cli.github.com/manual/gh_auth_login#:~:text=browser%20to%20authenticate-,%2D%2Dwith%2Dtoken,-Read%20token%20from)
5. continue to authenticate to the platform via
`sdk.WithClientCredentials`

Demo:
https://www.loom.com/share/96327c747ad94551a7487fc51fe31a7f?sid=6e2b81e1-7fe7-4f9f-8f34-0f6dd2a23ca6
  • Loading branch information
jakedoublev authored May 3, 2024
1 parent e6f44e0 commit c20dc1e
Show file tree
Hide file tree
Showing 26 changed files with 218 additions and 114 deletions.
1 change: 1 addition & 0 deletions cmd/auth-clearCachedCredentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

var clearCachedCredsCmd = man.Docs.GetCommand("auth/clear-cached-credentials",
man.WithRun(auth_clearCreds),
man.WithHiddenFlags("with-client-creds", "with-client-creds-file"),
)

func auth_clearCreds(cmd *cobra.Command, args []string) {
Expand Down
57 changes: 41 additions & 16 deletions cmd/auth-clientCredentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,66 +3,91 @@ package cmd
import (
"errors"
"fmt"
"log/slog"

"github.com/opentdf/otdfctl/pkg/cli"
"github.com/opentdf/otdfctl/pkg/handlers"
"github.com/opentdf/otdfctl/pkg/man"
"github.com/spf13/cobra"
"github.com/zalando/go-keyring"
)

var clientCredentialsCmd = man.Docs.GetCommand("auth/client-credentials",
man.WithRun(auth_clientCredentials),
var (
clientCredentialsCmd = man.Docs.GetCommand("auth/client-credentials",
man.WithRun(auth_clientCredentials),
)
noCacheCreds bool
)

func auth_clientCredentials(cmd *cobra.Command, args []string) {
flagHelper := cli.NewFlagHelper(cmd)
clientID := flagHelper.GetOptionalString("client-id")
clientSecret := flagHelper.GetOptionalString("client-secret")
var err error

if clientCredsFile != "" {
creds, err := handlers.GetClientCredsFromFile(clientCredsFile)
if err != nil {
cli.ExitWithError("Failed to parse client credentials JSON", err)
}
clientID = creds.ClientID
clientSecret = creds.ClientSecret
}
// if not provided by flag, check keyring cache for clientID
if clientID == "" {
fmt.Println("No client-id provided. Attempting to retrieve the default from keyring.")
retrievedClientID, err := handlers.GetClientIDFromCache()
if err != nil || retrievedClientID == "" {
slog.Debug("No client-id provided. Attempting to retrieve the default from keyring.")
clientID, err = handlers.GetClientIDFromCache()
if err != nil || clientID == "" {
cli.ExitWithError("Please provide required flag: (client-id)", errors.New("no client-id found"))
} else {
clientID = retrievedClientID
fmt.Println(cli.SuccessMessage("Retrieved stored client-id from keyring"))
slog.Debug(cli.SuccessMessage("Retrieved stored client-id from keyring"))
}
}

// check if we have a clientSecret in the keyring, if a null value is passed in
if clientSecret == "" {
retrievedSecret, krErr := keyring.Get(handlers.TOKEN_URL, clientID)
if krErr == nil || retrievedSecret == "" {
clientSecret, err = handlers.GetClientSecretFromCache(clientID)
if err == nil || clientSecret == "" {
cli.ExitWithError("Please provide required flag: (client-secret)", errors.New("no client-secret found"))
} else {
clientSecret = retrievedSecret
fmt.Println(cli.SuccessMessage("Retrieved stored client-secret from keyring"))
slog.Debug("Retrieved stored client-secret from keyring")
}
}

_, err := handlers.GetTokenWithClientCredentials(cmd.Context(), clientID, clientSecret, handlers.TOKEN_URL, false)
tok, err := handlers.GetTokenWithClientCreds(cmd.Context(), clientID, clientSecret, handlers.TOKEN_URL, noCacheCreds)
if err != nil {
cli.ExitWithError("An error occurred during login. Please check your credentials and try again", err)
}

fmt.Println(cli.SuccessMessage("Successfully logged in with client ID and secret"))
if !noCacheCreds {
fmt.Println(cli.SuccessMessage("Successfully logged in with client ID and secret"))
} else {
fmt.Print(tok.AccessToken)
}
}

func init() {
clientCredentialsCmd := man.Docs.GetCommand("auth/client-credentials",
man.WithRun(auth_clientCredentials),
// use the individual client-id and client-secret flags here instead of the global with-client-creds flag
man.WithHiddenFlags("with-client-creds"),
)
clientCredentialsCmd.Flags().String(
clientCredentialsCmd.Flags().StringP(
clientCredentialsCmd.GetDocFlag("client-id").Name,
clientCredentialsCmd.GetDocFlag("client-id").Shorthand,
clientCredentialsCmd.GetDocFlag("client-id").Default,
clientCredentialsCmd.GetDocFlag("client-id").Description,
)
clientCredentialsCmd.Flags().String(
clientCredentialsCmd.Flags().StringP(
clientCredentialsCmd.GetDocFlag("client-secret").Name,
clientCredentialsCmd.GetDocFlag("client-secret").Shorthand,
clientCredentialsCmd.GetDocFlag("client-secret").Default,
clientCredentialsCmd.GetDocFlag("client-secret").Description,
)
clientCredentialsCmd.Flags().BoolVarP(
&noCacheCreds,
clientCredentialsCmd.GetDocFlag("no-cache").Name,
clientCredentialsCmd.GetDocFlag("no-cache").Shorthand,
clientCredentialsCmd.GetDocFlag("no-cache").DefaultAsBool(),
clientCredentialsCmd.GetDocFlag("no-cache").Description,
)
}
2 changes: 1 addition & 1 deletion cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
)

func config_updateOutput(cmd *cobra.Command, args []string) {
h := cli.NewHandler(cmd)
h := NewHandler(cmd)
defer h.Close()

flagHelper := cli.NewFlagHelper(cmd)
Expand Down
4 changes: 2 additions & 2 deletions cmd/dev-selectors.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ var (
)

func dev_selectorsGen(cmd *cobra.Command, args []string) {
h := cli.NewHandler(cmd)
h := NewHandler(cmd)
defer h.Close()

flagHelper := cli.NewFlagHelper(cmd)
Expand Down Expand Up @@ -62,7 +62,7 @@ func dev_selectorsGen(cmd *cobra.Command, args []string) {
}

func dev_selectorsTest(cmd *cobra.Command, args []string) {
h := cli.NewHandler(cmd)
h := NewHandler(cmd)
defer h.Close()

flagHelper := cli.NewFlagHelper(cmd)
Expand Down
21 changes: 21 additions & 0 deletions cmd/dev.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cmd
import (
"bufio"
"encoding/json"
"errors"
"fmt"
"io"
"os"
Expand All @@ -11,6 +12,7 @@ import (
"github.com/charmbracelet/lipgloss/table"
"github.com/opentdf/otdfctl/internal/config"
"github.com/opentdf/otdfctl/pkg/cli"
"github.com/opentdf/otdfctl/pkg/handlers"
"github.com/opentdf/otdfctl/pkg/man"
"github.com/opentdf/platform/protocol/go/common"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -153,6 +155,25 @@ func readBytesFromFile(filePath string) []byte {
return bytes
}

// instantiates a new handler with authentication via client credentials
func NewHandler(cmd *cobra.Command) handlers.Handler {
platformEndpoint := cmd.Flag("host").Value.String()

// load client credentials from file, JSON, or OS keyring
creds, err := handlers.GetClientCreds(clientCredsFile, []byte(clientCredsJSON))
if err != nil {
cli.ExitWithError("Failed to get client credentials", err)
}
h, err := handlers.New(platformEndpoint, creds.ClientID, creds.ClientSecret)
if err != nil {
if errors.Is(err, handlers.ErrUnauthenticated) {
cli.ExitWithError(fmt.Sprintf("Not logged in. Please authenticate via CLI auth flow(s) before using command (%s %s)", cmd.Parent().Use, cmd.Use), err)
}
cli.ExitWithError("Failed to connect to server", err)
}
return h
}

func init() {
designCmd := man.Docs.GetCommand("dev/design-system",
man.WithRun(dev_designSystem),
Expand Down
3 changes: 1 addition & 2 deletions cmd/interactive.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package cmd

import (
"github.com/opentdf/otdfctl/pkg/cli"
"github.com/opentdf/otdfctl/pkg/man"
"github.com/opentdf/otdfctl/tui"
"github.com/spf13/cobra"
Expand All @@ -10,7 +9,7 @@ import (
func init() {
cmd := man.Docs.GetCommand("interactive",
man.WithRun(func(cmd *cobra.Command, args []string) {
h := cli.NewHandler(cmd)
h := NewHandler(cmd)
tui.StartTea(h)
}),
)
Expand Down
4 changes: 2 additions & 2 deletions cmd/kas-grants.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
)

func policy_updateKasGrant(cmd *cobra.Command, args []string) {
h := cli.NewHandler(cmd)
h := NewHandler(cmd)
defer h.Close()

flagHelper := cli.NewFlagHelper(cmd)
Expand Down Expand Up @@ -51,7 +51,7 @@ func policy_updateKasGrant(cmd *cobra.Command, args []string) {
}

func policy_deleteKasGrant(cmd *cobra.Command, args []string) {
h := cli.NewHandler(cmd)
h := NewHandler(cmd)
defer h.Close()

flagHelper := cli.NewFlagHelper(cmd)
Expand Down
10 changes: 5 additions & 5 deletions cmd/kas-registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
var policy_kasRegistryCmd *cobra.Command

func policy_getKeyAccessRegistry(cmd *cobra.Command, args []string) {
h := cli.NewHandler(cmd)
h := NewHandler(cmd)
defer h.Close()

flagHelper := cli.NewFlagHelper(cmd)
Expand Down Expand Up @@ -43,7 +43,7 @@ func policy_getKeyAccessRegistry(cmd *cobra.Command, args []string) {
}

func policy_listKeyAccessRegistries(cmd *cobra.Command, args []string) {
h := cli.NewHandler(cmd)
h := NewHandler(cmd)
defer h.Close()

list, err := h.ListKasRegistryEntries()
Expand Down Expand Up @@ -73,7 +73,7 @@ func policy_listKeyAccessRegistries(cmd *cobra.Command, args []string) {
}

func policy_createKeyAccessRegistry(cmd *cobra.Command, args []string) {
h := cli.NewHandler(cmd)
h := NewHandler(cmd)
defer h.Close()

flagHelper := cli.NewFlagHelper(cmd)
Expand Down Expand Up @@ -122,7 +122,7 @@ func policy_createKeyAccessRegistry(cmd *cobra.Command, args []string) {
}

func policy_updateKeyAccessRegistry(cmd *cobra.Command, args []string) {
h := cli.NewHandler(cmd)
h := NewHandler(cmd)
defer h.Close()

flagHelper := cli.NewFlagHelper(cmd)
Expand Down Expand Up @@ -168,7 +168,7 @@ func policy_updateKeyAccessRegistry(cmd *cobra.Command, args []string) {
}

func policy_deleteKeyAccessRegistry(cmd *cobra.Command, args []string) {
h := cli.NewHandler(cmd)
h := NewHandler(cmd)
defer h.Close()

flagHelper := cli.NewFlagHelper(cmd)
Expand Down
14 changes: 6 additions & 8 deletions cmd/policy-attributeNamespaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,10 @@ import (

// TODO: add metadata to outputs once [https://github.com/opentdf/otdfctl/issues/73] is addressed

var (
policy_attributeNamespacesCmd = man.Docs.GetCommand("policy/attributes/namespaces")
)
var policy_attributeNamespacesCmd = man.Docs.GetCommand("policy/attributes/namespaces")

func policy_getAttributeNamespace(cmd *cobra.Command, args []string) {
h := cli.NewHandler(cmd)
h := NewHandler(cmd)
defer h.Close()

flagHelper := cli.NewFlagHelper(cmd)
Expand All @@ -40,7 +38,7 @@ func policy_getAttributeNamespace(cmd *cobra.Command, args []string) {
}

func policy_listAttributeNamespaces(cmd *cobra.Command, args []string) {
h := cli.NewHandler(cmd)
h := NewHandler(cmd)
defer h.Close()

state := cli.GetState(cmd)
Expand All @@ -65,7 +63,7 @@ func policy_listAttributeNamespaces(cmd *cobra.Command, args []string) {
}

func policy_createAttributeNamespace(cmd *cobra.Command, args []string) {
h := cli.NewHandler(cmd)
h := NewHandler(cmd)
defer h.Close()

flagHelper := cli.NewFlagHelper(cmd)
Expand All @@ -89,7 +87,7 @@ func policy_createAttributeNamespace(cmd *cobra.Command, args []string) {
}

func policy_deactivateAttributeNamespace(cmd *cobra.Command, args []string) {
h := cli.NewHandler(cmd)
h := NewHandler(cmd)
defer h.Close()

flagHelper := cli.NewFlagHelper(cmd)
Expand Down Expand Up @@ -121,7 +119,7 @@ func policy_deactivateAttributeNamespace(cmd *cobra.Command, args []string) {
}

func policy_updateAttributeNamespace(cmd *cobra.Command, args []string) {
h := cli.NewHandler(cmd)
h := NewHandler(cmd)
defer h.Close()

flagHelper := cli.NewFlagHelper(cmd)
Expand Down
16 changes: 8 additions & 8 deletions cmd/policy-attributeValues.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func policy_createAttributeValue(cmd *cobra.Command, args []string) {
metadataLabels := flagHelper.GetStringSlice("label", metadataLabels, cli.FlagHelperStringSliceOptions{Min: 0})
// TODO: support create with members when update is unblocked to remove/alter them after creation [https://github.com/opentdf/platform/issues/476]

h := cli.NewHandler(cmd)
h := NewHandler(cmd)
defer h.Close()

attr, err := h.GetAttribute(attrId)
Expand All @@ -42,7 +42,7 @@ func policy_getAttributeValue(cmd *cobra.Command, args []string) {
flagHelper := cli.NewFlagHelper(cmd)
id := flagHelper.GetRequiredString("id")

h := cli.NewHandler(cmd)
h := NewHandler(cmd)
defer h.Close()

v, err := h.GetAttributeValue(id)
Expand All @@ -54,7 +54,7 @@ func policy_getAttributeValue(cmd *cobra.Command, args []string) {
}

func policy_listAttributeValue(cmd *cobra.Command, args []string) {
h := cli.NewHandler(cmd)
h := NewHandler(cmd)
defer h.Close()
flagHelper := cli.NewFlagHelper(cmd)
attrId := flagHelper.GetRequiredString("attribute-id")
Expand Down Expand Up @@ -85,7 +85,7 @@ func policy_updateAttributeValue(cmd *cobra.Command, args []string) {
id := flagHelper.GetRequiredString("id")
metadataLabels := flagHelper.GetStringSlice("label", metadataLabels, cli.FlagHelperStringSliceOptions{Min: 0})

h := cli.NewHandler(cmd)
h := NewHandler(cmd)
defer h.Close()

_, err := h.GetAttributeValue(id)
Expand All @@ -105,7 +105,7 @@ func policy_deactivateAttributeValue(cmd *cobra.Command, args []string) {
flagHelper := cli.NewFlagHelper(cmd)
id := flagHelper.GetRequiredString("id")

h := cli.NewHandler(cmd)
h := NewHandler(cmd)
defer h.Close()

value, err := h.GetAttributeValue(id)
Expand Down Expand Up @@ -144,7 +144,7 @@ func policy_deactivateAttributeValue(cmd *cobra.Command, args []string) {
// id := flagHelper.GetRequiredString("id")
// members := flagHelper.GetStringSlice("member", attrValueMembers, cli.FlagHelperStringSliceOptions{})

// h := cli.NewHandler(cmd)
// h := NewHandler(cmd)
// defer h.Close()

// prev, err := h.GetAttributeValue(id)
Expand Down Expand Up @@ -179,7 +179,7 @@ func policy_deactivateAttributeValue(cmd *cobra.Command, args []string) {
// id := flagHelper.GetRequiredString("id")
// members := flagHelper.GetStringSlice("members", attrValueMembers, cli.FlagHelperStringSliceOptions{})

// h := cli.NewHandler(cmd)
// h := NewHandler(cmd)
// defer h.Close()

// prev, err := h.GetAttributeValue(id)
Expand Down Expand Up @@ -223,7 +223,7 @@ func policy_deactivateAttributeValue(cmd *cobra.Command, args []string) {
// id := flagHelper.GetRequiredString("id")
// members := flagHelper.GetStringSlice("members", attrValueMembers, cli.FlagHelperStringSliceOptions{})

// h := cli.NewHandler(cmd)
// h := NewHandler(cmd)
// defer h.Close()

// prev, err := h.GetAttributeValue(id)
Expand Down
Loading

0 comments on commit c20dc1e

Please sign in to comment.