Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OCM-4965: Keyring configuration storage #600

Merged
merged 1 commit into from
Apr 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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")
tylercreller marked this conversation as resolved.
Show resolved Hide resolved
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