Skip to content

Commit

Permalink
Merge pull request #1599 from liouk/web-login_4.13
Browse files Browse the repository at this point in the history
[release-4.13] AUTH-443: Add OAuth2 Authorization Code Grant Flow for login
  • Loading branch information
openshift-merge-bot[bot] committed Nov 15, 2023
2 parents 8026ed7 + d91f4f2 commit c7c6eb2
Show file tree
Hide file tree
Showing 60 changed files with 3,145 additions and 917 deletions.
5 changes: 3 additions & 2 deletions go.mod
Expand Up @@ -29,10 +29,11 @@ require (
github.com/moby/buildkit v0.0.0-20181107081847-c3a857e3fca0
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198
github.com/openshift/api v0.0.0-20230120195050-6ba31fa438f2
github.com/openshift/api v0.0.0-20230330150608-05635858d40f
github.com/openshift/build-machinery-go v0.0.0-20220913142420-e25cf57ea46d
github.com/openshift/client-go v0.0.0-20230120202327-72f107311084
github.com/openshift/library-go v0.0.0-20230227140230-39892725eed1
github.com/openshift/library-go v0.0.0-20231115094609-5e510a6e9a52
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.14.0
github.com/russross/blackfriday v1.6.0
Expand Down
11 changes: 7 additions & 4 deletions go.sum
Expand Up @@ -1082,16 +1082,16 @@ github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3
github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8=
github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI=
github.com/opencontainers/selinux v1.10.1/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI=
github.com/openshift/api v0.0.0-20230120195050-6ba31fa438f2 h1:+nw0/d4spq880W7S74Twi5YU2ulsl3/a9o4OEZptYp0=
github.com/openshift/api v0.0.0-20230120195050-6ba31fa438f2/go.mod h1:ctXNyWanKEjGj8sss1KjjHQ3ENKFm33FFnS5BKaIPh4=
github.com/openshift/api v0.0.0-20230330150608-05635858d40f h1:mGpCtfoehMcvmg/sSYLiv6nCbTl04cmtkUfYzP7H1AQ=
github.com/openshift/api v0.0.0-20230330150608-05635858d40f/go.mod h1:ctXNyWanKEjGj8sss1KjjHQ3ENKFm33FFnS5BKaIPh4=
github.com/openshift/build-machinery-go v0.0.0-20220913142420-e25cf57ea46d h1:RR4ah7FfaPR1WePizm0jlrsbmPu91xQZnAsVVreQV1k=
github.com/openshift/build-machinery-go v0.0.0-20220913142420-e25cf57ea46d/go.mod h1:b1BuldmJlbA/xYtdZvKi+7j5YGB44qJUJDZ9zwiNCfE=
github.com/openshift/client-go v0.0.0-20230120202327-72f107311084 h1:66uaqNwA+qYyQDwsMWUfjjau8ezmg1dzCqub13KZOcE=
github.com/openshift/client-go v0.0.0-20230120202327-72f107311084/go.mod h1:M3h9m001PWac3eAudGG3isUud6yBjr5XpzLYLLTlHKo=
github.com/openshift/gssapi v0.0.0-20161010215902-5fb4217df13b h1:it0YPE/evO6/m8t8wxis9KFI2F/aleOKsI6d9uz0cEk=
github.com/openshift/gssapi v0.0.0-20161010215902-5fb4217df13b/go.mod h1:tNrEB5k8SI+g5kOlsCmL2ELASfpqEofI0+FLBgBdN08=
github.com/openshift/library-go v0.0.0-20230227140230-39892725eed1 h1:G7ynzkST2dNwSueO7GIJbZ9BLQnffwDJV85D8YHVSFA=
github.com/openshift/library-go v0.0.0-20230227140230-39892725eed1/go.mod h1:xO4nAf0qa56dgvEJWVD1WuwSJ8JWPU1TYLBQrlutWnE=
github.com/openshift/library-go v0.0.0-20231115094609-5e510a6e9a52 h1:qP0GMFLjUeviPIjLaynal3LYfKQUMpvsQnRYFNputIU=
github.com/openshift/library-go v0.0.0-20231115094609-5e510a6e9a52/go.mod h1:tedJaJsajpyrlPVNoSfjOst6w2HHvvR4VPqKWeZzrbY=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw=
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
Expand All @@ -1107,6 +1107,8 @@ github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCko
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
Expand Down Expand Up @@ -1675,6 +1677,7 @@ golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand Down
22 changes: 18 additions & 4 deletions pkg/cli/login/login.go
Expand Up @@ -16,8 +16,8 @@ import (
"k8s.io/kubectl/pkg/util/templates"
"k8s.io/kubectl/pkg/util/term"

"github.com/openshift/library-go/pkg/oauth/tokenrequest"
"github.com/openshift/oc/pkg/helpers/flagtypes"
"github.com/openshift/oc/pkg/helpers/tokencmd"
)

var (
Expand All @@ -31,7 +31,8 @@ var (
The information required to login -- like username and password, a session token, or
the server details -- can be provided through flags. If not provided, the command will
prompt for user input as needed.
prompt for user input as needed. It is also possible to login through a web browser by
providing the respective flag.
`)

loginExample = templates.Examples(`
Expand All @@ -43,6 +44,9 @@ var (
# Log in to the given server with the given credentials (will not prompt interactively)
oc login localhost:8443 --username=myuser --password=mypass
# Log in to the given server through a browser
oc login --web --callback-port 8280 localhost:8443
`)
)

Expand All @@ -60,7 +64,7 @@ func NewCmdLogin(f kcmdutil.Factory, streams genericclioptions.IOStreams) *cobra

if err := o.Run(); kapierrors.IsUnauthorized(err) {
if err, isStatusErr := err.(*kapierrors.StatusError); isStatusErr {
if err.Status().Message != tokencmd.BasicAuthNoUsernameMessage {
if err.Status().Message != tokenrequest.BasicAuthNoUsernameMessage {
fmt.Fprintln(streams.Out, "Login failed (401 Unauthorized)")
fmt.Fprintln(streams.Out, "Verify you have provided the correct credentials.")
}
Expand All @@ -83,6 +87,8 @@ func NewCmdLogin(f kcmdutil.Factory, streams genericclioptions.IOStreams) *cobra
cmds.Flags().StringVarP(&o.Username, "username", "u", o.Username, "Username for server")
cmds.Flags().StringVarP(&o.Password, "password", "p", o.Password, "Password for server")

cmds.Flags().BoolVarP(&o.WebLogin, "web", "w", o.WebLogin, "Login with web browser")
cmds.Flags().Int32VarP(&o.CallbackPort, "callback-port", "c", o.CallbackPort, "Port for the callback server when using --web. Defaults to a random open port")
return cmds
}

Expand Down Expand Up @@ -167,10 +173,18 @@ func (o LoginOptions) Validate(cmd *cobra.Command, serverFlag string, args []str
return errors.New("Must have a config file already created")
}

if o.WebLogin && (o.Username != "" || o.Password != "" || o.Token != "") {
return errors.New("--web cannot be used along with --username, --password or --token")
}

if o.CallbackPort != 0 && !o.WebLogin {
return errors.New("--callback-port can only be specified along with --web")
}

return nil
}

// RunLogin contains all the necessary functionality for the OpenShift cli login command
// Run contains all the necessary functionality for the OpenShift cli login command
func (o LoginOptions) Run() error {
if err := o.GatherInfo(); err != nil {
return err
Expand Down
79 changes: 74 additions & 5 deletions pkg/cli/login/loginoptions.go
Expand Up @@ -6,7 +6,9 @@ import (
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"net"
"net/url"
"os"
"path/filepath"
"time"
Expand All @@ -19,16 +21,21 @@ import (
restclient "k8s.io/client-go/rest"
kclientcmd "k8s.io/client-go/tools/clientcmd"
kclientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"k8s.io/klog/v2"
kterm "k8s.io/kubectl/pkg/util/term"

projectv1typedclient "github.com/openshift/client-go/project/clientset/versioned/typed/project/v1"
"github.com/openshift/library-go/pkg/oauth/tokenrequest"
"github.com/openshift/library-go/pkg/oauth/tokenrequest/challengehandlers"
occhallengers "github.com/openshift/oc/pkg/helpers/authchallengers"
"github.com/openshift/oc/pkg/helpers/errors"
cliconfig "github.com/openshift/oc/pkg/helpers/kubeconfig"
"github.com/openshift/oc/pkg/helpers/motd"
"github.com/openshift/oc/pkg/helpers/project"
loginutil "github.com/openshift/oc/pkg/helpers/project"
"github.com/openshift/oc/pkg/helpers/term"
"github.com/openshift/oc/pkg/helpers/tokencmd"
"github.com/openshift/oc/pkg/version"
"github.com/pkg/browser"
)

const defaultClusterURL = "https://localhost:8443"
Expand All @@ -47,9 +54,11 @@ type LoginOptions struct {
InsecureTLS bool

// flags and printing helpers
Username string
Password string
Project string
Username string
Password string
Project string
WebLogin bool
CallbackPort int32

// infra
StartingKubeConfig *kclientcmdapi.Config
Expand All @@ -70,6 +79,12 @@ type LoginOptions struct {
genericclioptions.IOStreams
}

type passwordPrompter func(r io.Reader, w io.Writer, format string, a ...interface{}) string

func (p passwordPrompter) PromptForPassword(r io.Reader, w io.Writer, format string, a ...interface{}) string {
return p(r, w, format, a)
}

func NewLoginOptions(streams genericclioptions.IOStreams) *LoginOptions {
return &LoginOptions{
IOStreams: streams,
Expand Down Expand Up @@ -246,10 +261,21 @@ func (o *LoginOptions) gatherAuthInfo() error {
clientConfig.CertFile = o.CertFile
clientConfig.KeyFile = o.KeyFile

token, err := tokencmd.RequestToken(o.Config, o.In, o.Username, o.Password)
var token string
if o.WebLogin {
loginURLHandler := func(u *url.URL) error {
loginURL := u.String()
fmt.Fprintf(o.Out, "Opening login URL in the default browser: %s\n", loginURL)
return browser.OpenURL(loginURL)
}
token, err = tokenrequest.RequestTokenWithLocalCallback(o.Config, loginURLHandler, int(o.CallbackPort))
} else {
token, err = tokenrequest.RequestTokenWithChallengeHandlers(o.Config, o.getAuthChallengeHandler())
}
if err != nil {
return err
}

clientConfig.BearerToken = token

me, err := project.WhoAmI(clientConfig)
Expand All @@ -263,6 +289,49 @@ func (o *LoginOptions) gatherAuthInfo() error {
return nil
}

func (o *LoginOptions) getAuthChallengeHandler() challengehandlers.ChallengeHandler {
var challengeHandlers []challengehandlers.ChallengeHandler
var webConsoleURL string

serverVersionDetector := version.NewServerVersionRetriever(o.Config)
serverVersion, err := serverVersionDetector.RetrieveServerVersion()
// this feature was introduced in Openshift 4.11 which should correspond to 1.24
if err == nil && serverVersion.MajorNumber >= 1 && serverVersion.MinorNumber >= 24 {
webConsoleURL = o.Config.Host + "/console"
}

if occhallengers.GSSAPIEnabled() {
klog.V(6).Info("GSSAPI Enabled")
challengeHandlers = append(challengeHandlers,
occhallengers.NewNegotiateChallengeHandler(occhallengers.NewGSSAPINegotiator(o.Username)),
)
}

if occhallengers.SSPIEnabled() {
klog.V(6).Info("SSPI Enabled")
challengeHandlers = append(challengeHandlers,
occhallengers.NewNegotiateChallengeHandler(occhallengers.NewSSPINegotiator(o.Username, o.Password, o.Config.Host, webConsoleURL, o.In)))
}

challengeHandlers = append(challengeHandlers,
challengehandlers.NewMultiHandler(
challengehandlers.NewBasicChallengeHandler(
o.Config.Host,
o.In, o.Out,
passwordPrompter(term.PromptForPasswordString),
o.Username, o.Password),
))

var handler challengehandlers.ChallengeHandler
if len(challengeHandlers) == 1 {
handler = challengeHandlers[0]
} else {
handler = challengehandlers.NewMultiHandler(challengeHandlers...)
}

return handler
}

// Discover the projects available for the established session and take one to use. It
// fails in case of no existing projects, and print out useful information in case of
// multiple projects.
Expand Down
7 changes: 5 additions & 2 deletions pkg/cli/login/loginoptions_test.go
Expand Up @@ -273,7 +273,7 @@ func (r *oauthMetadataResponse) Serialize() ([]byte, error) {
}

func TestPreserveErrTypeAuthInfo(t *testing.T) {
invoked := make(chan struct{}, 3)
invoked := make(chan struct{}, 4)
oauthResponse := []byte{}

server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Expand All @@ -284,10 +284,13 @@ func TestPreserveErrTypeAuthInfo(t *testing.T) {
t.Fatalf("unexpected request handled by test server: %v: %v", r.Method, r.URL)
}

if r.URL.Path == oauthMetadataEndpoint {
switch r.URL.Path {
case oauthMetadataEndpoint:
w.WriteHeader(http.StatusOK)
w.Write(oauthResponse)
return
case "version":
return
}
w.WriteHeader(http.StatusUnauthorized)
}))
Expand Down
@@ -1,4 +1,4 @@
package tokencmd
package authchallengers

import (
"encoding/base64"
Expand All @@ -7,6 +7,8 @@ import (
"strings"

"k8s.io/klog/v2"

"github.com/openshift/library-go/pkg/oauth/tokenrequest/challengehandlers"
)

// Negotiator defines the minimal interface needed to interact with GSSAPI to perform a negotiate challenge/response
Expand All @@ -31,7 +33,7 @@ type NegotiateChallengeHandler struct {
negotiator Negotiator
}

func NewNegotiateChallengeHandler(negotiator Negotiator) ChallengeHandler {
func NewNegotiateChallengeHandler(negotiator Negotiator) challengehandlers.ChallengeHandler {
return &NegotiateChallengeHandler{negotiator: negotiator}
}

Expand Down
@@ -1,4 +1,4 @@
package tokencmd
package authchallengers

import (
"errors"
Expand Down
@@ -1,7 +1,7 @@
//go:build gssapi
// +build gssapi

package tokencmd
package authchallengers

import (
"errors"
Expand Down
@@ -1,7 +1,7 @@
//go:build !gssapi
// +build !gssapi

package tokencmd
package authchallengers

func GSSAPIEnabled() bool {
return false
Expand Down

0 comments on commit c7c6eb2

Please sign in to comment.