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

Bug 1839621: Add an ability to proxy requests to Che Workspace #5332

Merged
merged 14 commits into from
May 27, 2020
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion cmd/bridge/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ func main() {

k8sAuthServiceAccountBearerToken = string(bearerToken)

// If running in an OpenShift cluster, set up a proxy to the prometheus-k8s serivce running in the openshift-monitoring namespace.
// If running in an OpenShift cluster, set up a proxy to the prometheus-k8s service running in the openshift-monitoring namespace.
if *fServiceCAFile != "" {
serviceCertPEM, err := ioutil.ReadFile(*fServiceCAFile)
if err != nil {
Expand Down Expand Up @@ -344,6 +344,7 @@ func main() {
HeaderBlacklist: []string{"Cookie", "X-CSRFToken"},
Endpoint: &url.URL{Scheme: "https", Host: openshiftMeteringHost, Path: "/api"},
}
srv.TerminalProxyTLSConfig = serviceProxyTLSConfig
}

case "off-cluster":
Expand Down Expand Up @@ -401,6 +402,8 @@ func main() {
}
}

srv.TerminalProxyTLSConfig = serviceProxyTLSConfig

default:
bridge.FlagFatalf("k8s-mode", "must be one of: in-cluster, off-cluster")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export const initTerminal = (
workspaceName: string,
workspaceNamespace: string,
): Promise<TerminalInitData> => {
const url = `/api/terminal/${workspaceNamespace}/${workspaceName}/exec/init`;
const url = `/api/terminal/proxy/${workspaceNamespace}/${workspaceName}/exec/init`;
const payload = {
kubeconfig: {
username,
Expand Down
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ require (
github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea
github.com/gorilla/websocket v1.4.0
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
github.com/stretchr/testify v1.4.0
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
google.golang.org/grpc v1.24.0
gopkg.in/square/go-jose.v2 v2.4.1 // indirect
gopkg.in/yaml.v2 v2.2.4
helm.sh/helm/v3 v3.0.1
k8s.io/api v0.0.0-20191016110408-35e52d86657a
k8s.io/apiextensions-apiserver v0.0.0-20191016113550-5357c4baaf65
k8s.io/apimachinery v0.0.0-20191004115801-a2eda9f80ab8
k8s.io/cli-runtime v0.0.0-20191016114015-74ad18325ed5
k8s.io/client-go v0.0.0-20191016111102-bec269661e48
k8s.io/klog v1.0.0
Expand Down
11 changes: 11 additions & 0 deletions pkg/server/server.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package server

import (
"crypto/tls"
"fmt"
"html/template"
"io"
Expand All @@ -17,6 +18,7 @@ import (
helmhandlerspkg "github.com/openshift/console/pkg/helm/handlers"
"github.com/openshift/console/pkg/proxy"
"github.com/openshift/console/pkg/serverutils"
"github.com/openshift/console/pkg/terminal"
"github.com/openshift/console/pkg/version"
)

Expand Down Expand Up @@ -97,6 +99,7 @@ type Server struct {
ThanosTenancyProxyConfig *proxy.Config
AlertManagerProxyConfig *proxy.Config
MeteringProxyConfig *proxy.Config
TerminalProxyTLSConfig *tls.Config
// A lister for resource listing of a particular kind
MonitoringDashboardConfigMapLister ResourceLister
KnativeEventSourceCRDLister ResourceLister
Expand Down Expand Up @@ -220,6 +223,14 @@ func (s *Server) HTTPHandler() http.Handler {
})),
)

terminalProxy := terminal.NewProxy(
s.TerminalProxyTLSConfig,
s.K8sProxyConfig.TLSClientConfig,
s.K8sProxyConfig.Endpoint)

handle(terminal.ProxyEndpoint, authHandlerWithUser(terminalProxy.HandleProxy))
handleFunc(terminal.AvailableEndpoint, terminalProxy.HandleProxyEnabled)

if s.prometheusProxyEnabled() {
// Only proxy requests to the Prometheus API, not the UI.
var (
Expand Down
30 changes: 30 additions & 0 deletions pkg/terminal/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package terminal

import (
authv1 "k8s.io/api/authorization/v1"
)

// checkUserPermissions checks if the terminal proxy is supported for a given user.
// Returns true if we're willing to proxy the user's token, false otherwise. We don't
// want to proxy highly privileged tokens to avoid privilege escalation issues.
func (p *Proxy) checkUserPermissions(token string) (bool, error) {
client, err := p.createTypedClient(token)
if err != nil {
return false, err
}

sar := &authv1.SelfSubjectAccessReview{
Spec: authv1.SelfSubjectAccessReviewSpec{
ResourceAttributes: &authv1.ResourceAttributes{
Namespace: "openshift-operators",
Verb: "create",
Resource: "pods",
},
},
}
res, err := client.AuthorizationV1().SelfSubjectAccessReviews().Create(sar)
if err != nil || res == nil {
return false, err
}
return !res.Status.Allowed, nil
}
50 changes: 50 additions & 0 deletions pkg/terminal/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package terminal

import (
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
)

// createDynamicClient create dynamic client with the configured token to be used
func (p *Proxy) createDynamicClient(token string) (dynamic.Interface, error) {
config, err := p.getConfig(token)
if err != nil {
return nil, err
}

client, err := dynamic.NewForConfig(dynamic.ConfigFor(config))
if err != nil {
return nil, err
}
return client, nil
}

func (p *Proxy) createTypedClient(token string) (*kubernetes.Clientset, error) {
config, err := p.getConfig(token)
if err != nil {
return nil, err
}

return kubernetes.NewForConfig(config)
}

func (p *Proxy) getConfig(token string) (*rest.Config, error) {
var tlsClientConfig rest.TLSClientConfig
if p.TLSClientConfig.InsecureSkipVerify {
// off-cluster mode
tlsClientConfig.Insecure = true
} else {
inCluster, err := rest.InClusterConfig()
if err != nil {
return nil, err
}
tlsClientConfig = inCluster.TLSClientConfig
}

return &rest.Config{
Host: p.ClusterEndpoint.Host,
TLSClientConfig: tlsClientConfig,
BearerToken: token,
}, nil
}
42 changes: 42 additions & 0 deletions pkg/terminal/operator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package terminal

import (
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
)

const (
webhookName = "workspace.che.eclipse.org"
)

// workspaceOperatorIsRunning checks if the workspace operator is running and webhooks are enabled,
// which is a prerequisite for sending a user's token to a workspace.
func workspaceOperatorIsRunning() (bool, error) {
config, err := rest.InClusterConfig()
if err != nil {
return false, err
}
client, err := kubernetes.NewForConfig(config)
if err != nil {
return false, err
}

_, err = client.AdmissionregistrationV1().MutatingWebhookConfigurations().Get(webhookName, metav1.GetOptions{})
if err != nil {
if errors.IsNotFound(err) {
return false, nil
}
return false, err
}

_, err = client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Get(webhookName, metav1.GetOptions{})
if err != nil {
if errors.IsNotFound(err) {
return false, nil
}
return false, err
}
return true, nil
}
Loading