Skip to content

Commit 9c4968f

Browse files
authored
feat: improve auth with client credentials (#286)
1 parent a7aa89f commit 9c4968f

16 files changed

+339
-234
lines changed

cmd/auth-clearCachedCredentials.go

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99
"github.com/spf13/cobra"
1010
)
1111

12-
var clearCachedCredsCmd = man.Docs.GetCommand("auth/clear-cached-credentials",
12+
var auth_clearClientCredentialsCmd = man.Docs.GetCommand("auth/clear-client-credentials",
1313
man.WithRun(auth_clearCreds),
1414
man.WithHiddenFlags("with-client-creds", "with-client-creds-file"),
1515
)
@@ -18,9 +18,22 @@ func auth_clearCreds(cmd *cobra.Command, args []string) {
1818
flagHelper := cli.NewFlagHelper(cmd)
1919
host := flagHelper.GetRequiredString("host")
2020

21-
if err := handlers.ClearCachedCredentials(host); err != nil {
22-
cli.ExitWithError("Failed to clear cached client credentials and token", err)
21+
p := cli.NewPrinter(true)
22+
23+
p.Printf("Clearing cached client credentials for %s... ", host)
24+
if err := handlers.NewKeyring(host).DeleteClientCredentials(); err != nil {
25+
fmt.Println("failed")
26+
cli.ExitWithError("Failed to clear cached client credentials", err)
2327
}
28+
p.Println("ok")
29+
}
30+
31+
func init() {
32+
auth_clearClientCredentialsCmd.Flags().String(
33+
auth_clearClientCredentialsCmd.GetDocFlag("all").Name,
34+
auth_clearClientCredentialsCmd.GetDocFlag("all").Description,
35+
auth_clearClientCredentialsCmd.GetDocFlag("all").Default,
36+
)
2437

25-
fmt.Println(cli.SuccessMessage("Cached client credentials and token are clear."))
38+
authCmd.AddCommand(&auth_clearClientCredentialsCmd.Command)
2639
}

cmd/auth-clientCredentials.go

Lines changed: 26 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,94 +1,57 @@
11
package cmd
22

33
import (
4-
"errors"
54
"fmt"
6-
"log/slog"
75

86
"github.com/opentdf/otdfctl/pkg/cli"
97
"github.com/opentdf/otdfctl/pkg/handlers"
108
"github.com/opentdf/otdfctl/pkg/man"
119
"github.com/spf13/cobra"
1210
)
1311

14-
var (
15-
clientCredentialsCmd = man.Docs.GetCommand("auth/client-credentials",
16-
man.WithRun(auth_clientCredentials),
17-
)
18-
noCacheCreds bool
12+
var clientCredentialsCmd = man.Docs.GetCommand("auth/client-credentials",
13+
man.WithRun(auth_clientCredentials),
14+
man.WithHiddenFlags("with-client-creds", "with-client-creds-file"),
1915
)
2016

2117
func auth_clientCredentials(cmd *cobra.Command, args []string) {
22-
var err error
18+
var c handlers.ClientCredentials
2319

2420
flagHelper := cli.NewFlagHelper(cmd)
2521
host := flagHelper.GetRequiredString("host")
2622
tlsNoVerify := flagHelper.GetOptionalBool("tls-no-verify")
27-
clientID := flagHelper.GetOptionalString("client-id")
28-
clientSecret := flagHelper.GetOptionalString("client-secret")
2923

30-
slog.Debug("Checking for client credentials file", slog.String("with-client-creds-file", clientCredsFile))
31-
if clientCredsFile != "" {
32-
creds, err := handlers.GetClientCredsFromFile(clientCredsFile)
33-
if err != nil {
34-
cli.ExitWithError("Failed to parse client credentials JSON", err)
35-
}
36-
clientID = creds.ClientID
37-
clientSecret = creds.ClientSecret
38-
}
24+
p := cli.NewPrinter(true)
3925

40-
// if not provided by flag, check keyring cache for clientID
41-
if clientID == "" {
42-
slog.Debug("No client-id provided. Attempting to retrieve the default from keyring.")
43-
clientID, err = handlers.GetClientIDFromCache(host)
44-
if err != nil || clientID == "" {
45-
cli.ExitWithError("Please provide required flag: (client-id)", errors.New("no client-id found"))
46-
} else {
47-
slog.Debug(cli.SuccessMessage("Retrieved stored client-id from keyring"))
48-
}
26+
if len(args) > 0 {
27+
c.ClientId = args[0]
28+
}
29+
if len(args) > 1 {
30+
c.ClientSecret = args[1]
4931
}
5032

51-
// check if we have a clientSecret in the keyring, if a null value is passed in
52-
if clientSecret == "" {
53-
clientSecret, err = handlers.GetClientSecretFromCache(host, clientID)
54-
if err == nil || clientSecret == "" {
55-
cli.ExitWithError("Please provide required flag: (client-secret)", errors.New("no client-secret found"))
56-
} else {
57-
slog.Debug("Retrieved stored client-secret from keyring")
58-
}
33+
if c.ClientId == "" {
34+
c.ClientId = cli.AskForInput("Enter client id: ")
35+
}
36+
if c.ClientSecret == "" {
37+
c.ClientSecret = cli.AskForSecret("Enter client secret: ")
5938
}
6039

61-
slog.Debug("Attempting to login with client credentials", slog.String("client-id", clientID))
62-
if err := handlers.GetTokenWithClientCreds(cmd.Context(), host, clientID, clientSecret, tlsNoVerify); err != nil {
40+
p.Printf("Logging in with client ID and secret for %s... ", host)
41+
if _, err := handlers.GetTokenWithClientCreds(cmd.Context(), host, c, tlsNoVerify); err != nil {
42+
fmt.Println("failed")
6343
cli.ExitWithError("An error occurred during login. Please check your credentials and try again", err)
6444
}
45+
p.Println("ok")
6546

66-
fmt.Println(cli.SuccessMessage("Successfully logged in with client ID and secret"))
47+
p.Print("Storing client ID and secret in keyring... ")
48+
if err := handlers.NewKeyring(host).SetClientCredentials(c); err != nil {
49+
fmt.Println("failed")
50+
cli.ExitWithError("Failed to cache client credentials", err)
51+
}
52+
p.Println("ok")
6753
}
6854

6955
func init() {
70-
clientCredentialsCmd := man.Docs.GetCommand("auth/client-credentials",
71-
man.WithRun(auth_clientCredentials),
72-
// use the individual client-id and client-secret flags here instead of the global with-client-creds flag
73-
man.WithHiddenFlags("with-client-creds", "with-client-creds-file"),
74-
)
75-
clientCredentialsCmd.Flags().StringP(
76-
clientCredentialsCmd.GetDocFlag("client-id").Name,
77-
clientCredentialsCmd.GetDocFlag("client-id").Shorthand,
78-
clientCredentialsCmd.GetDocFlag("client-id").Default,
79-
clientCredentialsCmd.GetDocFlag("client-id").Description,
80-
)
81-
clientCredentialsCmd.Flags().StringP(
82-
clientCredentialsCmd.GetDocFlag("client-secret").Name,
83-
clientCredentialsCmd.GetDocFlag("client-secret").Shorthand,
84-
clientCredentialsCmd.GetDocFlag("client-secret").Default,
85-
clientCredentialsCmd.GetDocFlag("client-secret").Description,
86-
)
87-
clientCredentialsCmd.Flags().BoolVarP(
88-
&noCacheCreds,
89-
clientCredentialsCmd.GetDocFlag("no-cache").Name,
90-
clientCredentialsCmd.GetDocFlag("no-cache").Shorthand,
91-
clientCredentialsCmd.GetDocFlag("no-cache").DefaultAsBool(),
92-
clientCredentialsCmd.GetDocFlag("no-cache").Description,
93-
)
56+
authCmd.AddCommand(&clientCredentialsCmd.Command)
9457
}

cmd/auth-printAccessToken.go

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package cmd
22

33
import (
4+
"context"
5+
"encoding/json"
46
"fmt"
57

68
"github.com/opentdf/otdfctl/pkg/cli"
@@ -9,17 +11,56 @@ import (
911
"github.com/spf13/cobra"
1012
)
1113

12-
var printAccessToken = man.Docs.GetCommand("auth/print-access-token",
13-
man.WithRun(auth_printAccessToken),
14-
)
14+
var auth_printAccessTokenCmd = man.Docs.GetCommand("auth/print-access-token",
15+
man.WithRun(auth_printAccessToken))
1516

1617
func auth_printAccessToken(cmd *cobra.Command, args []string) {
1718
flagHelper := cli.NewFlagHelper(cmd)
1819
host := flagHelper.GetRequiredString("host")
20+
jsonOut := flagHelper.GetOptionalBool("json")
21+
22+
printEnabled := !jsonOut
23+
p := cli.NewPrinter(printEnabled)
24+
25+
p.Printf("Getting stored client credentials for %s... ", host)
26+
clientCredentials, err := handlers.NewKeyring(host).GetClientCredentials()
27+
if err != nil {
28+
p.Println("failed")
29+
cli.ExitWithError("Client credentials not found. Please use `auth client-credentials` to set them", err)
30+
}
31+
p.Println("ok")
1932

20-
tok, err := handlers.GetOIDCTokenFromCache(host)
33+
p.Printf("Getting access token for %s... ", clientCredentials.ClientId)
34+
tok, err := handlers.GetTokenWithClientCreds(
35+
context.Background(),
36+
host,
37+
clientCredentials,
38+
flagHelper.GetOptionalBool("tls-no-verify"),
39+
)
2140
if err != nil {
22-
cli.ExitWithError("Failed to get OIDC token from cache", err)
41+
p.Println("failed")
42+
cli.ExitWithError("Failed to get token", err)
2343
}
24-
fmt.Print(tok)
44+
p.Println("ok")
45+
p.Printf("Access Token: %s\n", tok.AccessToken)
46+
47+
if jsonOut {
48+
d, err := json.MarshalIndent(tok, "", " ")
49+
if err != nil {
50+
cli.ExitWithError("Failed to marshal token to json", err)
51+
}
52+
53+
fmt.Println(string(d))
54+
return
55+
}
56+
}
57+
58+
func init() {
59+
auth_printAccessTokenCmd.Flags().Bool(
60+
auth_printAccessTokenCmd.GetDocFlag("json").Name,
61+
auth_printAccessTokenCmd.GetDocFlag("json").DefaultAsBool(),
62+
auth_printAccessTokenCmd.GetDocFlag("json").Description,
63+
)
64+
65+
authCmd.AddCommand(&auth_printAccessTokenCmd.Command)
2566
}

cmd/auth.go

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@ import (
88
"github.com/spf13/cobra"
99
)
1010

11+
var authCmd = man.Docs.GetCommand("auth", man.WithHiddenFlags(
12+
"with-client-creds",
13+
"with-client-creds-file",
14+
))
15+
1116
func init() {
12-
cmd := man.Docs.GetCommand("auth",
13-
man.WithSubcommands(clientCredentialsCmd),
14-
man.WithSubcommands(printAccessToken),
15-
man.WithSubcommands(clearCachedCredsCmd),
16-
)
17+
RootCmd.AddCommand(&authCmd.Command)
1718

18-
cmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {
19+
authCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {
1920
// not supported on linux
2021
if runtime.GOOS == "linux" {
2122
cli.ExitWithWarning(
@@ -24,6 +25,4 @@ func init() {
2425
)
2526
}
2627
}
27-
28-
RootCmd.AddCommand(&cmd.Command)
2928
}

cmd/dev.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ func NewHandler(cmd *cobra.Command) handlers.Handler {
172172
cli.ExitWithError("Failed to get client credentials", err)
173173
}
174174

175-
h, err := handlers.NewWithCredentials(host, creds.ClientID, creds.ClientSecret, tlsNoVerify)
175+
h, err := handlers.NewWithCredentials(host, creds.ClientId, creds.ClientSecret, tlsNoVerify)
176176
if err != nil {
177177
if errors.Is(err, handlers.ErrUnauthenticated) {
178178
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)

docs/man/auth/clear-cached-credentials.md

Lines changed: 0 additions & 8 deletions
This file was deleted.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
title: Clear the cached client credentials
3+
4+
command:
5+
name: clear-client-credentials
6+
flags:
7+
- name: all
8+
description: Clear all cached client credentials
9+
default: false
10+
---
11+
12+
Clear the cached client credentials from the OS keyring for the current platform endpoint.

docs/man/auth/client-credentials.md

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,11 @@ title: Authenticate to the platform with the client-credentials flow
33

44
command:
55
name: client-credentials
6-
flags:
7-
- name: client-id
8-
description: A clientId for use in client-credentials auth flow
9-
shorthand: i
10-
required: true
11-
- name: client-secret
12-
description: A clientSecret for use in client-credentials auth flow
13-
shorthand: s
14-
- name: no-cache
15-
description: Do not cache credentials on the native OS and print access token to stdout instead
6+
args:
7+
- client-id
8+
arbitrary_args:
9+
- client-secret
1610
---
1711

18-
Allows the user to login in via Client ID and Secret. The client credentials and OIDC Access Token will be stored
19-
in the OS-specific keychain by default, otherwise printed to `stdout` if `--no-cache` is passed.
12+
Allows the user to login in via Client Credentials flow. The client credentials will be stored safely
13+
in the OS keyring for future use.

docs/man/auth/print-access-token.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ title: Print the cached OIDC access token (if found)
33

44
command:
55
name: print-access-token
6+
flags:
7+
- name: json
8+
description: Print the full token in JSON format
9+
default: false
610
---
711

8-
Retrieves the cached OIDC Access Token from the OS-specific keychain and prints to stdout if found.
12+
Retrieves a new OIDC Access Token using the client credentials from the OS-specific keychain and prints to stdout if found.

go.mod

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ require (
2121
github.com/spf13/viper v1.19.0
2222
github.com/stretchr/testify v1.9.0
2323
github.com/zalando/go-keyring v0.2.5
24+
golang.org/x/oauth2 v0.22.0
2425
golang.org/x/term v0.22.0
2526
google.golang.org/grpc v1.65.0
2627
google.golang.org/protobuf v1.34.2
@@ -47,10 +48,14 @@ require (
4748
github.com/dustin/go-humanize v1.0.1 // indirect
4849
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
4950
github.com/fsnotify/fsnotify v1.7.0 // indirect
51+
github.com/go-jose/go-jose/v4 v4.0.4 // indirect
52+
github.com/go-logr/logr v1.4.2 // indirect
53+
github.com/go-logr/stdr v1.2.2 // indirect
5054
github.com/goccy/go-json v0.10.3 // indirect
5155
github.com/godbus/dbus/v5 v5.1.0 // indirect
5256
github.com/google/uuid v1.6.0 // indirect
5357
github.com/gorilla/css v1.0.0 // indirect
58+
github.com/gorilla/securecookie v1.1.2 // indirect
5459
github.com/gowebpki/jcs v1.0.1 // indirect
5560
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // indirect
5661
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
@@ -75,6 +80,7 @@ require (
7580
github.com/muesli/cancelreader v0.2.2 // indirect
7681
github.com/muesli/reflow v0.3.0 // indirect
7782
github.com/muesli/termenv v0.15.2 // indirect
83+
github.com/muhlemmer/gu v0.3.1 // indirect
7884
github.com/olekukonko/tablewriter v0.0.5 // indirect
7985
github.com/opentdf/platform/lib/ocrypto v0.1.5 // indirect
8086
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
@@ -85,6 +91,7 @@ require (
8591
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
8692
github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f // indirect
8793
github.com/segmentio/asm v1.2.0 // indirect
94+
github.com/sirupsen/logrus v1.9.3 // indirect
8895
github.com/sourcegraph/conc v0.3.0 // indirect
8996
github.com/spf13/afero v1.11.0 // indirect
9097
github.com/spf13/cast v1.6.0 // indirect
@@ -96,6 +103,12 @@ require (
96103
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
97104
github.com/yuin/goldmark v1.5.4 // indirect
98105
github.com/yuin/goldmark-emoji v1.0.2 // indirect
106+
github.com/zitadel/logging v0.6.0 // indirect
107+
github.com/zitadel/oidc/v3 v3.27.0 // indirect
108+
github.com/zitadel/schema v1.3.0 // indirect
109+
go.opentelemetry.io/otel v1.28.0 // indirect
110+
go.opentelemetry.io/otel/metric v1.28.0 // indirect
111+
go.opentelemetry.io/otel/trace v1.28.0 // indirect
99112
go.uber.org/multierr v1.11.0 // indirect
100113
golang.org/x/crypto v0.25.0 // indirect
101114
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect

0 commit comments

Comments
 (0)