This repository has been archived by the owner on Jan 24, 2023. It is now read-only.
/
main.go
304 lines (247 loc) · 9.67 KB
/
main.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
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
package kubernetes
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strconv"
"strings"
"errors"
"github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces"
"github.com/labstack/echo/v4"
log "github.com/sirupsen/logrus"
"github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/kubernetes/auth"
"github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/kubernetes/terminal"
)
// KubernetesSpecification is the endpoint that adds Kubernetes support to the backend
type KubernetesSpecification struct {
portalProxy interfaces.PortalProxy
endpointType string
kubeTerminal *terminal.KubeTerminal
}
type KubeStatus struct {
Kind string `json:"kind"`
ApiVersion string `json:"apiVersion"`
Metadata interface{} `json:"metadata"`
Status string `json:"status"`
Message string `json:"message"`
Reason string `json:"reason"`
Details interface{} `json:"details"`
Code int `json:"code"`
}
type kubeErrorStatus struct {
Type string `json:"type"`
Status string `json:"status"`
Message string `json:"message"`
}
type KubeAPIVersions struct {
Kind string `json:"kind"`
Versions []string `json:"versions"`
ServerAddressByClientCIDRs []interface{} `json:"serverAddressByClientCIDRs"`
}
const (
kubeEndpointType = "k8s"
defaultKubeClientID = "K8S_CLIENT"
// kubeDashboardPluginConfigSetting is config value sent back to the client to indicate if the kube dashboard ie enabled
kubeDashboardPluginConfigSetting = "kubeDashboardEnabled"
// kubeTerminalPluginConfigSetting is config value sent back to the client to indicate if the kube terminal is enabled
kubeTerminalPluginConfigSetting = "kubeTerminalEnabled"
)
func init() {
interfaces.AddPlugin("kubernetes", nil, Init)
}
// Init creates a new instance of the Kubernetes plugin
func Init(portalProxy interfaces.PortalProxy) (interfaces.StratosPlugin, error) {
kubeTerminal := terminal.NewKubeTerminal(portalProxy)
kube := &KubernetesSpecification{portalProxy: portalProxy, endpointType: kubeEndpointType, kubeTerminal: kubeTerminal}
if kubeTerminal != nil {
kubeTerminal.Kube = kube
}
return kube, nil
}
func (c *KubernetesSpecification) GetEndpointPlugin() (interfaces.EndpointPlugin, error) {
return c, nil
}
func (c *KubernetesSpecification) GetRoutePlugin() (interfaces.RoutePlugin, error) {
return c, nil
}
func (c *KubernetesSpecification) GetMiddlewarePlugin() (interfaces.MiddlewarePlugin, error) {
return nil, errors.New("Not implemented!")
}
func (c *KubernetesSpecification) GetType() string {
return kubeEndpointType
}
func (c *KubernetesSpecification) GetClientId() string {
return c.portalProxy.Env().String(defaultKubeClientID, "k8s")
}
func (c *KubernetesSpecification) Register(echoContext echo.Context) error {
log.Debug("Kubernetes Register...")
return c.portalProxy.RegisterEndpoint(echoContext, c.Info)
}
func (c *KubernetesSpecification) Validate(userGUID string, cnsiRecord interfaces.CNSIRecord, tokenRecord interfaces.TokenRecord) error {
log.Debugf("Validating Kubernetes endpoint connection for user: %s", userGUID)
response, err := c.portalProxy.DoProxySingleRequest(cnsiRecord.GUID, userGUID, "GET", "api/v1/pods?limit=1", nil, nil)
if err != nil {
return err
}
if response.StatusCode >= 400 {
if response.Error != nil {
return fmt.Errorf("Unable to connect to endpoint: %s", response.Error.Error())
}
return fmt.Errorf("Unable to connect to endpoint: %d => %s", response.StatusCode, response.Status)
}
return nil
}
func (c *KubernetesSpecification) Connect(ec echo.Context, cnsiRecord interfaces.CNSIRecord, userID string) (*interfaces.TokenRecord, bool, error) {
log.Debug("Kubernetes Connect...")
connectType := ec.FormValue("connect_type")
var authProvider = c.FindAuthProvider(connectType)
if authProvider == nil {
return nil, false, errors.New("Unsupported Auth connection type for Kubernetes endpoint")
}
tokenRecord, _, err := authProvider.FetchToken(cnsiRecord, ec)
if err != nil {
return nil, false, err
}
return tokenRecord, false, nil
}
// Init the Kubernetes Jetstream plugin
func (c *KubernetesSpecification) Init() error {
c.AddAuthProvider(auth.InitGKEKubeAuth(c.portalProxy))
c.AddAuthProvider(auth.InitAWSKubeAuth(c.portalProxy))
c.AddAuthProvider(auth.InitCertKubeAuth(c.portalProxy))
c.AddAuthProvider(auth.InitAzureKubeAuth(c.portalProxy))
c.AddAuthProvider(auth.InitOIDCKubeAuth(c.portalProxy))
c.AddAuthProvider(auth.InitKubeConfigAuth(c.portalProxy))
c.AddAuthProvider(auth.InitKubeTokenAuth(c.portalProxy))
c.AddAuthProvider(auth.InitKubeBasicAuth(c.portalProxy))
// Kube dashboard is enabled by Tech Preview mode
c.portalProxy.GetConfig().PluginConfig[kubeDashboardPluginConfigSetting] = strconv.FormatBool(c.portalProxy.GetConfig().EnableTechPreview)
// Kube terminal is enabled by Tech Preview mode AND the configuration being complete
c.portalProxy.GetConfig().PluginConfig[kubeTerminalPluginConfigSetting] = strconv.FormatBool(c.portalProxy.GetConfig().EnableTechPreview && c.kubeTerminal != nil)
// Kick off the cleanup of any old kube terminal pods
if c.kubeTerminal != nil {
c.kubeTerminal.StartCleanup()
}
return nil
}
func (c *KubernetesSpecification) AddAdminGroupRoutes(echoGroup *echo.Group) {
echoGroup.GET("/kube/cert", c.RequiresCert)
}
func (c *KubernetesSpecification) AddSessionGroupRoutes(echoGroup *echo.Group) {
// Kubernetes Dashboard Proxy
echoGroup.Any("/apps/kubedash/ui/:guid/*", c.kubeDashboardProxy)
echoGroup.GET("/kubedash/:guid/login", c.kubeDashboardLogin)
echoGroup.GET("/kubedash/:guid/status", c.kubeDashboardStatus)
echoGroup.POST("/kubedash/:guid/serviceAccount", c.kubeDashboardCreateServiceAccount)
echoGroup.DELETE("/kubedash/:guid/serviceAccount", c.kubeDashboardDeleteServiceAccount)
echoGroup.POST("/kubedash/:guid/installation", c.kubeDashboardInstallDashboard)
echoGroup.DELETE("/kubedash/:guid/installation", c.kubeDashboardDeleteDashboard)
// Helm Routes
echoGroup.GET("/helm/releases", c.ListReleases)
echoGroup.POST("/helm/install", c.InstallRelease)
echoGroup.DELETE("/helm/releases/:endpoint/:namespace/:name", c.DeleteRelease)
echoGroup.GET("/helm/releases/:endpoint/:namespace/:name/history", c.GetReleaseHistory)
echoGroup.GET("/helm/releases/:endpoint/:namespace/:name/status", c.GetReleaseStatus)
echoGroup.GET("/helm/releases/:endpoint/:namespace/:name", c.GetRelease)
echoGroup.POST("/helm/releases/:endpoint/:namespace/:name", c.UpgradeRelease)
// Kube Terminal
if c.kubeTerminal != nil {
echoGroup.GET("/kubeterminal/:guid", c.kubeTerminal.Start)
}
}
func (c *KubernetesSpecification) Info(apiEndpoint string, skipSSLValidation bool) (interfaces.CNSIRecord, interface{}, error) {
log.Debug("Kubernetes Info")
var v2InfoResponse interfaces.V2Info
var newCNSI interfaces.CNSIRecord
newCNSI.CNSIType = kubeEndpointType
_, err := url.Parse(apiEndpoint)
if err != nil {
return newCNSI, nil, err
}
log.Debug("Request Kube API Versions")
var httpClient = c.portalProxy.GetHttpClient(skipSSLValidation)
res, err := httpClient.Get(apiEndpoint + "/api")
if err != nil {
// This should ultimately catch 503 cert errors
return newCNSI, nil, err
}
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return newCNSI, nil, err
}
if res.StatusCode < 400 {
// No auth on kube set up, expect a successful APIVersions response - KubeAPIVersions
log.Debug("Kube API Versions Succeeded")
apiVersions := KubeAPIVersions{}
err := json.Unmarshal(body, &apiVersions)
if err != nil {
return newCNSI, nil, fmt.Errorf("Failed to parse output as kube kind APIVersions: %+v", err)
}
if apiVersions.Kind != "APIVersions" {
return newCNSI, nil, fmt.Errorf("Failed to parse output as kube kind APIVersions: %+v", apiVersions)
}
} else if res.StatusCode == 403 || res.StatusCode == 401 {
err := parseErrorResponse(body)
if err != nil {
return newCNSI, nil, fmt.Errorf("Failed to parse output as kube kind status: %+v", err)
}
} else {
return newCNSI, nil, fmt.Errorf("Dissallowed response code from `/api` call: %+v", res.StatusCode)
}
log.Debug("Kube API Versions Acceptable Response")
newCNSI.TokenEndpoint = apiEndpoint
newCNSI.AuthorizationEndpoint = apiEndpoint
return newCNSI, v2InfoResponse, nil
}
func parseErrorResponse(body []byte) error {
kubeStatus := KubeStatus{}
err := json.Unmarshal(body, &kubeStatus)
if err == nil {
// Expect a json message with a status
if kubeStatus.Kind == "Status" {
return nil
}
}
// Try the other format
errorStatus := kubeErrorStatus{}
err = json.Unmarshal(body, &errorStatus)
if err == nil {
// Expect the type to be error
if errorStatus.Type == "error" {
return nil
}
}
// Not one of the types we recognise
log.Debug(string(body))
return errors.New("Could not understand response from Kubernetes endpoint")
}
func (c *KubernetesSpecification) UpdateMetadata(info *interfaces.Info, userGUID string, echoContext echo.Context) {
}
func (c *KubernetesSpecification) RequiresCert(ec echo.Context) error {
url := ec.QueryParam("url")
log.Debug("Request Kube API Versions")
var httpClient = c.portalProxy.GetHttpClient(false)
_, err := httpClient.Get(url + "/api")
var response struct {
Status int
Required bool
Error bool
Message string
}
if err != nil {
if strings.Contains(err.Error(), "x509: certificate") {
response.Status = http.StatusOK
response.Required = true
} else {
response.Status = http.StatusInternalServerError
response.Error = true
response.Message = fmt.Sprintf("Failed to validate Kube certificate requirement: %+v", err)
}
} else {
response.Status = http.StatusOK
response.Required = false
}
return ec.JSON(response.Status, response)
}