Skip to content

Commit

Permalink
Merge pull request #5332 from sleshchenko/apiTerminal
Browse files Browse the repository at this point in the history
Bug 1839621: Add an ability to proxy requests to Che Workspace
  • Loading branch information
openshift-merge-robot committed May 27, 2020
2 parents 53be1af + 9187fcc commit c4ccb7d
Show file tree
Hide file tree
Showing 25 changed files with 411 additions and 5,106 deletions.
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
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

0 comments on commit c4ccb7d

Please sign in to comment.