-
Notifications
You must be signed in to change notification settings - Fork 7
/
login.go
207 lines (177 loc) · 7.56 KB
/
login.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
package login
import (
"context"
"errors"
"fmt"
"strings"
"github.com/fatih/color"
"github.com/giantswarm/microerror"
"k8s.io/client-go/tools/clientcmd"
"github.com/giantswarm/kubectl-gs/v2/pkg/installation"
"github.com/giantswarm/kubectl-gs/v2/pkg/kubeconfig"
)
func (r *runner) findContext(ctx context.Context, installationIdentifier string) (bool, error) {
if _, contextType := kubeconfig.IsKubeContext(installationIdentifier); contextType != kubeconfig.ContextTypeNone {
return true, r.loginWithKubeContextName(ctx, installationIdentifier)
} else if kubeconfig.IsCodeName(installationIdentifier) || kubeconfig.IsWCCodeName(installationIdentifier) {
return true, r.loginWithCodeName(ctx, installationIdentifier)
}
return false, nil
}
// loginWithKubeContextName switches the active kubernetes context to
// the one specified.
func (r *runner) loginWithKubeContextName(ctx context.Context, contextName string) error {
var contextAlreadySelected bool
var newLoginRequired bool
k8sConfigAccess := r.commonConfig.GetConfigAccess()
err := switchContext(ctx, k8sConfigAccess, contextName, r.loginOptions.switchToContext)
if IsContextAlreadySelected(err) {
contextAlreadySelected = true
} else if IsNewLoginRequired(err) || IsTokenRenewalFailed(err) {
newLoginRequired = true
} else if err != nil {
return microerror.Mask(err)
}
config, err := k8sConfigAccess.GetStartingConfig()
if err != nil {
return microerror.Mask(err)
}
if newLoginRequired || r.loginOptions.selfContained {
authType := kubeconfig.GetAuthType(config, contextName)
if authType == kubeconfig.AuthTypeAuthProvider {
// If we get here, we are sure that the kubeconfig context exists.
server, _ := kubeconfig.GetClusterServer(config, contextName)
err = r.loginWithURL(ctx, server, false, "")
if err != nil {
return microerror.Mask(err)
}
}
return nil
}
if r.flag.DeviceAuth {
fmt.Fprintf(r.stdout, color.YellowString("A valid `%s` context already exists, there is no need to log in again, ignoring the `device-flow` flag.\n\n"), contextName)
if clusterServer, exists := kubeconfig.GetClusterServer(config, contextName); exists {
fmt.Fprintf(r.stdout, "Run kubectl gs login with `%s` instead of the context name to force the re-login.\n", clusterServer)
} else {
fmt.Fprintln(r.stdout, "Run kubectl gs login with the API URL to force the re-login.")
}
}
if contextAlreadySelected {
fmt.Fprintf(r.stdout, "Context '%s' is already selected.\n", contextName)
} else if !r.loginOptions.isWC && r.loginOptions.switchToContext {
fmt.Fprintf(r.stdout, "Switched to context '%s'.\n", contextName)
}
return nil
}
// loginWithCodeName switches the active kubernetes context to
// one with the name derived from the installation code name.
func (r *runner) loginWithCodeName(ctx context.Context, codeName string) error {
contextName := kubeconfig.GenerateKubeContextName(codeName)
err := r.loginWithKubeContextName(ctx, contextName)
if err != nil {
return microerror.Mask(err)
}
fmt.Fprint(r.stdout, color.GreenString("You are logged in to the cluster '%s'.\n", codeName))
return nil
}
// loginWithURL performs the OIDC login into an installation's
// k8s api with a happa/k8s api URL.
func (r *runner) loginWithURL(ctx context.Context, path string, firstLogin bool, tokenOverride string) error {
i, err := r.commonConfig.GetInstallation(ctx, path, "")
if installation.IsUnknownUrlType(err) {
return microerror.Maskf(unknownUrlError, "'%s' is not a valid Giant Swarm Management API URL. Please check the spelling.\nIf not sure, pass the web UI URL of the installation or the installation handle as an argument instead.", path)
} else if err != nil {
return microerror.Mask(err)
}
if installation.GetUrlType(path) == installation.UrlTypeHappa {
fmt.Fprint(r.stdout, color.YellowString("Note: deriving Management API URL from web UI URL: %s\n", i.K8sApiURL))
}
err = r.loginWithInstallation(ctx, tokenOverride, i)
if err != nil {
return microerror.Mask(err)
}
contextName := kubeconfig.GenerateKubeContextName(i.Codename)
if r.loginOptions.selfContained {
fmt.Fprintf(r.stdout, "A new kubectl context has '%s' been created and stored in '%s'. You can select this context like this:\n\n", contextName, r.flag.SelfContained)
fmt.Fprintf(r.stdout, " kubectl cluster-info --kubeconfig %s \n", r.flag.SelfContained)
} else {
if firstLogin {
if !r.loginOptions.switchToContext {
fmt.Fprintf(r.stdout, "A new kubectl context '%s' has been created.", contextName)
fmt.Fprintf(r.stdout, " ")
} else {
fmt.Fprintf(r.stdout, "A new kubectl context '%s' has been created and selected.", contextName)
fmt.Fprintf(r.stdout, " ")
}
}
if !r.loginOptions.switchToContext {
fmt.Fprintf(r.stdout, "To switch to this context later, use either of these commands:\n\n")
} else {
fmt.Fprintf(r.stdout, "To switch back to this context later, use either of these commands:\n\n")
}
fmt.Fprintf(r.stdout, " kubectl gs login %s\n", i.Codename)
fmt.Fprintf(r.stdout, " kubectl config use-context %s\n", contextName)
}
return nil
}
func (r *runner) loginWithInstallation(ctx context.Context, tokenOverride string, i *installation.Installation) error {
k8sConfigAccess := r.commonConfig.GetConfigAccess()
var err error
var authResult authInfo
{
if len(tokenOverride) > 0 {
authResult = authInfo{
username: "automation",
token: tokenOverride,
}
} else {
contextName := kubeconfig.GenerateKubeContextName(i.Codename)
if r.flag.DeviceAuth || r.isDeviceAuthContext(k8sConfigAccess, contextName) {
authResult, err = handleDeviceFlowOIDC(r.stdout, r.stderr, i, r.flag.InternalAPI)
} else {
authResult, err = handleOIDC(ctx, r.stdout, r.stderr, i, r.flag.ConnectorID, r.flag.ClusterAdmin, r.flag.InternalAPI, r.flag.CallbackServerHost, r.flag.CallbackServerPort, r.flag.LoginTimeout)
if err != nil && errors.Is(err, context.DeadlineExceeded) || IsAuthResponseTimedOut(err) {
fmt.Fprintf(r.stderr, "\nYour authentication flow timed out after %s. Please execute the same command again.\n", r.flag.LoginTimeout.String())
fmt.Fprintf(r.stderr, "You can use the --login-timeout flag to configure a longer timeout interval, for example --login-timeout=%.0fs.\n", 2*r.flag.LoginTimeout.Seconds())
if errors.Is(err, context.DeadlineExceeded) {
return microerror.Maskf(authResponseTimedOutError, "failed to get an authentication response on time")
}
}
}
if err != nil {
return microerror.Mask(err)
}
}
}
if r.loginOptions.selfContained {
err = printMCCredentials(k8sConfigAccess, i, authResult, r.fs, r.flag.InternalAPI, r.flag.SelfContained)
if err != nil {
return microerror.Mask(err)
}
} else {
// Store kubeconfig and CA certificate.
err = storeMCCredentials(k8sConfigAccess, i, authResult, r.flag.InternalAPI, r.loginOptions.switchToContext)
if err != nil {
return microerror.Mask(err)
}
}
if len(authResult.email) > 0 {
fmt.Fprint(r.stdout, color.GreenString("Logged in successfully as '%s' on cluster '%s'.\n\n", authResult.email, i.Codename))
} else {
fmt.Fprint(r.stdout, color.GreenString("Logged in successfully as '%s' on cluster '%s'.\n\n", authResult.username, i.Codename))
}
return nil
}
func (r *runner) isDeviceAuthContext(k8sConfigAccess clientcmd.ConfigAccess, contextName string) bool {
config, err := k8sConfigAccess.GetStartingConfig()
if err != nil {
return false
}
if originContext, ok := config.Contexts[contextName]; ok {
return isDeviceAuthInfo(originContext.AuthInfo)
}
return false
}
func isDeviceAuthInfo(authInfo string) bool {
return strings.HasSuffix(authInfo, "-device")
}