diff --git a/.github/workflows/publish-release.yaml b/.github/workflows/publish-release.yaml index 6c29b281..c90406f3 100644 --- a/.github/workflows/publish-release.yaml +++ b/.github/workflows/publish-release.yaml @@ -87,11 +87,11 @@ jobs: # Build for the supported operating systems and architectures: build("darwin", "amd64") build("darwin", "arm64") - build("linux", "amd64") - build("linux", "arm64") - build("linux", "ppc64le") - build("linux", "s390x") - build("windows", "amd64") + # build("linux", "amd64") + # build("linux", "arm64") + # build("linux", "ppc64le") + # build("linux", "s390x") + # build("windows", "amd64") # Calculate the SHA256 digests: for asset in os.listdir(assets): diff --git a/Makefile b/Makefile index 658c37c0..96720932 100644 --- a/Makefile +++ b/Makefile @@ -37,6 +37,11 @@ cmds: install: go install ./cmd/ocm +# CGO_ENABLED=1 is required for Keychain support on macOS +.PHONY: install-cgo +install-cgo: + CGO_ENABLED=1 go install ./cmd/ocm + .PHONY: test tests test tests: cmds ginkgo run -r diff --git a/cmd/ocm/config/cmd.go b/cmd/ocm/config/cmd.go index 2db698da..6ffe14a3 100644 --- a/cmd/ocm/config/cmd.go +++ b/cmd/ocm/config/cmd.go @@ -23,6 +23,7 @@ import ( "github.com/spf13/cobra" + "github.com/openshift-online/ocm-cli/cmd/ocm/config/delete" "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" @@ -73,4 +74,5 @@ var Cmd = &cobra.Command{ func init() { Cmd.AddCommand(get.Cmd) Cmd.AddCommand(set.Cmd) + Cmd.AddCommand(delete.Cmd) } diff --git a/cmd/ocm/config/delete/delete.go b/cmd/ocm/config/delete/delete.go new file mode 100644 index 00000000..d66e968d --- /dev/null +++ b/cmd/ocm/config/delete/delete.go @@ -0,0 +1,56 @@ +/* +Copyright (c) 2024 Red Hat, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package delete + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/openshift-online/ocm-sdk-go/authentication/securestore" +) + +var args struct { + debug bool +} + +var Cmd = &cobra.Command{ + Use: "delete", + Short: "Deletes the existing configuration from the OS keyring", + Long: "Deletes the existing configuration from the OS keyring", + Args: cobra.ExactArgs(0), + RunE: run, + Hidden: true, +} + +func init() { + flags := Cmd.Flags() + flags.BoolVar( + &args.debug, + "debug", + false, + "Enable debug mode.", + ) +} + +func run(cmd *cobra.Command, argv []string) error { + err := securestore.RemoveConfigFromKeyring() + if err != nil { + return fmt.Errorf("can't delete config from keyring: %v", err) + } + return nil +} diff --git a/cmd/ocm/config/get/get.go b/cmd/ocm/config/get/get.go index a9dc002f..47da4cad 100644 --- a/cmd/ocm/config/get/get.go +++ b/cmd/ocm/config/get/get.go @@ -23,6 +23,7 @@ import ( "github.com/spf13/cobra" "github.com/openshift-online/ocm-cli/pkg/config" + "github.com/openshift-online/ocm-sdk-go/authentication/securestore" ) var args struct { @@ -83,9 +84,19 @@ func run(cmd *cobra.Command, argv []string) error { fmt.Fprintf(os.Stdout, "%s\n", cfg.URL) case "pager": fmt.Fprintf(os.Stdout, "%s\n", cfg.Pager) + case "keyrings": + fmt.Fprintf(os.Stdout, "%s\n", getKeyrings()) default: return fmt.Errorf("Unknown setting") } return nil } + +func getKeyrings() []string { + backends := securestore.AvailableBackends() + if len(backends) == 0 { + fmt.Printf("No keyrings available: %s\n", securestore.ErrNoBackendsAvailable) + } + return backends +} diff --git a/cmd/ocm/login/cmd.go b/cmd/ocm/login/cmd.go index b5798fd4..dc5852dc 100644 --- a/cmd/ocm/login/cmd.go +++ b/cmd/ocm/login/cmd.go @@ -220,9 +220,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, @@ -246,15 +246,31 @@ func run(cmd *cobra.Command, argv []string) error { ) } - // Load the configuration file: - cfg, err := config.Load() + // Set the authentication method: + var authMethod config.AuthMethodType + if args.useAuthCode { + authMethod = config.AuthCode + } else if args.useDeviceCode { + authMethod = config.DeviceCode + } else if haveClientCreds { + authMethod = config.ClientAuth + } else if havePassword { + authMethod = config.Password + } else { + authMethod = config.TokenAuth + } + + // Load the configuration: + cfg, err := config.LoginLoad(authMethod) 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) } + cfg.AuthMethod = string(authMethod) + if haveToken { // Encrypted tokens are assumed to be refresh tokens: if config.IsEncryptedToken(args.token) { @@ -342,7 +358,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 { @@ -353,8 +369,8 @@ func run(cmd *cobra.Command, argv []string) error { ssoHost := ssoURL.Scheme + "://" + ssoURL.Hostname() fmt.Println("Login successful") - fmt.Printf("To switch accounts, logout from %s and run `ocm logout` "+ - "before attempting to login again", ssoHost) + fmt.Println(fmt.Sprintf("To switch accounts, logout from %s and run `ocm logout` "+ + "before attempting to login again", ssoHost)) } return nil diff --git a/go.mod b/go.mod index 9e0b67b7..a5516b72 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ 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.398 + github.com/openshift-online/ocm-sdk-go v0.1.399 github.com/openshift/rosa v1.2.24 github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 github.com/spf13/cobra v1.7.0 @@ -25,22 +25,28 @@ require ( ) require ( + github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect + github.com/99designs/keyring v1.2.2 // 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.5.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/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 @@ -62,13 +68,13 @@ 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/zgalor/weberr v0.7.0 // indirect diff --git a/go.sum b/go.sum index 7fdfe2e4..12760244 100644 --- a/go.sum +++ b/go.sum @@ -31,6 +31,10 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs= +github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4= +github.com/99designs/keyring v1.2.2 h1:pZd3neh/EmUzWONb35LxQfvuY7kiSXAq3HQd97+XBn0= +github.com/99designs/keyring v1.2.2/go.mod h1:wes/FrByc8j7lFOAGLGSNEg8f/PaI3cgTBqhFkHUrPk= github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -73,9 +77,13 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI= github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE= +github.com/danieljoos/wincred v1.2.0/go.mod h1:FzQLLMKBFdvu+osBrnFODiv32YGwCfx0SkRa/eYHgec= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dvsekhvalnov/jose2go v1.5.0 h1:3j8ya4Z4kMCwT5nXIKFSV84YS+HdqSSO0VsTQxaLAeM= +github.com/dvsekhvalnov/jose2go v1.5.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -104,6 +112,8 @@ github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbV github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= +github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -174,6 +184,8 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= +github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= +github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -258,8 +270,6 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxv github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= @@ -304,16 +314,20 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs= +github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nwidger/jsoncolor v0.3.2 h1:rVJJlwAWDJShnbTYOQ5RM7yTA20INyKXlJ/fg4JMhHQ= github.com/nwidger/jsoncolor v0.3.2/go.mod h1:Cs34umxLbJvgBMnVNVqhji9BhoT/N/KinHqZptQ7cf4= github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc= github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ= -github.com/openshift-online/ocm-sdk-go v0.1.398 h1:6C1mDcPxzG4jSduOaWixTTI5gSEO+Jm7OW/00jVoWGI= -github.com/openshift-online/ocm-sdk-go v0.1.398/go.mod h1:tke8vKcE7eHKyRbkJv6qo4ljo919zhx04uyQTcgF5cQ= +github.com/openshift-online/ocm-sdk-go v0.1.399 h1:mmhX+vaB7+aeq5MMZwe9gJhCbWu63GXwpnW+EE+uiv0= +github.com/openshift-online/ocm-sdk-go v0.1.399/go.mod h1:tke8vKcE7eHKyRbkJv6qo4ljo919zhx04uyQTcgF5cQ= github.com/openshift/rosa v1.2.24 h1:vv0yYnWHx6CCPEAau/0rS54P2ksaf+uWXb1TQPWxiYE= github.com/openshift/rosa v1.2.24/go.mod h1:MVXB27O3PF8WoOic23I03mmq6/9kVxpFx6FKyLMCyrQ= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= @@ -352,8 +366,6 @@ github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0ua github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= @@ -378,6 +390,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -723,8 +737,9 @@ google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/pkg/config/config.go b/pkg/config/config.go index 8284af97..47388cbe 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -29,6 +29,7 @@ import ( "github.com/golang/glog" homedir "github.com/mitchellh/go-homedir" sdk "github.com/openshift-online/ocm-sdk-go" + "github.com/openshift-online/ocm-sdk-go/authentication/securestore" "github.com/openshift-online/ocm-cli/pkg/debug" "github.com/openshift-online/ocm-cli/pkg/info" @@ -51,15 +52,93 @@ type Config struct { URL string `json:"url,omitempty" doc:"URL of the API gateway. The value can be the complete URL or an alias. The valid aliases are 'production', 'staging' and 'integration'."` User string `json:"user,omitempty" doc:"User name."` Pager string `json:"pager,omitempty" doc:"Pager command, for example 'less'. If empty no pager will be used."` + AuthMethod string `json:"auth_method,omitempty" doc:"Authentication method used to login. Valid values are 'token', 'client-credentials', 'auth-code' and 'device-code'"` } -// Load loads the configuration from the configuration file. If the configuration file doesn't exist -// it will return an empty configuration object. +type AuthMethodType string + +const ( + ClientAuth AuthMethodType = "client-credentials" + TokenAuth AuthMethodType = "token" + AuthCode AuthMethodType = "auth-code" + DeviceCode AuthMethodType = "device-code" + Password AuthMethodType = "password" +) + +// Loads the configuration conditionally based on the auth method +// Should be used for login operations only. +func LoginLoad(authMethod AuthMethodType) (cfg *Config, err error) { + ocmCfg := os.Getenv("OCM_CONFIG") + useSecureStore := ocmCfg == securestore.SecureStoreConfigKey || + authMethod == AuthCode || authMethod == DeviceCode + + // If the OCM_CONFIG env var is set to `securestore`, + // or auth code/device code flow is being used, + // use the OS keyring instead + if useSecureStore { + loadedCfg, err := loadFromOS() + if err != nil { + return nil, err + } + return loadedCfg, nil + } + + // Remove any stored config from OS keyring + // Swallow error if the OS keyring errors + securestore.RemoveConfigFromKeyring() + // No OS keyring config exists, load from file + return loadFromFile() +} + +// Loads the configuration from the OS keyring first, then attempt to load from the configuration file. +// Should be used for operations other than login. func Load() (cfg *Config, err error) { + loadedCfg, _ := loadFromOS() + // Swallow error if the OS keyring is not available + if loadedCfg != nil { + return loadedCfg, nil + } + + // No OS keyring config exists, load from file + return loadFromFile() +} + +// Loads the configuration from the OS keyring. If the configuration file doesn't exist +// it will return an empty configuration object. +func loadFromOS() (cfg *Config, err error) { + cfg = &Config{} + + data, err := securestore.GetConfigFromKeyring() + if err != nil { + if err == securestore.ErrNoBackendsAvailable { + return nil, fmt.Errorf("an OS keyring is required for authentication: %v", err) + } + return nil, fmt.Errorf("can't load config from OS keyring: %v", err) + } + // No config found, return + if len(data) == 0 { + return nil, nil + } + err = json.Unmarshal(data, cfg) + if err != nil { + // Clear the config from the keyring if it's invalid + // so a new one can be created + err := securestore.RemoveConfigFromKeyring() + if err != nil { + return nil, fmt.Errorf("invalid configuration, automatic recovery failed: %v", err) + } + } + return cfg, nil +} + +// Loads the configuration from the configuration file. If the configuration file doesn't exist +// it will return an empty configuration object. +func loadFromFile() (cfg *Config, err error) { file, err := Location() if err != nil { return } + _, err = os.Stat(file) if os.IsNotExist(err) { cfg = &Config{} @@ -94,15 +173,35 @@ func Save(cfg *Config) error { if err != nil { return err } + + data, err := json.MarshalIndent(cfg, "", " ") + if err != nil { + return fmt.Errorf("can't marshal config: %v", err) + } + + ocmCfg := os.Getenv("OCM_CONFIG") + useSecureStore := ocmCfg == securestore.SecureStoreConfigKey || + cfg.AuthMethod == string(AuthCode) || cfg.AuthMethod == string(DeviceCode) + + // If the OCM_CONFIG env var is set to `securestore`, + // or auth code/device code flow is being used, + // use the OS keyring instead + if useSecureStore { + err := securestore.UpsertConfigToKeyring(data) + if err != nil { + if err == securestore.ErrNoBackendsAvailable { + return fmt.Errorf("an OS keyring is required for authentication: %v", err) + } + return fmt.Errorf("can't save config to OS keyring: %v", err) + } + return nil + } + dir := filepath.Dir(file) err = os.MkdirAll(dir, os.FileMode(0755)) if err != nil { return fmt.Errorf("can't create directory %s: %v", dir, err) } - data, err := json.MarshalIndent(cfg, "", " ") - if err != nil { - return fmt.Errorf("can't marshal config: %v", err) - } err = os.WriteFile(file, data, 0600) if err != nil { return fmt.Errorf("can't write file '%s': %v", file, err) diff --git a/tests/login_test.go b/tests/login_test.go index 9b8f099e..0361d2d9 100644 --- a/tests/login_test.go +++ b/tests/login_test.go @@ -75,7 +75,8 @@ var _ = Describe("Login", func() { "{{ $scope }}" {{ end }} ], - "access_token": "{{ .accessToken }}" + "access_token": "{{ .accessToken }}", + "auth_method": "token" }`, "url", sdk.DefaultURL, "tokenURL", ssoServer.URL(), @@ -122,7 +123,8 @@ var _ = Describe("Login", func() { "{{ $scope }}" {{ end }} ], - "access_token": "{{ .accessToken }}" + "access_token": "{{ .accessToken }}", + "auth_method": "client-credentials" }`, "url", sdk.DefaultURL, "tokenURL", ssoServer.URL(), @@ -171,7 +173,8 @@ var _ = Describe("Login", func() { {{ end }} ], "access_token": "{{ .accessToken }}", - "refresh_token": "{{ .refreshToken }}" + "refresh_token": "{{ .refreshToken }}", + "auth_method": "password" }`, "url", sdk.DefaultURL, "tokenURL", ssoServer.URL(),