Skip to content

Commit

Permalink
OCM-4965: Keyring configuration storage (#600)
Browse files Browse the repository at this point in the history
  • Loading branch information
tylercreller authored Apr 9, 2024
1 parent 0570cbd commit 42a6c63
Show file tree
Hide file tree
Showing 16 changed files with 958 additions and 59 deletions.
6 changes: 6 additions & 0 deletions .github/workflows/check-pull-request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ jobs:
steps:
- name: Checkout the source
uses: actions/checkout@v3
- name: Install Keyrings (macOS-only)
if: ${{ contains(fromJSON('["macos-latest"]'), matrix.platform) }}
run: brew install pass gnupg
- name: Install Keyrings (linux)
if: ${{ contains(fromJSON('["ubuntu-latest"]'), matrix.platform) }}
run: sudo apt-get install pass
- name: Setup Go
uses: actions/setup-go@v4
with:
Expand Down
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ An `~/config/ocm/ocm.json` file stores login credentials for a single API
server. Using multiple servers therefore requires having to log in and out a lot
or the ability to utilize multiple config files. The latter functionality is
provided with the `OCM_CONFIG` environment variable. If running `ocm login` was
successfull in both cases, the `ocm whoami` commands will return different
successful in both cases, the `ocm whoami` commands will return different
results:

```
Expand All @@ -129,6 +129,25 @@ $ OCM_CONFIG=$HOME/ocm.json.stg ocm whoami

NOTE: Tokens for production and staging will differ.

## Storing Configuration & Tokens in OS Keyring
The `OCM_KEYRING` environment variable provides the ability to store the OCM
configuration containing your tokens in your OS keyring. This is provided
as an alternative to storing the configuration in plain-text on your system.
`OCM_KEYRING` will override `OCM_CONFIG` if both are set.

`OCM_KEYRING` supports the following keyrings:

* [Windows Credential Manager](https://support.microsoft.com/en-us/windows/accessing-credential-manager-1b5c916a-6a16-889f-8581-fc16e8165ac0) - `wincred`
* [macOS Keychain](https://support.apple.com/en-us/guide/keychain-access/welcome/mac) - `keychain`
* Secret Service ([Gnome Keyring](https://wiki.gnome.org/Projects/GnomeKeyring), [KWallet](https://apps.kde.org/kwalletmanager5/), etc.) - `secret-service`
* [Pass](https://www.passwordstore.org/) - `pass`

| | wincred | keychain | secret-service | pass |
| ------------- | ------------- | ------------- | ------------- | ------------- |
| Windows | :heavy_check_mark: | :x: | :x: | :x: |
| macOS | :x: | :heavy_check_mark:* | :x: | :heavy_check_mark: |
| Linux | :x: | :x: | :heavy_check_mark: | :heavy_check_mark: |

## Obtaining Tokens

If you need the _OpenID_ access token to use it with some other tool, you can
Expand Down
12 changes: 11 additions & 1 deletion cmd/ocm/config/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/openshift-online/ocm-cli/cmd/ocm/config/get"
"github.com/openshift-online/ocm-cli/cmd/ocm/config/set"
"github.com/openshift-online/ocm-cli/pkg/config"
"github.com/openshift-online/ocm-cli/pkg/properties"
)

func configVarDocs() (ret string) {
Expand Down Expand Up @@ -60,7 +61,16 @@ The following variables are supported:
Note that "ocm config get access_token" gives whatever the file contains - may be missing or expired;
you probably want "ocm token" command instead which will obtain a fresh token if needed.
`, loc, configVarDocs())
If '%s' is set, the configuration file is ignored and the keyring is used instead. The
following backends are supported for the keyring:
- macOS: keychain, pass
- Linux: secret-service, pass
- Windows: wincred
Available Keyrings on your OS: %s
`, loc, configVarDocs(), properties.KeyringEnvKey, strings.Join(config.GetKeyrings(), ", "))
return
}

Expand Down
33 changes: 23 additions & 10 deletions cmd/ocm/login/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/openshift-online/ocm-cli/pkg/urls"
sdk "github.com/openshift-online/ocm-sdk-go"
"github.com/openshift-online/ocm-sdk-go/authentication"
"github.com/openshift-online/ocm-sdk-go/authentication/securestore"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -150,28 +151,40 @@ func init() {
"use-auth-code",
false,
"Login using OAuth Authorization Code. This should be used for most cases where a "+
"browser is available.",
"browser is available. See --use-device-code for remote hosts and containers.",
)
flags.BoolVar(
&args.useDeviceCode,
"use-device-code",
false,
"Login using OAuth Device Code. "+
"This should only be used for remote hosts and containers where browsers are "+
"not available. Use auth code for all other scenarios.",
"not available. See --use-auth-code for all other scenarios.",
)
}

var (
InitiateAuthCode = authentication.InitiateAuthCode
)

func run(cmd *cobra.Command, argv []string) error {
ctx := context.Background()

var err error

// Fail fast if OCM_KEYRING is provided and invalid
if keyring, ok := config.IsKeyringManaged(); ok {
err := securestore.ValidateBackend(keyring)
if err != nil {
return err
}
}

if args.useAuthCode {
fmt.Println("You will now be redirected to Red Hat SSO login")
// Short wait for a less jarring experience
time.Sleep(2 * time.Second)
token, err := authentication.InitiateAuthCode(oauthClientID)
token, err := InitiateAuthCode(oauthClientID)
if err != nil {
return fmt.Errorf("an error occurred while retrieving the token : %v", err)
}
Expand All @@ -184,7 +197,7 @@ func run(cmd *cobra.Command, argv []string) error {
ClientID: oauthClientID,
}
_, err = deviceAuthConfig.InitiateDeviceAuth(ctx)
if err != nil || deviceAuthConfig == nil {
if err != nil {
return fmt.Errorf("an error occurred while initiating device auth: %v", err)
}
deviceAuthResp := deviceAuthConfig.DeviceAuthResponse
Expand All @@ -201,9 +214,9 @@ func run(cmd *cobra.Command, argv []string) error {

// Check that we have some kind of credentials:
havePassword := args.user != "" && args.password != ""
haveSecret := args.clientID != "" && args.clientSecret != ""
haveClientCreds := args.clientID != "" && args.clientSecret != ""
haveToken := args.token != ""
if !havePassword && !haveSecret && !haveToken {
if !havePassword && !haveClientCreds && !haveToken {
// Allow bare `ocm login` to suggest the token page without noise of full help.
fmt.Fprintf(
os.Stderr,
Expand All @@ -227,10 +240,10 @@ func run(cmd *cobra.Command, argv []string) error {
)
}

// Load the configuration file:
// Load the configuration:
cfg, err := config.Load()
if err != nil {
return fmt.Errorf("Can't load config file: %v", err)
return fmt.Errorf("Can't load config: %v", err)
}
if cfg == nil {
cfg = new(config.Config)
Expand Down Expand Up @@ -331,7 +344,7 @@ func run(cmd *cobra.Command, argv []string) error {

err = config.Save(cfg)
if err != nil {
return fmt.Errorf("Can't save config file: %v", err)
return fmt.Errorf("can't save config: %v", err)
}

if args.useAuthCode || args.useDeviceCode {
Expand All @@ -343,7 +356,7 @@ func run(cmd *cobra.Command, argv []string) error {

fmt.Println("Login successful")
fmt.Printf("To switch accounts, logout from %s and run `ocm logout` "+
"before attempting to login again", ssoHost)
"before attempting to login again\n", ssoHost)
}

return nil
Expand Down
10 changes: 10 additions & 0 deletions cmd/ocm/logout/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/spf13/cobra"

"github.com/openshift-online/ocm-cli/pkg/config"
"github.com/openshift-online/ocm-sdk-go/authentication/securestore"
)

var Cmd = &cobra.Command{
Expand All @@ -33,6 +34,15 @@ var Cmd = &cobra.Command{
}

func run(cmd *cobra.Command, argv []string) error {

if keyring, ok := config.IsKeyringManaged(); ok {
err := securestore.RemoveConfigFromKeyring(keyring)
if err != nil {
return fmt.Errorf("can't remove configuration from keyring: %w", err)
}
return nil
}

// Load the configuration file:
cfg, err := config.Load()
if err != nil {
Expand Down
31 changes: 20 additions & 11 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,46 +12,54 @@ require (
github.com/nwidger/jsoncolor v0.3.2
github.com/onsi/ginkgo/v2 v2.11.0
github.com/onsi/gomega v1.27.8
github.com/openshift-online/ocm-sdk-go v0.1.407
github.com/openshift-online/ocm-sdk-go v0.1.413
github.com/openshift/rosa v1.2.24
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8
github.com/spf13/cobra v1.7.0
github.com/spf13/pflag v1.0.5
gitlab.com/c0b/go-ordered-json v0.0.0-20201030195603-febf46534d5a
golang.org/x/term v0.15.0
golang.org/x/term v0.17.0
golang.org/x/text v0.14.0
gopkg.in/yaml.v3 v3.0.1
k8s.io/apimachinery v0.27.3
)

require (
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
github.com/99designs/keyring v1.2.2 // indirect
github.com/alessio/shellescape v1.4.1 // indirect
github.com/aws/aws-sdk-go v1.44.110 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/briandowns/spinner v1.19.0 // indirect
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/danieljoos/wincred v1.2.0 // indirect
github.com/dvsekhvalnov/jose2go v1.6.0 // indirect
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-logr/logr v1.2.4 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/css v1.0.0 // indirect
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/itchyny/gojq v0.12.9 // indirect
github.com/itchyny/timefmt-go v0.1.4 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgconn v1.13.0 // indirect
github.com/jackc/pgconn v1.14.3 // indirect
github.com/jackc/pgio v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgproto3/v2 v2.3.1 // indirect
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
github.com/jackc/pgtype v1.12.0 // indirect
github.com/jackc/pgx/v4 v4.17.2 // indirect
github.com/jackc/pgproto3/v2 v2.3.3 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgtype v1.14.0 // indirect
github.com/jackc/pgx/v4 v4.18.3 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
Expand All @@ -62,20 +70,21 @@ require (
github.com/microcosm-cc/bluemonday v1.0.23 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mtibben/percent v0.2.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_golang v1.13.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect
github.com/zalando/go-keyring v0.2.3 // indirect
github.com/zgalor/weberr v0.7.0 // indirect
golang.org/x/crypto v0.17.0 // indirect
golang.org/x/net v0.19.0 // indirect
golang.org/x/crypto v0.20.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/oauth2 v0.15.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/tools v0.9.3 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.31.0 // indirect
Expand Down
Loading

0 comments on commit 42a6c63

Please sign in to comment.